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
40 changes: 24 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ as your source of truth.
panel](https://login.tailscale.com/admin) and copying down the name next to the
Tailscale logo in the upper left hand corner of the page.

### `oauth-client-id` and `audience`

**Optional** The ID and audience for a [federated identity](https://tailscale.com/kb/1581/workload-identity-federation)
for your tailnet. The federated identity must have the `policy_file` scope.

Either `api-key`, `oauth-client-id` and `oauth-secret`, or `oauth-client-id` and `audience` are required.

### `api-key`

**Optional** An API key authorized for your tailnet. You can get one [in the
admin panel](https://login.tailscale.com/admin/settings/keys).
Either `api-key` or `oauth-client-id` and `oauth-secret` are required.
Either `api-key`, `oauth-client-id` and `oauth-secret`, or `oauth-client-id` and `audience` are required.

Please note that API keys will expire in 90 days. Set up a monthly event to
rotate your Tailscale API key, or use an OAuth client.
rotate your Tailscale API key, or use a trust credential (OAuth client or federated identity).

### `oauth-client-id` and `oauth-secret`

**Optional** The ID and secret for an [OAuth client](https://tailscale.com/kb/1215/oauth-clients)
for your tailnet. The client must have the `acl` scope.
for your tailnet. The client must have the `policy_file` scope.

Either `api-key` or `oauth-client-id` and `oauth-secret` are required.
Either `api-key`, `oauth-client-id` and `oauth-secret`, or `oauth-client-id` and `audience` are required.

### `policy-file`

Expand Down Expand Up @@ -63,13 +70,16 @@ on:

jobs:
acls:
permissions:
contents: read
id-token: write # This is required for the Tailscale action to request a JWT from GitHub
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Fetch version-cache.json
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ./version-cache.json
key: version-cache.json-${{ github.run_id }}
Expand All @@ -81,7 +91,8 @@ jobs:
id: deploy-acl
uses: tailscale/gitops-acl-action@v1
with:
api-key: ${{ secrets.TS_API_KEY }}
oauth-client-id: ${{ secrets.TS_OAUTH_ID }}
audience: ${{ secrets.TS_AUDIENCE }}
tailnet: ${{ secrets.TS_TAILNET }}
action: apply

Expand All @@ -90,23 +101,20 @@ jobs:
id: test-acl
uses: tailscale/gitops-acl-action@v1
with:
api-key: ${{ secrets.TS_API_KEY }}
oauth-client-id: ${{ secrets.TS_OAUTH_ID }}
audience: ${{ secrets.TS_AUDIENCE }}
tailnet: ${{ secrets.TS_TAILNET }}
action: test
```

Generate a new API key [here](https://login.tailscale.com/admin/settings/keys).

Set a monthly calendar reminder to renew this key because Tailscale does not
currently support API key renewal (this will be updated to support that when
that feature is implemented).
Generate a new federated identity. See [here](https://login.tailscale.com/admin/settings/keys) for instructions.

Then open the secrets settings for your repo and add two secrets:

* `TS_API_KEY`: Your Tailscale API key from the earlier step
* `TS_OAUTH_ID`: Your federated identity's client ID
* `TS_AUDIENCE`: Your federated identity's audience
* `TS_TAILNET`: Your tailnet's name (it's next to the logo on the upper
left-hand corner of the [admin
panel](https://login.tailscale.com/admin/machines))
left-hand corner of the [admin panel](https://login.tailscale.com/admin/machines))

Once you do that, commit the changes and push them to GitHub. You will have CI
automatically test and push changes to your tailnet policy file to Tailscale.
Expand Down
29 changes: 21 additions & 8 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ inputs:
description: "Tailscale API key"
required: false
oauth-client-id:
description: "Tailscale OAuth ID"
description: "Tailscale OAuth or OIDC Federated Identity Client ID"
required: false
oauth-secret:
description: "Tailscale OAuth Secret"
required: false
audience:
description: "Tailscale OIDC Federated Identity Audience"
required: false
policy-file:
description: "Path to policy file"
required: true
Expand All @@ -24,29 +27,39 @@ runs:
using: "composite"
steps:
- name: Check Auth Info Empty
if: ${{ inputs['api-key'] == '' && inputs['oauth-secret'] == '' }}
if: ${{ inputs['api-key'] == '' && inputs['oauth-secret'] == '' && inputs['oauth-client-id'] == ''}}
shell: bash
run: |
echo "::error title=⛔ error hint::API Key or OAuth secret must be specified. Maybe you need to populate it in the Secrets for your workflow, see more in https://docs.github.com/en/actions/security-guides/encrypted-secrets and https://tailscale.com/s/oauth-clients"
echo "::error title=⛔ error hint::API Key, OAuth secret, or OAuth client ID and audience must be specified. Maybe you need to populate it in the Secrets for your workflow, see more in https://docs.github.com/en/actions/security-guides/encrypted-secrets and https://tailscale.com/s/trust-credentials
exit 1
- name: Check Conflicting Auth Info
if: ${{ inputs['api-key'] != '' && inputs['oauth-secret'] != '' }}
if: ${{ (inputs['api-key'] != '' && (inputs['oauth-secret'] != '' || inputs['audience'] != '')) || (inputs['oauth-secret'] != '' && (inputs['api-key'] != '' || inputs['audience'] != '')) || (inputs['audience'] != '' && (inputs['api-key'] != '' || inputs['oauth-secret'] != '')) }}
shell: bash
run: |
echo "::error title=⛔ error hint::only one of API Key or OAuth secret should be specified.
echo "::error title=⛔ error hint::only one of API Key, OAuth secret, or OAuth client ID and audience should be specified."
exit 1
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: 1.25.5
cache: false

- name: Fetch ID token
if: ${{ inputs['oauth-client-id'] != '' && inputs['audience'] != '' }}
shell: bash
id: fetch-id-token
run: |
ID_TOKEN=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${{ inputs.audience }}" | jq -r '.value')
echo "::add-mask::ID_TOKEN"
echo "id_token=$ID_TOKEN" >> $GITHUB_OUTPUT
- name: Gitops pusher
shell: bash
env:
# gitops-pusher will use OAUTH_ID and OAUTH_SECRET if non-empty,
# gitops-pusher will use OAUTH_ID and OAUTH_SECRET or
# OAUTH_ID and ID_TOKEN if non-empty,
# otherwise it will use API_KEY.
TS_OAUTH_ID: "${{ inputs.oauth-client-id }}"
TS_OAUTH_SECRET: "${{ inputs.oauth-secret }}"
TS_ID_TOKEN: "${{ steps.fetch-id-token.outputs.id_token }}"
TS_API_KEY: "${{ inputs.api-key }}"
TS_TAILNET: "${{ inputs.tailnet }}"
run: go run tailscale.com/cmd/gitops-pusher@2078eb56f3ca310821aae3fa140aa3b0d3bda2dc "--policy-file=${{ inputs.policy-file }}" "${{ inputs.action }}"
run: go run tailscale.com/cmd/gitops-pusher@4c37141ab780dbf6c037bd64fe48ab330441ad06 "--policy-file=${{ inputs.policy-file }}" "${{ inputs.action }}"