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 across 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 then 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 programatically 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).
  • WebDAV — Lower priority pattern matching and if none of the other rules match. An example is if Traefik answers for example.com and you were using WebDAV to download profile.png. The requst 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.

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

Leave a Reply

Your email address will not be published.