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:
CyberMind-FR 2026-01-26 08:49:04 +01:00
parent 0e9ed474dd
commit e79a643134
2 changed files with 160 additions and 3 deletions

View File

@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-app-haproxy
PKG_VERSION:=1.0.0
PKG_RELEASE:=15
PKG_RELEASE:=16
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
PKG_LICENSE:=MIT

View File

@ -555,6 +555,111 @@ _add_server_to_backend() {
# 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() {
load_config
@ -566,11 +671,25 @@ cmd_cert_list() {
[ -f "$cert" ] || continue
local name=$(basename "$cert" .pem)
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
else
echo " No certificates found"
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() {
@ -593,6 +712,18 @@ cmd_cert_add() {
[ -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..."
local staging_flag=""
@ -690,6 +821,24 @@ cmd_cert_add() {
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
local section="cert_$(echo "$domain" | tr '.-' '__')"
uci set haproxy.$section=certificate
@ -699,6 +848,13 @@ cmd_cert_add() {
uci commit haproxy
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() {
@ -1042,8 +1198,9 @@ case "${1:-}" in
add) shift; cmd_cert_add "$@" ;;
import) shift; cmd_cert_import "$@" ;;
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 ;;
*) echo "Usage: haproxyctl cert {list|add|import|renew|remove}" ;;
*) echo "Usage: haproxyctl cert {list|add|import|renew|verify|remove}" ;;
esac
;;