From 2b07e1266cc75a13e7b839b39f7719615a5377d3 Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 3 Mar 2025 12:37:55 +0100 Subject: [PATCH] Extend functionality --- README.md | 98 +++++++++++++++++------ certman.sh | 225 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 210 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index e2c3de3..7e6b024 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ # Certwarden Certificate Management -A bash script for managing SSL/TLS certificates through the Certwarden API. This tool provides a simple interface for downloading, installing, and managing certificates on your system. +A bash script for managing SSL/TLS certificates through the Certwarden API. This tool provides both automated and interactive interfaces for downloading, installing, and managing certificates on your system. ## Features -- Download certificates and private keys from Certwarden server -- Automatic installation with proper permissions -- List installed certificates -- Check certificate expiration dates +- Download and verify certificates and private keys from Certwarden server +- Automatic installation with proper permissions and ownership +- Certificate and key pair validation +- Service reload after certificate updates +- Certificate expiration monitoring - Interactive menu-driven interface -- Automated mode support through environment variables +- Automated mode support through environment configuration +- Proper error handling and logging +- Support for multiple certificates ## Prerequisites @@ -31,28 +34,79 @@ cd certman chmod +x certman.sh ``` - 3. Create a `.env` file with your configuration: ```bash -CERTWARDEN_SERVER="certwarden.dmz.skyfritt.net" -API_KEY="" +# Server Configuration +CERTWARDEN_SERVER="certwarden.dmz.skyfritt.net:443" -CERT_NAME="$(hostname).crt" # defaults to hostname -CERT_PATH="/etc/ssl/certs" -KEY_PATH="/etc/ssl/private" +# Certificate Paths +CERT_PATH="/etc/forgejo" +KEY_PATH="/etc/forgejo" +TEMP_PATH="/tmp/certman" +# Service Configuration +SERVICE_NAME="forgejo" +CERT_OWNER="git" +CERT_GROUP="git" +CERT_PERMISSIONS="644" +KEY_PERMISSIONS="600" + +# Certificate Configurations (JSON format) +CERTIFICATES='[ + { + "domain": "example.com", + "cert_api_key": "your_cert_api_key", + "key_api_key": "your_key_api_key" + } +]' + +# Optional: Auto mode configuration AUTO_MODE="false" -TEMP_PATH="/tmp/cert_temp" ``` ### Environment Variables Explained -| Variable | Description | Default Value | Required | -|----------|-------------|---------------|----------| -| CERTWARDEN_SERVER | Certwarden API server hostname | certwarden.dmz.skyfritt.net | Yes | -| API_KEY | Your Certwarden API key | Empty | Yes for auto mode | -| CERT_NAME | Certificate name to manage | $(hostname).crt | Yes | -| CERT_PATH | Directory for certificate storage | /etc/ssl/certs | Yes | -| KEY_PATH | Directory for private key storage | /etc/ssl/private | Yes | -| AUTO_MODE | Enable automated operation | false | No | -| TEMP_PATH | Temporary directory for downloads | /tmp/cert_temp | Yes | +| Variable | Description | Required | +|----------|-------------|----------| +| CERTWARDEN_SERVER | Certwarden API server hostname and port | Yes | +| CERT_PATH | Directory for certificate storage | Yes | +| KEY_PATH | Directory for private key storage | Yes | +| TEMP_PATH | Temporary directory for downloads | Yes | +| SERVICE_NAME | Service to reload after certificate updates | Yes | +| CERT_OWNER | User owner for certificate files | Yes | +| CERT_GROUP | Group owner for certificate files | Yes | +| CERT_PERMISSIONS | Certificate file permissions | Yes | +| KEY_PERMISSIONS | Private key file permissions | Yes | +| CERTIFICATES | JSON array of certificate configurations | Yes | +| AUTO_MODE | Enable automated operation | No | + +## Usage + +### Interactive Mode +Run the script without any arguments: +```bash +./certman.sh +``` + +This will present a menu with the following options: +1. Process all certificates +2. List installed certificates +3. Check certificate expiration +4. Exit + +### Automated Mode +Set `AUTO_MODE="true"` in the `.env` file and run the script. This is suitable for cron jobs. + +### Cron Configuration +Add these lines to your crontab for automated certificate management: +```cron +@reboot sleep 15 && /path/to/certman.sh +5 4 * * 2 /path/to/certman.sh +``` + +## Security Considerations + +- Store the script and `.env` file in a secure location with restricted permissions +- Use appropriate permissions for certificate and key files +- Keep API keys secure and rotate them periodically +- Run the script as a user with appropriate privileges diff --git a/certman.sh b/certman.sh index 96cf004..bea1390 100644 --- a/certman.sh +++ b/certman.sh @@ -1,22 +1,31 @@ #!/bin/bash +# Exit on any error, treat unset variables as errors, and propagate errors in pipelines +set -euo pipefail + +# Load environment variables if [ -f .env ]; then source .env else echo "No .env file found." + exit 1 fi +# Color definitions GREEN='\033[0;32m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# Temporary directory for certs +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + check_requirements() { - local required_commands=("curl" "jq") + local required_commands=("curl" "jq" "openssl") for cmd in "${required_commands[@]}"; do if ! command -v "$cmd" &> /dev/null; then echo -e "${RED}Error: Required command '$cmd' is not installed.${NC}" - echo "Please install it using your package manager." exit 1 fi done @@ -24,42 +33,59 @@ check_requirements() { validate_api_key() { local api_key=$1 - if [[ ! $api_key =~ ^[A-Za-z0-9_-]{32,}$ ]]; then - return 1 - fi - return 0 + [[ $api_key =~ ^[A-Za-z0-9_-]{32,}$ ]] } setup_directories() { - if ! mkdir -p "$CERT_PATH" "$KEY_PATH" "$TEMP_PATH"; then - echo -e "${RED}Error: Failed to create required directories${NC}" - exit 1 - fi + local dirs=("$CERT_PATH" "$KEY_PATH" "$TEMP_PATH") + for dir in "${dirs[@]}"; do + if ! mkdir -p "$dir"; then + echo -e "${RED}Error: Failed to create directory: $dir${NC}" + exit 1 + fi + done } -download_certificate() { - local cert_name=$1 - local api_key=$2 +download_and_verify_cert() { + local domain=$1 + local cert_api_key=$2 + local key_api_key=$3 + local temp_cert="$TEMP_DIR/$domain.crt" + local temp_key="$TEMP_DIR/$domain.key" - echo -e "${BLUE}Downloading certificate for $cert_name...${NC}" + echo -e "${BLUE}Processing certificate for $domain${NC}" # Download certificate - if curl -fL -o "$TEMP_PATH/$cert_name.crt" \ - -H "X-API-Key: $api_key" \ - "https://$CERTWARDEN_SERVER/certwarden/api/v1/download/certificates/$cert_name"; then - echo -e "${GREEN}Certificate downloaded successfully${NC}" - else - echo -e "${RED}Failed to download certificate${NC}" + if ! curl -fL -o "$temp_cert" -H "X-API-Key: $cert_api_key" \ + "https://$CERTWARDEN_SERVER/certwarden/api/v1/download/certificates/$domain"; then + echo -e "${RED}Failed to download certificate for $domain${NC}" return 1 fi # Download private key - if curl -fL -o "$TEMP_PATH/$cert_name.key" \ - -H "X-API-Key: $api_key" \ - "https://$CERTWARDEN_SERVER/certwarden/api/v1/download/privatekeys/$cert_name"; then - echo -e "${GREEN}Private key downloaded successfully${NC}" - else - echo -e "${RED}Failed to download private key${NC}" + if ! curl -fL -o "$temp_key" -H "X-API-Key: $key_api_key" \ + "https://$CERTWARDEN_SERVER/certwarden/api/v1/download/privatekeys/$domain"; then + echo -e "${RED}Failed to download private key for $domain${NC}" + return 1 + fi + + # Verify files are not empty + if [ ! -s "$temp_cert" ] || [ ! -s "$temp_key" ]; then + echo -e "${RED}Downloaded files are empty for $domain${NC}" + return 1 + fi + + # Validate certificate and key match + local cert_fingerprint + cert_fingerprint=$(openssl x509 -in "$temp_cert" -noout -pubkey | + openssl pkey -pubin -outform DER 2>/dev/null | + openssl dgst -sha256) + local key_fingerprint + key_fingerprint=$(openssl pkey -in "$temp_key" -pubout -outform DER 2>/dev/null | + openssl dgst -sha256) + + if [ "$cert_fingerprint" != "$key_fingerprint" ]; then + echo -e "${RED}Certificate and key do not match for $domain${NC}" return 1 fi @@ -67,41 +93,93 @@ download_certificate() { } install_certificate() { - local cert_name=$1 + local domain=$1 + local final_cert="$CERT_PATH/$domain.crt" + local final_key="$KEY_PATH/$domain.key" + local temp_cert="$TEMP_DIR/$domain.crt" + local temp_key="$TEMP_DIR/$domain.key" + local needs_reload=0 - # Move files to their final locations - if sudo mv "$TEMP_PATH/$cert_name.crt" "$CERT_PATH/" && \ - sudo mv "$TEMP_PATH/$cert_name.key" "$KEY_PATH/"; then - echo -e "${GREEN}Certificate and key installed successfully${NC}" - - # Set appropriate permissions - sudo chmod 644 "$CERT_PATH/$cert_name.crt" - sudo chmod 600 "$KEY_PATH/$cert_name.key" - - return 0 + # Check if certificate needs updating + if [ -f "$final_cert" ]; then + if ! cmp -s "$final_cert" "$temp_cert"; then + needs_reload=1 + fi else - echo -e "${RED}Failed to install certificate and key${NC}" - return 1 + needs_reload=1 + fi + + # Install new certificate and key + if [ $needs_reload -eq 1 ]; then + if ! cp -f "$temp_cert" "$final_cert" || ! cp -f "$temp_key" "$final_key"; then + echo -e "${RED}Failed to install certificate files for $domain${NC}" + return 1 + fi + + # Set permissions and ownership + if ! chown "$CERT_OWNER:$CERT_GROUP" "$final_cert" "$final_key" || \ + ! chmod "$CERT_PERMISSIONS" "$final_cert" || \ + ! chmod "$KEY_PERMISSIONS" "$final_key"; then + echo -e "${RED}Failed to set permissions for $domain${NC}" + return 1 + fi + + echo -e "${GREEN}Certificate updated for $domain${NC}" + return 0 + fi + + echo -e "${BLUE}Certificate already up to date for $domain${NC}" + return 2 +} + +process_certificates() { + local service_reloaded=0 + local certs_array + certs_array=$(echo "$CERTIFICATES" | jq -c '.[]') + + while IFS= read -r cert; do + local domain=$(echo "$cert" | jq -r '.domain') + local cert_api_key=$(echo "$cert" | jq -r '.cert_api_key') + local key_api_key=$(echo "$cert" | jq -r '.key_api_key') + + if download_and_verify_cert "$domain" "$cert_api_key" "$key_api_key"; then + if install_certificate "$domain"; then + service_reloaded=1 + fi + fi + done <<< "$certs_array" + + # Reload service if needed + if [ $service_reloaded -eq 1 ]; then + echo -e "${BLUE}Reloading $SERVICE_NAME service...${NC}" + if systemctl reload "$SERVICE_NAME"; then + echo -e "${GREEN}Service reloaded successfully${NC}" + else + echo -e "${RED}Failed to reload service${NC}" + return 1 + fi fi } -# Main menu function -main_menu() { - # If in auto mode and we have cert name and API key, process automatically - if [ "$AUTO_MODE" = "true" ] && [ -n "$CERT_NAME" ] && [ -n "$API_KEY" ]; then - if validate_api_key "$API_KEY"; then - if download_certificate "$CERT_NAME" "$API_KEY"; then - install_certificate "$CERT_NAME" - fi - else - echo -e "${RED}Invalid API key format in .env file${NC}" +check_cert_expiration() { + echo -e "\n${BLUE}Certificate Expiration Dates:${NC}" + for cert in "$CERT_PATH"/*.crt; do + if [ -f "$cert" ]; then + echo -n "$(basename "$cert"): " + openssl x509 -enddate -noout -in "$cert" fi + done +} + +main_menu() { + if [ "$AUTO_MODE" = "true" ]; then + process_certificates exit 0 fi while true; do - echo -e "\n${BLUE}Certwarden Certificate Management${NC}" - echo "1. Download and install new certificate" + echo -e "\n${BLUE}Certificate Management Menu${NC}" + echo "1. Process all certificates" echo "2. List installed certificates" echo "3. Check certificate expiration" echo "4. Exit" @@ -109,43 +187,11 @@ main_menu() { read -r -p "Select an option (1-4): " choice case $choice in - 1) - if [ -z "$CERT_NAME" ]; then - read -r -p "Enter certificate name: " CERT_NAME - fi - if [ -z "$API_KEY" ]; then - read -r -p "Enter API key: " API_KEY - fi - - if ! validate_api_key "$API_KEY"; then - echo -e "${RED}Invalid API key format${NC}" - continue - fi - - if download_certificate "$CERT_NAME" "$API_KEY"; then - install_certificate "$CERT_NAME" - fi - ;; - 2) - echo -e "\n${BLUE}Installed Certificates:${NC}" - ls -l "$CERT_PATH"/*.crt 2>/dev/null || echo "No certificates found" - ;; - 3) - echo -e "\n${BLUE}Certificate Expiration Dates:${NC}" - for cert in "$CERT_PATH"/*.crt; do - if [ -f "$cert" ]; then - echo -n "$(basename "$cert"): " - openssl x509 -enddate -noout -in "$cert" - fi - done - ;; - 4) - echo -e "${GREEN}Exiting...${NC}" - exit 0 - ;; - *) - echo -e "${RED}Invalid option${NC}" - ;; + 1) process_certificates ;; + 2) ls -l "$CERT_PATH"/*.crt 2>/dev/null || echo "No certificates found" ;; + 3) check_cert_expiration ;; + 4) echo -e "${GREEN}Exiting...${NC}"; exit 0 ;; + *) echo -e "${RED}Invalid option${NC}" ;; esac done } @@ -153,7 +199,4 @@ main_menu() { # Script initialization check_requirements setup_directories - -# Start the script -echo -e "${GREEN}Welcome to Certwarden Certificate Management${NC}" main_menu