certman/certman.sh
Ruben 4c68bc5d2a Improve certificate and key permission handling
Separate permission settings for certificate and key files
Add specific permissions for key files
Maintain consistent error handling for all file operations
2026-02-20 22:47:54 +01:00

292 lines
8.8 KiB
Bash
Executable file

#!/bin/bash
# Exit on any error, treat unset variables as errors, and propagate errors in pipelines
set -euo pipefail
load_env() {
local paths=(
"$(pwd)/.env"
"$(dirname "$(realpath "$0")")/.env"
"$(dirname "$(realpath "$0")")/.env.$(basename "$(realpath "$0")")"
"$(dirname "$(readlink "$0")")/.env.$(basename "$0")"
"/opt/skyfritt-tools-env/.env.$(basename "$0")"
)
for path in "${paths[@]}"; do
if [[ -f "$path" ]]; then
source "$path" && return 0
fi
done
echo "Error: .env file not found in any location" >&2
exit 1
}
load_env || exit 1
AUTO_MODE="false"
FORCE_UPDATE="false"
SERVICE_SUPPORTS="${SERVICE_SUPPORTS:-reload}"
FULLCHAIN_PEM="${FULLCHAIN_PEM:-true}"
while [[ $# -gt 0 ]]; do
case $1 in
--silent)
AUTO_MODE="true"
shift
;;
--force)
FORCE_UPDATE="true"
shift
;;
--disable-pem)
FULLCHAIN_PEM="false"
shift
;;
--restart)
SERVICE_SUPPORTS="restart"
shift
;;
*)
shift
;;
esac
done
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
check_requirements() {
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}"
exit 1
fi
done
}
validate_api_key() {
local api_key=$1
[[ $api_key =~ ^[A-Za-z0-9_-]{32,}$ ]]
}
setup_directories() {
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_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"
local temp_pem="$TEMP_DIR/$domain.pem"
echo -e "${BLUE}Processing certificate for $domain${NC}"
# Download certificate
if ! curl -s -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 -s -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
# Download fullchain PEM file
if ! curl -s -fL -o "$temp_pem" -H "X-API-Key: $cert_api_key.$key_api_key" \
"https://$CERTWARDEN_SERVER/certwarden/api/v1/download/privatecertchains/$domain"; then
echo -e "${RED}Failed to download fullchain PEM file for $domain${NC}"
return 1
fi
# Verify files are not empty
if [ ! -s "$temp_cert" ] || [ ! -s "$temp_key" ] || [ ! -s "$temp_pem" ]; 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)
local pem_fingerprint
pem_fingerprint=$(openssl x509 -in "$temp_pem" -noout -pubkey |
openssl pkey -pubin -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
if [[ "$cert_fingerprint" != "$pem_fingerprint" ]]; then
echo -e "${RED}Certificate and PEM file do not match for $domain${NC}"
return 1
fi
return 0
}
install_certificate() {
local domain=$1
local final_cert="$CERT_PATH/$domain.crt"
local final_key="$KEY_PATH/$domain.key"
local final_pem="$KEY_PATH/$domain.pem"
local temp_cert="$TEMP_DIR/$domain.crt"
local temp_key="$TEMP_DIR/$domain.key"
local temp_pem="$TEMP_DIR/$domain.pem"
local needs_reload=0
# Check if certificate needs updating
if [ "$FORCE_UPDATE" = "true" ]; then
needs_reload=1
elif [ "$FULLCHAIN_PEM" = "true" ] && [ -f "$final_pem" ]; then
if ! cmp -s "$final_pem" "$temp_pem"; then
needs_reload=1
fi
elif [ -f "$final_cert" ]; then
if ! cmp -s "$final_cert" "$temp_cert"; then
needs_reload=1
fi
else
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 for cert and key separately
if ! chown "$CERT_OWNER:$CERT_GROUP" "$final_cert" || \
! chmod "$CERT_PERMISSIONS" "$final_cert"; then
echo -e "${RED}Failed to set permissions for $final_cert${NC}"
return 1
fi
if ! chown "$CERT_OWNER:$CERT_GROUP" "$final_key" || \
! chmod "$KEY_PERMISSIONS" "$final_key"; then
echo -e "${RED}Failed to set permissions for $final_key${NC}"
return 1
fi
if [ "$FULLCHAIN_PEM" = "true" ]; then
if ! cp -f "$temp_pem" "$final_pem"; then
echo -e "${RED}Failed to install PEM file for $domain${NC}"
return 1
fi
if ! chown "$CERT_OWNER:$CERT_GROUP" "$final_pem" || \
! chmod "$KEY_PERMISSIONS" "$final_pem"; then
echo -e "${RED}Failed to set permissions for $final_pem${NC}"
return 1
fi
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 cert_api_key key_api_key
domain=$(echo "$cert" | jq -r '.domain')
cert_api_key=$(echo "$cert" | jq -r '.cert_api_key')
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
if [[ $SERVICE_SUPPORTS = reload ]]; 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
elif [[ $SERVICE_SUPPORTS = restart ]]; then
echo -e "${BLUE}Restarting $SERVICE_NAME service...${NC}"
if systemctl restart "$SERVICE_NAME"; then
echo -e "${GREEN}Service restarted successfully${NC}"
else
echo -e "${RED}Failed to restart service${NC}"
return 1
fi
fi
fi
}
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" ] || [ "$FORCE_UPDATE" = "true" ]; then
process_certificates
exit 0
fi
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. Force update all certificates"
read -r -p "Select an option (1-4): " choice
case $choice in
1) FORCE_UPDATE="false"; process_certificates ;;
2) ls -l "$CERT_PATH"/*.crt 2>/dev/null || echo "No certificates found" ;;
3) check_cert_expiration ;;
4) FORCE_UPDATE="true"; process_certificates; FORCE_UPDATE="false" ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
}
# Script initialization
check_requirements
setup_directories
main_menu