Categories
Networking Server SysAdmin

File Traefik

Serve files securely via SFTP, HTTPS, and WebDAV with SFTPGo proxied behind Traefik with Docker.

Setup

Traefik

If you’re new to Traefik and you haven’t read my Traefik 2.5 quick-start guide, please do so before proceeding.

We’ll create an SFTP entrypoint for port 2222/TCP in the Static Configuration, and define the rest of SFTPGo’s entry in the Dynamic Configuration.

Static Configuration

Adding the entrypoint in, the relevant section would appear in traefik.toml as:

[entryPoints]

    [entryPoints.web]
        address = ":80"

        [entryPoints.web.http]
            [entryPoints.web.http.redirections]
                [entryPoints.web.http.redirections.entryPoint]
                    to = "websecure"
                    scheme = "https"
                    permanent = true

    [entryPoints.websecure]
        address = ":443"

    [entryPoints.sftp]
        address = ":2222/tcp"

Dynamic Configuration

SFTPGo provides multiple services that span several ports:

  • SFTP
  • HTTP
  • Telemetry
  • WebDAV

We’re going to glue all of this together in Traefik, with two entrypoints:

  1. HTTPS: port 443/TCP
  2. SFTP: port 2222/TCP

Traefik will route directly to SFTP via TCP with the Proxy Protocol. Prioritized rulesets with path definitions will filter all other requests that Traefik will forward to the appropriate service. Attributes for the HTTPS requests to keep in mind:

  • Web interface for administrators and clients.
    • /web/admin — Restricted to bastion hosts (i.e., VPN servers).
    • /web/client — Accessible to the public.
  • API for scripts to programmatically administer.
    • /api — Restricted to bastion hosts and/or jump hosts (i.e., VPN servers and secure machines).
  • Assets — Multimedia resources (e.g., images, JavaScript, and CSS).
    • /static — Accessible to the public.
  • Index
    • / — By default, SFTPGo will redirect these requests to the web interface for clients, but I’ll demonstrate how to redirect these requests to the web interface for administrators.
  • Telemetry — Restricted to jump hosts (i.e., secured servers for administrative duties, such as processing raw data with Grafana).
    • /healthz — Health checks.
    • /metrics — Prometheus metrics.
    • /debug/pprof — Profiling with pprof.
  • WebDAV — Lower priority pattern matching if none of the other rules match. An example is if Traefik answers example.com and you used WebDAV to download profile.png. The request with the GET method would be for https://example.com/profile.png and would show up to Traefik as /profile.png. Since SFTPGo has such clearly defined URLs that we’ve defined in Traefik and have prioritized above this one, we can assume that anything else that doesn’t match rules for known paths should be a direct query from the WebDAV service.

The bastion host file will be located at /srv/traefik/traefik.d/core-bastions.toml to allow unbridled access from three servers to HTTP and TCP. The contents will be as follows:

[http.middlewares]
	[http.middlewares.bastion-hosts.ipWhiteList]
		sourceRange = [
			"1.1.1.1",	# Server 1
			"2.2.2.2",	# Server 2
			"3.3.3.3",	# Server 3
		]

[tcp.middlewares]
	[tcp.middlewares.bastion-hosts.ipWhiteList]
		sourceRange = [
			"1.1.1.1",	# Server 1
			"2.2.2.2",	# Server 2
			"3.3.3.3",	# Server 3
		]

Now we only need a single HTTPS entrypoint instead of three. Thus, the contents of /srv/traefik/traefik.d/sftpgo.toml are:

# Web Admin — Privileged user
[http.routers.Router-SFTPGo-Web-Admin]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/web/admin`)"
    priority = 100
    service = "SFTPGo-Web"
    tls = true
    middlewares = [
        "bastion-hosts",
    ]

# API — Privileged user
[http.routers.Router-SFTPGo-API]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/api`)"
    priority = 100
    service = "SFTPGo-Web"
    tls = true
    middlewares = [
        "bastion-hosts",
    ]

# Assets
[http.routers.Router-SFTPGo-Assets]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/static`)"
    priority = 100
    service = "SFTPGo-Web"
    tls = true

# Web Client
[http.routers.Router-SFTPGo-Web-Client]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/web/client`)"
    priority = 100
    service = "SFTPGo-Web"
    tls = true

# Index
[http.routers.Router-SFTPGo-Index]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && Method(`GET`) && Path(`/`)"
    priority = 100
    service = "SFTPGo-Web"
    tls = true
    middlewares = [
        "SFTPGo-Redirect-Index",
    ]

# Telemetry : Health checks — https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md#telemetry-server
[http.routers.Router-SFTPGo-Telemetry-Health]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/healthz`)"
    priority = 100
    service = "SFTPGo-Telemetry"
    tls = true
    middlewares = [
        "bastion-hosts",
    ]

# Telemetry : Prometheus metrics — https://github.com/drakkan/sftpgo/blob/main/docs/metrics.md
[http.routers.Router-SFTPGo-Telemetry-Metrics]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/metrics`)"
    priority = 100
    service = "SFTPGo-Telemetry"
    tls = true
    middlewares = [
        "bastion-hosts",
    ]

