MacOS Traefik Cloudflre

Keeping an note of exposing local MacOS with Traefik behind Cloudflare

By intent, in my setup I did not use Docker or Kubernetes.

Notes:

  • only HTTP traffic is allowed
  • requests are allowed only from Cloudflare
  • requests are verified with mTLS
  • obviously for this to work, port forwarding and static IP should be configured in home router
  • remember that Cloudflare can proxy only http traffic, so it is not possible to proxy lets say Postgresql
brew install traefik

yep, that's it :)

defaults:

  • plist - /opt/homebrew/Cellar/traefik/3.3.4/homebrew.mxcl.traefik.plist
  • config - /opt/homebrew/etc/traefik/traefik.toml
  • logs - /opt/homebrew/var/log/traefik.log

in my case, I need CLOUDFLARE_DNS_API_TOKEN environment variable and brew will override plist, so going to have my own configurations

~/.config/traefik/static.yml

log:
  # we need this for debugging, talke closer look at
  # DBG github.com/traefik/traefik/v3/cmd/traefik/traefik.go:114 > Static configuration loaded [json] staticConfiguration={
  # it will print actual configuration that Traefik uses
  level: DEBUG
accessLog:
  format: common

providers:
  file:
    filename: /Users/mini/.config/traefik/dynamic.yml

certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]
      storage: /Users/mini/.config/traefik/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - 1.1.1.1:53
          - 1.0.0.1:53

metrics:
  prometheus:
    {}
    #addEntryPointsLabels: true
    #addRoutersLabels: true
    #addServicesLabels: true

entryPoints:
  # note: we have only https entrypoint, no http at all
  https:
    address: :443
    # and it is set as default, so no need to specify it in routers
    # as well as configuring redirects here and there
    asDefault: true
    http:
      # here we are configuring default wildcard certificate
      tls:
        certResolver: cloudflare
        domains:
          - main: mac-blog.org.ua
            sans:
              - "*.mac-blog.org.ua"
    # we need this one for traefik to log client ip
    forwardedHeaders:
      trustedIPs:
        # https://www.cloudflare.com/ips-v4
        - 173.245.48.0/20
        - 103.21.244.0/22
        - 103.22.200.0/22
        - 103.31.4.0/22
        - 141.101.64.0/18
        - 108.162.192.0/18
        - 190.93.240.0/20
        - 188.114.96.0/20
        - 197.234.240.0/22
        - 198.41.128.0/17
        - 162.158.0.0/15
        - 104.16.0.0/13
        - 104.24.0.0/14
        - 172.64.0.0/13
        - 131.0.72.0/22
        # https://www.cloudflare.com/ips-v6
        - 2400:cb00::/32
        - 2606:4700::/32
        - 2803:f800::/32
        - 2405:b500::/32
        - 2405:8100::/32
        - 2a06:98c0::/29
        - 2c0f:f248::/32

# quite simple but awesome plugin to protect some services
experimental:
  plugins:
    google-oidc-auth-middleware:
      moduleName: github.com/andrewkroh/google-oidc-auth-middleware
      version: v0.1.0

~/.config/traefik/dynamic.yml

http:
  routers:
    # note how simple routers are - just define host and service and you are ready to go
    foo:
      rule: Host(`foo.mac-blog.org.ua`)
      service: foo
    bar:
      rule: Host(`bar.mac-blog.org.ua`)
      service: bar
      middlewares:
        - auth
  services:
    foo:
      loadBalancer:
        servers:
          - url: http://localhost:3000/
    bar:
      loadBalancer:
        servers:
          - url: http://localhost:9090/

  middlewares:
    # just as example, we can use built in basic asuth
    basicauth:
      basicAuth:
        users:
          # htpasswd -nb hello world
          - hello:$apr1$xwaBOwva$/FgrbWFvzTIRvm4p9LK4h1
    # added in static.yml
    auth:
      plugin:
        google-oidc-auth-middleware:
          authorized:
            emails:
              - [email protected]
          cookie:
            secret: some_random_string_at_least_32_characters_longs
            # insecure: true
            name: google
          oidc:
            clientID: xxx.apps.googleusercontent.com
            clientSecret: GOCSPX-xxxxxxxxxxxxxxxxxxx

tls:
  options:
    default:
      # require SNI to be present and valid
      sniStrict: true
      # cloudflare origin pull certificate
      clientAuth:
        caFiles:
          # https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
          - /Users/mini/.config/traefik/authenticated_origin_pull_ca.pem
        clientAuthType: RequireAndVerifyClientCert

~/.config/traefik/traefik.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>traefik</string>
    <key>LimitLoadToSessionType</key>
    <array>
      <string>Aqua</string>
      <string>Background</string>
      <string>LoginWindow</string>
      <string>StandardIO</string>
      <string>System</string>
    </array>
    <key>ProgramArguments</key>
    <array>
      <string>/opt/homebrew/opt/traefik/bin/traefik</string>
      <string>--configfile=/Users/mini/.config/traefik/static.yml</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
      <key>CLOUDFLARE_DNS_API_TOKEN</key>
      <string>TODO_PASTE_CLOUDFLARE_ACCESS_TOKEN_HERE</string>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/opt/homebrew/var/log/traefik.log</string>
    <key>StandardOutPath</key>
    <string>/opt/homebrew/var/log/traefik.log</string>
    <key>WorkingDirectory</key>
    <string>/opt/homebrew/var</string>
  </dict>
</plist>

Cloudflare access token should have Zone Read and DNS Edit permissions

While you are configuring everything you always may test it like so:

CLOUDFLARE_DNS_API_TOKEN=xxxxxxxx traefik --configfile=/Users/mini/.config/traefik/static.yml

And finally:

ln -s ~/.config/traefik/traefik.plist ~/Library/LaunchAgents/traefik.plist

launchctl load ~/Library/LaunchAgents/traefik.plist

Other commands used often:

ln -s ~/.config/traefik/traefik.plist ~/Library/LaunchAgents/traefik.plist

launchctl load ~/Library/LaunchAgents/traefik.plist
launchctl unload ~/Library/LaunchAgents/traefik.plist
rm ~/Library/LaunchAgents/traefik.plist

tail -f /opt/homebrew/var/log/traefik.log

ps aux | grep traefik