feat(stacks): migrate traefik
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -64,3 +64,7 @@ terraform.rc
|
|||||||
*.agekey
|
*.agekey
|
||||||
age-key.txt
|
age-key.txt
|
||||||
keys.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