Implement dual-key git access for dev users
- Generate server-side SSH keypair for git access from server - Upload both laptop key and server key to Forgejo - Add mandatory key revocation in dev-remove.sh - Fix: use forgejo@ instead of git@ for SSH URLs - Keys named username-laptop and username-devserver - Key comment includes DO-NOT-REUSE warning Closes ops-jrz1-rfx
This commit is contained in:
parent
99b187fa5a
commit
d9c1848e88
|
|
@ -76,7 +76,7 @@ create_user() {
|
||||||
log_info "Added to devs group"
|
log_info "Added to devs group"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up SSH key
|
# Set up SSH directory and login key (authorized_keys)
|
||||||
local ssh_dir="/home/$username/.ssh"
|
local ssh_dir="/home/$username/.ssh"
|
||||||
mkdir -p "$ssh_dir"
|
mkdir -p "$ssh_dir"
|
||||||
echo "$ssh_key" > "$ssh_dir/authorized_keys"
|
echo "$ssh_key" > "$ssh_dir/authorized_keys"
|
||||||
|
|
@ -84,9 +84,28 @@ create_user() {
|
||||||
chmod 600 "$ssh_dir/authorized_keys"
|
chmod 600 "$ssh_dir/authorized_keys"
|
||||||
chown -R "$username:users" "$ssh_dir"
|
chown -R "$username:users" "$ssh_dir"
|
||||||
if [[ "$user_exists" == true ]]; then
|
if [[ "$user_exists" == true ]]; then
|
||||||
log_info "Updated SSH key"
|
log_info "Updated SSH login key"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Generate server-side keypair for git access (if not exists)
|
||||||
|
local server_key="$ssh_dir/id_ed25519"
|
||||||
|
if [[ ! -f "$server_key" ]]; then
|
||||||
|
log_info "Generating server-side SSH key for git access..."
|
||||||
|
sudo -u "$username" ssh-keygen -t ed25519 \
|
||||||
|
-f "$server_key" \
|
||||||
|
-N '' \
|
||||||
|
-C "$username@jrz1-server-DO-NOT-REUSE" \
|
||||||
|
>/dev/null 2>&1
|
||||||
|
log_info "Server-side SSH key generated"
|
||||||
|
else
|
||||||
|
log_info "Server-side SSH key already exists"
|
||||||
|
fi
|
||||||
|
# Ensure strict permissions
|
||||||
|
chmod 700 "$ssh_dir"
|
||||||
|
chmod 600 "$server_key"
|
||||||
|
chmod 644 "$server_key.pub"
|
||||||
|
chown -R "$username:users" "$ssh_dir"
|
||||||
|
|
||||||
# Set up user's shell config (may not exist on NixOS)
|
# Set up user's shell config (may not exist on NixOS)
|
||||||
# .profile = login shells (SSH), .bashrc = interactive non-login
|
# .profile = login shells (SSH), .bashrc = interactive non-login
|
||||||
local profile="/home/$username/.profile"
|
local profile="/home/$username/.profile"
|
||||||
|
|
@ -183,9 +202,37 @@ EOF
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper function to upload a single SSH key to Forgejo
|
||||||
|
upload_forgejo_key() {
|
||||||
|
local username="$1"
|
||||||
|
local pubkey="$2"
|
||||||
|
local title="$3"
|
||||||
|
local token="$4"
|
||||||
|
local forgejo_url="http://localhost:3000"
|
||||||
|
|
||||||
|
local http_code
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X POST "$forgejo_url/api/v1/admin/users/$username/keys" \
|
||||||
|
-H "Authorization: token $token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"title\": \"$title\",
|
||||||
|
\"key\": \"$pubkey\",
|
||||||
|
\"read_only\": false
|
||||||
|
}" 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ "$http_code" == "201" ]]; then
|
||||||
|
log_info "Forgejo key '$title' added"
|
||||||
|
elif [[ "$http_code" == "422" ]]; then
|
||||||
|
log_info "Forgejo key '$title' already exists"
|
||||||
|
else
|
||||||
|
log_warn "Forgejo key '$title' upload returned HTTP $http_code"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
provision_forgejo() {
|
provision_forgejo() {
|
||||||
local username="$1"
|
local username="$1"
|
||||||
local ssh_key="$2"
|
local login_key="$2"
|
||||||
local token_file="/run/secrets/forgejo-api-token"
|
local token_file="/run/secrets/forgejo-api-token"
|
||||||
local forgejo_url="http://localhost:3000"
|
local forgejo_url="http://localhost:3000"
|
||||||
|
|
||||||
|
|
@ -244,23 +291,18 @@ EOF
|
||||||
log_info "Credentials written to ~/.forgejo-credentials"
|
log_info "Credentials written to ~/.forgejo-credentials"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload SSH key to Forgejo (ignore if already exists)
|
# Upload BOTH keys to Forgejo:
|
||||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
# 1. Login key (user's laptop key) - for direct laptop→git access
|
||||||
-X POST "$forgejo_url/api/v1/admin/users/$username/keys" \
|
upload_forgejo_key "$username" "$login_key" "$username-laptop" "$token"
|
||||||
-H "Authorization: token $token" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{
|
|
||||||
\"title\": \"dev-server-key\",
|
|
||||||
\"key\": \"$ssh_key\",
|
|
||||||
\"read_only\": false
|
|
||||||
}" 2>/dev/null) || true
|
|
||||||
|
|
||||||
if [[ "$http_code" == "201" ]]; then
|
# 2. Server-side key - for git access from within the server
|
||||||
log_info "SSH key added to Forgejo"
|
local server_pubkey_file="/home/$username/.ssh/id_ed25519.pub"
|
||||||
elif [[ "$http_code" == "422" ]]; then
|
if [[ -r "$server_pubkey_file" ]]; then
|
||||||
log_info "SSH key already in Forgejo"
|
local server_pubkey
|
||||||
|
server_pubkey=$(cat "$server_pubkey_file")
|
||||||
|
upload_forgejo_key "$username" "$server_pubkey" "$username-devserver" "$token"
|
||||||
else
|
else
|
||||||
log_warn "Forgejo key upload returned HTTP $http_code (may already exist)"
|
log_warn "Server-side key not found at $server_pubkey_file"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,7 +339,7 @@ print_onboarding() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "## Git Access"
|
echo "## Git Access"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Clone repos: git clone git@git.clarun.xyz:org/repo.git"
|
echo " Clone repos: git clone forgejo@git.clarun.xyz:org/repo.git"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Forgejo account: $username"
|
echo " Forgejo account: $username"
|
||||||
echo " Credentials file: ~/.forgejo-credentials"
|
echo " Credentials file: ~/.forgejo-credentials"
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@
|
||||||
# - Home directory (or archives if --archive flag)
|
# - Home directory (or archives if --archive flag)
|
||||||
# - Maubot plugin symlinks
|
# - Maubot plugin symlinks
|
||||||
#
|
#
|
||||||
# NOTE: Does NOT delete Forgejo account automatically (too dangerous).
|
# NOTE: Revokes Forgejo SSH keys automatically (prevents zombie access).
|
||||||
# Manual suspension required via Forgejo admin UI.
|
# Does NOT delete Forgejo account (too dangerous - may delete repos).
|
||||||
|
# Manual account suspension still recommended via Forgejo admin UI.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -28,6 +29,75 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
||||||
log_dry() { echo -e "${CYAN}[DRY-RUN]${NC} $1"; }
|
log_dry() { echo -e "${CYAN}[DRY-RUN]${NC} $1"; }
|
||||||
|
|
||||||
|
revoke_forgejo_keys() {
|
||||||
|
local username="$1"
|
||||||
|
local token_file="/run/secrets/forgejo-api-token"
|
||||||
|
local forgejo_url="http://localhost:3000"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
log_dry "Would revoke Forgejo SSH keys for '$username'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if token file exists
|
||||||
|
if [[ ! -r "$token_file" ]]; then
|
||||||
|
log_warn "Forgejo API token not found - cannot revoke keys automatically"
|
||||||
|
log_warn "MANUAL ACTION REQUIRED: Delete SSH keys for '$username' in Forgejo"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local token
|
||||||
|
token=$(cat "$token_file")
|
||||||
|
|
||||||
|
log_info "Revoking Forgejo SSH keys for '$username'..."
|
||||||
|
|
||||||
|
# Get list of user's keys
|
||||||
|
local keys_json
|
||||||
|
keys_json=$(curl -s \
|
||||||
|
-H "Authorization: token $token" \
|
||||||
|
"$forgejo_url/api/v1/admin/users/$username/keys" 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ -z "$keys_json" || "$keys_json" == "null" ]]; then
|
||||||
|
log_info "No Forgejo keys found for '$username'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse key IDs and delete each one
|
||||||
|
# Using grep/sed since jq may not be available
|
||||||
|
local key_ids
|
||||||
|
key_ids=$(echo "$keys_json" | grep -o '"id":[0-9]*' | sed 's/"id"://' || true)
|
||||||
|
|
||||||
|
if [[ -z "$key_ids" ]]; then
|
||||||
|
log_info "No Forgejo keys found for '$username'"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local revoked=0
|
||||||
|
local failed=0
|
||||||
|
for key_id in $key_ids; do
|
||||||
|
local http_code
|
||||||
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X DELETE "$forgejo_url/api/v1/admin/users/$username/keys/$key_id" \
|
||||||
|
-H "Authorization: token $token" 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ "$http_code" == "204" ]]; then
|
||||||
|
((revoked++))
|
||||||
|
else
|
||||||
|
log_warn "Failed to revoke key $key_id (HTTP $http_code)"
|
||||||
|
((failed++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $revoked -gt 0 ]]; then
|
||||||
|
log_info "Revoked $revoked Forgejo SSH key(s)"
|
||||||
|
fi
|
||||||
|
if [[ $failed -gt 0 ]]; then
|
||||||
|
log_warn "$failed key(s) failed to revoke - manual cleanup may be needed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: $0 <username> [--archive] [--dry-run]"
|
echo "Usage: $0 <username> [--archive] [--dry-run]"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -223,6 +293,9 @@ main() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Revoke Forgejo SSH keys BEFORE removing user (prevents zombie access)
|
||||||
|
revoke_forgejo_keys "$username"
|
||||||
|
|
||||||
remove_maubot_symlinks "$username"
|
remove_maubot_symlinks "$username"
|
||||||
|
|
||||||
if [[ "$archive" == true ]]; then
|
if [[ "$archive" == true ]]; then
|
||||||
|
|
@ -233,14 +306,14 @@ main() {
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
log_dry "Dry run complete - no changes made"
|
log_dry "Dry run complete - no changes made"
|
||||||
log_dry "Would print Forgejo suspension reminder"
|
|
||||||
else
|
else
|
||||||
log_info "Dev '$username' removed successfully"
|
log_info "Dev '$username' removed successfully"
|
||||||
|
|
||||||
# Remind admin to handle Forgejo account
|
# Remind admin to handle Forgejo account (keys already revoked)
|
||||||
echo ""
|
echo ""
|
||||||
log_warn "ACTION REQUIRED: Suspend Forgejo account for '$username'"
|
log_warn "RECOMMENDED: Suspend Forgejo account for '$username'"
|
||||||
echo " Visit: https://git.clarun.xyz/admin/users"
|
echo " SSH keys have been revoked automatically."
|
||||||
|
echo " To fully disable the account, visit: https://git.clarun.xyz/admin/users"
|
||||||
echo " Find user '$username' and either:"
|
echo " Find user '$username' and either:"
|
||||||
echo " - Prohibit login (keeps repos/PRs intact)"
|
echo " - Prohibit login (keeps repos/PRs intact)"
|
||||||
echo " - Delete user (WARNING: may delete their repos)"
|
echo " - Delete user (WARNING: may delete their repos)"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue