File size: 2,649 Bytes
d9ee7c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Cloudflare Access setup β€” service-token auth for the app

Both tunnel hostnames must sit behind Cloudflare Access so the open internet
cannot reach Ollama or SSH. The Kotlin app authenticates with a **service
token** (Client ID + Client Secret) β€” programmatic, no browser login on the
phone.

## 1. Create the Access applications

Cloudflare dashboard β†’ **Zero Trust β†’ Access β†’ Applications β†’ Add an application β†’ Self-hosted**.

Create one for each hostname (replace `yourdomain.com` with your domain):

| Application name | Domain |
|---|---|
| command-center-ollama | `ollama-internal.yourdomain.com` |
| command-center-ssh | `ssh.yourdomain.com` |

Session duration: 24h is fine (service tokens are not interactive sessions).

## 2. Create the service token

**Zero Trust β†’ Access β†’ Service Tokens β†’ Create Service Token.**

- Name: `kotlin-command-center`
- It shows a **Client ID** and **Client Secret** exactly once. Copy both now β€”
  the secret cannot be retrieved later (generate a new token if lost).

## 3. Attach the Allow policy to both applications

For each application β†’ **Policies β†’ Add a policy**:

- Policy name: `app-service-token`
- Action: **Allow**
- Configure rules β†’ **Add require β†’ Service Token** β†’ select `kotlin-command-center`
- (Optional but recommended) Add an `AND` rule: **IP ranges** limited to the
  egress ranges you actually use, if they are stable.

Result: any request without a valid `CF-Access-Client-Id` /
`CF-Access-Client-Secret` pair is rejected at the edge before it ever touches
your tunnel.

## 4. Wire the secrets into the stack

| Secret | Where it goes |
|---|---|
| Client ID + Client Secret | `worker/` via `wrangler secret put CF_CLIENT_ID` / `CF_CLIENT_SECRET` (Worker attaches them when calling the tunnel) |
| Client ID + Client Secret | Kotlin app config (`CC_CF_CLIENT_ID`, `CC_CF_CLIENT_SECRET`) β€” used on the **SSH WebSocket handshake** (`wss://ssh.yourdomain.com`) |
| `APP_SECRET` (you invent) | `wrangler secret put APP_SECRET` and the app's `CC_APP_SECRET` β€” authenticates the phone to the Worker |

The phone never talks to `ollama-internal.yourdomain.com` directly; only the
Worker holds the service token for that hostname. The phone uses the service
token only for the SSH WebSocket handshake.

## 5. Rotation

- Service token compromised or phone lost: **Access β†’ Service Tokens β†’ delete**
  the token and create a new one, then `wrangler secret put` the new values and
  update the app config. The old token stops working immediately.
- `APP_SECRET`: pick a fresh 32+ byte random value
  (`openssl rand -hex 32`), rotate the same way.