Traefik¶
Traefik is a modern reverse proxy, directly plugged to the docker engine.
It is in our opinion so much more convenient than nginx as all containers can be exposed simply via
traefik just by setting some labels
in their docker-compose
service
sections. This means that thanks to
docker compose all the stack is version controlled.
Huge source of inspiration¶
If you only had to check one traefik resource in your life (other than official documentation, well written but terribly lacking concrete examples in our opinion), it would of course be Tiangolo one. Our work is heavily inspired by his 🥰. As usual we cannot thanks Tiangolo enough for sharing all his knowledge!
Setting up the VM¶
All the stacks assume that you have docker and related installed on your machine.
If working with a recent Ubuntu Operating System, you can go check on our setup script
Setting up the traefik stack¶
There is no sense in setting up locally Traefik, we thus just present the production setup.
All relevant information can be found in the docker-compose.traefik.yml
file (here).
You will need to create one folder traefik_volume
that will welcome certificates generated by let's encrypt.
Note
if you want to learn more on https and DNS, we highly recommend these funny resources:
Warning
Traefik combined with let's encrypt will create an acme.json
file in traefik_volume
.
Do NOT change access rights of the file, nor edit it manually, otherwise you'll break all https accesses to your websites.
Warning
In the past two years, it happened to us once or twice that a human action broke the traefik container.
When deleting and recreating the traefik container with docker-compose.traefik.yml
, be aware that you recreate a new traefik-network
.
This network is then different from the one still used by both all your alive containers working hand in hand with traefik,
and the ones presumably referenced (in a way too advanced for our current understanding of the matter ðŸ˜. If you know more, do not hesitate to contact us 😊.) in the acme.json
.
As a consequence, https will stop working. To restore it:
- stop-remove all your containers
- delete
acme.json
- recreate the Traefik stack
- recreate the db stack
- recreate all other needed stacks
This should restore correct https behaviour! Technical parenthesis that gave us some headache at some point closed 😂
You will need to create a .env
file and setting
traefik_username
: username that will be used in a basic-auth middleware that can serve as a basic authentication mecanism. it is used in this very stack to protect the traefik dashboard that comes out of the box (accessible attraefik_dashboard_url
)traefik_hashed_password
: hashedPASSWORD
associated totraefik_username
. To be generated withopenssl passwd -apr1 PASSWORD
.traefik_dashboard_url
: entry to the DNS address you created in order to reach the traefik dashboardtraefik_volume
: where to store theacme.json
generated by let's encryptacme_email
: the email of the person (hopefully) responsible for this stack. Has to be filled, the actual value is not that relevant (in our experience).
To start the stack in production, you can make use of the Makefile
provided in the repo (make help
to list available commands). Then launch
make traefik-launch
$ make traefik-launch
---> 100%
Traefik successfully setup!
traefik dashboard will be accessible at traefik_dashboard_url
(protected by traefik_username
and PASSWORD
)
Traefik middlewares¶
Out of the box, our docker-compose stacks implements five traefik middlewares, all related to security.
- a middleware providing basic authentication
- a middleware to ensure http to https redirection
- an implementation of the fail2ban middleware
- an implementation of the traefik-modsecurity-plugin: a web application firewall(WAF) middleware
- a middleware to ensure secure https headers to urls protected by it.
Let us review them in order
Basic Authentication¶
This middlewares is quite straightforward. It protects applications that cannot do so on their own with a basic username and password mechanism.
To use it, add in the labels
section of the traefik
service:
# admin-auth middleware with HTTP Basic auth Using env variables USERNAME and HASHED_PASSWORD
# https://doc.traefik.io/traefik/v2.0/middlewares/basicauth/
- traefik.http.middlewares.admin-auth.basicauth.users=${traefik_username}:${traefik_hashed_password}
See the official documentation for more information.
http to https redirection¶
This middlewares is also quite straightforward. It ensures that all http requests are redirected towards https.
To use it, add in the labels
section of the traefik
service:
# https-redirect middleware to redirect HTTP to HTTPS
# https://doc.traefik.io/traefik/v2.0/middlewares/redirectscheme/
- traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
- traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
See the official documentation for more information.
fail2ban¶
This middleware does not exist in vanilla traefik, and a plugin has to be used in order for using fail2ban.
To install it, add in the command
section of the traefik
service:
# Add fail2ban plugin
- --experimental.plugins.fail2ban.modulename=github.com/tomMoulard/fail2ban
- --experimental.plugins.fail2ban.version=v0.7.1
Then in the labels
section:
# Fail2ban middleware, see https://github.com/tomMoulard/fail2ban for more information
- traefik.http.middlewares.fail2ban.plugin.fail2ban.rules.bantime=1h
- traefik.http.middlewares.fail2ban.plugin.fail2ban.rules.enabled=true
- traefik.http.middlewares.fail2ban.plugin.fail2ban.rules.findtime=1m
- traefik.http.middlewares.fail2ban.plugin.fail2ban.rules.maxretry=500
traefik-modsecurity-plugin¶
This plugin is morally a wrapper around owasp modsecurity crs (CRS means Core Rule Set, for french speaking person this could have been a good joke 😋, but it is not) .
To install it, add in the command
section of the traefik
service:
# Add waf plugin
- --experimental.plugins.traefik-modsecurity-plugin.modulename=github.com/acouvreur/traefik-modsecurity-plugin
- --experimental.plugins.traefik-modsecurity-plugin.version=v1.3.0
Then in the labels
section:
# Waf middleware. See https://github.com/acouvreur/traefik-modsecurity-plugin for more info
- traefik.http.middlewares.waf.plugin.traefik-modsecurity-plugin.modSecurityUrl=http://waf:8080
- traefik.http.middlewares.waf.plugin.traefik-modsecurity-plugin.maxBodySize=10485760
modSecurityUrl
is the only compulsory option.
Warning
Contrary to what is explained documentation the port to put is 8080 (and not 80). Presumably the official documentation was not updated since this breaking change. This was the source of a lot of headache on our side, so if we can save your time, it is for the better 😊
the waf
service defined in the label corresponds to the following service
waf:
# see https://github.com/acouvreur/traefik-modsecurity-plugin for more information
image: owasp/modsecurity-crs:nginx
container_name: waf
environment:
- BACKEND=http://dummy
volumes:
- ./RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
env_file:
- ./.env
networks:
# Use the public network created to be shared between Traefik and
# any other service that needs to be publicly available with HTTPS
- traefik-network
Info
- We use the nginx version and not the apache one for this very this very technical reason.
- Core rule set is as its name clearly states a set of rules that trigger in specific conditions. Additional information can be read here and here
- Unfortunately, this rule system can trigger some false positives (quite a lot in fact). It is a tedious task to find them for a specific application, and implement the custom exceptions associated.
- This is done in our case with the AFTER exceptions (that applies at waf boot, just after all the rules have been loaded.
- Exceptions should be put in a file called
RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
sitting alongside withdocker-compose.traefik.yml
.
Other options are available and can be put in the asociated .env
. They are documented here better
than we could. The dummy
service corresponds to
dummy:
# Subtlety to trick waf and avoid one of this for every service we want to protect with waf
# info: https://korteke.medium.com/traefik-proxy-with-web-application-firewall-waf-cb4cd65f34f7
image: containous/whoami
container_name: dummy
env_file:
- ./.env
networks:
# Use the public network created to be shared between Traefik and
# any other service that needs to be publicly available with HTTPS
- traefik-network
Secure headers¶
The security-headers
middleware is heavily based on good header practices presented in the
official traefik documentation.
Another resource that we found useful can be found here.
To create the security-headers
middleware, add in the labels
section of the traefik
service:
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#accesscontrolallowmethods
- traefik.http.middlewares.security-headers.headers.accesscontrolallowmethods=GET, OPTIONS, PUT
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#accesscontrolmaxage
- traefik.http.middlewares.security-headers.headers.accesscontrolmaxage=100
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#addvaryheader
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
- traefik.http.middlewares.security-headers.headers.addvaryheader=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#hostsproxyheaders
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
- traefik.http.middlewares.security-headers.headers.hostsproxyheaders=X-Forwarded-Host
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#sslredirect
- traefik.http.middlewares.security-headers.headers.sslredirect=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#sslproxyheaders
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
- traefik.http.middlewares.security-headers.headers.sslproxyheaders.X-Forwarded-Proto=https
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#stsseconds
- traefik.http.middlewares.security-headers.headers.stsseconds=63072000
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#stsincludesubdomains
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
- traefik.http.middlewares.security-headers.headers.stsincludesubdomains=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#stspreload
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
- traefik.http.middlewares.security-headers.headers.stspreload=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#forcestsheader
- traefik.http.middlewares.security-headers.headers.forcestsheader=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#framedeny
# https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/X-Frame-Options
# If used with pgadmin, too restrictive https://www.pgadmin.org/docs/pgadmin4/development/config_py.html
# - traefik.http.middlewares.security-headers.headers.framedeny=true
# Less restrictive but still restrictive https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/X-Frame-Options
- traefik.http.middlewares.security-headers.headers.customFrameOptionsValue=sameorigin
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#contenttypenosniff
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
- traefik.http.middlewares.security-headers.headers.contenttypenosniff=true
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#referrerpolicy
# https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Referrer-Policy
- traefik.http.middlewares.security-headers.headers.referrerpolicy=same-origin
# https://doc.traefik.io/traefik/v2.0/middlewares/headers/#customresponseheaders
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
- traefik.http.middlewares.security-headers.headers.customresponseheaders.permissions-policy=camera=(); microphone=(); geolocation=(); payment=(); usb=(); vr=();
# https://robots-txt.com/x-robots-tag/
- traefik.http.middlewares.security-headers.headers.customresponseheaders.X-Robots-Tag=none,noarchive,nosnippet,notranslate,noimageindex
# https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- traefik.http.middlewares.security-headers.headers.customresponseheaders.content-security-policy=style-src 'self' 'unsafe-inline';
# see https://github.com/traefik/traefik/issues/9859 for this particular corner case
# Needed to please https://www.hardenize.com/
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
- traefik.http.middlewares.security-headers.headers.customresponseheaders.X-XSS-Protection=0
Hopefully each header has its corresponding documentation and useful information listed.
Middlware chain.¶
To ease reuse of the middlewares, we provide two chains
# Add secured middleware chain
- traefik.http.middlewares.secured.chain.middlewares=fail2ban,waf,security-headers
# Add auth-secured middleware chain
- traefik.http.middlewares.auth-secured.chain.middlewares=fail2ban,waf,security-headers,admin-auth
To use them, just put in the SERVICE
label section of your docker compose stack
# Enable auth-secured chain, using the middleware chain created above
- traefik.http.routers.SERVICE-https.middlewares=auth-secured