# Telemetry : Profiling — https://github.com/drakkan/sftpgo/blob/main/docs/profiling.md
[http.routers.Router-SFTPGo-Telemetry-Profiling]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`) && PathPrefix(`/debug/pprof`)"
    priority = 100
    service = "SFTPGo-Telemetry"
    tls = true
    middlewares = [
        "bastion-hosts",
    ]

# WebDAV
[http.routers.Router-SFTPGo-WebDAV]
    entrypoints = ["websecure"]
    rule = "Host(`example.com`)"
    priority = 90
    service = "SFTPGo-WebDAV"
    tls = true

# SFTP
[tcp.routers.SFTP]
    entryPoints = ["sftp"]
    rule = "HostSNI(`*`)"   # Catch every request (only available rule for non-tls routers.)
    service = "SFTPGo-SFTP"

# Middlewares
[http.middlewares]

    # Index page
    [http.middlewares.SFTPGo-Redirect-Index.redirectRegex]
        regex = ".*"
        replacement = "/web/admin"

# Services

# TCP
[tcp.services]
    [tcp.services.SFTPGo-SFTP.loadBalancer]

# Enable Proxy Protocol
        [tcp.services.SFTPGo-SFTP.loadBalancer.proxyProtocol]
            version = 2

# TCP: SFTP
        [[tcp.services.SFTPGo-SFTP.loadBalancer.servers]]
            address = "sftpgo:1111"

# HTTP
[http.services]

# HTTP: Web
    [http.services.SFTPGo-Web.loadBalancer]
        [[http.services.SFTPGo-Web.loadBalancer.servers]]
            url = "http://sftpgo:2222"

# HTTP: Telemetry
    [http.services.SFTPGo-Telemetry.loadBalancer]
        [[http.services.SFTPGo-Telemetry.loadBalancer.servers]]
            url = "http://sftpgo:3333"

# HTTP: WebDAV
    [http.services.SFTPGo-WebDAV.loadBalancer]
        [[http.services.SFTPGo-WebDAV.loadBalancer.servers]]
            url = "http://sftpgo:4444"

Docker Compose

I prefer to configure SFTPGo almost entirely with its environment variables, and have based several of the values below from the default sftpgo.json file found in the repository. In my prior Traefik article, I covered how I go about using Link-local address segments to prevent my containers from conflicting with other networks (and the host itself), and I am doing the same here.

Thus, the contents of /etc/docker/compose/sftpgo/docker-compose.yaml are:

---
version: "3.0"

networks:

    default:

        driver: bridge
        ipam:
            config:
                - subnet: 169.254.0.0/29

services:

#-------------------------------------------------------------------------------

    traefik:

        image: traefik:v2.6
        container_name: traefik

        depends_on:
            - "sftpgo"

        # Public-facing ports
        ports:
            - "80:80/tcp"       # HTTP
            - "443:443/tcp"     # HTTPS
            - "2222:2222/tcp"   # SFTP

        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:ro  # Listen to the Docker events
            - /srv/traefik/traefik.toml:/traefik.toml:ro    # Static config.
            - /srv/traefik/traefik.d:/etc/traefik:ro        # Dynamic config.
            - /srv/traefik/logs:/logs                       # Diagnosis / Traffic
            - /srv/traefik/users:/users:ro                  # Basic auth. users
            - /srv/traefik/plugins:/plugins-local:ro        # Local plugins without Traefik Pilot
            - /srv/letsencrypt:/letsencrypt:ro              # Offsite certs.
            - /etc/ssl/certs/:/cacerts:ro                   # Certificate Authorities

        restart: "always"

#-------------------------------------------------------------------------------

    sftpgo:

        image: drakkan/sftpgo:v2-alpine
        container_name: sftpgo

        # Peer-facing ports
        expose:
            - "1111/tcp"
            - "2222/tcp"
            - "3333/tcp"
            - "4444/tcp"

        environment:

            # Proxy Protocol
            - "SFTPGO_COMMON__PROXY_PROTOCOL=2"
            - "SFTPGO_COMMON__PROXY_ALLOWED=169.254.0.0/29"

            # SFTP
            - "SFTPGO_SFTPD__BINDINGS__0__PORT=1111"

            # HTTP
            - "SFTPGO_HTTPD__BINDINGS__0__PORT=2222"
            - "SFTPGO_HTTPD__BINDINGS__0__PROXY_ALLOWED=169.254.0.0/29"

            # Telemetry
            - "SFTPGO_TELEMETRY__BIND_PORT=3333"
            - "SFTPGO_TELEMETRY__BIND_ADDRESS=0.0.0.0"

            # Uncomment if you want to profile SFTPGo, see https://github.com/drakkan/sftpgo/blob/main/docs/profiling.md
            # - "SFTPGO_TELEMETRY__ENABLE_PROFILER=true"

            # WebDAV
            - "SFTPGO_WEBDAVD__BINDINGS__0__PORT=4444"
            - "SFTPGO_WEBDAVD__BINDINGS__0__PROXY_ALLOWED=169.254.0.0/29"

            # Defender (similar concept as Fail2ban) — see https://github.com/drakkan/sftpgo/blob/main/docs/defender.md
            - "SFTPGO_COMMON__DEFENDER__ENABLED=true"
            - "SFTPGO_COMMON__DEFENDER__BAN_TIME=30"
            - "SFTPGO_COMMON__DEFENDER__BAN_TIME_INCREMENT=50"
            - "SFTPGO_COMMON__DEFENDER__THRESHOLD=15"
            - "SFTPGO_COMMON__DEFENDER__OBSERVATION_TIME=30"

        volumes:
            - /srv/sftpgo/home:/srv/sftpgo/data
            - /srv/sftpgo/backups:/srv/sftpgo/backups
            - /srv/sftpgo/config:/var/lib/sftpgo

        restart: unless-stopped

3 replies on “File Traefik”

I always get this error:
middleware “bastion-hosts@file” does not exist

How can I fix this?

May I ask if you could explain a bit what Index is used for and why you would want to redirect these requests to the web interface for administrators instead of the web interface for clients?

Leave a Reply

Your email address will not be published. Required fields are marked *