chore: upgrade reverseproxy role for use with vagrant and ssl

This commit is contained in:
Bert-Jan Fikse 2025-11-07 11:52:41 +01:00
parent a4aa64777e
commit 9e7b2b3b84
Signed by: bert-jan
GPG key ID: C1E0AB516AC16D1A
7 changed files with 177 additions and 104 deletions

View file

@ -2,13 +2,68 @@
---
# defaults file for reverseproxy
# Base directory configuration (inherited from base role or defined here)
docker_compose_base_dir: /etc/docker/compose
docker_volume_base_dir: /srv/data
# Service-specific configuration
service_name: reverseproxy
docker_compose_dir: "{{ docker_compose_base_dir }}/{{ service_name }}"
docker_volume_dir: "{{ docker_volume_base_dir }}/{{ service_name }}"
# Provider configuration
use_static_services: false # Use all_services from services.yml for outward-facing proxies
use_docker_provider: true # Use Docker provider for service discovery via labels
use_ssl: false # Enable SSL termination with Let's Encrypt
enable_dashboard: true # Enable Traefik dashboard
# Deployment mode: 'dmz' or 'backend'
# - dmz: Public-facing reverse proxy that routes to backend servers using file provider
# - backend: Application server with docker provider for local container discovery
reverseproxy_mode: "backend"
# SSL configuration
use_ssl: true
ssl_email: "admin@example.com"
ssl_cert_resolver: "dns" # Certificate resolver name
# Certificate mode: 'acme' for Let's Encrypt with DNS challenge or 'selfsigned' for self-signed certs
cert_mode: "selfsigned" # Use selfsigned for vagrant, acme for production
# ACME DNS Challenge with RFC2136 (TSIG) configuration
acme_dns_zone: "" # e.g., "digitalboard._acme.digitalboard.ch."
acme_dns_nameserver: "" # e.g., "192.168.1.1:53"
acme_tsig_algorithm: "hmac-sha256"
acme_tsig_key: "" # TSIG key name
acme_tsig_secret: "" # TSIG secret
acme_propagation_timeout: "120"
acme_polling_interval: "2"
acme_ttl: "60"
# Self-signed certificate configuration (for vagrant/testing)
selfsigned_cert_dir: "{{ docker_volume_dir }}/certs"
selfsigned_cert_days: 365
selfsigned_common_name: "*.local.test"
# Dashboard
enable_dashboard: false
# Access log configuration
enable_access_logs: true
access_log_format: "common"
log_level: "INFO"
# Network name
traefik_network: "proxy"
# Services to expose (defined by application roles via host_vars or group_vars)
# Each backend server should define this variable with their services
# reverseproxy_services:
# - name: httpbin
# domain: httpbin.example.com
# port: 8080
# protocol: http # http or https
# entrypoints: [websecure] # optional, defaults based on SSL config
# DMZ mode: Explicit backend server mapping
# Define which backend servers this DMZ proxy should route to
# If empty or undefined, routes to all servers in backend_servers group
backend_servers_to_proxy: []
# Example:
# backend_servers_to_proxy:
# - backend1
# - backend2

View file

@ -30,6 +30,5 @@ galaxy_info:
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
dependencies:
- digitalboard.core.base

View file

@ -2,23 +2,23 @@
---
# tasks file for reverseproxy
- name: Gather service information from all hosts
setup:
delegate_to: "{{ item }}"
delegate_facts: true
loop: "{{ groups['all_servers'] }}"
when: use_static_services | bool
- name: Build service registry from all hosts
- name: Determine which backend servers to proxy (DMZ mode)
set_fact:
all_services: "{{ all_services | default([]) + hostvars[item].services | default([]) | map('combine', {'backend_host': item}) | list }}"
loop: "{{ groups['all_servers'] }}"
when: use_static_services | bool
_backend_servers: "{{ backend_servers_to_proxy if backend_servers_to_proxy | length > 0 else groups['backend_servers'] | default([]) }}"
when: reverseproxy_mode == 'dmz'
- name: Build service registry from backend servers (DMZ mode)
set_fact:
proxied_services: "{{ proxied_services | default([]) + hostvars[item].reverseproxy_services | default([]) | map('combine', {'backend_host': hostvars[item].ansible_host | default(item)}) | list }}"
loop: "{{ _backend_servers | default([]) }}"
when: reverseproxy_mode == 'dmz'
- name: Debug service registry
debug:
var: all_services
when: use_static_services | bool
var: proxied_services
when:
- reverseproxy_mode == 'dmz'
- proxied_services is defined
- name: Create docker compose directory
file:
@ -26,33 +26,45 @@
state: directory
mode: '0755'
- name: Create docker volume directories
- name: Create docker volume directory
file:
path: "{{ docker_volume_dir }}/traefik/{{ item }}"
path: "{{ docker_volume_dir }}"
state: directory
mode: '0755'
loop:
- letsencrypt
- name: Create traefik config directory
file:
path: "{{ docker_volume_dir }}/config"
state: directory
mode: '0755'
when: reverseproxy_mode == 'dmz'
- name: Create letsencrypt directory
file:
path: "{{ docker_volume_dir }}/letsencrypt"
state: directory
mode: '0755'
when: cert_mode == 'acme'
- name: Create traefik Docker network
community.docker.docker_network:
name: traefik
name: "{{ traefik_network }}"
state: present
- name: Generate traefik static configuration
template:
src: traefik.yml.j2
dest: "{{ docker_compose_dir }}/traefik.yml"
dest: "{{ docker_volume_dir }}/traefik.yml"
mode: '0644'
notify: restart traefik
- name: Generate traefik services configuration for discovered services
- name: Generate traefik dynamic configuration for DMZ services
template:
src: services.yml.j2
dest: "{{ docker_compose_dir }}/services.yml"
dest: "{{ docker_volume_dir }}/config/services.yml"
mode: '0644'
notify: restart traefik
when: use_static_services | bool
when: reverseproxy_mode == 'dmz'
- name: Create docker-compose file for traefik
template:

