Sync before Archive
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
# {{ ansible_managed }}
|
||||
# Certbot post-renewal hook for HAProxy
|
||||
# Combines certificate and private key for HAProxy consumption
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
LETSENCRYPT_LIVE="/etc/letsencrypt/live"
|
||||
HAPROXY_CERT_DIR="{{ haproxy_ssl_cert_dir }}/live"
|
||||
HAPROXY_USER="{{ haproxy_user }}"
|
||||
HAPROXY_GROUP="{{ haproxy_group }}"
|
||||
LOG_FILE="{{ haproxy_log_dir }}/certbot-hook.log"
|
||||
|
||||
# Ensure HAProxy cert directory exists
|
||||
mkdir -p "$HAPROXY_CERT_DIR"
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "Starting certificate deployment for HAProxy"
|
||||
|
||||
# Track if any certificates were updated
|
||||
CERTS_UPDATED=false
|
||||
|
||||
# Process each certificate in Let's Encrypt live directory
|
||||
for cert_dir in "$LETSENCRYPT_LIVE"/*; do
|
||||
if [ ! -d "$cert_dir" ] || [ "$(basename "$cert_dir")" = "README" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
CERT_NAME=$(basename "$cert_dir")
|
||||
|
||||
# Check if certificate files exist
|
||||
if [ ! -f "$cert_dir/fullchain.pem" ] || [ ! -f "$cert_dir/privkey.pem" ]; then
|
||||
log "WARNING: Missing certificate files for $CERT_NAME, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
log "Processing certificate: $CERT_NAME"
|
||||
|
||||
# Create combined PEM file (fullchain + privkey)
|
||||
# HAProxy requires the certificate chain and private key in a single file
|
||||
COMBINED_PEM="$HAPROXY_CERT_DIR/${CERT_NAME}.pem"
|
||||
|
||||
# Combine certificates
|
||||
cat "$cert_dir/fullchain.pem" "$cert_dir/privkey.pem" > "$COMBINED_PEM.tmp"
|
||||
|
||||
# Check if certificate actually changed
|
||||
if [ -f "$COMBINED_PEM" ] && cmp -s "$COMBINED_PEM" "$COMBINED_PEM.tmp"; then
|
||||
log "Certificate $CERT_NAME unchanged, skipping"
|
||||
rm "$COMBINED_PEM.tmp"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Move new certificate into place
|
||||
mv "$COMBINED_PEM.tmp" "$COMBINED_PEM"
|
||||
|
||||
# Set proper permissions
|
||||
chown "$HAPROXY_USER:$HAPROXY_GROUP" "$COMBINED_PEM"
|
||||
chmod 600 "$COMBINED_PEM"
|
||||
|
||||
log "Created/Updated: $COMBINED_PEM"
|
||||
CERTS_UPDATED=true
|
||||
done
|
||||
|
||||
# Create/update default certificate symlink
|
||||
# Use the first wildcard cert or the first available cert as default
|
||||
DEFAULT_CERT="{{ haproxy_ssl_default_crt }}"
|
||||
WILDCARD_CERT="$HAPROXY_CERT_DIR/local.mk-labs.cloud.pem"
|
||||
|
||||
if [ -f "$WILDCARD_CERT" ]; then
|
||||
ln -sf "$WILDCARD_CERT" "$DEFAULT_CERT"
|
||||
log "Updated default certificate symlink to wildcard cert"
|
||||
elif [ -n "$(ls -A "$HAPROXY_CERT_DIR" 2>/dev/null)" ]; then
|
||||
FIRST_CERT=$(ls "$HAPROXY_CERT_DIR"/*.pem 2>/dev/null | head -n1)
|
||||
ln -sf "$FIRST_CERT" "$DEFAULT_CERT"
|
||||
log "Updated default certificate symlink to $FIRST_CERT"
|
||||
fi
|
||||
|
||||
# Only reload HAProxy if certificates were actually updated
|
||||
if [ "$CERTS_UPDATED" = true ]; then
|
||||
log "Certificates updated, validating HAProxy configuration"
|
||||
|
||||
# Validate HAProxy configuration
|
||||
if haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg >/dev/null 2>&1; then
|
||||
log "HAProxy configuration valid, reloading service"
|
||||
|
||||
if systemctl reload haproxy; then
|
||||
log "HAProxy reloaded successfully"
|
||||
else
|
||||
log "ERROR: Failed to reload HAProxy"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "ERROR: HAProxy configuration validation failed"
|
||||
haproxy -c -f {{ haproxy_config_dir }}/haproxy.cfg 2>&1 | tee -a "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "No certificates were updated, skipping HAProxy reload"
|
||||
fi
|
||||
|
||||
log "Certificate deployment complete"
|
||||
exit 0
|
||||
@@ -0,0 +1,12 @@
|
||||
# {{ ansible_managed }}
|
||||
# Cloudflare API credentials for Certbot DNS-01 challenge
|
||||
# This file contains sensitive information and should be protected
|
||||
|
||||
# Cloudflare API Token (RECOMMENDED)
|
||||
# Create at: https://dash.cloudflare.com/profile/api-tokens
|
||||
# Required permissions: Zone:DNS:Edit for specific zone
|
||||
dns_cloudflare_api_token = {{ haproxy_certbot_cloudflare_api_token }}
|
||||
|
||||
# Alternative: Global API Key (NOT RECOMMENDED - too permissive)
|
||||
# dns_cloudflare_email = your-email@example.com
|
||||
# dns_cloudflare_api_key = your-global-api-key
|
||||
188
ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2
Normal file
188
ansible/playbooks/roles/haproxy/templates/haproxy.cfg.j2
Normal file
@@ -0,0 +1,188 @@
|
||||
{# ansible/roles/haproxy/templates/haproxy.cfg.j2 #}
|
||||
#---------------------------------------------------------------------
|
||||
# HAProxy Configuration for {{ inventory_hostname }}
|
||||
# Generated by Ansible - DO NOT EDIT MANUALLY
|
||||
# Magic Kingdom Labs - lightning-lane load balancer
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Global settings
|
||||
#---------------------------------------------------------------------
|
||||
global
|
||||
log 127.0.0.1 {{ haproxy_global_log_facility }} {{ haproxy_global_log_level }}
|
||||
chroot {{ haproxy_chroot_dir }}
|
||||
pidfile /var/run/haproxy.pid
|
||||
maxconn {{ haproxy_global_maxconn }}
|
||||
user {{ haproxy_user }}
|
||||
group {{ haproxy_group }}
|
||||
daemon
|
||||
nbthread {{ haproxy_global_nbthread }}
|
||||
|
||||
# Runtime socket for management
|
||||
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
|
||||
stats timeout 30s
|
||||
|
||||
# SSL/TLS configuration
|
||||
ssl-default-bind-ciphers {{ haproxy_ssl_ciphers }}
|
||||
ssl-default-bind-options {{ haproxy_ssl_options }}
|
||||
ssl-default-server-ciphers {{ haproxy_ssl_ciphers }}
|
||||
ssl-default-server-options {{ haproxy_ssl_options }}
|
||||
|
||||
# Performance tuning
|
||||
tune.ssl.default-dh-param 2048
|
||||
tune.ssl.cachesize 100000
|
||||
tune.ssl.lifetime 600
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Common defaults
|
||||
#---------------------------------------------------------------------
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
option httplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
option redispatch
|
||||
retries 3
|
||||
timeout connect {{ haproxy_timeout_connect }}
|
||||
timeout client {{ haproxy_timeout_client }}
|
||||
timeout server {{ haproxy_timeout_server }}
|
||||
timeout http-request 10s
|
||||
timeout http-keep-alive 10s
|
||||
timeout check 10s
|
||||
maxconn 3000
|
||||
|
||||
# Error pages
|
||||
errorfile 400 /etc/haproxy/errors/400.http
|
||||
errorfile 403 /etc/haproxy/errors/403.http
|
||||
errorfile 408 /etc/haproxy/errors/408.http
|
||||
errorfile 500 /etc/haproxy/errors/500.http
|
||||
errorfile 502 /etc/haproxy/errors/502.http
|
||||
errorfile 503 /etc/haproxy/errors/503.http
|
||||
errorfile 504 /etc/haproxy/errors/504.http
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Stats interface
|
||||
#---------------------------------------------------------------------
|
||||
{% if haproxy_stats_enable %}
|
||||
listen stats
|
||||
bind *:{{ haproxy_stats_port }}
|
||||
mode http
|
||||
stats enable
|
||||
stats uri {{ haproxy_stats_uri }}
|
||||
stats refresh {{ haproxy_stats_refresh }}
|
||||
stats realm Magic\ Kingdom\ Labs\ -\ HAProxy\ Statistics
|
||||
stats auth {{ haproxy_stats_username }}:{{ haproxy_stats_password }}
|
||||
stats admin if TRUE
|
||||
{% endif %}
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# HTTP Redirect Frontend (HTTP -> HTTPS)
|
||||
#---------------------------------------------------------------------
|
||||
frontend http_front
|
||||
bind *:80
|
||||
mode http
|
||||
|
||||
# Redirect all HTTP traffic to HTTPS
|
||||
redirect scheme https code 301
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# HTTPS Frontend with SNI
|
||||
#---------------------------------------------------------------------
|
||||
frontend https_front
|
||||
bind *:443 ssl crt {{ haproxy_ssl_cert_dir }}/live crt {{ haproxy_ssl_default_crt }} alpn h2,http/1.1
|
||||
mode http
|
||||
|
||||
# Security headers
|
||||
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
http-response set-header X-Frame-Options "SAMEORIGIN"
|
||||
http-response set-header X-Content-Type-Options "nosniff"
|
||||
http-response set-header X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Logging
|
||||
option httplog
|
||||
option forwardfor
|
||||
|
||||
{% if haproxy_frontends is defined and haproxy_frontends | length > 0 %}
|
||||
{% for frontend in haproxy_frontends %}
|
||||
{% if frontend.acls is defined %}
|
||||
# ACLs for {{ frontend.name }}
|
||||
{% for acl in frontend.acls %}
|
||||
acl {{ acl }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for frontend in haproxy_frontends %}
|
||||
{% if frontend.use_backends is defined %}
|
||||
# Backend routing for {{ frontend.name }}
|
||||
{% for use_backend in frontend.use_backends %}
|
||||
use_backend {{ use_backend }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
# Default backend
|
||||
default_backend no_match_back
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Backends
|
||||
#---------------------------------------------------------------------
|
||||
{% for backend in haproxy_backends %}
|
||||
backend {{ backend.name }}
|
||||
mode {{ backend.mode | default('http') }}
|
||||
balance {{ backend.balance | default('roundrobin') }}
|
||||
{% if backend.options is defined %}
|
||||
{% for option in backend.options %}
|
||||
option {{ option }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if backend.http_check is defined %}
|
||||
option httpchk {{ backend.http_check }}
|
||||
{% endif %}
|
||||
{% if backend.servers is defined %}
|
||||
{% for server in backend.servers %}
|
||||
server {{ server.name }} {{ server.address }}{% if server.check | default(false) %} check{% endif %}{% if server.backup | default(false) %} backup{% endif %}{% if server.weight is defined %} weight {{ server.weight }}{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# Default backend for unmatched requests
|
||||
backend no_match_back
|
||||
mode http
|
||||
http-request deny deny_status 404
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# TCP Services
|
||||
#---------------------------------------------------------------------
|
||||
{% for tcp_service in haproxy_tcp_services %}
|
||||
# {{ tcp_service.name | upper }} Service (TCP)
|
||||
frontend {{ tcp_service.name }}_front
|
||||
bind *:{{ tcp_service.frontend_port }}
|
||||
mode tcp
|
||||
option tcplog
|
||||
{% if tcp_service.timeout_client is defined %}
|
||||
timeout client {{ tcp_service.timeout_client }}
|
||||
{% endif %}
|
||||
default_backend {{ tcp_service.name }}_back
|
||||
|
||||
backend {{ tcp_service.name }}_back
|
||||
mode tcp
|
||||
balance {{ tcp_service.balance | default('roundrobin') }}
|
||||
{% if tcp_service.timeout_server is defined %}
|
||||
timeout server {{ tcp_service.timeout_server }}
|
||||
{% endif %}
|
||||
{% if tcp_service.timeout_connect is defined %}
|
||||
timeout connect {{ tcp_service.timeout_connect }}
|
||||
{% endif %}
|
||||
{% if tcp_service.servers is defined %}
|
||||
{% for server in tcp_service.servers %}
|
||||
server {{ server.name }} {{ server.address }}:{{ tcp_service.backend_port }}{% if server.check | default(true) %} check{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
14
ansible/playbooks/roles/haproxy/templates/logrotate.j2
Normal file
14
ansible/playbooks/roles/haproxy/templates/logrotate.j2
Normal file
@@ -0,0 +1,14 @@
|
||||
{# ansible/roles/haproxy/templates/logrotate.j2 #}
|
||||
{{ haproxy_log_dir }}/*.log {
|
||||
daily
|
||||
rotate 14
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
delaycompress
|
||||
sharedscripts
|
||||
postrotate
|
||||
/bin/kill -HUP `cat /var/run/rsyslog.pid 2> /dev/null` 2> /dev/null || true
|
||||
/bin/kill -HUP `cat /var/run/haproxy.pid 2> /dev/null` 2> /dev/null || true
|
||||
endscript
|
||||
}
|
||||
Reference in New Issue
Block a user