certman/certman.sh
Ruben ab59a58c55 Use install for certificate and key file installation
Replace separate cp and chmod operations with single install commands
for certificate, key, and PEM file installation to ensure proper
permissions and ownership are set in one operation
2026-02-20 23:07:44 +01:00

288 lines
8.9 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
}
setup_directories() {
local dirs=("$CERT_PATH" "$KEY_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 [ "$FULLCHAIN_PEM" = "true" ]; then
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
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
if [ "$FULLCHAIN_PEM" = "true" ] && [ ! -s "$temp_pem" ]; then
echo -e "${RED}Downloaded PEM file is empty for $domain${NC}"
return 1
fi
# Validate certificate and key match
local cert_fingerprint key_fingerprint
cert_fingerprint=$(openssl x509 -in "$temp_cert" -noout -pubkey |
openssl pkey -pubin -outform DER |
openssl dgst -sha256) || true
key_fingerprint=$(openssl pkey -in "$temp_key" -pubout -outform DER |
openssl dgst -sha256) || true
if [ -z "$cert_fingerprint" ] || [ -z "$key_fingerprint" ]; then
echo -e "${RED}Failed to extract fingerprints for $domain${NC}"
return 1
fi
if [ "$cert_fingerprint" != "$key_fingerprint" ]; then
echo -e "${RED}Certificate and key do not match for $domain${NC}"
return 1
fi
if [ "$FULLCHAIN_PEM" = "true" ]; then
local pem_fingerprint
pem_fingerprint=$(openssl x509 -in "$temp_pem" -noout -pubkey |
openssl pkey -pubin -outform DER |
openssl dgst -sha256) || true
if [ -z "$pem_fingerprint" ]; then
echo -e "${RED}Failed to extract PEM fingerprint 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
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 [ ! -f "$final_cert" ] || [ ! -f "$final_key" ]; then
needs_reload=1
elif ! cmp -s "$final_cert" "$temp_cert" || ! cmp -s "$final_key" "$temp_key"; then
needs_reload=1
elif [ "$FULLCHAIN_PEM" = "true" ] && [ -f "$final_pem" ] && ! cmp -s "$final_pem" "$temp_pem"; then
needs_reload=1
elif [ "$FULLCHAIN_PEM" = "true" ] && [ ! -f "$final_pem" ]; then
needs_reload=1
fi
# Install new certificate and key
if [ $needs_reload -eq 1 ]; then
if ! install -m "$CERT_PERMISSIONS" -o "$CERT_OWNER" -g "$CERT_GROUP" "$temp_cert" "$final_cert"; then
echo -e "${RED}Failed to install certificate for $domain${NC}"
return 1
fi
if ! install -m "$KEY_PERMISSIONS" -o "$CERT_OWNER" -g "$CERT_GROUP" "$temp_key" "$final_key"; then
echo -e "${RED}Failed to install private key for $domain${NC}"
return 1
fi
if [ "$FULLCHAIN_PEM" = "true" ]; then
if ! install -m "$KEY_PERMISSIONS" -o "$CERT_OWNER" -g "$CERT_GROUP" "$temp_pem" "$final_pem"; then
echo -e "${RED}Failed to install PEM file for $domain${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