Skip to content

Instantly share code, notes, and snippets.

@brunogama
Created June 1, 2025 05:18
Show Gist options
  • Save brunogama/10600356fd7601159b05df23f6378939 to your computer and use it in GitHub Desktop.
Save brunogama/10600356fd7601159b05df23f6378939 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# dump-api-keys - Export API keys from macOS Keychain as environment variables
# Usage:
# eval $(dump-api-keys) # Load all API keys into environment
# dump-api-keys > keys.env # Save to file for sourcing
# dump-api-keys --pattern FOO # Only dump keys containing FOO
#
# This script exports all API keys (service names ending with _KEY)
# that were stored using the store-api-key script.
set -euo pipefail
# Script name for error messages
readonly SCRIPT_NAME="$(basename "$0")"
# Default pattern matches all keys ending with _KEY
PATTERN=".*API_KEY$"
SAFE_MODE=false
# Function to display usage information
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Export API keys from macOS Keychain as environment variables.
Options:
-p, --pattern PATTERN Only export keys matching PATTERN (regex)
-s, --safe Generate a sourceable file instead of eval-ready output
-h, --help Show this help message
Examples:
# Load all API keys into current shell
eval \$($SCRIPT_NAME)
# Save to file and source it (safer)
$SCRIPT_NAME > ~/.api-keys.env
source ~/.api-keys.env
# Only export keys containing "AWS"
eval \$($SCRIPT_NAME --pattern AWS)
# Export in safe mode (with instructions)
$SCRIPT_NAME --safe > api-keys.sh
Security Note:
Using eval is convenient but can be dangerous. Consider using the
file-based approach (saving to a file and sourcing it) for better security.
EOF
exit 0
}
# Function to display error messages
error() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-p|--pattern)
PATTERN="$2"
shift 2
;;
-s|--safe)
SAFE_MODE=true
shift
;;
-h|--help)
usage
;;
*)
error "Unknown option: $1" 1
;;
esac
done
# Check if running on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
error "This script requires macOS" 2
fi
# Check if security command is available
if ! command -v security &> /dev/null; then
error "The 'security' command is not available" 2
fi
# Function to get all API key service names from keychain
get_api_key_services() {
# Dump keychain and extract service names
# The dump-keychain output includes lines like: "svce"<blob>="SERVICE_NAME"
security dump-keychain 2>/dev/null | \
awk -F'"' '/"svce"<blob>=/ && $4 ~ /'"$PATTERN"'/ {print $4}' | \
sort -u || true
}
# Function to escape a value for safe shell usage
escape_value() {
printf '%q' "$1"
}
# Header for safe mode
if [[ "$SAFE_MODE" == "true" ]]; then
cat << 'EOF'
#!/bin/bash
# Generated by dump-api-keys
# Source this file to load API keys into your environment
# Usage: source this_file.sh
EOF
fi
# Get list of API key services
services=$(get_api_key_services)
if [[ -z "$services" ]]; then
if [[ "$SAFE_MODE" == "true" ]]; then
echo "# No API keys found matching pattern: $PATTERN" >&2
else
echo "# No API keys found matching pattern: $PATTERN" >&2
fi
exit 0
fi
# Export count for reporting
count=0
# Process each service
while IFS= read -r service; do
# Skip empty lines
[[ -z "$service" ]] && continue
# Validate service name (alphanumeric, underscore, dash only)
if ! [[ "$service" =~ ^[A-Za-z0-9_-]+$ ]]; then
echo "# Warning: Skipping invalid service name: $service" >&2
continue
fi
# Retrieve the API key value
if api_key=$(security find-generic-password -a "$USER" -s "$service" -w 2>/dev/null); then
# Escape the value for safe shell usage
escaped_value=$(escape_value "$api_key")
# Output the export statement
echo "export $service=$escaped_value"
((count++))
else
echo "# Warning: Could not retrieve key for: $service" >&2
fi
done <<< "$services"
# Summary in safe mode
if [[ "$SAFE_MODE" == "true" ]]; then
echo ""
echo "# Exported $count API keys"
fi
# If no keys were exported successfully
if [[ $count -eq 0 ]]; then
echo "# No API keys could be retrieved" >&2
exit 1
fi
#!/bin/bash
#
# get-api-key - Retrieve API keys from macOS Keychain
# Usage: get-api-key <service_name>
# Example: get-api-key SERVICEA_API_KEY
#
# This script uses the macOS security command to retrieve API keys
# from the user's login keychain.
set -euo pipefail
# Script name for error messages
readonly SCRIPT_NAME="$(basename "$0")"
# Function to display usage information
usage() {
cat << EOF
Usage: $SCRIPT_NAME <service_name>
Retrieve an API key from the macOS Keychain.
Arguments:
service_name The name/identifier for the API key (e.g., SERVICEA_API_KEY)
Example:
$SCRIPT_NAME SERVICEA_API_KEY
Notes:
- The key must have been previously stored with store-api-key
- You may be prompted to allow access on first use
- The API key is printed to stdout
EOF
exit 1
}
# Function to display error messages
error() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Check if running on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
error "This script requires macOS" 2
fi
# Check if security command is available
if ! command -v security &> /dev/null; then
error "The 'security' command is not available" 2
fi
# Check number of arguments
if [[ $# -ne 1 ]]; then
usage
fi
# Assign argument to variable
readonly SERVICE_NAME="$1"
# Validate service name
if [[ -z "$SERVICE_NAME" ]]; then
error "Service name cannot be empty" 3
fi
# Validate service name format (alphanumeric, underscore, dash)
if ! [[ "$SERVICE_NAME" =~ ^[A-Za-z0-9_-]+$ ]]; then
error "Service name must contain only letters, numbers, underscores, and dashes" 3
fi
# Retrieve the API key from the keychain
# -a: account name (using current user)
# -s: service name
# -w: output password only
API_KEY=$(security find-generic-password \
-a "$USER" \
-s "$SERVICE_NAME" \
-w \
2>/dev/null) || {
error "API key for '$SERVICE_NAME' not found in keychain" 4
}
# Output the API key
echo "$API_KEY"
#!/bin/bash
#
# store-api-key - Store API keys securely in macOS Keychain
# Usage: store-api-key <service_name> <api_key> [--force]
# Example: store-api-key SERVICEA_API_KEY randomapikey
#
# This script uses the macOS security command to store API keys
# in the user's login keychain securely.
set -euo pipefail
# Script name for error messages
readonly SCRIPT_NAME="$(basename "$0")"
# Default settings
FORCE_UPDATE=false
# Function to display usage information
usage() {
cat << EOF
Usage: $SCRIPT_NAME <service_name> <api_key> [--force]
Store an API key securely in the macOS Keychain.
Arguments:
service_name The name/identifier for the API key (e.g., SERVICEA_API_KEY)
api_key The API key value to store
Options:
--force Override existing key without confirmation
Example:
$SCRIPT_NAME SERVICEA_API_KEY randomapikey
$SCRIPT_NAME SERVICEA_API_KEY newkey --force
Notes:
- The key is stored in the user's login keychain
- If a key with the same service name exists, you'll be prompted to confirm unless --force is used
- The script requires macOS and the 'security' command
EOF
exit 1
}
# Function to display error messages
error() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Check if running on macOS
if [[ "$OSTYPE" != "darwin"* ]]; then
error "This script requires macOS" 2
fi
# Check if security command is available
if ! command -v security &> /dev/null; then
error "The 'security' command is not available" 2
fi
# Parse arguments
if [[ $# -lt 2 || $# -gt 3 ]]; then
usage
fi
# Check for force flag
if [[ $# -eq 3 && "$3" == "--force" ]]; then
FORCE_UPDATE=true
elif [[ $# -eq 3 ]]; then
error "Unknown option: $3" 1
fi
# Assign arguments to variables
readonly SERVICE_NAME="$1"
readonly API_KEY="$2"
# Validate service name
if [[ -z "$SERVICE_NAME" ]]; then
error "Service name cannot be empty" 3
fi
# Validate API key
if [[ -z "$API_KEY" ]]; then
error "API key cannot be empty" 3
fi
# Validate service name format (alphanumeric, underscore, dash)
if ! [[ "$SERVICE_NAME" =~ ^[A-Za-z0-9_-]+$ ]]; then
error "Service name must contain only letters, numbers, underscores, and dashes" 3
fi
# Check if the key already exists in the keychain
if security find-generic-password -s "$SERVICE_NAME" &>/dev/null; then
if [[ "$FORCE_UPDATE" == false ]]; then
read -p "API key '$SERVICE_NAME' already exists in keychain. Overwrite? (y/n): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "Operation cancelled. API key not updated."
exit 0
fi
fi
echo "Updating existing API key for '$SERVICE_NAME'..."
else
echo "Creating new API key for '$SERVICE_NAME'..."
fi
# Store the API key in the keychain
# -a: account name (using current user)
# -s: service name
# -w: password/secret
# -U: update if exists
if security add-generic-password \
-a "$USER" \
-s "$SERVICE_NAME" \
-w "$API_KEY" \
-U \
2>/dev/null; then
echo "✅ Successfully stored API key for '$SERVICE_NAME' in keychain"
else
error "Failed to store API key in keychain" 4
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment