feat: comprehensive security hardening
Some checks failed
CI/CD / syntax-check (push) Successful in 43s
CI/CD / deploy (push) Failing after 59s

Traefik:
- Enable access logs → /var/log/traefik/access.log (needed for CrowdSec)
- Add global security headers middleware: HSTS, X-Frame-Options, CSP,
  nosniff, XSS filter, referrer policy, permissions policy
- Add rate limiting: default 100/s, API 30/s, admin 10/s (strict)
- Add Authelia ForwardAuth middleware for SSO integration

CrowdSec (new service):
- Analyzes Traefik access logs + auth.log in real time
- Community IP reputation blocklist (crowdsecurity/traefik + http-cve)
- Firewall bouncer: bans malicious IPs at kernel level (iptables)

Authelia (new service, auth.csrx.ru):
- 2FA/SSO portal with TOTP (Google Authenticator)
- Protects: traefik.csrx.ru, sync.csrx.ru, /god-mode/ in Plane
- Session: 12h expiry, 30m inactivity, Redis backend
- argon2id password hashing

Container security:
- Add security_opt: no-new-privileges to traefik, vaultwarden,
  forgejo, grafana, authelia

CI/CD security:
- Remove hardcoded server IP 87.249.49.32 from workflow
- Use SSH_KNOWN_HOSTS secret instead of ssh-keyscan (prevents MITM)
- Added SSH_KNOWN_HOSTS secret to Forgejo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jack 2026-03-22 03:44:54 +07:00
parent a42ff4afc7
commit aa9706bbc4
14 changed files with 428 additions and 54 deletions

View file

@ -38,9 +38,10 @@ jobs:
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
printf '%s' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -p 22 87.249.49.32 >> ~/.ssh/known_hosts
printf '%s' "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/known_hosts
- name: Write vault password
run: |

View file

@ -9,6 +9,7 @@ domain_plane: "plane.{{ domain_base }}"
domain_sync: "sync.{{ domain_base }}"
domain_traefik: "traefik.{{ domain_base }}"
domain_dashboard: "dashboard.{{ domain_base }}"
domain_auth: "auth.{{ domain_base }}"
# Service paths
services_root: /opt/services
@ -28,6 +29,11 @@ forgejo_runner_token: "{{ vault_forgejo_runner_token }}"
grafana_admin_password: "{{ vault_grafana_admin_password }}"
alertmanager_telegram_token: "{{ vault_alertmanager_telegram_token }}"
alertmanager_telegram_chat_id: "{{ vault_alertmanager_telegram_chat_id }}"
authelia_jwt_secret: "{{ vault_authelia_jwt_secret }}"
authelia_session_secret: "{{ vault_authelia_session_secret }}"
authelia_storage_key: "{{ vault_authelia_storage_key }}"
authelia_admin_password_hash: "{{ vault_authelia_admin_password_hash }}"
crowdsec_bouncer_key: "{{ vault_crowdsec_bouncer_key }}"
# CI/CD deploy key (public key — not a secret)
ci_deploy_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHdr9mRSSUqt7Ym4wA5RpVyz76wEXSOtVfh2/yCSMIbg ci-deploy@forgejo-runner"

View file

@ -1,50 +1,76 @@
$ANSIBLE_VAULT;1.1;AES256
63336635333534363962303037316537376162313965663565623562613761653239313330303333
6163643864313062373336313037383661636238313230650a383062386234313130633231663832
64313337663263313362303964326231386236353339663638656235393633643265653338306139
6463356531653538620a333037376239343430343335336635636361386335303963373364636162
36333531323565616132643431633166633937616630313834623934646134386337393166393834
64326237306432376638623833666638316564353134613365323162336136373335396534623838
32306435383062393933666430386338376531663236323430366162613363666361353066383138
39303864636333343235623136633132663230333433356638653237643635383061366537383462
33616235613164323164643662363862656630393361633764313731323638396232353235313838
62636231326533616538323734303039613238633533336363376639643630306439353838623966
64343135386361663633333162653064306130633263653335313431616365666336613737326237
38343731313234663263633431623561653932333161633731346632326262353565356637666230
61666134613038313665333162653534313034643562353134356131353938393834633265336561
62633063646237393966373334353437636136343938323132356432653664353062363630363633
62613066383232353630373130353433356430356435396136373261653761386435636331643636
37333964393461626461613662616235653036366434613432613864616633323436643331333866
37393235343361363438326466633632323131313830663835663431393137336261616138646666
65383166306638353838643933353065386661663833633666626263653135336631383332643939
61656561666564643335383566363861303939346538333730636364343234306539343263383336
32653439653937313434396466306265336231356437393965363366306134633062356637313464
31386538353039316631613265386261303239653965303163646561316366336234333866653663
30633666383338623661666232643166613638373763333666653963656165366432643665643838
36313135396462303339323638326235666538333530323638336330343263383634643532623164
63633633656630363665636664343863343836636134396634653661613633613165623039626466
33333932386435333335346135663661313831306636623061396565643665396432343732383463
34336333313261643531376432353036336639343834366631393234663064303865396438366432
38313630623936393765393233633437386234316233393934316334663533656538383534623563
66646466613233313234356166363539653239366433623864663239623964643062393535393038
32303132626332303130643064633530313731653831656432323163643733383962343239633436
37386561386637363331666336366539313538353838626364626635333432383936613664326338
66656438656134323632366265383062336566393030386333646632653132623931663863323966
34373232373361326466353066323436653738336431303663383835356436613331313064393835
38636365643061626238356539313133383564393365373131383664326234326332633035373433
36666533636461653638356637363830633464316139646336623636346563663439356630623534
62396436306265336362303735383933356464363437633236363637343665333035633532356637
65353634316365346235313138636461663830636232623534363164393965323265623262643934
62306339653433353839643830653733343062396164383934393262303135666130643233366639
30636565663235323333333933386432333430323433633335383638383632393832323839626437
34636638303261616333653632313266336662366663303432616138636330323232353033326630
34633366613265373864613238663533306334303164386566656133643535303431643938663135
36373838316435613766633261613836353135373832656363643664323533623333336433383761
62363532356563353535643531316338363231333331383236613838386266666139346334353434
30323266616337643034393363333437393630636337303632303530653866376465343364313637
38393863666366613130326165316236396136383132323238653962343734353666333662306163
37643465336432623266623932356330326537316261646239623232396233303637353863633337
38333665306665656431656262643861643435616538313530376438383738373530316565373835
36346263656635363464636533663630363362313461363766373231326434346435386432343866
62376262313065386130363533326133623165666633353264666463633062626434303362636434
32623165363835623931386434663961343037373835346262663738373461633366
61316166393964386231353533353731353730326134323862666166373430346531383435396264
3339363034336365363263643165656264333030323036640a313937396562326539633430643931
61626330343235646637653065666237626564376130376662366238336135373836613362643963
6335393437333362390a393930373132366161333762643535373232613136306664346662366231
33643832306638306130653937323863643237346231363432623462313534386162373866663362
34313665323632393766626535656239333231396438383833663835623963323530323663323539
38373639623235396133373632613337353538666666303538333637333537363162323238376366
65393233366162643835316439613262326531373961646336626232626334643331643438663834
66386332316561333435313535666161323661663038343464383130663131363130303238626632
62383934643539623434333566376463653930353833333433633364383764393732633734633636
37646139656634666666613161396631353164363831353366393365643336376330613565353966
32363431666534316165366636376164353165333738326230326232386137666366353636633865
66623264623730653866663030646431386238633662336162623665356536613832346131316230
34353664373930636361383961646334633838366636343335313438663836623761666235383431
33373534653930623666323433326636633133336538633166333362353663356264323936623763
37396338623962626638346538363565316262646232336266393936323839613533666465663439
63343635346333636539373335323831366630356536336262353534643035323765653366656363
63393534396135393061653234646362303066386133333736643739373164623034396361363539
37326532643064656666363735333535643765643433633131356334393434333939623239343761
32383966646435363936346464616233313865303264333331613437396635373336383664326665
30366436303264633762336234356431666238353535396234383133353362366465363834643666
33653732646264343636646266653138313634346239623764656136303462316364656234623833
35313561323464346435656565633036383264373436313164363262643164616436663564643032
30646637653036663533663430386134663237333030336430373936323738653030353564313464
39393562383735313237366433646431356364363039353539366133333237303532653965666364
35393830336533373133366666653765366562336539646131636633326434393164343530633737
34626263636163626333373438376137636139643263646336643735316462313361663834613031
37396233633831393536313838313964343762383363356238393761646230393334303836633735
65666366393932346636396237333166323936613732333036323333343637393931393534323166
61626334613035353137303365373365373837616336343838643365616538623538653238363664
30356539663763633337393162623764376265646435663064303930643364396439626661666532
38306364356463643866336530303430633766336236616135326462353163343637336438373534
37616536386131366634633663633566313238366133376131353666663464306463313232626436
61306236663332373764303566643332343530366362376134653437356630613937346663323131
65376563666434653132383032643830386465363965653530323036623034313764306136366239
34666538623232646266666537353033386661333861386564316662386233636265366536386135
35653735383231616664366338623264326663353730623461613766613432396233383061383464
35376461323233633938646532373663396233396463323565633539353630653934616231636166
39633037333963653061386362316662613235646236326666633164336661373963386339633932
64616436333637373930373062333463336562303439623937643136323735616231303762373161
62666438613038363833626664316435316331373030343738356438323563313565613039326639
65386533306132663964386330396566623063633433653439383235373761363037633138376461
65323465663137363135336662643432616437323466656666313437333366626234623765643033
30633132636235383561373566366465393664663464643965363634323466303433623361613061
66623861336537333339636161636564356239636562363166326635646166653933376634626234
61643738346263646664356134313138373331343731366532383264613931353030313061636135
30343430383630633966393933396238366463373934653130656433633437323137326666633964
32626639396165323334393263393961663666623137643834373065383966353835613335636362
31646635356233323730393039366162613331393465633139616432353462363165333530373364
32343935643933326136383835633232356263343264373437383630313537343138383135613832
32383738353435323437336137626231343535633364666663633133353662383139383364373837
37386133383135326662383661346639393134313931383637613631343836646663663834336632
64383734373362316666343031393764393161613035373863323839383237643863326664656465
34306637316466366332666237313064366534323961373166663339333439303365633137386236
64313338383037613439626462323737393034303732303537636565353033386365653239326131
32326162663766626264653965323134366664386238393564386163613165383661303832633565
31306335393439323635653731363931663364613438373130623437376638386364333266643838
35303436383839653434316632616163623264326531616439643437663538376333366432666165
38363635653864383662346235353561316233656332383031643938613735396635363436313735
66396535383030353437626165626432646634613434643830303434643530636566333063323366
30663738326562343732376662366566636330346435373838363165643666393764343832656638
65666134616265633138376133386438666465666661323631373539666330616638306439636533
34346365333462623438333930376133383233373064366336343937616638376163303435313163
34303537306532396230383236393731663230393135386133316638343735373666306337376235
34616639326432386266373361306537343637356335613136346261316433613464323263646134
31326232323738313830353535313363663363393037653631353932613834346133616535666361
62353539646331656665323763396662313137366261336139356231646663646564646536373433
62646234393737666635626536656636316535343661626364376536633461383530633135396137
32343163373265623138333162653231636336373661316530633331346463393365353462336136
66336236313765616436646532336164363261656262646135653734376331646665353139613037
65323338316139353837623661353134656164613362313632656163643737353435366432666564
36303631643331373965616239353762663862636232326234643663383664613666303538316465
36323232663263653238393066663839653539343536316461333964316132353531333936663461
37326337653930306637333163343431626663633139303263646639313862313365326665376264
373039623038653731373939343537376634

View file

@ -27,3 +27,8 @@ grafana_image: "grafana/grafana:11.6.1" # https://hub
alertmanager_image: "prom/alertmanager:v0.28.1" # https://hub.docker.com/r/prom/alertmanager/tags
loki_image: "grafana/loki:3.4.3" # https://hub.docker.com/r/grafana/loki/tags
promtail_image: "grafana/promtail:3.4.3" # https://hub.docker.com/r/grafana/promtail/tags
crowdsec_image: "crowdsecurity/crowdsec:v1.6.8" # https://hub.docker.com/r/crowdsecurity/crowdsec/tags
crowdsec_bouncer_image: "crowdsecurity/cs-firewall-bouncer:v0.0.31" # https://hub.docker.com/r/crowdsecurity/cs-firewall-bouncer/tags
authelia_image: "authelia/authelia:4.38" # https://hub.docker.com/r/authelia/authelia/tags
redis_image: "redis:7-alpine" # shared with plane-redis
authelia_admin_user: "admin"

View file

@ -134,6 +134,33 @@
mode: "0644"
notify: Restart stack
- name: Deploy CrowdSec acquisition config
ansible.builtin.template:
src: crowdsec/acquis.yaml.j2
dest: "{{ services_root }}/crowdsec/acquis.yaml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0644"
notify: Restart stack
- name: Deploy Authelia configuration
ansible.builtin.template:
src: authelia/configuration.yml.j2
dest: "{{ services_root }}/authelia/configuration.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
notify: Restart stack
- name: Deploy Authelia users database
ansible.builtin.template:
src: authelia/users.yml.j2
dest: "{{ services_root }}/authelia/users.yml"
owner: "{{ deploy_user }}"
group: "{{ deploy_group }}"
mode: "0600"
notify: Restart stack
- name: Create acme.json for Let's Encrypt certificates
ansible.builtin.file:
path: "{{ services_root }}/traefik/acme.json"

