286 lines
10 KiB
YAML
286 lines
10 KiB
YAML
---
|
|
# Configure HAProxy + Let's Encrypt ACME in container
|
|
# Sets up SSL termination and request routing
|
|
|
|
- name: Configure HAProxy + Let's Encrypt ACME for Veza V5 Ultra
|
|
hosts: edge
|
|
become: true
|
|
gather_facts: true
|
|
|
|
vars:
|
|
domain: "{{ domain | default('veza.talas.fr') }}"
|
|
acme_email: "{{ acme_email | default('ops@talas.fr') }}"
|
|
haproxy_container: "veza-haproxy"
|
|
webroot_port: 8888
|
|
|
|
tasks:
|
|
- name: Install HAProxy and ACME tools in container
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- apt update
|
|
incus exec {{ haproxy_container }} -- apt install -y haproxy dehydrated nginx-light
|
|
register: install_result
|
|
failed_when: false
|
|
|
|
- name: Display installation result
|
|
debug:
|
|
var: install_result.stdout_lines
|
|
|
|
- name: Create ACME webroot directory
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- mkdir -p /var/www/acme-challenge
|
|
incus exec {{ haproxy_container }} -- chown -R www-data:www-data /var/www/acme-challenge
|
|
register: webroot_result
|
|
failed_when: false
|
|
|
|
- name: Configure nginx for ACME challenges
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- tee /etc/nginx/sites-available/acme << 'EOF'
|
|
server {
|
|
listen 127.0.0.1:{{ webroot_port }};
|
|
server_name _;
|
|
root /var/www/acme-challenge;
|
|
location /.well-known/acme-challenge/ {
|
|
try_files $uri =404;
|
|
}
|
|
}
|
|
EOF
|
|
register: nginx_config_result
|
|
failed_when: false
|
|
|
|
- name: Enable nginx ACME site
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- ln -sf /etc/nginx/sites-available/acme /etc/nginx/sites-enabled/
|
|
incus exec {{ haproxy_container }} -- rm -f /etc/nginx/sites-enabled/default
|
|
incus exec {{ haproxy_container }} -- systemctl restart nginx
|
|
register: nginx_enable_result
|
|
failed_when: false
|
|
|
|
- name: Configure dehydrated for Let's Encrypt
|
|
command: incus exec {{ haproxy_container }} -- bash -c 'echo "CA=\"https://acme-v02.api.letsencrypt.org/directory\"" > /etc/dehydrated/config'
|
|
register: dehydrated_config_result
|
|
failed_when: false
|
|
|
|
- name: Add CHALLENGETYPE to dehydrated config
|
|
command: incus exec {{ haproxy_container }} -- bash -c 'echo "CHALLENGETYPE=\"http-01\"" >> /etc/dehydrated/config'
|
|
register: dehydrated_config_result2
|
|
failed_when: false
|
|
|
|
- name: Add WELLKNOWN to dehydrated config
|
|
command: incus exec {{ haproxy_container }} -- bash -c 'echo "WELLKNOWN=\"/var/www/acme-challenge\"" >> /etc/dehydrated/config'
|
|
register: dehydrated_config_result3
|
|
failed_when: false
|
|
|
|
- name: Add CONTACT_EMAIL to dehydrated config
|
|
command: incus exec {{ haproxy_container }} -- bash -c 'echo "CONTACT_EMAIL=\"{{ acme_email }}\"" >> /etc/dehydrated/config'
|
|
register: dehydrated_config_result4
|
|
failed_when: false
|
|
|
|
- name: Add HOOK to dehydrated config
|
|
command: incus exec {{ haproxy_container }} -- bash -c 'echo "HOOK=\"/etc/dehydrated/hook.sh\"" >> /etc/dehydrated/config'
|
|
register: dehydrated_config_result
|
|
failed_when: false
|
|
|
|
- name: Create dehydrated hook script
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- bash -c 'cat > /etc/dehydrated/hook.sh << "EOF"
|
|
#!/bin/bash
|
|
# Dehydrated hook for HAProxy certificate management
|
|
|
|
case "$1" in
|
|
"deploy_cert")
|
|
# Deploy certificate to HAProxy
|
|
cat "$3" "$5" > /etc/haproxy/certs/${2}.pem
|
|
systemctl reload haproxy
|
|
;;
|
|
"clean_challenge")
|
|
# Clean up challenge files
|
|
rm -f /var/www/acme-challenge/*
|
|
;;
|
|
"deploy_challenge")
|
|
# Deploy challenge file
|
|
cp "$2" "/var/www/acme-challenge/$3"
|
|
;;
|
|
"unchanged_cert")
|
|
# Certificate unchanged
|
|
;;
|
|
esac
|
|
EOF'
|
|
register: hook_script_result
|
|
failed_when: false
|
|
|
|
- name: Make hook script executable
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- chmod +x /etc/dehydrated/hook.sh
|
|
register: hook_executable_result
|
|
failed_when: false
|
|
|
|
- name: Create HAProxy configuration
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- tee /etc/haproxy/haproxy.cfg << 'EOF'
|
|
global
|
|
daemon
|
|
user haproxy
|
|
group haproxy
|
|
log stdout local0
|
|
chroot /var/lib/haproxy
|
|
stats socket /run/haproxy/admin.sock mode 660 level admin
|
|
stats timeout 30s
|
|
tune.ssl.default-dh-param 2048
|
|
|
|
defaults
|
|
mode http
|
|
log global
|
|
option httplog
|
|
option dontlognull
|
|
option log-health-checks
|
|
option forwardfor
|
|
option httpchk GET /health
|
|
timeout connect 5000
|
|
timeout client 50000
|
|
timeout server 50000
|
|
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
|
|
|
|
# ACME challenge backend
|
|
backend acme
|
|
server acme 127.0.0.1:{{ webroot_port }} check
|
|
|
|
# API backend
|
|
backend be_api
|
|
balance roundrobin
|
|
option httpchk GET /api/health
|
|
http-check expect status 200
|
|
server api1 10.10.0.101:8080 check inter 2000 rise 2 fall 3
|
|
|
|
# WebSocket backend
|
|
backend be_ws
|
|
mode tcp
|
|
balance roundrobin
|
|
server ws1 10.10.0.102:8081 check inter 2000 rise 2 fall 3
|
|
|
|
# Stream backend
|
|
backend be_stream
|
|
balance roundrobin
|
|
option httpchk GET /stream/health
|
|
http-check expect status 200
|
|
server stream1 10.10.0.103:8082 check inter 2000 rise 2 fall 3
|
|
|
|
# Web frontend backend
|
|
backend be_web
|
|
balance roundrobin
|
|
option httpchk GET /
|
|
http-check expect status 200
|
|
server web1 10.10.0.104:3000 check inter 2000 rise 2 fall 3
|
|
|
|
# HTTP frontend (redirect to HTTPS)
|
|
frontend http_frontend
|
|
bind *:80
|
|
acl acme_challenge path_beg /.well-known/acme-challenge/
|
|
use_backend acme if acme_challenge
|
|
redirect scheme https code 301 if !acme_challenge
|
|
|
|
# HTTPS frontend
|
|
frontend https_frontend
|
|
bind *:443 ssl crt /etc/haproxy/certs/{{ domain }}.pem alpn h2,http/1.1
|
|
|
|
# Security headers
|
|
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
|
http-response set-header X-Content-Type-Options "nosniff"
|
|
http-response set-header X-Frame-Options "DENY"
|
|
http-response set-header X-XSS-Protection "1; mode=block"
|
|
http-response set-header Referrer-Policy "no-referrer"
|
|
http-response set-header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' wss:;"
|
|
|
|
# Routing rules
|
|
acl is_api path_beg /api
|
|
acl is_ws path_beg /ws
|
|
acl is_stream path_beg /stream
|
|
|
|
use_backend be_api if is_api
|
|
use_backend be_ws if is_ws
|
|
use_backend be_stream if is_stream
|
|
default_backend be_web
|
|
|
|
# Statistics
|
|
listen stats
|
|
bind *:8404
|
|
stats enable
|
|
stats uri /stats
|
|
stats refresh 30s
|
|
stats admin if TRUE
|
|
EOF
|
|
register: haproxy_config_result
|
|
failed_when: false
|
|
|
|
- name: Create HAProxy certificates directory
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- mkdir -p /etc/haproxy/certs
|
|
register: certs_dir_result
|
|
failed_when: false
|
|
|
|
- name: Generate self-signed certificate (temporary)
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- openssl req -x509 -newkey rsa:4096 -keyout /etc/haproxy/certs/{{ domain }}.pem -out /etc/haproxy/certs/{{ domain }}.pem -days 365 -nodes -subj "/C=FR/ST=France/L=Paris/O=Veza/OU=IT/CN={{ domain }}"
|
|
register: self_signed_result
|
|
failed_when: false
|
|
|
|
- name: Start HAProxy service
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- systemctl enable haproxy
|
|
incus exec {{ haproxy_container }} -- systemctl start haproxy
|
|
register: haproxy_start_result
|
|
failed_when: false
|
|
|
|
- name: Check HAProxy status
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- systemctl status haproxy
|
|
register: haproxy_status
|
|
failed_when: false
|
|
|
|
- name: Display HAProxy status
|
|
debug:
|
|
var: haproxy_status.stdout_lines
|
|
|
|
- name: Request Let's Encrypt certificate
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- dehydrated -c -d {{ domain }}
|
|
register: acme_cert_result
|
|
failed_when: false
|
|
|
|
- name: Display ACME certificate result
|
|
debug:
|
|
var: acme_cert_result.stdout_lines
|
|
|
|
- name: Setup certificate renewal cron
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- tee /etc/cron.d/dehydrated << 'EOF'
|
|
0 12 * * * root /usr/bin/dehydrated -c
|
|
EOF
|
|
register: cron_result
|
|
failed_when: false
|
|
|
|
- name: Test HAProxy configuration
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- haproxy -c -f /etc/haproxy/haproxy.cfg
|
|
register: haproxy_test_result
|
|
failed_when: false
|
|
|
|
- name: Display HAProxy test result
|
|
debug:
|
|
var: haproxy_test_result.stdout_lines
|
|
|
|
post_tasks:
|
|
- name: Show HAProxy statistics
|
|
command: |
|
|
incus exec {{ haproxy_container }} -- curl -s http://localhost:8404/stats
|
|
register: haproxy_stats
|
|
failed_when: false
|
|
|
|
- name: Display HAProxy statistics
|
|
debug:
|
|
var: haproxy_stats.stdout_lines
|