Skip to content

Commit c75f428

Browse files
committed
action.yml,src: add support for workload identity federation
Add support for workload identity federation based authentication. Updates tailscale/corp#31264 Signed-off-by: Mario Minardi <[email protected]>
1 parent c8de7fa commit c75f428

File tree

4 files changed

+102
-30
lines changed

4 files changed

+102
-30
lines changed

README.md

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,58 @@ by adding a step to your workflow.
1414
1515
Subsequent steps in the Action can then access nodes in your Tailnet.
1616
17-
oauth-client-id and oauth-secret are an [OAuth client](https://tailscale.com/s/oauth-clients/)
17+
oauth-client-id and oauth-secret are an [OAuth client][kb-oauth-clients]
1818
for the tailnet to be accessed. We recommend storing these as
1919
[GitHub Encrypted Secrets.](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
2020
OAuth clients used for this purpose must have the
21-
[`auth_keys` scope.](https://tailscale.com/kb/1215/oauth-clients#scopes)
21+
[`auth_keys` scope.][kb-trust-credentials-scopes]
2222

23-
tags is a comma-separated list of one or more [ACL Tags](https://tailscale.com/kb/1068/acl-tags/)
23+
tags is a comma-separated list of one or more [Tags][kb-tags]
2424
for the node. At least one tag is required: an OAuth client is not associated
2525
with any of the Users on the tailnet, it has to Tag its nodes.
2626

27-
Nodes created by this Action are [marked as Ephemeral](https://tailscale.com/s/ephemeral-nodes) to
27+
Nodes created by this Action are [marked as Ephemeral][kb-ephemeral-nodes] to
2828
and log out immediately after finishing their CI run, at which point they are automatically removed
29-
by the coordination server. The nodes are also [marked Preapproved](https://tailscale.com/kb/1085/auth-keys/)
30-
on tailnets which use [Device Approval](https://tailscale.com/kb/1099/device-approval/)
29+
by the coordination server. The nodes are also [marked Preapproved][kb-auth-keys]
30+
on tailnets which use [Device Approval][kb-device-approval]
31+
32+
33+
### Workload identity federation
34+
[Workload identity federation][kb-workload-identity-federation] can also be used for authenticating nodes with your tailnet:
35+
36+
```yaml
37+
- name: Tailscale
38+
uses: tailscale/github-action@v4
39+
with:
40+
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
41+
audience: ${{ secrets.TS_AUDIENCE }}
42+
tags: tag:ci
43+
```
44+
45+
Workload identity federation requires the `id-token: write` [permission setting](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-cloud-providers#adding-permissions-settings) for the workflow:
46+
47+
```yaml
48+
permissions:
49+
id-token: write # This is required for the tailscale action to request a JWT from GitHub
50+
```
51+
52+
Federated identity credentials used for this purpose must have the [`auth_keys` scope.][kb-trust-credentials-scopes]
53+
54+
tags is a comma-separated list of one or more [Tags][kb-tags]
55+
for the node. At least one tag is required: a federated identity is not associated
56+
with any of the Users on the tailnet, it has to Tag its nodes.
57+
58+
> [!IMPORTANT]
59+
> Tailscale version `1.90.1` or later is required for workload identity federation.
3160

3261
## Prerequisites
3362

3463
Before using the Tailscale GitHub Action, ensure you have the following:
3564

36-
1. A Tailscale account with <Role>Owner, Admin, or Network admin</Role> permissions.
65+
1. A Tailscale account with Owner, Admin, or Network admin permissions.
3766
1. A GitHub repository that you have admin access to (required to set up the GitHub Action).
38-
1. At least one configured [tag][kb-tags].
39-
1. An [OAuth client][kb-oauth-clients] ID and secret OR an [auth key][kb-auth-keys].
67+
1. At least one configured [tag][kb-tags] if using OAuth or workload identity federation.
68+
1. An [OAuth client][kb-oauth-clients] ID and secret, [federated identity][kb-workload-identity-federation] client ID and audience, OR an [auth key][kb-auth-keys].
4069
1. A runner image version >= 2.237.1 (required to support running Node.js 24).
4170

4271
## Eventual consistency
@@ -55,22 +84,21 @@ You can do this by adding a list of hosts to ping to the action configuration:
5584
ping: 100.x.y.z,my-machine.my-tailnet.ts.net
5685
```
5786

58-
or with the [tailscale ping](https://tailscale.com/kb/1080/cli#ping) command if you do not know the peers at the time of installing Tailscale in the workflow:
87+
or with the [tailscale ping][kb-cli-ping] command if you do not know the peers at the time of installing Tailscale in the workflow:
5988

6089
```bash
6190
tailscale ping my-target.my-tailnet.ts.net
6291
```
6392

64-
The `ping` option will wait up to to 3 minutes for a connection (direct or relayed).
93+
The `ping` option will wait up to 3 minutes for a connection (direct or relayed).
6594

6695
## Tailnet Lock
6796

68-
If you are using this Action in a [Tailnet
69-
Lock](https://tailscale.com/kb/1226/tailnet-lock) enabled network, you need to:
97+
If you are using this Action in a [Tailnet Lock][kb-tailnet-lock] enabled network, you need to:
7098

71-
- Authenticate using an ephemeral reusable [pre-signed auth key](https://tailscale.com/kb/1226/tailnet-lock#add-a-node-using-a-pre-signed-auth-key)
99+
- Authenticate using an ephemeral reusable [pre-signed auth key][kb-tailnet-lock-pre-signed]
72100
rather than an OAuth client.
73-
- Specify a [state directory](https://tailscale.com/kb/1278/tailscaled#flags-to-tailscaled) for the
101+
- Specify a [state directory][kb-tailscaled-flags] for the
74102
client to store the Tailnet Key Authority data in.
75103

76104
```yaml
@@ -139,4 +167,16 @@ the GitHub Action leaves tailscale binaries installed but stops the tailscale ba
139167

140168
### requested tags [tag:mytag] are invalid or not permitted
141169

142-
You may encounter this error when using an OAuth client. OAuth clients must have the [`auth_keys` scope](https://tailscale.com/kb/1215/oauth-clients#scopes) with one or more [tags](https://tailscale.com/kb/1068/acl-tags/), and the tags specified with `tags` must match all tags on the OAuth client.
170+
You may encounter this error when using an OAuth client. OAuth clients must have the [`auth_keys` scope][kb-trust-credentials-scopes] with one or more [tags][kb-tags], and the tags specified with `tags` must match all tags on the OAuth client.
171+
172+
[kb-auth-keys]: https://tailscale.com/kb/1085/auth-keys
173+
[kb-cli-ping]: https://tailscale.com/kb/1080/cli#ping
174+
[kb-device-approval]: https://tailscale.com/kb/1099/device-approval
175+
[kb-ephemeral-nodes]: https://tailscale.com/kb/1111/ephemeral-nodes
176+
[kb-oauth-clients]: https://tailscale.com/kb/1215/oauth-clients
177+
[kb-tags]: https://tailscale.com/kb/1068/tags
178+
[kb-tailnet-lock]: https://tailscale.com/kb/1226/tailnet-lock
179+
[kb-tailnet-lock-pre-signed]: https://tailscale.com/kb/1226/tailnet-lock#add-a-node-using-a-pre-signed-auth-key
180+
[kb-tailscaled-flags]: https://tailscale.com/kb/1278/tailscaled#flags-to-tailscaled
181+
[kb-trust-credentials-scopes]: https://tailscale.com/kb/1623/trust-credentials#scopes
182+
[kb-workload-identity-federation]: https://tailscale.com/kb/1581/workload-identity-federation

action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ inputs:
1313
required: false
1414
deprecationMessage: 'An OAuth API client https://tailscale.com/s/oauth-clients is recommended instead of an authkey'
1515
oauth-client-id:
16-
description: 'Your Tailscale OAuth Client ID.'
16+
description: 'Your Tailscale OAuth or Workload Identity Federation Client ID.'
17+
required: false
18+
audience:
19+
description: 'Your Tailscale Workload Identity Federation Audience'
1720
required: false
1821
oauth-secret:
1922
description: 'Your Tailscale OAuth Client Secret.'

dist/index.js

Lines changed: 19 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface TailscaleConfig {
3030
arch: string;
3131
authKey: string;
3232
oauthClientId: string;
33+
audience: string;
3334
oauthSecret: string;
3435
tags: string;
3536
hostname: string;
@@ -214,6 +215,7 @@ async function getInputs(): Promise<TailscaleConfig> {
214215
arch: "",
215216
authKey: authKey,
216217
oauthClientId: core.getInput("oauth-client-id") || "",
218+
audience: core.getInput("audience") || "",
217219
oauthSecret: oauthSecret,
218220
tags: core.getInput("tags") || "",
219221
hostname: core.getInput("hostname") || "",
@@ -237,9 +239,13 @@ async function getInputs(): Promise<TailscaleConfig> {
237239
}
238240

239241
function validateAuth(config: TailscaleConfig): void {
240-
if (!config.authKey && (!config.oauthSecret || !config.tags)) {
242+
if (
243+
!config.authKey &&
244+
(!config.oauthSecret || !config.tags) &&
245+
(!config.audience || !config.oauthClientId || !config.tags)
246+
) {
241247
throw new Error(
242-
"OAuth identity empty, please provide either an auth key or OAuth secret and tags."
248+
"Please provide either an auth key, OAuth secret and tags, or federated identity client ID and audience with tags."
243249
);
244250
}
245251
}
@@ -678,14 +684,24 @@ async function connectToTailscale(
678684
hostname = hostname.substring(0, 63);
679685

680686
// Prepare auth and tags
681-
let finalAuthKey = config.authKey;
687+
const authArgs: string[] = [];
682688
const tagsArg: string[] = [];
683689

684-
if (config.oauthSecret) {
685-
finalAuthKey = `${config.oauthSecret}?preauthorized=true&ephemeral=true`;
690+
if (config.authKey) {
691+
authArgs.push(`--authkey=${config.authKey}`);
692+
} else {
686693
tagsArg.push(`--advertise-tags=${config.tags}`);
687-
}
688694

695+
if (config.oauthSecret) {
696+
authArgs.push(
697+
`--authkey=${config.oauthSecret}?preauthorized=true&ephemeral=true`
698+
);
699+
} else if (config.audience) {
700+
const token = await core.getIDToken(config.audience);
701+
authArgs.push(`--client-id=${config.oauthClientId}`);
702+
authArgs.push(`--id-token=${token}`);
703+
}
704+
}
689705
// Platform-specific args
690706
const platformArgs: string[] = [];
691707
if (runnerOS === runnerWindows) {
@@ -696,11 +712,11 @@ async function connectToTailscale(
696712
const upArgs = [
697713
"up",
698714
...tagsArg,
699-
`--authkey=${finalAuthKey}`,
700715
`--hostname=${hostname}`,
701716
"--accept-routes",
702717
...platformArgs,
703718
...config.args.split(" ").filter(Boolean),
719+
...authArgs,
704720
];
705721

706722
// Retry logic

0 commit comments

Comments
 (0)