Skip to content

Instantly share code, notes, and snippets.

@bogorad
Last active March 5, 2025 09:38
Show Gist options
  • Save bogorad/7207247ec22727466d1e5d4fac3e73c3 to your computer and use it in GitHub Desktop.
Save bogorad/7207247ec22727466d1e5d4fac3e73c3 to your computer and use it in GitHub Desktop.
generate-host-key.sh
#!/usr/bin/env bash
set -euo pipefail
# Function to log error and exit
die() {
echo "ERROR: $1" >&2
exit 1
}
# Function for logging with timestamps
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}
# Function to show usage
usage() {
cat << EOF
Usage: $0 [OPTIONS] HOSTNAME [HOSTNAME...]
Options:
-h, --help Show this help message
Arguments:
HOSTNAME One or more hostnames to process
EOF
exit 1
}
# Function to validate Bitwarden login status
check_rbw_status() {
rbw unlock || die "Failed to login to Bitwarden, try manually via 'rbw login'"
}
# Function to cleanup temporary files
cleanup() {
log "Cleaning up temporary files..."
if [[ -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
log "Removed temporary directory: $TEMP_DIR"
fi
}
# Function to verify sops.yaml syntax
verify_sops_yaml() {
local yaml_file="$1"
if ! sops -v "$yaml_file" &>/dev/null; then
die "YAML validation failed for $yaml_file"
fi
log "YAML validation successful"
}
# Function to backup existing configuration
backup_config() {
local file="$1"
if [[ -f "$file" ]]; then
local backup
backup="${file}.backup-$(date +'%Y%m%d-%H%M%S')"
cp "$file" "$backup"
log "Backup created: $backup"
fi
}
# Function to process a single host
process_host() {
local host="$1"
local key_dir="$TEMP_DIR/$host"
local key_name="${KEY_PREFIX}${host}"
local fake_editor="/tmp/fake-editor.sh"
log "Processing host: $host"
# Validate hostname
[[ $host =~ ^[a-zA-Z0-9-]+$ ]] ||
die "Invalid hostname $host. Use only letters, numbers, and hyphens."
# Check for existing configurations
if [[ -f "$SOPS_YAML" ]] && grep -q "&$host" "$SOPS_YAML" 2>/dev/null; then
die "Host $host already exists in $SOPS_YAML"
fi
rbw get "$key_name" &>/dev/null &&
die "Key '$key_name' already exists in Bitwarden"
# Create directory
mkdir -p "$key_dir" ||
die "Failed to create directory $key_dir"
# Generate SSH key
log "Generating ED25519 SSH key..."
ssh-keygen -t ed25519 -N "" -f "$key_dir/ssh_host_ed25519_key" -q ||
die "Failed to generate SSH key"
# Store private key in Bitwarden
log "Storing private key in Bitwarden..."
# cleanup fake editor
rm -rf "$fake_editor"
# create fake-editor.sh
cat <<EOL > "$fake_editor"
#!/usr/bin/env bash
echo "no-password" >\$1
cat "$key_dir/ssh_host_ed25519_key" >>\$1
EOL
# make it executable
chmod +x "$fake_editor"
# now run rbw that will call our fake-editor
EDITOR="$fake_editor" rbw add "$key_name" ||
die "Failed to store key in Bitwarden"
# Convert SSH key to age format
log "Converting SSH key to age format..."
age_key=$(ssh-to-age < "$key_dir/ssh_host_ed25519_key.pub") ||
die "Failed to convert SSH key to age format"
# Update .sops.yaml
if [[ ! -f "$SOPS_YAML" ]]; then
log "Creating new $SOPS_YAML file..."
cat > "$SOPS_YAML" << EOF
keys:
- &$host $age_key # Added $(date +'%Y-%m-%d')
creation_rules:
- path_regex: secrets/[^/]+\.yaml$
key_groups:
- age:
- *$host
EOF
else
log "Updating existing $SOPS_YAML file..."
sed -i "/^keys:/a\\ - \&$host $age_key # Added $(date +'%Y-%m-%d')" "$SOPS_YAML"
sed -i "/age:/a\\ - *$host" "$SOPS_YAML"
fi
log "Completed processing for host: $host"
}
# Parse command line arguments
HOSTS=()
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-*)
die "Unknown option: $1"
;;
*)
HOSTS+=("$1")
shift
;;
esac
done
# Check if we have any hosts to process
[[ ${#HOSTS[@]} -eq 0 ]] && usage
# Set variables
KEY_PREFIX="ssh-host-key-"
SOPS_YAML="../.sops.yaml"
# Create secure temporary directory
TEMP_DIR=$(mktemp -d) || die "Failed to create temporary directory"
chmod 700 "$TEMP_DIR"
# Check for required tools
for cmd in ssh-to-age rbw ssh-keygen sops; do
command -v "$cmd" &> /dev/null ||
die "$cmd not found. Please install it first."
done
# Check Bitwarden login status
check_rbw_status
# Setup cleanup trap
trap cleanup EXIT
# Backup existing configuration
backup_config "$SOPS_YAML"
# Process each host
for host in "${HOSTS[@]}"; do
process_host "$host"
done
# Verify the YAML
verify_sops_yaml "$SOPS_YAML"
# sync rbw just in case
rbw sync
# update keys in sops secrets
[[ -f "../secrets/secrets.yaml" ]] && sops updatekeys secrets/secrets.yaml
log "Successfully completed all operations"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment