Skip to content

Commit b713a1f

Browse files
committed
docs: explain OAuth client use with the tailscale client and GitHub Action
The upstream client only runs the OAuth exchange for tskey-client- secrets and reads the control URL from a baseURL attribute, not --login-server. Document the prefix swap, baseURL, and authkey-not-oauth-secret for the GitHub Action.
1 parent 078afd5 commit b713a1f

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

docs/ref/api.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,39 @@ Headscale server at `/api/v1/docs` for details.
5858
https://headscale.example.com/api/v1/auth/register
5959
```
6060

61+
## OAuth clients and the Tailscale GitHub Action
62+
63+
Headscale also serves a subset of the Tailscale-compatible API at `/api/v2`, which
64+
accepts **OAuth 2.0 client-credentials** in addition to API keys. This lets parts
65+
of the Tailscale ecosystem drive Headscale, including the official
66+
[`tailscale/github-action`](https://github.com/tailscale/github-action).
67+
68+
Create an OAuth client and note its secret (shown once):
69+
70+
```shell
71+
headscale oauth-clients create --scope auth_keys --tag tag:ci
72+
```
73+
74+
The official action can use the secret as an auth key, but the upstream Tailscale
75+
client has two requirements when targeting a self-hosted control server:
76+
77+
- The secret must be prefixed `tskey-client-`. Headscale issues `hskey-client-…`
78+
but accepts the `tskey-client-` alias, so swap the prefix.
79+
- The OAuth exchange URL is read from a `baseURL` attribute on the secret itself
80+
(not from `--login-server`). Append your Headscale URL.
81+
82+
```yaml
83+
- uses: tailscale/github-action@v4
84+
with:
85+
# hskey-client-... with the prefix swapped to tskey-client- and baseURL appended
86+
authkey: tskey-client-<id>-<secret>?baseURL=https://headscale.example.com&ephemeral=true&preauthorized=true
87+
args: --login-server=https://headscale.example.com --advertise-tags=tag:ci
88+
```
89+
90+
Use the action's `authkey` input, not `oauth-secret`: the latter appends its own
91+
query parameters and would mangle `baseURL`. The client must advertise the tag(s)
92+
the OAuth client owns.
93+
6194
## Remote control
6295

6396
The `headscale` binary can control a Headscale instance from a remote machine over the HTTP API.

hscontrol/api/v2/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,38 @@ operator is OAuth-only. Supporting OAuth lets all of them drive Headscale.
7575
**Argon2id** hash of the secret (no JWT, no signing keys). `OAuthClient` and
7676
`OAuthAccessToken` live in `types/oauth.go` and `db/oauth.go`.
7777

78+
## OAuth with the tailscale client and GitHub Action
79+
80+
The upstream `tailscale` client (and the official
81+
[`tailscale/github-action`](https://github.com/tailscale/github-action)) can use
82+
an OAuth client secret directly as an auth key: it runs the client-credentials
83+
exchange itself (`feature/oauthkey`), mints an auth key, and registers. Two
84+
Tailscale-specific quirks apply when pointing it at Headscale:
85+
86+
- It only attempts the exchange when the secret is prefixed **`tskey-client-`**.
87+
Headscale issues `hskey-client-…` but `AuthenticateOAuthClient` accepts the
88+
`tskey-client-` alias too, so swap the prefix: `tskey-${SECRET#hskey-}`.
89+
- It exchanges against a **`baseURL`** read from the secret's query string
90+
(default `https://api.tailscale.com`); `--login-server` only sets the
91+
registration server, not the exchange. Append your Headscale URL.
92+
- The exchange requires `--advertise-tags`; registration accepts advertised tags
93+
that are a subset of the key's own tags.
94+
95+
So a working auth key is
96+
`tskey-client-<id>-<secret>?baseURL=https://headscale.example.com&ephemeral=true&preauthorized=true`.
97+
In the GitHub Action, pass it as the `authkey` input (the `oauth-secret` input is
98+
unusable here: it appends its own query parameters, which collide with `baseURL`):
99+
100+
```yaml
101+
- uses: tailscale/github-action@v4
102+
with:
103+
authkey: tskey-client-<id>-<secret>?baseURL=https://headscale.example.com&ephemeral=true&preauthorized=true
104+
args: --login-server=https://headscale.example.com --advertise-tags=tag:ci
105+
```
106+
107+
The OAuth client needs the `auth_keys` scope and the tag(s) it assigns. The
108+
end-to-end flow is exercised in `.github/workflows/test-v2-api.yaml`.
109+
78110
## Adding an endpoint
79111

80112
Worked example: the keys resource (`keys.go`) = Tailscale auth keys = Headscale

0 commit comments

Comments
 (0)