feat(haproxy): Add certificate staging/production validation
- Add cert_is_production() to detect Let's Encrypt staging certificates - Add cert_validate_public() to verify certificate publicly via curl/openssl - Add cert_info() to display certificate details (domain, issuer, dates) - Add cmd_cert_verify command for on-demand certificate verification - Update cmd_cert_list to show staging/production status with icons - Update cmd_cert_add to warn about staging mode and verify after issuance - Bump package release to r16 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e9ed474dd
commit
e79a643134
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=secubox-app-haproxy
|
PKG_NAME:=secubox-app-haproxy
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.0
|
||||||
PKG_RELEASE:=15
|
PKG_RELEASE:=16
|
||||||
|
|
||||||
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
|
||||||
PKG_LICENSE:=MIT
|
PKG_LICENSE:=MIT
|
||||||
|
|||||||
@ -555,6 +555,111 @@ _add_server_to_backend() {
|
|||||||
# Certificate Management
|
# Certificate Management
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
|
# Check if certificate is from Let's Encrypt Production (not Staging)
|
||||||
|
cert_is_production() {
|
||||||
|
local cert_file="$1"
|
||||||
|
[ -f "$cert_file" ] || return 1
|
||||||
|
|
||||||
|
# Check the issuer - staging certs have "(STAGING)" in the issuer
|
||||||
|
local issuer=$(openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null)
|
||||||
|
if echo "$issuer" | grep -qi "staging\|test\|fake"; then
|
||||||
|
return 1 # Staging certificate
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Let's Encrypt production issuers
|
||||||
|
if echo "$issuer" | grep -qiE "Let's Encrypt|R3|R10|R11|E1|E2|ISRG"; then
|
||||||
|
return 0 # Production certificate
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if it's a self-signed or other CA
|
||||||
|
return 0 # Assume production for other CAs
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate certificate publicly using external service
|
||||||
|
cert_validate_public() {
|
||||||
|
local domain="$1"
|
||||||
|
local timeout=10
|
||||||
|
|
||||||
|
# Try to connect and verify the certificate
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
if curl -sS --max-time "$timeout" -o /dev/null "https://$domain" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: use openssl s_client
|
||||||
|
if command -v openssl >/dev/null 2>&1; then
|
||||||
|
local result=$(echo | timeout "$timeout" openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get certificate info
|
||||||
|
cert_info() {
|
||||||
|
local cert_file="$1"
|
||||||
|
[ -f "$cert_file" ] || return 1
|
||||||
|
|
||||||
|
local subject=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null | sed 's/subject=//')
|
||||||
|
local issuer=$(openssl x509 -in "$cert_file" -noout -issuer 2>/dev/null | sed 's/issuer=//')
|
||||||
|
local not_after=$(openssl x509 -in "$cert_file" -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||||
|
local not_before=$(openssl x509 -in "$cert_file" -noout -startdate 2>/dev/null | cut -d= -f2)
|
||||||
|
|
||||||
|
echo "Subject: $subject"
|
||||||
|
echo "Issuer: $issuer"
|
||||||
|
echo "Valid From: $not_before"
|
||||||
|
echo "Valid Until: $not_after"
|
||||||
|
|
||||||
|
if cert_is_production "$cert_file"; then
|
||||||
|
echo "Type: PRODUCTION (publicly trusted)"
|
||||||
|
else
|
||||||
|
echo "Type: STAGING/TEST (NOT publicly trusted!)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify and report certificate status
|
||||||
|
cmd_cert_verify() {
|
||||||
|
load_config
|
||||||
|
|
||||||
|
local domain="$1"
|
||||||
|
if [ -z "$domain" ]; then
|
||||||
|
echo "Usage: haproxyctl cert verify <domain>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local cert_file="$CERTS_PATH/$domain.pem"
|
||||||
|
if [ ! -f "$cert_file" ]; then
|
||||||
|
log_error "Certificate not found: $cert_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Certificate Information for $domain:"
|
||||||
|
echo "======================================"
|
||||||
|
cert_info "$cert_file"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if it's production
|
||||||
|
if ! cert_is_production "$cert_file"; then
|
||||||
|
log_warn "This is a STAGING certificate - NOT trusted by browsers!"
|
||||||
|
log_warn "To get a production certificate, ensure staging='0' in config and re-issue"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try public validation
|
||||||
|
echo "Public Validation:"
|
||||||
|
if cert_validate_public "$domain"; then
|
||||||
|
log_info "Certificate is publicly valid and accessible"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warn "Could not verify certificate publicly"
|
||||||
|
log_warn "Ensure DNS points to this server and port 443 is accessible"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
cmd_cert_list() {
|
cmd_cert_list() {
|
||||||
load_config
|
load_config
|
||||||
|
|
||||||
@ -566,11 +671,25 @@ cmd_cert_list() {
|
|||||||
[ -f "$cert" ] || continue
|
[ -f "$cert" ] || continue
|
||||||
local name=$(basename "$cert" .pem)
|
local name=$(basename "$cert" .pem)
|
||||||
local expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
|
local expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||||
echo " $name - Expires: ${expiry:-Unknown}"
|
local type_icon="✅"
|
||||||
|
if ! cert_is_production "$cert"; then
|
||||||
|
type_icon="⚠️ STAGING"
|
||||||
|
fi
|
||||||
|
echo " $name - Expires: ${expiry:-Unknown} $type_icon"
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
echo " No certificates found"
|
echo " No certificates found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Show current mode
|
||||||
|
local staging=$(uci -q get haproxy.acme.staging)
|
||||||
|
echo ""
|
||||||
|
if [ "$staging" = "1" ]; then
|
||||||
|
echo "⚠️ ACME Mode: STAGING (certificates will NOT be trusted by browsers)"
|
||||||
|
echo " To use production: uci set haproxy.acme.staging='0' && uci commit haproxy"
|
||||||
|
else
|
||||||
|
echo "✅ ACME Mode: PRODUCTION (certificates will be publicly trusted)"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_cert_add() {
|
cmd_cert_add() {
|
||||||
@ -593,6 +712,18 @@ cmd_cert_add() {
|
|||||||
|
|
||||||
[ -z "$email" ] && { log_error "ACME email not configured. Set in LuCI > Services > HAProxy > Settings"; return 1; }
|
[ -z "$email" ] && { log_error "ACME email not configured. Set in LuCI > Services > HAProxy > Settings"; return 1; }
|
||||||
|
|
||||||
|
# Warn about staging mode
|
||||||
|
if [ "$staging" = "1" ]; then
|
||||||
|
log_warn "=========================================="
|
||||||
|
log_warn "STAGING MODE ENABLED!"
|
||||||
|
log_warn "Certificate will NOT be trusted by browsers"
|
||||||
|
log_warn "To use production: uci set haproxy.acme.staging='0' && uci commit haproxy"
|
||||||
|
log_warn "=========================================="
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
log_info "Using Let's Encrypt PRODUCTION (certificates will be publicly trusted)"
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Requesting certificate for $domain..."
|
log_info "Requesting certificate for $domain..."
|
||||||
|
|
||||||
local staging_flag=""
|
local staging_flag=""
|
||||||
@ -690,6 +821,24 @@ cmd_cert_add() {
|
|||||||
|
|
||||||
chmod 600 "$CERTS_PATH/$domain.pem"
|
chmod 600 "$CERTS_PATH/$domain.pem"
|
||||||
|
|
||||||
|
# Verify certificate type (production vs staging)
|
||||||
|
echo ""
|
||||||
|
if cert_is_production "$CERTS_PATH/$domain.pem"; then
|
||||||
|
log_info "✅ Certificate is from PRODUCTION CA (publicly trusted)"
|
||||||
|
else
|
||||||
|
log_warn "⚠️ Certificate is from STAGING CA (NOT publicly trusted!)"
|
||||||
|
log_warn " Browsers will show security warnings for this certificate"
|
||||||
|
log_warn " To get a production certificate:"
|
||||||
|
log_warn " 1. uci set haproxy.acme.staging='0'"
|
||||||
|
log_warn " 2. uci commit haproxy"
|
||||||
|
log_warn " 3. haproxyctl cert remove $domain"
|
||||||
|
log_warn " 4. haproxyctl cert add $domain"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show certificate info
|
||||||
|
echo ""
|
||||||
|
cert_info "$CERTS_PATH/$domain.pem"
|
||||||
|
|
||||||
# Add to UCI
|
# Add to UCI
|
||||||
local section="cert_$(echo "$domain" | tr '.-' '__')"
|
local section="cert_$(echo "$domain" | tr '.-' '__')"
|
||||||
uci set haproxy.$section=certificate
|
uci set haproxy.$section=certificate
|
||||||
@ -699,6 +848,13 @@ cmd_cert_add() {
|
|||||||
uci commit haproxy
|
uci commit haproxy
|
||||||
|
|
||||||
log_info "Certificate installed for $domain"
|
log_info "Certificate installed for $domain"
|
||||||
|
|
||||||
|
# Offer to verify publicly if production
|
||||||
|
if cert_is_production "$CERTS_PATH/$domain.pem"; then
|
||||||
|
echo ""
|
||||||
|
log_info "To verify the certificate is working publicly, run:"
|
||||||
|
log_info " haproxyctl cert verify $domain"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_cert_import() {
|
cmd_cert_import() {
|
||||||
@ -1042,8 +1198,9 @@ case "${1:-}" in
|
|||||||
add) shift; cmd_cert_add "$@" ;;
|
add) shift; cmd_cert_add "$@" ;;
|
||||||
import) shift; cmd_cert_import "$@" ;;
|
import) shift; cmd_cert_import "$@" ;;
|
||||||
renew) shift; cmd_cert_add "$@" ;;
|
renew) shift; cmd_cert_add "$@" ;;
|
||||||
|
verify) shift; cmd_cert_verify "$@" ;;
|
||||||
remove) shift; rm -f "$CERTS_PATH/$1.pem"; uci delete haproxy.cert_${1//[.-]/_} 2>/dev/null ;;
|
remove) shift; rm -f "$CERTS_PATH/$1.pem"; uci delete haproxy.cert_${1//[.-]/_} 2>/dev/null ;;
|
||||||
*) echo "Usage: haproxyctl cert {list|add|import|renew|remove}" ;;
|
*) echo "Usage: haproxyctl cert {list|add|import|renew|verify|remove}" ;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user