View file

@ -31,3 +31,6 @@
- grafana/provisioning/dashboards/json
- prometheus/rules
- loki
- traefik/logs
- crowdsec
- authelia

View file

@ -25,6 +25,9 @@
- "{{ alertmanager_image }}"
- "{{ loki_image }}"
- "{{ promtail_image }}"
- "{{ crowdsec_image }}"
- "{{ crowdsec_bouncer_image }}"
- "{{ authelia_image }}"
register: pull_result
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
retries: 5

View file

@ -0,0 +1,77 @@
# Generated by Ansible — do not edit manually
# Authelia v4 configuration
theme: dark
server:
host: 0.0.0.0
port: 9091
log:
level: warn
jwt_secret: "{{ authelia_jwt_secret }}"
default_redirection_url: "https://{{ domain_auth }}"
session:
name: authelia_session
secret: "{{ authelia_session_secret }}"
expiration: 12h
inactivity: 30m
domain: "{{ domain_base }}"
redis:
host: authelia-redis
port: 6379
regulation:
max_retries: 3
find_time: 2m
ban_time: 10m
storage:
encryption_key: "{{ authelia_storage_key }}"
local:
path: /config/db.sqlite3
notifier:
disable_startup_check: true
filesystem:
filename: /config/notifications.txt
authentication_backend:
password_reset:
disable: false
file:
path: /config/users.yml
password:
algorithm: argon2id
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
access_control:
default_policy: deny
rules:
# Authelia portal itself — всегда доступен
- domain: "{{ domain_auth }}"
policy: bypass
# Traefik dashboard — только admin, требует 2FA
- domain: "{{ domain_traefik }}"
policy: two_factor
subject: "group:admins"
# Syncthing — только admin, требует 2FA
- domain: "{{ domain_sync }}"
policy: two_factor
subject: "group:admins"
# Plane god-mode — только admin, требует 2FA
- domain: "{{ domain_plane }}"
resources:
- "^/god-mode/.*$"
policy: two_factor
subject: "group:admins"

View file

@ -0,0 +1,12 @@
# Generated by Ansible — do not edit manually
# Authelia users database
# To update password hash: docker exec authelia authelia crypto hash generate argon2 --password 'yourpassword'
# To set up TOTP: visit https://{{ domain_auth }} and login — QR code will appear on first use
users:
{{ authelia_admin_user }}:
displayname: "Admin"
password: "{{ authelia_admin_password_hash }}"
email: "{{ acme_email }}"
groups:
- admins

View file

@ -0,0 +1,18 @@
# Generated by Ansible — do not edit manually
# CrowdSec acquisition config: what log sources to watch
---
filenames:
- /var/log/traefik/access.log
labels:
type: traefik
---
filenames:
- /var/log/auth.log
labels:
type: syslog
---
filenames:
- /var/log/syslog
labels:
type: syslog

View file

