Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mdm.xml
docker-compose.local.yml
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
ARG DEBIAN_RELEASE=bullseye
ARG LICENSE=''
FROM docker.io/debian:$DEBIAN_RELEASE-slim
ARG DEBIAN_RELEASE
COPY entrypoint.sh /
ENV DEBIAN_FRONTEND=noninteractive
ENV LICENSE=${LICENSE}

RUN true && \
apt update && \
Expand All @@ -16,6 +13,8 @@ RUN curl https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --out
apt update && \
apt install cloudflare-warp -y --no-install-recommends

COPY entrypoint.sh /

RUN apt remove -y curl && \
apt clean -y && \
rm -rf /var/lib/apt/lists/* && \
Expand Down
95 changes: 90 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ docker run -d -p 40000:40000 --restart unless-stopped seiry/cloudflare-warp-prox
docker run -d -p 40000:40000 --restart unless-stopped ghcr.io/seiry/cloudflare-warp-proxy
```

SOCKS5 proxy server will be listening at port 40000.
A **SOCKS5** proxy will be listening at port 40000 (this is WARP's proxy mode,
exposed via `socat`). The same port also accepts HTTP `CONNECT`, so you can point
either a `socks5h://` / `socks5://` or an `http://` client at it — see [test](#test).

### docker-compose

Expand All @@ -31,26 +33,106 @@ services:
restart: unless-stopped
environment:
# use your own wrap+ key or zero trust key.
- LICENSE=''
- LICENSE=
# to enroll into a Zero Trust org, mount a full MDM config — see "managed deployment" below
# volumes:
# - ./mdm.xml:/var/lib/cloudflare-warp/mdm.xml:ro
logging:
driver: json-file
options:
max-size: 1m

```

## managed deployment (Zero Trust)

To join a Cloudflare Zero Trust organization — e.g. to route through a China local-network
partner or third-party network partner — mount a full [MDM config](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/deployment/mdm-deployment/parameters/)
at `/var/lib/cloudflare-warp/mdm.xml`. `warp-svc` reads it on startup and enrolls the device
using a **service token**. The same file pins the partner's endpoint overrides
(`override_warp_endpoint`, etc.) and puts the client into proxy mode.


### 1. create a service token with enrollment permission

- **Access controls → Service credentials → Service Tokens → Create Service Token** — copy the
Client ID (ends in `.access`) and Client Secret (shown only once).
- **Team & Resources → Devices → Management → Device enrollment permissions → Manage → Policies
→ Create policy**, set **Action = `Service Auth`** (not `Allow` — `Allow` does not work for
service tokens), Selector = your token, then add the policy to the enrollment permissions and
**Save**.

doc: https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/deployment/device-enrollment/#check-for-service-token

### 2. write `mdm.xml`

```xml
<dict>
<!-- China / third-party network partner endpoints -->
<key>override_api_endpoint</key>
<string>1.1.1.1</string>
<key>override_doh_endpoint</key>
<string>1.1.1.1</string>
<key>override_warp_endpoint</key>
<string>1.1.1.1:443</string>

<!-- Zero Trust enrollment via a service token -->
<key>organization</key>
<string>your-team-name</string>
<key>auth_client_id</key>
<string>xxxxxxxx.access</string>
<key>auth_client_secret</key>
<string>xxxxxxxxxxxxxxxx</string>

<!-- proxy mode only supports MASQUE -->
<key>warp_tunnel_protocol</key>
<string>masque</string>

<!-- run as a local SOCKS proxy on 40001 (socat forwards 40000 -> 40001) -->
<key>service_mode</key>
<string>proxy</string>
<key>proxy_port</key>
<integer>40001</integer>
<key>onboarding</key>
<false/>
</dict>
```

`service_mode`/`proxy_port` put the client into proxy mode locally. The Cloudflare One
Client gives precedence to local settings, so this overrides the org's device profile —
keep `proxy_port` at `40001` to match the `socat` forward (`40000 → 40001`) in the container.

### 3. run with the file mounted

```
docker run -d -p 40000:40000 --restart unless-stopped \
-v ./mdm.xml:/var/lib/cloudflare-warp/mdm.xml:ro \
seiry/cloudflare-warp-proxy
```

A service-token enrolled device shows up under **My Team → Devices** with email
`non_identity@<team-name>.cloudflareaccess.com`.

### 4. (optional) set proxy mode in the dashboard instead

Instead of `service_mode`/`proxy_port` in `mdm.xml` above, you can manage the mode centrally:
in **Settings → WARP Client → Device settings**, use a profile that targets this device (match
e.g. its `non_identity@…` email so you don't change other users) and set **Service mode = Proxy
mode, port `40001`**.


## test

```bash
curl https://www.cloudflare.com/cdn-cgi/trace/ -x socks5h://127.1:40000 # remote dns mode
curl https://www.cloudflare.com/cdn-cgi/trace -x socks5h://127.1:40000 # remote dns mode

# or

curl https://www.cloudflare.com/cdn-cgi/trace/ -x socks5://127.1:40000 # local dns mode
curl https://www.cloudflare.com/cdn-cgi/trace -x socks5://127.1:40000 # local dns mode

# or

curl https://www.cloudflare.com/cdn-cgi/trace/ -x http://127.1:40000 # http mode
curl https://www.cloudflare.com/cdn-cgi/trace -x http://127.1:40000 # http mode

```

Expand All @@ -68,3 +150,6 @@ gateway=off

* new version of cloudflare warp (rust version), now only allow using `MASQUE` protocol in proxy mode. With this error message if you try to use `WireGuard`
> `Connection error error=InvalidKey("Proxy mode only supports MASQUE")`

per Cloudflare's [Set up local proxy mode](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/cloudflare-one-client/configure/modes/#set-up-local-proxy-mode) docs:
> Ensure the Device tunnel protocol is set to MASQUE.
9 changes: 7 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ services:
ports:
- 40000:40000
restart: unless-stopped
environment:
- LICENSE='' # use your own key, for example zero trust or warp+
# environment:
# - LICENSE= # use your own key, for warp+

# to enroll into a Zero Trust org, mount a full MDM
# config. see README "managed deployment (Zero Trust)"
# volumes:
# - ./mdm.xml:/var/lib/cloudflare-warp/mdm.xml:ro
logging:
driver: json-file
options:
Expand Down
46 changes: 37 additions & 9 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
#!/bin/bash

MDM_FILE=/var/lib/cloudflare-warp/mdm.xml

(
while ! warp-cli --accept-tos registration new; do
sleep 1
>&2 echo "Awaiting warp-svc become online..."
done
warp-cli --accept-tos mode proxy
warp-cli --accept-tos proxy port 40001
if grep -q '<key>organization</key>' "$MDM_FILE" 2>/dev/null; then
# Managed deployment: a mounted mdm.xml enrolls the device into a Zero Trust
# organization (via a service token). warp-svc auto-enrolls on startup, so we just
# wait for the daemon to come online instead of creating our own registration.
>&2 echo "mdm.xml with organization found — using managed (Zero Trust) enrollment"
until warp-cli --accept-tos status >/dev/null 2>&1; do
sleep 1
>&2 echo "Awaiting warp-svc become online..."
done
else
# Consumer deployment: create a free registration. Newer clients auto-register on
# startup and the registration persists across restarts, so "registration new" can
# fail with "Old registration is still around" — treat that as success instead of
# looping forever (otherwise socat below never starts).
while true; do
if out="$(warp-cli --accept-tos registration new 2>&1)"; then
break
fi
echo "$out" | grep -q "Old registration is still around" && break
sleep 1
>&2 echo "Awaiting warp-svc become online..."
done
fi

# Proxy mode. On consumer accounts we set it here. On managed (Team/Zero Trust) accounts
# the mode is dictated by the org device profile and these calls return "Invalid setting
# for this account type" — in that case set the profile's service mode to "Proxy mode"
# (port 40001) in the Zero Trust dashboard, and ignore the errors below.
warp-cli --accept-tos mode proxy 2>/dev/null || >&2 echo "mode set by org profile, skipping local mode/port"
warp-cli --accept-tos proxy port 40001 2>/dev/null || true

# docker-compose's `- LICENSE='...'` keeps the surrounding quotes as part of the value,
# which makes warp-cli reject the key — strip a single pair of leading/trailing quotes.
LICENSE="${LICENSE#[\"\']}"
LICENSE="${LICENSE%[\"\']}"
if [ "$LICENSE" != "" ]; then
warp-cli --accept-tos registration license "$LICENSE"
warp-cli --accept-tos registration license "$LICENSE" || true
fi

warp-cli --accept-tos connect
socat TCP-LISTEN:40000,fork TCP:localhost:40001 # socat is used to redirect traffic from 40000 to 40001
) &

exec warp-svc


29 changes: 29 additions & 0 deletions mdm.example.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<dict>
<!-- third-party network partner endpoints -->
<key>override_api_endpoint</key>
<string>1.1.1.1</string>
<key>override_doh_endpoint</key>
<string>1.1.1.1</string>
<key>override_warp_endpoint</key>
<string>1.1.1.1:443</string>

<!-- Zero Trust enrollment via a service token -->
<key>organization</key>
<string>your_organization</string>
<key>auth_client_id</key>
<string>your_client_id.access</string>
<key>auth_client_secret</key>
<string>your_client_secret</string>

<!-- proxy mode only supports MASQUE -->
<key>warp_tunnel_protocol</key>
<string>masque</string>

<!-- run as a local SOCKS proxy on 40001 (socat forwards 40000 -> 40001) -->
<key>service_mode</key>
<string>proxy</string>
<key>proxy_port</key>
<integer>40001</integer>
<key>onboarding</key>
<false/>
</dict>