Traefik 2.0 in a docker swarm environment

traefik logo

Context

Traefik is an edge router, it distribute all my HTTP HTTPS and TCP request to the good docker container.
This page dscribe how i setup a docker traefik instance in my personal docker swarm
Environement : 2 managers and 2 workers. Hosted on 4 DigitalOcean's droplets.

This article explain how i setup trafik and his dashborad.

Docker file

I want traefik to listen port :80 and :443 and i'll publish the ports in host mode to be sure to have the real IP's of the clients
(I need them for security purpose, allow only my IP by example) :

    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
        - target: 443
        published: 443
        protocol: tcp
        mode: host

For the :443 port, i want a valid SLL certificate, so i need to define a Cloudflare environement variable filled with my cloudflare mail and API key (this could be done in the static configuration file, but, i simply choose to write my credentals here):

    environment:
      - "CF_API_EMAIL=me@domain.com"
      - "CF_API_KEY=cloudflare_super_secure_key"

Next step is to bind some needed volumes to my container :

    volumes:
      # Directory for letsencrypt files
      - "/var/data/traefik2/letsencrypt:/letsencrypt"

      # Mount the docker socket
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

      # Mount the static traefik.toml file
      - "/var/data/traefik2/conf/traefik.toml:/etc/traefik/traefik.toml"

      # Directory for the logs
      - "/var/data/traefik2/log/:/log"

      # Directory for the config files
      - "/var/data/traefik2/conf:/config"

      # Set the same timezone as the host
      - "/etc/timezone:/etc/timezone:ro"
      - "/etc/localtime:/etc/localtime:ro"

You can view the full docker-compose.yml file here

Configuration files

traefik.toml

Now that the compose file is ready It's time to setup traefik.toml static configuration file.

First let's send Anonymous usage to help the Traefik team

[global]
  sendAnonymousUsage = true

I then need to tell traefik witch port to use (aka the same ports i exposed in the docker-file.yml)

[entryPoints]
  [entryPoints.http]
    address = ":80"
  [entryPoints.https]
    address = ":443"

The next step is to define the providers

[providers]

  # Docker
  [providers.docker]

    # Traefik will watch for docker container change
    watch = true

    # The endpoint to look at
    endpoint = "unix:///var/run/docker.sock"

    # My containers will  never be exposed if i don't configure them
    exposedByDefault = false

    # Docker is in swarm mode
    swarmMode = true

    # The default docker network to look at
    network = "lan_nosecure"

  # File (aka traefik.toml and/or others dynamic configuration files)  
  [providers.file]

    # My configuratioin files will be located in thiis directory
    directory = "/config"

    # Traefik will watch for file change
    watch = true

I want the API to be rechable (i'll define later in the dynamic.toml file how to access it)

[api]
  insecure = false
  dashboard = true
  debug = true

Send metrics to my influxDB

[metrics]
  [metrics.influxDB]
    address = "http://IP:8086"
    protocol = "http"
    pushInterval = "10s"
    database = "traefik"
    retentionPolicy = ""
    addEntryPointsLabels = true
    addServicesLabels = true

Define the log and the access files

[log]
  level = "error"
  filePath = "/log/traefik.log"

[accessLog]
  filePath = "/log/access.log"

I'll next set the SSL certificate configuration

[certificatesResolvers]
  [certificatesResolvers.cloudflare]
    [certificatesResolvers.cloudflare.acme]

      # Where to save my certificates
      storage = "/letsencrypt/acme.json"    

      # When you're good to go you can remove this line (it's the letsencrypt testing server)  
      caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"

      # I choose to use the DNS challenge and wait 10 seconds before check
      [certificatesResolvers.cloudflare.acme.dnsChallenge]
        provider = "cloudflare"
        delayBeforeCheck = 10

You can view the full traefik.toml file here

dynamic.toml

The hard part !

I consider this part as the "tricky" part of the setup as it's more complex to write. It's require patience and no typo !

I first want all my HTTP traffic to be SSL encrypted so i define a global redirect. All the HTTP requests will be routed to the HTTPS router :

[http.routers]

# Define a router and name it redirecttohttps
[http.routers.redirecttohttps]

    # This router will use this entrypoint
    entryPoints = ["http"]

    # Definition of what to catch (this regex means all)
    rule = "HostRegexp(`{host:.+}`)"

    # The traffic that enter trought this entrypoint is send to this middleware
    middlewares = ["httpsredirect"]

    # Each middleware must have a service
    service = "noop"

I also have to define this service (to avoid an error, even if this one will never be called).

[http.services]
      [http.services.noop.loadBalancer]
        [[http.services.noop.loadBalancer.servers]]
          url = "http://pony.club"

Time to define the httpsredirect middleware.

[http.middlewares]
  [http.middlewares.httpsredirect.redirectScheme]
    scheme = "https"
    permanent = true

This will tell traefik to listen all the HTTP requests (:80 as defined in traefik.toml) and redirect them to the HTTPS entrypoint (:443 as defined in traefik.toml)

I also want the traefik dashboard to be rechable from home, so i defined the router named traefik and apply the home-ipwhitelist middleware to the requests.

  [http.routers.traefik]

    # on witch entrypoint this router will listen  
    entryPoints = ["https"]

    # the rule definition
    rule = "Host(`traefik.ch1.ninja`)"

    # the requests have to go trought theses middlewares before the service
    middlewares = ["home-ipwhitelist@file", "security-headers@file"]

    # Witch service this router will serve
    service = "api@internal"

    # this define the resolver this router will use to generate the SSL certiificate
    [http.routers.traefik.tls]
      certResolver = "cloudflare"

Lets define the middlewares used by the traefik router.

This one is for the headers, you can see on securityheaders.com that this configuration is graded "A".
It include, CORS headers, security headers and some policy.
A good ressource for undestanding headers : developer.mozilla.org

  [http.middlewares.security-headers.headers]
      AccessControlAllowMethods = ["GET", "OPTIONS", "PUT"]
      AccessControlAllowOrigin = "origin-list-or-null"
      AccessControlMaxAge = 100
      #AddVaryHeader = true
      BrowserXssFilter = true
      ContentTypeNosniff = true
      ForceSTSHeader = true
      FrameDeny = true
      SSLRedirect = true
      #STSIncludeSubdomains = true
      STSPreload = true
      CustomFrameOptionsValue = "SAMEORIGIN"
      ReferrerPolicy = "same-origin"
      FeaturePolicy = "geolocation 'self'"
      STSSeconds = 315360000

Now the home IP middleware.

  [http.middlewares.home-ipwhitelist.ipWhiteList]
    sourceRange = ["homeIP/32"]

You can view the full traefik.toml file here

tls.toml

I have separated this configuration in a dedicated file, you can see on www.ssllabs.com that this configuration is secured.

[tls.options]

 [tls.options.default]
    sniStrict = true
    cipherSuites = [
      "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
      "TLS_AES_128_GCM_SHA256",
      "TLS_AES_256_GCM_SHA384",
      "TLS_CHACHA20_POLY1305_SHA256",
      "TLS_FALLBACK_SCSV"
      ]

Find the tls.toml file here

Here we are !, the configuration is done, and, as you can see (you navigate to this site trought traefik right now) it's workinig like a charm !

I really thanks the traefik team for this fast, secure and marvelous product .

Thanks for reading.