@ -26,6 +26,9 @@ networks:
monitoring:
driver: bridge
internal: true
authelia-internal:
driver: bridge
internal: true
volumes:
vaultwarden_data:
@ -41,6 +44,8 @@ volumes:
prometheus_data:
grafana_data:
loki_data:
crowdsec_data:
authelia_data:
services:
@ -50,6 +55,8 @@ services:
image: {{ traefik_image }}
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
@ -60,6 +67,7 @@ services:
- {{ services_root }}/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- {{ services_root }}/traefik/dynamic:/etc/traefik/dynamic:ro
- {{ services_root }}/traefik/acme.json:/acme/acme.json
- {{ services_root }}/traefik/logs:/var/log/traefik
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
@ -71,6 +79,8 @@ services:
image: {{ vaultwarden_image }}
container_name: vaultwarden
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- backend
volumes:
@ -94,6 +104,8 @@ services:
image: {{ forgejo_image }}
container_name: forgejo
restart: unless-stopped
security_opt:
- no-new-privileges:true
depends_on:
forgejo-db:
condition: service_healthy
@ -484,6 +496,8 @@ services:
image: {{ grafana_image }}
container_name: grafana
restart: unless-stopped
security_opt:
- no-new-privileges:true
depends_on:
- prometheus
networks:
@ -532,5 +546,75 @@ services:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ services_root }}/traefik/logs:/var/log/traefik:ro
- {{ services_root }}/loki/promtail.yml:/etc/promtail/config.yml:ro
command: -config.file=/etc/promtail/config.yml
# ── Security Stack ─────────────────────────────────────────────────────────
# CrowdSec: анализирует логи Traefik, банит злоумышленников по IP
# Использует community-репутацию + локальный анализ поведения
crowdsec:
image: {{ crowdsec_image }}
container_name: crowdsec
restart: unless-stopped
networks:
- monitoring
environment:
- COLLECTIONS=crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/linux
- GID=1000
volumes:
- crowdsec_data:/var/lib/crowdsec/data
- {{ services_root }}/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- {{ services_root }}/traefik/logs:/var/log/traefik:ro
- /var/log/auth.log:/var/log/auth.log:ro
- /var/log/syslog:/var/log/syslog:ro
# Bouncer: получает решения от CrowdSec и блокирует IP через firewall
crowdsec-bouncer:
image: {{ crowdsec_bouncer_image }}
container_name: crowdsec-bouncer
restart: unless-stopped
networks:
- monitoring
environment:
- CROWDSEC_BOUNCER_API_KEY=${CROWDSEC_BOUNCER_KEY}
- CROWDSEC_AGENT_HOST=crowdsec:8080
- GID=0
cap_add:
- NET_ADMIN
- NET_RAW
# ── Authelia: 2FA SSO portal ───────────────────────────────────────────────
# Защищает: Traefik dashboard, Syncthing, Plane /god-mode/
# Вход: логин + пароль + TOTP (Google Authenticator)
authelia:
image: {{ authelia_image }}
container_name: authelia
restart: unless-stopped
depends_on:
- authelia-redis
networks:
- backend
- authelia-internal
volumes:
- authelia_data:/config
- {{ services_root }}/authelia/configuration.yml:/config/configuration.yml:ro
- {{ services_root }}/authelia/users.yml:/config/users.yml:ro
environment:
- AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET}
- AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET}
- AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_KEY}
- TZ=UTC
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9091/api/health"]
interval: 30s
timeout: 5s
retries: 3
authelia-redis:
image: {{ redis_image }}
container_name: authelia-redis
restart: unless-stopped
networks:
- authelia-internal
command: redis-server --appendonly yes --maxmemory 64mb --maxmemory-policy allkeys-lru

View file

@ -12,3 +12,7 @@ DOMAIN_SYNC={{ domain_sync }}
DOMAIN_TRAEFIK={{ domain_traefik }}
FORGEJO_RUNNER_TOKEN={{ forgejo_runner_token }}
GRAFANA_ADMIN_PASSWORD={{ grafana_admin_password }}
AUTHELIA_JWT_SECRET={{ authelia_jwt_secret }}
AUTHELIA_SESSION_SECRET={{ authelia_session_secret }}
AUTHELIA_STORAGE_KEY={{ authelia_storage_key }}
CROWDSEC_BOUNCER_KEY={{ crowdsec_bouncer_key }}

View file

