|
1 | | -This document serves as a guide for a Proof of Concept (POC) aimed at validating Cloudflare Zero Trust forwards using Traefik's forward-auth service. The rust service is compatible with both Traefik 2.x and Traefik 3.x versions, originally i setout ot build a traefik only plugin but the limitations of the current WASM implementation follows R1 spec and as such is limited. |
| 1 | +# Traefik Forward Auth for Cloudflare Access |
2 | 2 |
|
3 | | -This is a port of: https://github.com/nihaopaul/Traefik-ForwardAuth-Cloudflare-Access |
| 3 | +A small service that lets **Traefik** enforce **Cloudflare Zero Trust** access rules on any route it serves. |
4 | 4 |
|
5 | | -The primary goal is to demonstrate how to authenticate Traefik requests through Cloudflare Zero Trust, ensuring secure access control. |
| 5 | +Cloudflare Access puts a login screen in front of your apps and hands the browser a signed `CF_Authorization` cookie. Traefik can't check that cookie by itself. This service does: Traefik asks it about every request, and it answers `200` (allow) or `403` (deny). Works with Traefik 2.x and 3.x. |
6 | 6 |
|
7 | | -Additionally most configurations have moved to environmental variables which allow you to dockerise it, and since AUDs will come and go along with certificates the certficates refresh every 24hours while the auds refresh every hour using cloudflare APIS. |
| 7 | +> Originally attempted as a native Traefik plugin, but Traefik's WASM support follows the R1 spec and is too limited. This is a Rust port of [Traefik-ForwardAuth-Cloudflare-Access](https://github.com/nihaopaul/Traefik-ForwardAuth-Cloudflare-Access). |
8 | 8 |
|
9 | | -#### setup a read only API token with permission: Account > Access: apps and policies > Read |
| 9 | +## How it works |
10 | 10 |
|
11 | | -``` |
12 | | -CF_TOKEN=_this is your cf token_ |
13 | | -``` |
| 11 | +1. A user hits your app. Traefik pauses the request and asks this service `GET /auth`, forwarding the cookies. |
| 12 | +2. The service reads the `CF_Authorization` cookie. No cookie → `403`, and Cloudflare shows the login page. |
| 13 | +3. It verifies the cookie's JWT signature against your team's public keys, and checks the token was issued for one of *your* applications (its `aud`). |
| 14 | +4. Valid → `200` and Traefik serves the request. Anything else → `403`. |
14 | 15 |
|
15 | | -#### take from the cloudflare dashboard url https://dash.cloudflare.com/{your ORG ID} |
| 16 | +It keeps itself current in the background, so you don't restart it when things change in Cloudflare: |
16 | 17 |
|
17 | | -``` |
18 | | -CF_ORG=_this is your ID for cloudflare_ |
19 | | -``` |
| 18 | +- **Public keys** refresh every 24 hours (Cloudflare rotates them). |
| 19 | +- **Application list** refreshes every hour (so new apps start working on their own). |
20 | 20 |
|
21 | | -#### what you have configured in cloudflare zero trust: team domain |
| 21 | +Nothing is stored on disk and no state is kept between requests. |
22 | 22 |
|
23 | | -``` |
24 | | -CF_DOMAIN=https://{yourdomain}.cloudflareaccess.com |
25 | | -traefik config such as auth.yml |
| 23 | +## Quick start |
| 24 | + |
| 25 | +```yaml |
| 26 | +services: |
| 27 | + forward-auth-rust: |
| 28 | + image: nihaopaul/forward-auth-rust:0.4.0 |
| 29 | + restart: unless-stopped |
| 30 | + environment: |
| 31 | + CF_DOMAIN: https://yourteam.cloudflareaccess.com |
| 32 | + CF_ORG: your-cloudflare-account-id |
| 33 | + CF_TOKEN: your-read-only-api-token |
| 34 | + PORT: '9001' |
| 35 | + ports: |
| 36 | + - '9001:9001' |
| 37 | + deploy: |
| 38 | + resources: |
| 39 | + limits: |
| 40 | + cpus: '1' |
| 41 | + memory: 50M |
26 | 42 | ``` |
27 | 43 |
|
28 | | -```yml |
| 44 | +Then tell Traefik to use it as a middleware: |
| 45 | +
|
| 46 | +```yaml |
29 | 47 | http: |
30 | 48 | middlewares: |
31 | | - test-auth: |
| 49 | + cf-auth: |
32 | 50 | forwardAuth: |
33 | | - address: "http://IP:PORT/auth" |
| 51 | + address: "http://forward-auth-rust:9001/auth" |
34 | 52 | ``` |
35 | 53 |
|
36 | | -then under the domain specify the provider, you probably dont want to do this on a writable dashboard or API. |
| 54 | +And apply it to a route: |
37 | 55 |
|
38 | | -```yml |
| 56 | +```yaml |
39 | 57 | http: |
40 | 58 | routers: |
41 | 59 | dashboard: |
42 | | - rule: Host(`{your domain}`) |
| 60 | + rule: Host(`traefik.example.com`) |
43 | 61 | service: api@internal |
44 | | - middlewares: |
45 | | - - test-auth |
46 | 62 | entryPoints: |
47 | | - - "websecure" |
| 63 | + - websecure |
| 64 | + middlewares: |
| 65 | + - cf-auth |
48 | 66 | ``` |
| 67 | +
|
| 68 | +## Configuration |
| 69 | +
|
| 70 | +| Variable | Required | Description | |
| 71 | +| --- | --- | --- | |
| 72 | +| `CF_DOMAIN` | yes | Your Zero Trust team domain, including `https://` — the hostname where Cloudflare shows your login page. | |
| 73 | +| `CF_ORG` | yes | Your Cloudflare **account ID** — the value in your dashboard URL, `https://dash.cloudflare.com/{account-id}`. | |
| 74 | +| `CF_TOKEN` | yes | API token with **Account → Access: Apps and Policies → Read**. Read-only is enough. | |
| 75 | +| `PORT` | no | Port to listen on. Defaults to `3000`. | |
| 76 | + |
| 77 | +The service listens on `0.0.0.0` and exposes a single endpoint, `GET /auth`. |
| 78 | + |
| 79 | +## Notes |
| 80 | + |
| 81 | +**Protect the right things.** This gates on "is this a valid login for one of my Cloudflare apps" — it does not evaluate per-application policies. Two apps behind the same instance can accept each other's tokens, so think carefully before putting it in front of a writable dashboard or API. |
| 82 | + |
| 83 | +**Only `linux/amd64` images are published.** On arm64 hosts you'll need emulation, or build the image yourself. |
| 84 | + |
| 85 | +**Pin a version.** The example uses `:0.4.0` rather than `:latest` so an unattended `docker compose pull` can't change what you're running. |
| 86 | + |
| 87 | +## Building from source |
| 88 | + |
| 89 | +Requires [devbox](https://www.jetify.com/devbox) (which supplies the Rust toolchain): |
| 90 | + |
| 91 | +```bash |
| 92 | +devbox run test # run the test suite |
| 93 | +devbox run build # release build |
| 94 | +``` |
| 95 | + |
| 96 | +The container image is a statically linked musl binary on `distroless/static`, about 12 MB uncompressed, with no shell and no libc in the runtime layer. |
0 commit comments