gero.dev LogoGero Gerke
Published on

Securing internal and external traffic with two Traefik instances

Introduction

Many enterprises, startups, and individuals managing their own homelab encounter the challenge of simultaneously exposing public services to the internet while maintaining exclusive access to certain internal services through VPN connections. As discussed in a previous article where I detailed my setup for cloudflared + traefik + docker, enabling web hosting without exposing ports, this article goes into the process of integrating a secondary Traefik instance into your setup. This second instance efficiently manages traffic for services designated solely for internal usage. Functioning as a gatekeeper, this Traefik instance not only oversees the internal SSL certificate administration but also ensures the proper routing of requests to internal services.

Diagram of the dual-Traefik setup

Configuration

Two Traefik instances

The strategy behind employing two Traefik instances hinges on leveraging the power of Traefik's Label-constraint feature, a mechanism that allows selective filtration of containers based on Docker labels.

To implement this, include the following code snippet in the traefik.yaml configuration of the Traefik instance responsible for handling external traffic:

traefik.yaml
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    constraints: "Label(`dev.gero.expose-externally`,`true`)"

This configuration instructs the external Traefik instance to exclusively route traffic to containers carrying the dev.gero.expose-externally=true label. It's worth noting that you can customize the label according to your specific requirements. However, remember to apply this label to your external services prior to restarting the external Traefik instance, as failing to do so could result in the loss of service detection by the external Traefik instance.

With the label-based routing established for external traffic, you can proceed to integrate a second Traefik instance that manages internal traffic.

docker-compose.traefik-internal.yaml
version: '3'

services:
  traefik-internal:
    image: traefik:latest
    container_name: traefik-internal
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy-internal
    ports:
      # Bind on secure IP (local, VPN) only
      - 100.100.100.100:80:80
      - 100.100.100.100:443:443
    environment:
      - "[email protected]"
      - "CF_API_KEY=yourcfapitoken1234"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
    labels:
      # Expose Internally
      - "dev.gero.expose-internally=true"
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-internal.entrypoints=http"
      - "traefik.http.routers.traefik-internal.rule=Host(`traefik-internal-dashboard.local.my.tld`)"
      - "traefik.http.middlewares.traefik-internal-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.traefik-internal.middlewares=traefik-internal-https-redirect"
      - "traefik.http.routers.traefik-internal-secure.entrypoints=https"
      - "traefik.http.routers.traefik-internal-secure.rule=Host(`traefik-internal-dashboard.local.my.tld`)"
      - "traefik.http.routers.traefik-internal-secure.middlewares=traefik-internal-auth"
      - "traefik.http.routers.traefik-internal-secure.tls=true"
      - "traefik.http.routers.traefik-internal-secure.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik-internal-secure.tls.domains[0].main=local.my.tld"
      - "traefik.http.routers.traefik-internal-secure.tls.domains[0].sans=*.local.my.tld"
      - "traefik.http.routers.traefik-internal-secure.service=api@internal"

networks:
  proxy-internal:
    external: true

Although this looks quite complicated, it's only a couple of components mixed together. The second Traefik instance binds on a designated IP address (like an internal address from 192.168.10.0/24 or from an overlay VPN like Tailscale), ensuring the confinement of internal services within your network and preventing any inadvertent external exposure. Additional labels are assigned to the container to enable the Traefik dashboard with HTTPS redirection and a wildcard certificate. This design ensures that only a certificate for local.my.tld and *.local.my.tld is requested from LetsEncrypt, thereby maintaining confidentiality about your internal services and preventing leaks through Certificate Transparency Logs (like crt.sh).

To ensure that the internal Traefik instance solely forwards traffic to internal services, a Label-constraint is also employed. The complete traefik.yaml configuration for the internal Traefik instance takes the form as presented below:

traefik.yaml
api:
  dashboard: true
  debug: true
entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"
serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    constraints: "Label(`dev.gero.expose-internally`,`true`)"
  file:
    filename: /config.yml
certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

Conclusion

In the ever-evolving landscape of networking and security, finding solutions to efficiently manage both internal and external services can be a daunting challenge. The approach of employing two Traefik instances, each tailored to distinct traffic needs, emerges as a practical solution to this problem.

By utilizing the Traefik Label-constraint feature, you can seamlessly integrate two Traefik instances into your infrastructure. The primary Traefik instance, responsible for external traffic, selectively forwards requests based on the presence of a designated label. This ensures that only services with the appropriate label are exposed to the internet, maintaining a secure and controlled external presence.

The secondary Traefik instance handles internal services, providing a comprehensive SSL certificate management system and efficient forwarding of requests within your internal network. The strategic use of IP filtering, HTTPS redirection, and wildcard certificates not only enhances security but also promotes streamlined management of your internal services. This approach safeguards sensitive internal resources from inadvertent exposure while maintaining the flexibility and scalability required in modern networking setups.

In conclusion, the incorporation of two Traefik instances, each attuned to distinct traffic requirements, offers a pragmatic solution for organizations seeking to strike a balance between external accessibility and internal security. This approach showcases the adaptability of Traefik as a tool to facilitate efficient and secure traffic management, regardless of the complexities posed by diverse service environments.