@ -9,7 +9,7 @@ http:
tls:
certresolver: letsencrypt
service: api@internal
middlewares: [traefik-auth]
middlewares: [authelia@docker, rate-limit-strict]
vaultwarden:
rule: "Host(`{{ domain_vault }}`)"
@ -17,6 +17,7 @@ http:
tls:
certresolver: letsencrypt
service: vaultwarden
middlewares: [rate-limit-default]
forgejo:
rule: "Host(`{{ domain_git }}`)"
@ -24,6 +25,7 @@ http:
tls:
certresolver: letsencrypt
service: forgejo
middlewares: [rate-limit-default]
plane-api:
rule: "Host(`{{ domain_plane }}`) && (PathPrefix(`/api/`) || PathPrefix(`/auth/`))"
@ -31,6 +33,7 @@ http:
tls:
certresolver: letsencrypt
service: plane-api
middlewares: [rate-limit-api]
plane:
rule: "Host(`{{ domain_plane }}`)"
@ -38,6 +41,25 @@ http:
tls:
certresolver: letsencrypt
service: plane-web
middlewares: [rate-limit-default]
plane-godmode:
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/god-mode/`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-admin
middlewares: [authelia@docker, rate-limit-strict]
priority: 10
plane-spaces:
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/spaces/`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: plane-space
middlewares: [rate-limit-default]
priority: 10
syncthing:
rule: "Host(`{{ domain_sync }}`)"
@ -45,7 +67,7 @@ http:
tls:
certresolver: letsencrypt
service: syncthing
middlewares: [syncthing-auth]
middlewares: [authelia@docker, rate-limit-strict]
grafana:
rule: "Host(`{{ domain_dashboard }}`)"
@ -53,6 +75,15 @@ http:
tls:
certresolver: letsencrypt
service: grafana
middlewares: [rate-limit-default]
authelia:
rule: "Host(`{{ domain_auth }}`)"
entrypoints: [websecure]
tls:
certresolver: letsencrypt
service: authelia
middlewares: [rate-limit-strict]
services:
vaultwarden:
@ -75,6 +106,16 @@ http:
servers:
- url: "http://plane-web:3000"
plane-admin:
loadBalancer:
servers:
- url: "http://plane-admin:80"
plane-space:
loadBalancer:
servers:
- url: "http://plane-space:3000"
syncthing:
loadBalancer:
servers:
@ -85,7 +126,51 @@ http:
servers:
- url: "http://grafana:3000"
authelia:
loadBalancer:
servers:
- url: "http://authelia:9091"
middlewares:
# ── Security Headers (applied globally via entrypoint) ─────────────────
security-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=()"
customResponseHeaders:
X-Robots-Tag: "noindex, nofollow"
Server: ""
# ── Rate Limiting ──────────────────────────────────────────────────────
# Default: 100 req/s burst 50 — general web traffic
rate-limit-default:
rateLimit:
average: 100
burst: 50
period: 1s
# API: 30 req/s burst 20 — API endpoints
rate-limit-api:
rateLimit:
average: 30
burst: 20
period: 1s
# Strict: 10 req/s burst 5 — admin/login interfaces
rate-limit-strict:
rateLimit:
average: 10
burst: 5
period: 1s
# ── Auth (legacy basic auth kept for direct fallback) ─────────────────
traefik-auth:
basicAuth:
users:
@ -95,3 +180,14 @@ http:
basicAuth:
users:
- "{{ syncthing_basic_auth_htpasswd }}"
# ── Authelia ForwardAuth ───────────────────────────────────────────────
authelia:
forwardAuth:
address: "http://authelia:9091/api/verify?rd=https://{{ domain_auth }}"
trustForwardHeader: true
authResponseHeaders:
- Remote-User
- Remote-Groups
- Remote-Email
- Remote-Name

View file

@ -8,7 +8,16 @@ global:
log:
level: INFO
accessLog: {}
accessLog:
filePath: /var/log/traefik/access.log
bufferingSize: 100
fields:
defaultMode: keep
headers:
defaultMode: drop
names:
User-Agent: keep
Referer: drop
api:
dashboard: true
@ -26,6 +35,9 @@ entryPoints:
scheme: https
websecure:
address: ":443"
http:
middlewares:
- security-headers@file
certificatesResolvers:
letsencrypt: