diff --git a/README.md b/README.md index bbab563..5d93ed1 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,10 @@ 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. -### `api-key` +### `oidc-client-id` and `oidc-audience` -**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. - -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. - -### `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. +**Required** The OIDC client ID and audience for an [OIDC Credential](https://tailscale.com/kb/1581/workload-identity-federation#configure-federated-identities-in-the-admin-console) +for your tailnet. The credential must have the `policy_file` scope. Either `api-key` or `oauth-client-id` and `oauth-secret` are required. @@ -44,7 +35,7 @@ out to production. ## Getting Started -Set up a new GitHub repository that will contain your tailnet policy file. Open the [Access Controls page of the admin console](https://login.tailscale.com/admin/acls) and copy your policy file to +Set up a new GitHub repository that will contain your tailnet policy file. Open the [Access Controls page of the admin console](https://login.tailscale.com/admin/acls/file) and copy your policy file to a file in that repo called `policy.hujson`. If you want to change this name to something else, you will need to add the @@ -63,10 +54,13 @@ on: jobs: acls: + permissions: + contents: read + id-token: write # required for generating an OIDC token runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Fetch version-cache.json uses: actions/cache@v4 @@ -81,7 +75,8 @@ jobs: id: deploy-acl uses: tailscale/gitops-acl-action@v1 with: - api-key: ${{ secrets.TS_API_KEY }} + oidc-client-id: ${{ secrets.TS_OIDC_CLIENT_ID }} + oidc-audience: ${{ secrets.TS_OIDC_AUDIENCE }} tailnet: ${{ secrets.TS_TAILNET }} action: apply @@ -90,23 +85,23 @@ jobs: id: test-acl uses: tailscale/gitops-acl-action@v1 with: - api-key: ${{ secrets.TS_API_KEY }} + oidc-client-id: ${{ secrets.TS_OIDC_CLIENT_ID }} + oidc-audience: ${{ secrets.TS_OIDC_AUDIENCE }} tailnet: ${{ secrets.TS_TAILNET }} action: test ``` -Generate a new API key [here](https://login.tailscale.com/admin/settings/keys). +Generate a new credential via [Trust Credentials](https://login.tailscale.com/admin/settings/trust-credentials): + +Choose an *OpenID Connect* credential, and set the *Issuer* as `Github`. The Subject should be `repo:/::*`. ([refer to Github's documentation on the `sub` field](https://docs.github.com/en/actions/reference/security/oidc#example-subject-claims).) -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). +In the *Scopes* step, choose **Policy File: Write** (or *Read* if not using the `apply` action.) -Then open the secrets settings for your repo and add two secrets: +Then open the secrets settings for your repo and add three secrets: -* `TS_API_KEY`: Your Tailscale API key from the earlier step -* `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)) +* `TS_OIDC_CLIENT_ID`: Your Tailscale OIDC Client ID from the earlier step +* `TS_OIDC_AUDIENCE`: Your Tailscale OIDC Audience from the earlier step +* `TS_TAILNET`: Your tailnet's ID/name (it's available on the [admin panel Settings](https://login.tailscale.com/admin/settings/general)) 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. diff --git a/action.yml b/action.yml index 5a688ef..844e6af 100644 --- a/action.yml +++ b/action.yml @@ -2,17 +2,14 @@ name: "Sync Tailscale ACLs" description: "Push changes to Tailscale and run ACL tests in CI" inputs: tailnet: - description: "Tailnet name (eg. example.com, xe.github, tailscale.org.github)" + description: "Tailnet ID or Name (eg. TCZrp4oabE12CNTRL, example.com, xe.github, tailscale.org.github)" + required: true + oidc-client-id: + description: "Tailscale OIDC Client ID" + required: true + oidc-audience: + description: "Tailscale OIDC Audience" required: true - api-key: - description: "Tailscale API key" - required: false - oauth-client-id: - description: "Tailscale OAuth ID" - required: false - oauth-secret: - description: "Tailscale OAuth Secret" - required: false policy-file: description: "Path to policy file" required: true @@ -23,18 +20,26 @@ inputs: runs: using: "composite" steps: - - name: Check Auth Info Empty - if: ${{ inputs['api-key'] == '' && inputs['oauth-secret'] == '' }} + - name: Generate OIDC JWT from Github Actions + id: get_gh_oidc_token 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" - exit 1 - - name: Check Conflicting Auth Info - if: ${{ inputs['api-key'] != '' && inputs['oauth-secret'] != '' }} + OIDC_JWT=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${{ inputs.oidc-audience }}" | jq -r '.value') + echo "::add-mask::$OIDC_JWT" + echo "jwt=$OIDC_JWT" >> $GITHUB_OUTPUT + + - name: Exchange OIDC JWT with a short-lived Tailscale API Key + id: tailscale_api_key shell: bash run: | - echo "::error title=⛔ error hint::only one of API Key or OAuth secret should be specified. - exit 1 + RESPONSE=$(curl -X POST https://api.tailscale.com/api/v2/oauth/token-exchange \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "client_id=${{ inputs.oidc-client-id }}" \ + -d "jwt=${{ steps.get_gh_oidc_token.outputs.jwt }}") + export ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token') + echo "::add-mask::$ACCESS_TOKEN" + echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT + - uses: actions/setup-go@v5 with: go-version: 1.22.4 @@ -43,10 +48,6 @@ runs: - name: Gitops pusher shell: bash env: - # gitops-pusher will use OAUTH_ID and OAUTH_SECRET if non-empty, - # otherwise it will use API_KEY. - TS_OAUTH_ID: "${{ inputs.oauth-client-id }}" - TS_OAUTH_SECRET: "${{ inputs.oauth-secret }}" - TS_API_KEY: "${{ inputs.api-key }}" + TS_API_KEY: "${{ steps.tailscale_api_key.outputs.access_token }}" TS_TAILNET: "${{ inputs.tailnet }}" run: go run tailscale.com/cmd/gitops-pusher@66aa77416744037baec93206ae212012a2314f83 "--policy-file=${{ inputs.policy-file }}" "${{ inputs.action }}"