View file

@ -1,21 +1,39 @@
services:
traefik:
image: traefik:v3.5
container_name: traefik
image: traefik:latest
container_name: reverseproxy
restart: always
{% if cert_mode == 'acme' %}
environment:
RFC2136_NAMESERVER: "{{ acme_dns_nameserver }}"
RFC2136_TSIG_ALGORITHM: "{{ acme_tsig_algorithm }}"
RFC2136_TSIG_KEY: "{{ acme_tsig_key }}"
RFC2136_TSIG_SECRET: "{{ acme_tsig_secret }}"
RFC2136_PROPAGATION_TIMEOUT: "{{ acme_propagation_timeout }}"
RFC2136_POLLING_INTERVAL: "{{ acme_polling_interval }}"
RFC2136_TTL: "{{ acme_ttl }}"
{% endif %}
ports:
- "80:80"
- "443:443"
{% if enable_dashboard %}
- "8080:8080" # Dashboard
- "8080:8080"
{% endif %}
volumes:
- {{ docker_volume_dir }}/traefik/etc/traefik:/etc/traefik:ro
- {{ docker_volume_dir }}/traefik/letsencrypt:/letsencrypt
- {{ docker_volume_dir }}/traefik.yml:/traefik.yml:ro
{% if cert_mode == 'acme' %}
- {{ docker_volume_dir }}/letsencrypt:/letsencrypt
{% endif %}
{% if reverseproxy_mode == 'dmz' %}
- {{ docker_volume_dir }}/config:/config:ro
{% endif %}
{% if reverseproxy_mode == 'backend' %}
- /var/run/docker.sock:/var/run/docker.sock:ro
{% endif %}
networks:
- traefik
- {{ traefik_network }}
networks:
traefik:
{{ traefik_network }}:
name: {{ traefik_network }}
external: true

View file

@ -1,47 +1,11 @@
{% if enable_dashboard %}
api:
dashboard: true
insecure: true
{% endif %}
{% if enable_access_logs %}
accessLog:
format: {{ access_log_format }}
{% endif %}
entryPoints:
web:
address: ":80"
{% if use_ssl %}
http:
redirections:
entryPoint:
to: websecure
scheme: https
{% endif %}
websecure:
address: ":443"
providers:
{% if use_static_services | default(false) %}
file:
filename: /etc/traefik/services.yml
watch: true
{% endif %}
{% if use_docker_provider | default(true) %}
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
{% endif %}
certificatesResolvers:
letsencrypt:
acme:
email: admin@digitalboard.ch
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
global:
checkNewVersion: false
sendAnonymousUsage: false
http:
middlewares:
secure-headers:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true

View file

@ -1,30 +1,35 @@
{% if use_static_services | default(false) %}
http:
routers:
{% for service in all_services %}
{% for service in proxied_services %}
{{ service.name }}:
rule: "Host(`{{ service.domain }}`)"
service: {{ service.name }}-service
entryPoints:
{% if use_ssl | default(false) %}
- websecure
- {{ 'websecure' if use_ssl else 'web' }}
{% if use_ssl %}
tls:
certResolver: letsencrypt
{% if cert_mode == 'acme' %}
certResolver: {{ ssl_cert_resolver }}
{% else %}
- web
{}
{% endif %}
{% endif %}
{% endfor %}
services:
{% for service in all_services %}
{% for service in proxied_services %}
{{ service.name }}-service:
loadBalancer:
passHostHeader: true
servers:
- url: "{{ service.upstream_protocol }}://{{ service.backend_host }}:{{ service.port }}"
{% if service.health_check is defined %}
healthCheck:
path: "{{ service.health_check }}"
interval: "30s"
- url: "{{ service.protocol }}://{{ service.backend_host }}:{{ service.port }}"
{% if service.protocol == 'https' and cert_mode == 'selfsigned' %}
serversTransport: insecure-transport
{% endif %}
{% endfor %}
{% if cert_mode == 'selfsigned' %}
serversTransports:
insecure-transport:
insecureSkipVerify: true
{% endif %}

View file

@ -1,9 +1,17 @@
log:
level: {{ log_level }}
{% if enable_dashboard %}
api:
dashboard: true
insecure: true
{% endif %}
{% if enable_access_logs %}
accessLog:
format: {{ access_log_format }}
{% endif %}
entryPoints:
web:
address: ":80"
@ -18,24 +26,36 @@ entryPoints:
address: ":443"
providers:
{% if use_static_services | default(false) %}
{% if reverseproxy_mode == 'dmz' %}
file:
filename: /etc/traefik/services.yml
directory: /config
watch: true
{% endif %}
{% if use_docker_provider | default(true) %}
{% if reverseproxy_mode == 'backend' %}
docker:
endpoint: "unix:///var/run/docker.sock"
network: {{ traefik_network }}
exposedByDefault: false
{% endif %}
{% if use_ssl and cert_mode == 'acme' %}
certificatesResolvers:
letsencrypt:
{{ ssl_cert_resolver }}:
acme:
email: admin@digitalboard.ch
email: {{ ssl_email }}
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
dnsChallenge:
provider: rfc2136
resolvers:
- "{{ acme_dns_nameserver }}"
{% endif %}
{% if use_ssl %}
tls:
options:
default:
minVersion: VersionTLS12
{% endif %}
global:
checkNewVersion: false