diff --git a/scripts/dev-add.sh b/scripts/dev-add.sh index 7e447d4..1841daa 100755 --- a/scripts/dev-add.sh +++ b/scripts/dev-add.sh @@ -76,7 +76,7 @@ create_user() { log_info "Added to devs group" fi - # Set up SSH key + # Set up SSH directory and login key (authorized_keys) local ssh_dir="/home/$username/.ssh" mkdir -p "$ssh_dir" echo "$ssh_key" > "$ssh_dir/authorized_keys" @@ -84,9 +84,28 @@ create_user() { chmod 600 "$ssh_dir/authorized_keys" chown -R "$username:users" "$ssh_dir" if [[ "$user_exists" == true ]]; then - log_info "Updated SSH key" + log_info "Updated SSH login key" 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) # .profile = login shells (SSH), .bashrc = interactive non-login local profile="/home/$username/.profile" @@ -183,9 +202,37 @@ EOF 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() { local username="$1" - local ssh_key="$2" + local login_key="$2" local token_file="/run/secrets/forgejo-api-token" local forgejo_url="http://localhost:3000" @@ -244,23 +291,18 @@ EOF log_info "Credentials written to ~/.forgejo-credentials" fi - # Upload SSH key to Forgejo (ignore if already exists) - 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\": \"dev-server-key\", - \"key\": \"$ssh_key\", - \"read_only\": false - }" 2>/dev/null) || true + # Upload BOTH keys to Forgejo: + # 1. Login key (user's laptop key) - for direct laptop→git access + upload_forgejo_key "$username" "$login_key" "$username-laptop" "$token" - if [[ "$http_code" == "201" ]]; then - log_info "SSH key added to Forgejo" - elif [[ "$http_code" == "422" ]]; then - log_info "SSH key already in Forgejo" + # 2. Server-side key - for git access from within the server + local server_pubkey_file="/home/$username/.ssh/id_ed25519.pub" + if [[ -r "$server_pubkey_file" ]]; then + local server_pubkey + server_pubkey=$(cat "$server_pubkey_file") + upload_forgejo_key "$username" "$server_pubkey" "$username-devserver" "$token" 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 } @@ -297,7 +339,7 @@ print_onboarding() { echo "" echo "## Git Access" 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 " Forgejo account: $username" echo " Credentials file: ~/.forgejo-credentials" diff --git a/scripts/dev-remove.sh b/scripts/dev-remove.sh index 328bbea..9dba172 100755 --- a/scripts/dev-remove.sh +++ b/scripts/dev-remove.sh @@ -7,8 +7,9 @@ # - Home directory (or archives if --archive flag) # - Maubot plugin symlinks # -# NOTE: Does NOT delete Forgejo account automatically (too dangerous). -# Manual suspension required via Forgejo admin UI. +# NOTE: Revokes Forgejo SSH keys automatically (prevents zombie access). +# Does NOT delete Forgejo account (too dangerous - may delete repos). +# Manual account suspension still recommended via Forgejo admin UI. 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_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() { echo "Usage: $0 [--archive] [--dry-run]" echo "" @@ -223,6 +293,9 @@ main() { fi fi + # Revoke Forgejo SSH keys BEFORE removing user (prevents zombie access) + revoke_forgejo_keys "$username" + remove_maubot_symlinks "$username" if [[ "$archive" == true ]]; then @@ -233,14 +306,14 @@ main() { if [[ "$DRY_RUN" == true ]]; then log_dry "Dry run complete - no changes made" - log_dry "Would print Forgejo suspension reminder" else log_info "Dev '$username' removed successfully" - # Remind admin to handle Forgejo account + # Remind admin to handle Forgejo account (keys already revoked) echo "" - log_warn "ACTION REQUIRED: Suspend Forgejo account for '$username'" - echo " Visit: https://git.clarun.xyz/admin/users" + log_warn "RECOMMENDED: Suspend Forgejo account for '$username'" + 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 " - Prohibit login (keeps repos/PRs intact)" echo " - Delete user (WARNING: may delete their repos)"