270 lines
9.2 KiB
YAML
270 lines
9.2 KiB
YAML
|
|
---
|
||
|
|
# Configure HAProxy inside container with Let's Encrypt ACME HTTP-01
|
||
|
|
# Handles SSL termination and routing for Veza V5 Ultra
|
||
|
|
|
||
|
|
- name: Configure HAProxy in container with ACME
|
||
|
|
hosts: edge
|
||
|
|
become: true
|
||
|
|
gather_facts: true
|
||
|
|
|
||
|
|
vars:
|
||
|
|
haproxy_container: veza-haproxy
|
||
|
|
acme_webroot_port: 8888
|
||
|
|
haproxy_certs_dir: /etc/haproxy/certs
|
||
|
|
acme_webroot_dir: /var/www/acme-challenge
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
- name: Install HAProxy and ACME tools in container
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
apt update && apt install -y \
|
||
|
|
haproxy \
|
||
|
|
curl \
|
||
|
|
wget \
|
||
|
|
socat \
|
||
|
|
cron \
|
||
|
|
nginx-light \
|
||
|
|
openssl
|
||
|
|
"
|
||
|
|
register: haproxy_install_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Display HAProxy installation result
|
||
|
|
debug:
|
||
|
|
var: haproxy_install_result.stdout_lines
|
||
|
|
when: haproxy_install_result.stdout_lines is defined
|
||
|
|
|
||
|
|
- name: Create ACME webroot directory
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- mkdir -p {{ acme_webroot_dir }}
|
||
|
|
register: webroot_create_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create HAProxy certificates directory
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- mkdir -p {{ haproxy_certs_dir }}
|
||
|
|
register: certs_dir_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Install dehydrated for ACME
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
cd /opt && \
|
||
|
|
git clone https://github.com/dehydrated-io/dehydrated.git && \
|
||
|
|
chmod +x dehydrated/dehydrated
|
||
|
|
"
|
||
|
|
register: dehydrated_install_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create dehydrated configuration
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
cat > /opt/dehydrated/config << 'EOF'
|
||
|
|
WELLKNOWN={{ acme_webroot_dir }}
|
||
|
|
DOMAINS_TXT=/opt/dehydrated/domains.txt
|
||
|
|
HOOK=/opt/dehydrated/hook.sh
|
||
|
|
CHALLENGETYPE=http-01
|
||
|
|
EOF
|
||
|
|
"
|
||
|
|
register: dehydrated_config_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create domains file for ACME
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
echo '{{ domain }}' > /opt/dehydrated/domains.txt
|
||
|
|
"
|
||
|
|
register: domains_file_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create ACME hook script
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
cat > /opt/dehydrated/hook.sh << 'EOF'
|
||
|
|
#!/bin/bash
|
||
|
|
case \"\$1\" in
|
||
|
|
deploy_challenge)
|
||
|
|
# Start nginx for ACME challenge
|
||
|
|
nginx -c /etc/nginx/nginx.conf -g 'daemon on;'
|
||
|
|
;;
|
||
|
|
clean_challenge)
|
||
|
|
# Stop nginx after challenge
|
||
|
|
nginx -s quit
|
||
|
|
;;
|
||
|
|
deploy_cert)
|
||
|
|
# Combine cert and key for HAProxy
|
||
|
|
cat \$3 \$5 > {{ haproxy_certs_dir }}/{{ domain }}.pem
|
||
|
|
# Reload HAProxy
|
||
|
|
systemctl reload haproxy
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
EOF
|
||
|
|
chmod +x /opt/dehydrated/hook.sh
|
||
|
|
"
|
||
|
|
register: hook_script_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create nginx config for ACME webroot
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
cat > /etc/nginx/nginx.conf << 'EOF'
|
||
|
|
events { worker_connections 1024; }
|
||
|
|
http {
|
||
|
|
server {
|
||
|
|
listen {{ acme_webroot_port }};
|
||
|
|
location /.well-known/acme-challenge/ {
|
||
|
|
root {{ acme_webroot_dir }};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EOF
|
||
|
|
"
|
||
|
|
register: nginx_config_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create HAProxy configuration
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
cat > /etc/haproxy/haproxy.cfg << 'EOF'
|
||
|
|
global
|
||
|
|
log stdout local0
|
||
|
|
chroot /var/lib/haproxy
|
||
|
|
stats socket /run/haproxy/admin.sock mode 660 level admin
|
||
|
|
stats timeout 30s
|
||
|
|
user haproxy
|
||
|
|
group haproxy
|
||
|
|
daemon
|
||
|
|
maxconn 20000
|
||
|
|
ssl-default-bind-options no-sslv3 no-tls-tickets
|
||
|
|
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
|
||
|
|
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||
|
|
|
||
|
|
defaults
|
||
|
|
log global
|
||
|
|
mode http
|
||
|
|
option httplog
|
||
|
|
option dontlognull
|
||
|
|
option forwardfor
|
||
|
|
timeout connect 5s
|
||
|
|
timeout client 50s
|
||
|
|
timeout server 50s
|
||
|
|
timeout http-request 5s
|
||
|
|
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
|
||
|
|
|
||
|
|
frontend http_frontend
|
||
|
|
bind *:80
|
||
|
|
acl acme_challenge path_beg /.well-known/acme-challenge/
|
||
|
|
use_backend acme_backend if acme_challenge
|
||
|
|
redirect scheme https code 301 if !acme_challenge
|
||
|
|
|
||
|
|
frontend https_frontend
|
||
|
|
bind *:443 ssl crt {{ haproxy_certs_dir }}/{{ domain }}.pem alpn h2,http/1.1
|
||
|
|
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 Referrer-Policy no-referrer
|
||
|
|
http-response set-header Content-Security-Policy \"default-src 'self'; connect-src 'self' wss://{{ domain }} https://{{ domain }}; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'\"
|
||
|
|
|
||
|
|
acl is_api path_beg /api
|
||
|
|
acl is_ws path_beg /ws
|
||
|
|
acl is_stream path_beg /stream
|
||
|
|
|
||
|
|
use_backend api_backend if is_api
|
||
|
|
use_backend ws_backend if is_ws
|
||
|
|
use_backend stream_backend if is_stream
|
||
|
|
default_backend web_backend
|
||
|
|
|
||
|
|
backend acme_backend
|
||
|
|
server acme_server 127.0.0.1:{{ acme_webroot_port }}
|
||
|
|
|
||
|
|
backend api_backend
|
||
|
|
server api_server veza-backend:{{ veza_backend_port }} check
|
||
|
|
|
||
|
|
backend ws_backend
|
||
|
|
mode tcp
|
||
|
|
server ws_server veza-chat:{{ veza_chat_port }} check
|
||
|
|
|
||
|
|
backend stream_backend
|
||
|
|
server stream_server veza-stream:{{ veza_stream_port }} check
|
||
|
|
|
||
|
|
backend web_backend
|
||
|
|
server web_server veza-web:{{ veza_web_port }} check
|
||
|
|
EOF
|
||
|
|
"
|
||
|
|
register: haproxy_config_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Create self-signed certificate for initial setup
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
openssl req -x509 -newkey rsa:2048 -keyout {{ haproxy_certs_dir }}/{{ domain }}.key -out {{ haproxy_certs_dir }}/{{ domain }}.crt -days 365 -nodes -subj '/CN={{ domain }}' && \
|
||
|
|
cat {{ haproxy_certs_dir }}/{{ domain }}.crt {{ haproxy_certs_dir }}/{{ domain }}.key > {{ haproxy_certs_dir }}/{{ domain }}.pem
|
||
|
|
"
|
||
|
|
register: selfsigned_cert_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: Display HAProxy start result
|
||
|
|
debug:
|
||
|
|
var: haproxy_start_result.stdout_lines
|
||
|
|
when: haproxy_start_result.stdout_lines is defined
|
||
|
|
|
||
|
|
- name: Check HAProxy status
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- systemctl status haproxy
|
||
|
|
register: haproxy_status_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Display HAProxy status
|
||
|
|
debug:
|
||
|
|
var: haproxy_status_result.stdout_lines
|
||
|
|
when: haproxy_status_result.stdout_lines is defined
|
||
|
|
|
||
|
|
- name: Create ACME renewal cron job
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- bash -c "
|
||
|
|
echo '0 2 * * * /opt/dehydrated/dehydrated -c' | crontab -
|
||
|
|
"
|
||
|
|
register: cron_setup_result
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Display cron setup result
|
||
|
|
debug:
|
||
|
|
var: cron_setup_result.stdout_lines
|
||
|
|
when: cron_setup_result.stdout_lines is defined
|
||
|
|
|
||
|
|
post_tasks:
|
||
|
|
- 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
|
||
|
|
when: haproxy_test_result.stdout_lines is defined
|
||
|
|
|
||
|
|
- name: Show final HAProxy status
|
||
|
|
command: |
|
||
|
|
incus exec {{ haproxy_container }} -- netstat -tlnp | grep haproxy
|
||
|
|
register: final_haproxy_status
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Display final HAProxy status
|
||
|
|
debug:
|
||
|
|
var: final_haproxy_status.stdout_lines
|
||
|
|
when: final_haproxy_status.stdout_lines is defined
|