mirror of
https://github.com/tailscale/github-action.git
synced 2026-08-20 21:09:20 +00:00
add oidc workload identity federation flow to github action
updates
This commit is contained in:
parent
84a3f23bb4
commit
5476c5851e
3 changed files with 132 additions and 5 deletions
30
.github/workflows/tailscale.yml
vendored
30
.github/workflows/tailscale.yml
vendored
|
|
@ -1,5 +1,8 @@
|
|||
name: tailscale
|
||||
|
||||
permissions:
|
||||
id-token: write # This is required for requesting the JWT
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
|
@ -15,12 +18,25 @@ jobs:
|
|||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, windows-11-arm]
|
||||
cache: ['false', 'true']
|
||||
auth: ['oauth', 'oauth-legacy', 'oidc']
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Tailscale Action
|
||||
- name: Tailscale Action (OAuth)
|
||||
if: matrix.auth == 'oauth'
|
||||
uses: ./
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_GRANULAR_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TS_OAUTH_GRANULAR_SECRET }}
|
||||
tags: tag:ci
|
||||
use-cache: ${{ matrix.cache }}
|
||||
|
||||
# This job runs as a sanity check to ensure we have not broken the ability for OAuth clients using
|
||||
# our legacy scopes to successfully connect to tailnets using this action.
|
||||
- name: Tailscale Action (OAuth legacy)
|
||||
if: matrix.auth == 'oauth-legacy'
|
||||
uses: ./
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
|
|
@ -28,6 +44,16 @@ jobs:
|
|||
tags: tag:ci
|
||||
use-cache: ${{ matrix.cache }}
|
||||
|
||||
- name: Tailscale Action (OIDC)
|
||||
if: matrix.auth == 'oidc'
|
||||
uses: ./
|
||||
with:
|
||||
oidc-client-id: ${{ secrets.TS_OIDC_CLIENT_ID }}
|
||||
oidc-aud-claim: ${{ secrets.TS_OIDC_AUD_CLAIM }}
|
||||
tags: tag:ci
|
||||
tailnet: ${{ secrets.TS_TAILNET_NAME }}
|
||||
use-cache: ${{ matrix.cache }}
|
||||
|
||||
- name: check for tailscale connection
|
||||
shell: bash
|
||||
run:
|
||||
|
|
@ -41,4 +67,4 @@ jobs:
|
|||
echo "::error::Unexpected extra files: $extra_files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
15
README.md
15
README.md
|
|
@ -29,6 +29,19 @@ be automatically removed by the coordination server a short time after they
|
|||
finish their run. The nodes are also [marked Preapproved](https://tailscale.com/kb/1085/auth-keys/)
|
||||
on tailnets which use [Device Approval](https://tailscale.com/kb/1099/device-approval/)
|
||||
|
||||
## Authenticate with GitHub's OIDC provider (alpha)
|
||||
|
||||
The Tailscale GitHub action can use an OIDC token provided by GitHub to authenticate the workflow to your tailnet. This functionality is currently available as a private alpha. To join the alpha program, contact your account rep or email `sam@tailscale.com`.
|
||||
|
||||
```yaml
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v3
|
||||
with:
|
||||
oidc-client-id: ${{ secrets.TS_OIDC_CLIENT_ID }}
|
||||
oidc-aud-claim: ${{ secrets.TS_OIDC_AUD_CLAIM }}
|
||||
tags: tag:ci
|
||||
```
|
||||
|
||||
## Tailnet Lock
|
||||
|
||||
If you are using this Action in a [Tailnet
|
||||
|
|
@ -92,4 +105,4 @@ You can opt in to caching Tailscale binaries by passing `'true'` to the `use-cac
|
|||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||
use-cache: 'true'
|
||||
```
|
||||
```
|
||||
92
action.yml
92
action.yml
|
|
@ -17,6 +17,15 @@ inputs:
|
|||
oauth-secret:
|
||||
description: 'Your Tailscale OAuth Client Secret.'
|
||||
required: false
|
||||
oidc-client-id:
|
||||
description: 'Your Tailscale OIDC Client ID.'
|
||||
required: false
|
||||
oidc-aud-claim:
|
||||
description: 'Your Tailscale OIDC audience claim'
|
||||
required: false
|
||||
tailnet:
|
||||
description: 'Your Tailscale tailnet name (e.g., example.com)'
|
||||
required: false
|
||||
tags:
|
||||
description: 'Comma separated list of Tags to be applied to nodes. The OAuth client must have permission to apply these tags.'
|
||||
required: false
|
||||
|
|
@ -66,12 +75,82 @@ runs:
|
|||
echo "::error title=⛔ error hint::Support Linux, Windows, and macOS Only"
|
||||
exit 1
|
||||
- name: Check Auth Info Empty
|
||||
if: ${{ inputs.authkey == '' && (inputs['oauth-secret'] == '' || inputs.tags == '') }}
|
||||
if: ${{ inputs.authkey == '' && (inputs['oauth-secret'] == '' || inputs.tags == '') && (inputs['oidc-client-id'] == '' || inputs['oidc-aud-claim'] == '') }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::error title=⛔ error hint::OAuth identity empty, 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::OAuth and OIDC identity are empty, 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: get OIDC token from GitHub Actions
|
||||
if: ${{ inputs['oidc-client-id'] != '' && inputs['oidc-aud-claim'] != '' && inputs['oauth-secret'] == '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
JWT=$(curl -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${{ inputs['oidc-aud-claim'] }}" | jq -r '.value')
|
||||
if [ -z "$JWT" ]; then
|
||||
echo "::error title=⛔ error hint::Failed to get JWT from GitHub Actions OIDC provider"
|
||||
exit 1
|
||||
fi
|
||||
echo "::add-mask::$JWT" # Mask the JWT in the logs
|
||||
echo "JWT=$JWT" >> $GITHUB_ENV
|
||||
|
||||
- name: Exchange JWT OIDC token and get authkey
|
||||
if: ${{ inputs['oidc-client-id'] != '' && inputs['oidc-aud-claim'] != '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Exchanging JWT OIDC token for short-lived API token..."
|
||||
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=$JWT")
|
||||
export ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
|
||||
echo "::add-mask::$ACCESS_TOKEN" # Mask the access token in the logs
|
||||
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
|
||||
echo "::error title=⛔ error hint::Failed to get ACCESS_TOKEN from OIDC token exchange, response: $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Retrieving authkey from Tailscale API"
|
||||
|
||||
# Convert comma-separated tags to JSON array format
|
||||
TAGS_JSON=$(echo "${{ inputs.tags }}" | sed 's/,/","/g' | sed 's/^/["/' | sed 's/$/"]/')
|
||||
|
||||
# Make the API call and capture both the response and curl info
|
||||
CURL_OUTPUT=$(curl -s -w "\n%{http_code}" -X POST https://api.tailscale.com/api/v2/tailnet/${{ inputs.tailnet }}/keys \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
|
||||
--data "{
|
||||
\"keyType\": \"auth\",
|
||||
\"capabilities\": {
|
||||
\"devices\": {
|
||||
\"create\": {
|
||||
\"reusable\": false,
|
||||
\"ephemeral\": true,
|
||||
\"preauthorized\": true,
|
||||
\"tags\": $TAGS_JSON
|
||||
}
|
||||
}
|
||||
},
|
||||
\"expirySeconds\": 3600
|
||||
}")
|
||||
|
||||
# Extract HTTP status code (last line) and JSON response (everything else)
|
||||
HTTP_STATUS=$(echo "$CURL_OUTPUT" | tail -n1)
|
||||
JSON_RESPONSE=$(echo "$CURL_OUTPUT" | sed '$d')
|
||||
echo "::add-mask::$JSON_RESPONSE" # Mask the JSON response in the logs
|
||||
|
||||
if [ "$HTTP_STATUS" != "200" ]; then
|
||||
echo "::error title=⛔ error hint::Tailscale API returned HTTP $HTTP_STATUS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export AUTHKEY=$(echo "$JSON_RESPONSE" | jq -r '.key')
|
||||
echo "::add-mask::$AUTHKEY" # Mask the auth key in the logs
|
||||
if [ -z "$AUTHKEY" ] || [ "$AUTHKEY" = "null" ]; then
|
||||
echo "::error title=⛔ error hint::Failed to get AUTHKEY from Tailscale API"
|
||||
exit 1
|
||||
fi
|
||||
echo "AUTHKEY=$AUTHKEY" >> $GITHUB_ENV
|
||||
|
||||
- name: Set Resolved Version
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
@ -310,6 +389,15 @@ runs:
|
|||
TAILSCALE_AUTHKEY="${{ inputs['oauth-secret'] }}?preauthorized=true&ephemeral=true"
|
||||
TAGS_ARG="--advertise-tags=${{ inputs.tags }}"
|
||||
fi
|
||||
|
||||
if [ -n "${{ inputs['oidc-aud-claim'] }}" ] && [ -n "${{ inputs['oidc-client-id'] }}" ]; then
|
||||
if [ -z "$AUTHKEY" ]; then
|
||||
echo "::error title=⛔ error hint::Failed to get AUTHKEY from OIDC token exchange"
|
||||
exit 1
|
||||
fi
|
||||
TAILSCALE_AUTHKEY="$AUTHKEY"
|
||||
TAGS_ARG="--advertise-tags=${{ inputs.tags }}"
|
||||
fi
|
||||
if [ "${{ runner.os }}" != "Windows" ]; then
|
||||
MAYBE_SUDO="sudo -E"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue