InfluxDB 2.0 - Quickly Enabling TLS Encryption with Traefik
Tags: docker, security, microservices, devops
Published: 2021-03-24 11:25:34

Jumpstart your influxdb stack with automated TLS certificate management.

## Use case

So you're running a bunch of IoT/Finance or Cryptocurrency services with Docker or Kubernetes and decided to use that shiny InfluxDB 2.0 dashboard in _production_. Heck, maybe even play around with the [flux][3] API too - for those [extra fancy][4] queries.

![infludb 2.0 dashboard](https://www.influxdata.com/wp-content/uploads/next-gen-data-exploration-analytics-influxdb-2.0.png)
*(Who needs Grafana?)*

Obviously, at this point you'll need to secure the traffic between your web application and your server (plus the microservices). This is exactly what [TLS][2] encryption does - you know, adds that `https://` you see everywhere.

## What you will need

- You are running running your backend in Docker containers. This is either Docker, Docker Swarm or Kubernetes, or any other vendor that runs docker containers defined thru a YAML compose file or similar.
- You are either running or planning to run [Traefik][6] as your proxy.
- You own a domain name and can manage its DNS records.

Optional:

- Your DNS Service allows [DNS-01 ACME Challenge][5] to generate wildcard TLS certificates.
- You have the [necessary credentials][7] to access your DNS Service API.

## Instructions

1. If you opt for the simpler [HTTP-01 ACME Challenge][11], make sure you **create all the necessary DNS records beforehand** (i.e. create *A* or *CNAME* records), including any wildcard subdomain names. Having said that, if your _really need_ wildcard certificates for subdomains, you should use the DNS-01 ACME Challenge.

    > CAUTION: There are [pros and cons][5] for using each type of challenge. Use industry best practices and your own judgement when deciding which method to use for your project.

    Below is an example of typical DNS record table for a *HTTP-01 Challenge*:

    | Type | Hostname | Value | TTL (seconds) |
    |-|-|-|-|
    | A | example.com | *directs to* 203.0.113.123 | 3600 |
    | A | traefik.example.com | *directs to* 203.0.113.123 | 3600 |
    | CNAME | influxdb.example.com | *is an alias of* example.com | 43200 |

    Our main domain name is *example.com* and we are running two services that will be exposed via subdomain names *traefik.\** and *influxdb.\**. If you have more services you want accessible via subdomain, now's the time to add them here. TTL settings are irrelevant for this tutorial, so feel free to [dive deeper][8] into DNS on your own time.

    And here is the same table when attempting the *DNS-01 Challenge*:

    | Type | Hostname | Value | TTL (seconds |
    |-|-|-|-|
    | A | *.example.com | *directs to* 203.0.113.123 | 3600 |
    | A | example.com | *directs to* 203.0.113.123 | 3600 |

2. We will store our certificates in a bind mounted `acme.json`. Therefore, appropriate permissions need to be set beforehand for Traefik to access (accept it as secure) this file:

    ```sh
    touch acme.json; chmod 600 acme.json
    ```

3. (Re)define your InfluxDB container by applying the following `labels`:

    For Docker (using Compose):

    ```yaml
      ...
      my-influxdb:
        image: influxdb:2.0
        labels:
          # traefik router settings
          traefik.http.routers.my-influxdb.rule: "Host(`influxdb.example.com`)"
          traefik.http.routers.my-influxdb.tls: "true"
          traefik.http.routers.my-influxdb.tls.certresolver: "myresolver"
          traefik.http.routers.my-influxdb.entrypoints: "websecure"
    ```

    For Docker Swarm:

    ```yaml
      ...
      my-influxdb:
        image: influxdb:2.0
        deploy:
          labels:
            # traefik router settings
            traefik.http.routers.my-influxdb.rule: "Host(`influxdb.example.com`)"
            traefik.http.routers.my-influxdb.tls: "true"
            traefik.http.routers.my-influxdb.tls.certresolver: "myresolver"
            traefik.http.routers.my-influxdb.entrypoints: "websecure"

            # traefik service settings
            traefik.http.services.my-influxdb.loadbalancer.server.port: "8086"
    ```

    The names `my-influxdb`, `myresolver`, and `websecure` are unique identifiers in the Traefik configuration representing the `router` (service), the `certificate resolver`, and the `entrypoint` (port 443), respectively. Here, we defined the router `my-influxdb` for now, whereas the latter two will be defined in the following step. Notice that we **do not expose service ports** in our compose file anymore, since all traffic will now be handled by Traefik.

    > Note: for Swarm, Traefik requires you explicitly define at least one port the service is running on, even it if has no listening ports. (see [Port Detection][9] - Traefik Docs)

    Don't forget to deploy this new configuration using `docker compose up` or `docker stack deploy -c docker-compose.yml <stack_name>`.

4. Let's now add the Traefik container to your compose file.

    Below are 4 different ways to do this - depending on the combination of orchestrator and type of ACME Challenge.
    
    In all cases, we are using the *staging* Let's Encrypt server - this helps us to generate new certificates in case we mess up much faster since the [rate limit is much higher][10]. Once everything works, simply delete the line defining `certificateresolvers.myresolver.acme.caserver` and redeploy your Traefik container.

    In addition, the DNS server I'm using for the DNS-01 Challenge is `digitalocean`, which requires I define a `DO_AUTH_TOKEN`. Depending on your DNS provider, you will need to [define different credentials][7].

    1. For Docker (Compose) + HTTP-01 Challenge:

        ```yml
        traefik:
          image: "traefik:v2.0.0"
          command:
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.address=:443
            - --providers.docker
            - --api
            - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
            - --certificatesresolvers.myresolver.acme.email=sample@email.com
            - --certificatesresolvers.myresolver.acme.storage=acme.json
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
            - "./acme.json:/acme.json"
          labels:
            # Traefik Dashboard
            traefik.http.routers.traefik.rule: "Host(`traefik.sample.com`)"
            traefik.http.routers.traefik.service: "api@internal"
            traefik.http.routers.traefik.tls.certresolver: "myresolver"
            traefik.http.routers.traefik.entrypoints: "websecure"
            
            # Redirect to HTTPS
            traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: "https"
            traefik.http.routers.http-catchall.rule: "hostregexp(`{host:.+}`)"
            traefik.http.routers.http-catchall.entrypoints: "web"
            traefik.http.routers.http-catchall.middlewares: "redirect-to-https"
        ```

    2. For Docker (Compose) + DNS-01 Challenge:

        ```yml
        traefik:
          image: "traefik:v2.0.0"
          environment:
            DO_AUTH_TOKEN: "0f923jf..."
          command:
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.address=:443
            - --providers.docker
            - --api
            - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
            - --certificatesresolvers.myresolver.acme.dnschallenge.provider=digitalocean
            - --certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=0
            - --certificatesresolvers.myresolver.acme.email=sample@email.com
            - --certificatesresolvers.myresolver.acme.storage=acme.json
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
            - "./acme.json:/acme.json"
          labels:
            # Traefik Dashboard
            traefik.http.routers.traefik.rule: "Host(`traefik.sample.com`)"
            traefik.http.routers.traefik.service: "api@internal"
            traefik.http.routers.traefik.tls.certresolver: "myresolver"
            traefik.http.routers.traefik.entrypoints: "websecure"
            
            # Redirect to HTTPS
            traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: "https"
            traefik.http.routers.http-catchall.rule: "hostregexp(`{host:.+}`)"
            traefik.http.routers.http-catchall.entrypoints: "web"
            traefik.http.routers.http-catchall.middlewares: "redirect-to-https"
        ```

    3. For Docker Swarm + HTTP-01 Challenge:

        ```yml
        traefik:
          image: "traefik:v2.0.0"
          command:
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.address=:443
            - --providers.docker
            - --api
            - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
            - --certificatesresolvers.myresolver.acme.email=sample@email.com
            - --certificatesresolvers.myresolver.acme.storage=acme.json
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
            - "./acme.json:/acme.json"
          deploy:
            labels:
              # Traefik Dashboard
              traefik.http.routers.traefik.rule: "Host(`traefik.sample.com`)"
              traefik.http.routers.traefik.service: "api@internal"
              traefik.http.routers.traefik.tls.certresolver: "myresolver"
              traefik.http.routers.traefik.entrypoints: "websecure"
              
              # Redirect to HTTPS
              traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: "https"
              traefik.http.routers.http-catchall.rule: "hostregexp(`{host:.+}`)"
              traefik.http.routers.http-catchall.entrypoints: "web"
              traefik.http.routers.http-catchall.middlewares: "redirect-to-https"

              # service definitions
              traefik.http.services.traefik.loadbalancer.server.port: "80"
              traefik.http.services.traefik.loadbalancer.server.port: "443"
        ```

    4. For Docker Swarm + DNS-01 Challenge:

        ```yml
        traefik:
          image: "traefik:v2.0.0"
          environment:
            DO_AUTH_TOKEN: "0f923jf..."
          command:
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.address=:443
            - --providers.docker
            - --api
            - --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
            - --certificatesresolvers.myresolver.acme.dnschallenge.provider=digitalocean
            - --certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=0
            - --certificatesresolvers.myresolver.acme.email=sample@email.com
            - --certificatesresolvers.myresolver.acme.storage=acme.json
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
            - "./acme.json:/acme.json"
          deploy:
            labels:
              # Traefik Dashboard
              traefik.http.routers.traefik.rule: "Host(`traefik.sample.com`)"
              traefik.http.routers.traefik.service: "api@internal"
              traefik.http.routers.traefik.tls.certresolver: "myresolver"
              traefik.http.routers.traefik.entrypoints: "websecure"
              
              # Redirect to HTTPS
              traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: "https"
              traefik.http.routers.http-catchall.rule: "hostregexp(`{host:.+}`)"
              traefik.http.routers.http-catchall.entrypoints: "web"
              traefik.http.routers.http-catchall.middlewares: "redirect-to-https"

              # service definitions
              traefik.http.services.traefik.loadbalancer.server.port: "80"
              traefik.http.services.traefik.loadbalancer.server.port: "443"
        ```

    Again, don't forget to deploy this new service and after a couple of seconds you should be up and running.

## Switch to Production

As mentioned in step 4, you'll be using the _staging_ Let's Encrypt server for certificate generation. Thus, your certificate won't be trusted by the browser yet:

![before TLS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/or09mi6ngfmqfc929gt7.jpg)

Easy fix: as soon as everything works as expected with the _invalid_ certificate simply delete the line defining `certificateresolvers.myresolver.acme.caserver` and redeploy your Traefik container.

![After TLS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fuqkjpsjfj7ga4okchu.jpg)


## Still confused?

1. [Why use HTTPS?][1]
2. [What is TLS][2]

[1]: https://snyk.io/blog/10-reasons-to-use-https/
[2]: https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/
[3]: https://docs.influxdata.com/influxdb/cloud/reference/flux/stdlib/
[4]: https://www.influxdata.com/blog/top-5-hurdles-for-flux-beginners-and-resources-for-learning-to-use-flux/
[5]: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge
[6]: https://doc.traefik.io/traefik/
[7]: https://doc.traefik.io/traefik/https/acme/#providers
[8]: https://en.wikipedia.org/wiki/Time_to_live
[9]: https://doc.traefik.io/traefik/providers/docker/#port-detection_1
[10]: https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605
[11]: https://letsencrypt.org/docs/challenge-types/#http-01-challenge
[5]: https://letsencrypt.org/docs/challenge-types/
[6]: https://doc.traefik.io/traefik/
[7]: https://doc.traefik.io/traefik/https/acme/#providers