feat: add Git and 1Password credential helper and update configurations
This commit is contained in:
130
docs/git-1password-setup.md
Normal file
130
docs/git-1password-setup.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Git + 1Password HTTPS Credential Helper
|
||||||
|
|
||||||
|
This setup allows Git to automatically fetch HTTPS credentials from 1Password without storing them locally.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **1Password CLI installed**: The `op` command should be available
|
||||||
|
|
||||||
|
- On Ubuntu/Debian: Install from 1Password's official repository
|
||||||
|
- Package name: `1password-cli` (included in base.txt)
|
||||||
|
|
||||||
|
2. **1Password CLI authenticated**: You must be signed in to 1Password CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
op signin
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **jq installed**: For JSON parsing (included in base.txt)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
The credential helper is automatically configured in your `.gitconfig`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[credential]
|
||||||
|
helper = !~/.dotfiles/scripts/git-credential-1password.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Storing Credentials in 1Password
|
||||||
|
|
||||||
|
For each Git HTTPS remote you want to use, create an item in 1Password with:
|
||||||
|
|
||||||
|
1. **Title**: Include the hostname (e.g., "GitHub", "gitlab.example.com", "bitbucket.org")
|
||||||
|
2. **Username field**: Your Git username
|
||||||
|
3. **Password field**: Your Git password/token
|
||||||
|
4. **URL field** (optional but recommended): The full HTTPS URL of the repository
|
||||||
|
|
||||||
|
#### Examples:
|
||||||
|
|
||||||
|
**GitHub Personal Access Token:**
|
||||||
|
|
||||||
|
- Title: "GitHub"
|
||||||
|
- Username: your-github-username
|
||||||
|
- Password: ghp_xxxxxxxxxxxxxxxxxxxx
|
||||||
|
- URL: https://github.com
|
||||||
|
|
||||||
|
**GitLab Token:**
|
||||||
|
|
||||||
|
- Title: "gitlab.example.com"
|
||||||
|
- Username: your-gitlab-username
|
||||||
|
- Password: glpat-xxxxxxxxxxxxxxxxxxxx
|
||||||
|
- URL: https://gitlab.example.com
|
||||||
|
|
||||||
|
### Using with Git
|
||||||
|
|
||||||
|
Once set up, Git operations will automatically prompt 1Password for credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone a private repo
|
||||||
|
git clone https://github.com/user/private-repo.git
|
||||||
|
|
||||||
|
# Push to origin
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Add a new HTTPS remote
|
||||||
|
git remote add upstream https://github.com/upstream/repo.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. When Git needs HTTPS credentials, it calls the credential helper
|
||||||
|
2. The helper searches 1Password for items matching the hostname
|
||||||
|
3. It looks for matches in:
|
||||||
|
- URL fields containing the hostname
|
||||||
|
- Item titles containing the hostname
|
||||||
|
- Additional information containing the hostname
|
||||||
|
4. Returns the username and password to Git
|
||||||
|
5. Git uses these credentials for the operation
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "1Password CLI (op) not found"
|
||||||
|
|
||||||
|
Install 1Password CLI or ensure it's in your PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if installed
|
||||||
|
which op
|
||||||
|
|
||||||
|
# Install if missing (Ubuntu/Debian)
|
||||||
|
curl -sS https://downloads.1password.com/linux/keys/1password.asc | sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
|
||||||
|
echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main' | sudo tee /etc/apt/sources.list.d/1password.list
|
||||||
|
sudo apt update && sudo apt install 1password-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Not signed in to 1Password CLI"
|
||||||
|
|
||||||
|
Sign in to 1Password CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
op signin
|
||||||
|
```
|
||||||
|
|
||||||
|
### "No matching item found"
|
||||||
|
|
||||||
|
- Ensure the 1Password item title or URL contains the Git hostname
|
||||||
|
- Check that the item has username and password fields
|
||||||
|
- Try creating a new item with a clear title matching the hostname
|
||||||
|
|
||||||
|
### Test the Helper Manually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test the credential helper directly
|
||||||
|
echo -e "protocol=https\nhost=github.com\n" | ~/.dotfiles/scripts/git-credential-1password.sh get
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Benefits
|
||||||
|
|
||||||
|
- Credentials are never stored in plain text on disk
|
||||||
|
- Works with 1Password's security features (Touch ID, master password, etc.)
|
||||||
|
- Credentials are fetched fresh each time (no caching)
|
||||||
|
- Works seamlessly with existing 1Password setup
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Only works with HTTPS Git remotes (SSH remotes continue to use SSH keys)
|
||||||
|
- Requires 1Password CLI to be signed in
|
||||||
|
- May prompt for 1Password unlock depending on your security settings
|
||||||
@@ -11,3 +11,5 @@ tree
|
|||||||
htop
|
htop
|
||||||
unzip
|
unzip
|
||||||
zip
|
zip
|
||||||
|
jq
|
||||||
|
1password-cli
|
||||||
|
|||||||
150
scripts/git-credential-1password.sh
Executable file
150
scripts/git-credential-1password.sh
Executable file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Git credential helper for 1Password CLI
|
||||||
|
# This script integrates with Git's credential system to fetch credentials from 1Password
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source utilities for logging
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
if [ -f "$SCRIPT_DIR/utils.sh" ]; then
|
||||||
|
source "$SCRIPT_DIR/utils.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to check if 1Password CLI is available and signed in
|
||||||
|
check_op_cli() {
|
||||||
|
if ! command -v op >/dev/null 2>&1; then
|
||||||
|
echo "Error: 1Password CLI (op) not found. Please install it first." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if signed in
|
||||||
|
if ! op account list >/dev/null 2>&1; then
|
||||||
|
echo "Error: Not signed in to 1Password CLI. Please run 'op signin' first." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse Git credential input
|
||||||
|
parse_input() {
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -z "$line" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$line" in
|
||||||
|
protocol=*)
|
||||||
|
protocol="${line#protocol=}"
|
||||||
|
;;
|
||||||
|
host=*)
|
||||||
|
host="${line#host=}"
|
||||||
|
;;
|
||||||
|
username=*)
|
||||||
|
username="${line#username=}"
|
||||||
|
;;
|
||||||
|
password=*)
|
||||||
|
password="${line#password=}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get credentials from 1Password
|
||||||
|
get_credentials() {
|
||||||
|
local search_term="$1"
|
||||||
|
|
||||||
|
# Try to find item by URL/host first
|
||||||
|
local item_uuid
|
||||||
|
if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" '
|
||||||
|
.[] | select(
|
||||||
|
(.urls[]?.href // "" | test($host; "i")) or
|
||||||
|
(.title | test($host; "i")) or
|
||||||
|
(.additional_information | test($host; "i"))
|
||||||
|
) | .id' | head -1); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$item_uuid" ]; then
|
||||||
|
# Fallback: search by title containing the host
|
||||||
|
if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" '
|
||||||
|
.[] | select(.title | test($host; "i")) | .id' | head -1); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$item_uuid" ]; then
|
||||||
|
echo "No matching item found in 1Password for: $search_term" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the item details
|
||||||
|
local item_json
|
||||||
|
if ! item_json=$(op item get "$item_uuid" --format=json 2>/dev/null); then
|
||||||
|
echo "Failed to retrieve item from 1Password" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract username and password
|
||||||
|
local op_username op_password
|
||||||
|
op_username=$(echo "$item_json" | jq -r '.fields[] | select(.id == "username" or .label == "username") | .value // empty' | head -1)
|
||||||
|
op_password=$(echo "$item_json" | jq -r '.fields[] | select(.id == "repo_token" or .label == "repo_token") | .value // empty' | head -1)
|
||||||
|
|
||||||
|
if [ -z "$op_username" ] || [ -z "$op_password" ]; then
|
||||||
|
echo "Username or password not found in 1Password item" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "username=$op_username"
|
||||||
|
echo "password=$op_password"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main credential helper logic
|
||||||
|
case "${1:-}" in
|
||||||
|
get)
|
||||||
|
# Initialize variables
|
||||||
|
protocol=""
|
||||||
|
host=""
|
||||||
|
username=""
|
||||||
|
password=""
|
||||||
|
|
||||||
|
# Parse input from Git
|
||||||
|
parse_input
|
||||||
|
|
||||||
|
# Only handle HTTPS requests
|
||||||
|
if [ "$protocol" != "https" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check 1Password CLI availability
|
||||||
|
if ! check_op_cli; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Search for credentials
|
||||||
|
if [ -n "$host" ]; then
|
||||||
|
if get_credentials "$host"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If we get here, no credentials were found
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
store)
|
||||||
|
# We don't store credentials in 1Password via this helper
|
||||||
|
# Users should add them manually to 1Password
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
erase)
|
||||||
|
# We don't erase credentials from 1Password via this helper
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {get|store|erase}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -30,3 +30,7 @@ alias install_neofetch='sudo apt install -y neofetch'
|
|||||||
alias memoryinfo='sudo dmidecode -t memory | grep -i "Type:\|Speed:\|Size:"'
|
alias memoryinfo='sudo dmidecode -t memory | grep -i "Type:\|Speed:\|Size:"'
|
||||||
alias install_packages='bash ~/.dotfiles/packages/install.sh ~/.dotfiles'
|
alias install_packages='bash ~/.dotfiles/packages/install.sh ~/.dotfiles'
|
||||||
alias update_list='sudo apt update && sudo apt list --upgradable'
|
alias update_list='sudo apt update && sudo apt list --upgradable'
|
||||||
|
|
||||||
|
# Git + 1Password helpers
|
||||||
|
alias test_git_1p='echo "Testing Git 1Password credential helper for github.com:" && echo -e "protocol=https\nhost=github.com\n" | ~/.dotfiles/scripts/git-credential-1password.sh get'
|
||||||
|
alias op_signin='op signin'
|
||||||
|
|||||||
@@ -14,3 +14,5 @@ format = ssh
|
|||||||
program = /opt/1Password/op-ssh-sign
|
program = /opt/1Password/op-ssh-sign
|
||||||
[commit]
|
[commit]
|
||||||
gpgsign = true
|
gpgsign = true
|
||||||
|
[credential]
|
||||||
|
helper = !~/.dotfiles/scripts/git-credential-1password.sh
|
||||||
|
|||||||
Reference in New Issue
Block a user