From 19f72b1386759d181daabb6eb776b959f5068a0b Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 16 Sep 2025 10:54:36 +0200 Subject: [PATCH] Update self-signed SSL cert logic to use correct IP vs DNS name labels (#1223) --- apps/server/entrypoint.sh | 47 +++++++++++++++++- dockerfiles/all-in-one/s6-scripts/init/script | 49 +++++++++++++++++-- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/apps/server/entrypoint.sh b/apps/server/entrypoint.sh index fa60f1333..7a0c885bb 100644 --- a/apps/server/entrypoint.sh +++ b/apps/server/entrypoint.sh @@ -36,6 +36,38 @@ needs_cert_regeneration() { return 1 } +# Function to check if a string is an IP address (IPv4 or IPv6) +is_ip_address() { + local value="$1" + + # Check for IPv4 pattern + if echo "$value" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then + # Validate each octet is <= 255 + local valid=1 + # Use a simple approach to split the IP address + local o1=$(echo "$value" | cut -d. -f1) + local o2=$(echo "$value" | cut -d. -f2) + local o3=$(echo "$value" | cut -d. -f3) + local o4=$(echo "$value" | cut -d. -f4) + for octet in "$o1" "$o2" "$o3" "$o4"; do + if [ "$octet" -gt 255 ]; then + valid=0 + break + fi + done + if [ "$valid" -eq 1 ]; then + return 0 # It's a valid IPv4 + fi + fi + + # Check for IPv6 pattern (simplified check) + if echo "$value" | grep -qE '^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$|^::1$|^::$'; then + return 0 # It's likely IPv6 + fi + + return 1 # Not an IP address +} + # Generate self-signed SSL certificate if not exists or hostname changed if needs_cert_regeneration; then echo "Generating new SSL certificate (10 years validity)..." @@ -49,12 +81,23 @@ if needs_cert_regeneration; then -out /etc/nginx/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" else - # Generate certificate with the hostname and include localhost as SAN + # Determine if the hostname is an IP address or a DNS name + if is_ip_address "$HOSTNAME_VALUE"; then + # It's an IP address - use IP: prefix in SAN + SAN_ENTRY="IP:${HOSTNAME_VALUE}" + echo "Detected IP address: ${HOSTNAME_VALUE}" + else + # It's a DNS name - use DNS: prefix in SAN + SAN_ENTRY="DNS:${HOSTNAME_VALUE}" + echo "Detected hostname: ${HOSTNAME_VALUE}" + fi + + # Generate certificate with the appropriate SAN entry openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/key.pem \ -out /etc/nginx/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ - -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" + -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1" fi # Set proper permissions diff --git a/dockerfiles/all-in-one/s6-scripts/init/script b/dockerfiles/all-in-one/s6-scripts/init/script index ecff5bea2..dfe3e8a4e 100644 --- a/dockerfiles/all-in-one/s6-scripts/init/script +++ b/dockerfiles/all-in-one/s6-scripts/init/script @@ -176,6 +176,38 @@ needs_cert_regeneration() { return 1 } +# Function to check if a string is an IP address (IPv4 or IPv6) +is_ip_address() { + local value="$1" + + # Check for IPv4 pattern + if echo "$value" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then + # Validate each octet is <= 255 + local valid=1 + # Use a simple approach to split the IP address + local o1=$(echo "$value" | cut -d. -f1) + local o2=$(echo "$value" | cut -d. -f2) + local o3=$(echo "$value" | cut -d. -f3) + local o4=$(echo "$value" | cut -d. -f4) + for octet in "$o1" "$o2" "$o3" "$o4"; do + if [ "$octet" -gt 255 ]; then + valid=0 + break + fi + done + if [ "$valid" -eq 1 ]; then + return 0 # It's a valid IPv4 + fi + fi + + # Check for IPv6 pattern (simplified check) + if echo "$value" | grep -qE '^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$|^::1$|^::$'; then + return 0 # It's likely IPv6 + fi + + return 1 # Not an IP address +} + # Generate SSL certificates if needed or hostname changed if needs_cert_regeneration; then log 0 "" @@ -199,19 +231,30 @@ if needs_cert_regeneration; then >/dev/null 2>&1 fi else - # Generate certificate with the hostname and include localhost as SAN + # Determine if the hostname is an IP address or a DNS name + if is_ip_address "$HOSTNAME_VALUE"; then + # It's an IP address - use IP: prefix in SAN + SAN_ENTRY="IP:${HOSTNAME_VALUE}" + log 1 "[init] Detected IP address: ${HOSTNAME_VALUE}" + else + # It's a DNS name - use DNS: prefix in SAN + SAN_ENTRY="DNS:${HOSTNAME_VALUE}" + log 1 "[init] Detected hostname: ${HOSTNAME_VALUE}" + fi + + # Generate certificate with the appropriate SAN entry if [ "$VERBOSITY" -ge 2 ]; then openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /certificates/ssl/key.pem \ -out /certificates/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ - -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" + -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1" else openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /certificates/ssl/key.pem \ -out /certificates/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ - -addext "subjectAltName=DNS:${HOSTNAME_VALUE},DNS:localhost,IP:127.0.0.1" \ + -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1" \ >/dev/null 2>&1 fi fi