feat(stacks): migrate traefik
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -64,3 +64,7 @@ terraform.rc
|
||||
*.agekey
|
||||
age-key.txt
|
||||
keys.txt
|
||||
|
||||
# Le stockage ACME de Traefik : clé de compte Let's Encrypt et clés privées de
|
||||
# tous les certificats. Vit sur le serveur uniquement, jamais dans le dépôt.
|
||||
acme.json
|
||||
|
||||
42
stacks/traefik/docker-compose.yml
Normal file
42
stacks/traefik/docker-compose.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.6
|
||||
container_name: traefik2
|
||||
restart: always
|
||||
# Pas de `command:` : Traefik lit /etc/traefik/traefik.toml par défaut.
|
||||
# Un `--log.level` ici écraserait silencieusement celui du toml.
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./traefik.toml:/etc/traefik/traefik.toml:ro
|
||||
- ./dynamic:/etc/traefik/dynamic:ro
|
||||
# Doit exister comme FICHIER avant le premier `up`, sinon Docker crée un
|
||||
# répertoire à sa place et Traefik redemande tous les certificats.
|
||||
- ./acme.json:/acme.json
|
||||
- traefik_log:/var/log/traefik/
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
secrets:
|
||||
- crowdsec_lapi_key
|
||||
networks:
|
||||
- traefik-proxy
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=traefik-proxy
|
||||
- traefik.http.routers.traefik_secure.entrypoints=web-secure
|
||||
- traefik.http.routers.traefik_secure.rule=Host(`chioggia.opytex.org`)
|
||||
- traefik.http.routers.traefik_secure.tls.certresolver=letsencrypt
|
||||
- traefik.http.routers.traefik_secure.service=api@internal
|
||||
- traefik.http.routers.traefik_secure.middlewares=authentik@file
|
||||
|
||||
secrets:
|
||||
crowdsec_lapi_key:
|
||||
environment: CROWDSEC_LAPI_KEY
|
||||
|
||||
volumes:
|
||||
traefik_log:
|
||||
external: true
|
||||
|
||||
networks:
|
||||
traefik-proxy:
|
||||
external: true
|
||||
18
stacks/traefik/dynamic/chioggia.opytex.yml
Normal file
18
stacks/traefik/dynamic/chioggia.opytex.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
http:
|
||||
middlewares:
|
||||
authentik:
|
||||
forwardAuth:
|
||||
address: http://authentik:9000/outpost.goauthentik.io/auth/traefik
|
||||
trustForwardHeader: true
|
||||
authResponseHeaders:
|
||||
- X-authentik-username
|
||||
- X-authentik-groups
|
||||
- X-authentik-email
|
||||
- X-authentik-name
|
||||
- X-authentik-uid
|
||||
- X-authentik-jwt
|
||||
- X-authentik-meta-jwks
|
||||
- X-authentik-meta-outpost
|
||||
- X-authentik-meta-provider
|
||||
- X-authentik-meta-app
|
||||
- X-authentik-meta-version
|
||||
18
stacks/traefik/dynamic/crowdsec.yml
Normal file
18
stacks/traefik/dynamic/crowdsec.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
http:
|
||||
middlewares:
|
||||
crowdsec:
|
||||
plugin:
|
||||
bouncer:
|
||||
enabled: true
|
||||
crowdsecMode: stream
|
||||
crowdsecLapiScheme: http
|
||||
crowdsecLapiHost: crowdsec:8080
|
||||
# La clé elle-même est dans secrets.env.yaml. Le provider fichier de
|
||||
# Traefik ne substitue aucune variable d'environnement : c'est ce
|
||||
# `...File` qui permet de garder ce fichier versionnable en clair.
|
||||
crowdsecLapiKeyFile: /run/secrets/crowdsec_lapi_key
|
||||
logLevel: ERROR
|
||||
# crowdsecAppsecEnabled: true
|
||||
# crowdsecAppsecHost: crowdsec:7422
|
||||
# crowdsecAppsecFailureBlock: false
|
||||
# crowdsecAppsecUnreachableBlock: false
|
||||
28
stacks/traefik/dynamic/opytex.toml
Normal file
28
stacks/traefik/dynamic/opytex.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[http]
|
||||
|
||||
[http.routers]
|
||||
[http.routers.opytexorg]
|
||||
entryPoints = ["web-secure"]
|
||||
service = "opytexorg"
|
||||
middlewares = ["opytexorgstripprefix@file", "opytexorgindex@file", "opytexerror@file"]
|
||||
rule = "Host(`opytex.org`)"
|
||||
[http.routers.opytexorg.tls]
|
||||
certResolver = "letsencrypt"
|
||||
[[http.routers.opytexorg.tls.domains]]
|
||||
main = "opytex.org"
|
||||
|
||||
[http.services]
|
||||
[http.services.opytexorg.loadBalancer]
|
||||
[[http.services.opytexorg.loadBalancer.servers]]
|
||||
url = "http://minio:9000"
|
||||
|
||||
[http.middlewares]
|
||||
[http.middlewares.opytexorgindex.replacePathRegex]
|
||||
regex = "^(.*)/$"
|
||||
replacement = "$1/index.html"
|
||||
[http.middlewares.opytexorgstripprefix.addPrefix]
|
||||
prefix = "/opytex.org"
|
||||
[http.middlewares.opytexerror.errors]
|
||||
status = ["400-499", "500-599"]
|
||||
service = "opytexorg@file"
|
||||
query = "opytex.org/404.html"
|
||||
22
stacks/traefik/dynamic/raw.toml
Normal file
22
stacks/traefik/dynamic/raw.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[http]
|
||||
|
||||
[http.routers]
|
||||
[http.routers.raw]
|
||||
entryPoints = ["web-secure"]
|
||||
service = "raw"
|
||||
middlewares = ["rawstripprefix@file"]
|
||||
rule = "Host(`raw.opytex.org`)"
|
||||
[http.routers.raw.tls]
|
||||
certResolver = "letsencrypt"
|
||||
[[http.routers.raw.tls.domains]]
|
||||
main = "raw.opytex.org"
|
||||
|
||||
[http.services]
|
||||
[http.services.raw.loadBalancer]
|
||||
[[http.services.raw.loadBalancer.servers]]
|
||||
url = "http://minio:9000"
|
||||
|
||||
[http.middlewares]
|
||||
[http.middlewares.rawstripprefix.addPrefix]
|
||||
prefix = "/raw"
|
||||
|
||||
25
stacks/traefik/secrets.env.yaml
Normal file
25
stacks/traefik/secrets.env.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
CROWDSEC_LAPI_KEY: ENC[AES256_GCM,data:4gTTw3pdbkmpssdRWroB8sEzibQKE3i+STir1gAh1N5W/f58gi6K8lpF8w==,iv:MUEjf0tTf5S+rg8Yb+7PlxgNqGqOGbLHwc8Gd5UAwpM=,tag:rI9tOy/Qzb3XzJgbZXOa6Q==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkeDlpb3N3amFuTTcwamFy
|
||||
a0pSTDhTRExLUUxjM1ZIcmU2Rko3enM1dkZNCm9OZHdxcTBxQnB2RzFEbkNOTEJ6
|
||||
WUdTR3NBeXV3KzlnM2N1YkIvbDJJQUUKLS0tIE9ZQzVKbnZES2tRdkhpUjZjQTdP
|
||||
Qm1EdGR6TitNaTFVS1pyTGhGK08wbGMK9ZwsDE2ih9EI87MNqFuA65XFZBAL2akF
|
||||
4ftZM3DDQsvL62DWPYi6t466zg1kH2R2zx/wz9Jil4LKY13vJgz+bQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age18m2zl3pwgw97djshzcf4wmhrqxksl4erqse6edkpxpkg5rcz0ujs8u4c7s
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlUS9sZXVCLzIwcE4xdXdx
|
||||
K2R5eTJnTWs3NHRlbEVsMEFhSnJIdGdpS0ZzCmNmVnVqYnlUS09pK25hcFJySnRn
|
||||
SS81MHpJYksyaFNUeXFEWWFPQXU2R1EKLS0tIHBnZU1OcGZsSnRCM1hIYWlIZ3hK
|
||||
UGZhSjVRRldXOUI2d3lZNlM1WE9lb2cK1ByWPZ8taCHED0jQiUkldAehZ7KQYPJx
|
||||
E20o7wnn45LHx1Km+R91CeNTj7NFIXCpLZrhcXo89knvL4qFNCkT+Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1fh70nny5hzz8a8g9077kgfca0lhlh3wsjf4fjrv4klx5ucdtpdmsazuqdj
|
||||
lastmodified: "2026-08-14T04:19:11Z"
|
||||
mac: ENC[AES256_GCM,data:VVPI//syL8fm81WyyIgxLlRdNYdQr9IZT2s5Kf9TygBqFwhqooaVHiYu1uaka3gEpFf0fZkfz3iB7i/vkT+/c6MoXKSUkVrUmT2FaDEZM2WITv3C7+Nh0hrcrksWGBXXsUSBW0ZnFm7WPG/SlYoyqcF+W9oHbA/fa4nCHJY4pBk=,iv:KsPgBCPwgbYf9opXpUgd6DHqf+M2Zuu0oIBdQAA1+p4=,tag:0e9wO7H6LQU5YHjjJsClNQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
54
stacks/traefik/traefik.toml
Normal file
54
stacks/traefik/traefik.toml
Normal file
@@ -0,0 +1,54 @@
|
||||
[global]
|
||||
checkNewVersion = true
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "INFO"
|
||||
filePath = "/var/log/traefik/traefik.log"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
[entryPoints.web.http.redirections.entryPoint]
|
||||
to = "web-secure"
|
||||
scheme = "https"
|
||||
|
||||
[entryPoints.web-secure]
|
||||
address = ":443"
|
||||
|
||||
[accessLog]
|
||||
filePath = "/var/log/traefik/access.log"
|
||||
bufferingSize = 100
|
||||
|
||||
[providers]
|
||||
[providers.file]
|
||||
directory = "/etc/traefik/dynamic"
|
||||
|
||||
[providers.docker]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
watch = true
|
||||
exposedbydefault = false
|
||||
|
||||
[api]
|
||||
dashboard = true
|
||||
debug = false
|
||||
|
||||
# Sans ce bloc, `plugin.bouncer` référencé par dynamic/crowdsec.yml n'existe
|
||||
# pas : le middleware crowdsec@file devient invalide et TOUS les routeurs qui
|
||||
# l'utilisent tombent en erreur (404). Traefik ne lit cette configuration qu'au
|
||||
# démarrage — un conteneur déjà lancé survit à sa disparition, la panne
|
||||
# n'apparaît qu'à la recréation suivante. C'est arrivé le 2026-08-13.
|
||||
[experimental]
|
||||
[experimental.plugins.bouncer]
|
||||
moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
version = "v1.4.5"
|
||||
|
||||
[certificatesResolvers.letsencrypt.acme]
|
||||
email = "benjamin.bertrand@opytex.org"
|
||||
# Chemin ABSOLU, et monté depuis l'hôte. En relatif, le fichier atterrissait
|
||||
# dans le répertoire de travail du conteneur : perdu à chaque recréation,
|
||||
# avec réémission complète des certificats et plafond Let's Encrypt au bout.
|
||||
storage = "/acme.json"
|
||||
tlschallenge = true
|
||||
[certificatesResolvers.letsencrypt.acme.httpChallenge]
|
||||
entryPoint = "web"
|
||||
Reference in New Issue
Block a user