Skip to content

Commit d71ffda

Browse files
authored
docs: add docs for workload identity with GitHub Actions (#967)
1 parent 1e739a1 commit d71ffda

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

docs/docs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@
272272
{
273273
"tab": "Integrations",
274274
"groups": [
275+
{
276+
"group": "Workload Identity",
277+
"pages": [
278+
"integrations/workload-identity/overview",
279+
"integrations/workload-identity/github-actions"
280+
]
281+
},
275282
{
276283
"group": "API",
277284
"pages": [
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
title: GitHub Actions
3+
---
4+
5+
This guide explains how to configure Workload Identity for GitHub Actions to authenticate with Bytebase without storing long-lived credentials.
6+
7+
## Step 1: Create a Workload Identity in Bytebase
8+
9+
1. Go to **IAM & Admin** > **Users & Groups**.
10+
2. Click **Add User** in the upper-right corner.
11+
3. Select **Workload Identity** as the Type.
12+
4. Fill in the configuration:
13+
14+
| Field | Description | Example |
15+
|-------|-------------|---------|
16+
| **Name** | Display name for this identity | `GitHub Actions Deploy` |
17+
| **Email** | Unique email for this identity (must end with `@workload.bytebase.com`) | `[email protected]` |
18+
| **Platform** | Select GitHub Actions | `GitHub Actions` |
19+
| **Owner** | GitHub organization or username | `my-org` |
20+
| **Repository** | Repository name | `my-repo` |
21+
| **Branch** | Branch name (use `*` for all branches) | `main` |
22+
23+
5. Click **Confirm** to create the Workload Identity.
24+
25+
## Step 2: Assign Roles
26+
27+
After creating the Workload Identity, assign the `GitOps Service Agent` role to enable automated CI/CD workflows:
28+
29+
1. Go to your project's **Settings** > **Members**.
30+
2. Click **Grant Access**.
31+
3. Enter the Workload Identity email (e.g., `[email protected]`).
32+
4. Select the **GitOps Service Agent** role.
33+
5. Click **Confirm**.
34+
35+
<Tip>
36+
37+
The `GitOps Service Agent` role is designed for automated CI/CD workflows, allowing the identity to create and execute database changes. See [Roles and Permissions](/administration/roles) for details.
38+
39+
</Tip>
40+
41+
## Step 3: Configure GitHub Actions Workflow
42+
43+
In your GitHub Actions workflow, add the following configuration:
44+
45+
### Request OIDC Token
46+
47+
Add `id-token: write` permission and use the `actions/github-script` action to get the token:
48+
49+
```yaml
50+
name: Deploy Database Changes
51+
52+
on:
53+
push:
54+
branches: [main]
55+
56+
permissions:
57+
id-token: write # Required for OIDC token
58+
contents: read
59+
60+
env:
61+
BYTEBASE_URL: https://bytebase.example.com
62+
WORKLOAD_IDENTITY_EMAIL: [email protected]
63+
64+
jobs:
65+
deploy:
66+
runs-on: ubuntu-latest
67+
steps:
68+
- name: Checkout
69+
uses: actions/checkout@v4
70+
71+
- name: Get Bytebase Token
72+
id: bytebase-token
73+
uses: actions/github-script@v7
74+
with:
75+
script: |
76+
const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}');
77+
core.setSecret(token);
78+
core.setOutput('token', token);
79+
80+
- name: Exchange for Bytebase API Token
81+
id: exchange
82+
run: |
83+
RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
84+
-H "Content-Type: application/json" \
85+
-d "{\"token\": \"${{ steps.bytebase-token.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}")
86+
87+
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken')
88+
echo "::add-mask::$ACCESS_TOKEN"
89+
echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
90+
91+
- name: Call Bytebase API
92+
run: |
93+
curl -s "${BYTEBASE_URL}/v1/projects" \
94+
-H "Authorization: Bearer ${{ steps.exchange.outputs.access_token }}"
95+
```
96+
97+
## Complete Example
98+
99+
Here's a complete workflow that creates a database change using Workload Identity:
100+
101+
```yaml
102+
name: Database Schema Change
103+
104+
on:
105+
push:
106+
branches: [main]
107+
paths:
108+
- 'migrations/**'
109+
110+
permissions:
111+
id-token: write
112+
contents: read
113+
114+
env:
115+
BYTEBASE_URL: https://bytebase.example.com
116+
WORKLOAD_IDENTITY_EMAIL: [email protected]
117+
PROJECT: projects/my-project
118+
119+
jobs:
120+
deploy:
121+
runs-on: ubuntu-latest
122+
steps:
123+
- name: Checkout
124+
uses: actions/checkout@v4
125+
126+
- name: Get OIDC Token
127+
id: oidc
128+
uses: actions/github-script@v7
129+
with:
130+
script: |
131+
const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}');
132+
core.setSecret(token);
133+
core.setOutput('token', token);
134+
135+
- name: Exchange Token
136+
id: auth
137+
run: |
138+
RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \
139+
-H "Content-Type: application/json" \
140+
-d "{\"token\": \"${{ steps.oidc.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}")
141+
142+
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken')
143+
if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then
144+
echo "Failed to get access token"
145+
echo $RESPONSE
146+
exit 1
147+
fi
148+
149+
echo "::add-mask::$ACCESS_TOKEN"
150+
echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
151+
152+
- name: Create Plan
153+
id: plan
154+
run: |
155+
# Read migration SQL file
156+
SQL_CONTENT=$(cat migrations/latest.sql | jq -Rs .)
157+
158+
RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/plans" \
159+
-H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \
160+
-H "Content-Type: application/json" \
161+
-d "{
162+
\"title\": \"Migration from GitHub Actions\",
163+
\"steps\": [{
164+
\"specs\": [{
165+
\"changeDatabaseConfig\": {
166+
\"target\": \"instances/prod/databases/mydb\",
167+
\"type\": \"MIGRATE\",
168+
\"sheet\": \"${SQL_CONTENT}\"
169+
}
170+
}]
171+
}]
172+
}")
173+
174+
PLAN_NAME=$(echo $RESPONSE | jq -r '.name')
175+
echo "plan_name=$PLAN_NAME" >> $GITHUB_OUTPUT
176+
177+
- name: Create Issue and Rollout
178+
run: |
179+
# Create issue
180+
curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/issues" \
181+
-H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \
182+
-H "Content-Type: application/json" \
183+
-d "{
184+
\"title\": \"Schema migration\",
185+
\"type\": \"DATABASE_CHANGE\",
186+
\"plan\": \"${{ steps.plan.outputs.plan_name }}\"
187+
}"
188+
```
189+
190+
## Troubleshooting
191+
192+
### Token Exchange Fails
193+
194+
If the token exchange returns an error:
195+
196+
1. **Verify the repository and branch**: Check that your workflow's repository, branch match the configured values in Bytebase.
197+
198+
2. **Check the audience**: Ensure the audience in your `getIDToken()` call matches `https://github.com/{owner}`.
199+
200+
### Permission Denied
201+
202+
If API calls return permission errors:
203+
204+
1. Verify the Workload Identity has the `GitOps Service Agent` role assigned.
205+
2. Check that the Workload Identity is a member of the target project.
206+
207+
### Debug Token Claims
208+
209+
To inspect the OIDC token claims, decode the JWT:
210+
211+
```yaml
212+
- name: Debug Token
213+
run: |
214+
echo "${{ steps.oidc.outputs.token }}" | cut -d. -f2 | base64 -d | jq .
215+
```
216+
217+
This shows the token's claims including `sub`, `aud`, and `iss` that Bytebase validates.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: Overview
3+
---
4+
5+
Workload Identity allows CI/CD pipelines and external services to authenticate with Bytebase using OpenID Connect (OIDC) tokens, eliminating the need to store long-lived credentials as secrets.
6+
7+
## Why Workload Identity?
8+
9+
Traditional CI/CD integration requires storing Service Account keys as secrets in your CI/CD platform. This approach has security risks:
10+
11+
- **Secret sprawl**: Credentials stored in multiple locations increase attack surface
12+
- **No automatic rotation**: Long-lived keys require manual rotation
13+
- **Broad access**: Leaked credentials can be used from anywhere
14+
15+
Workload Identity solves these problems by:
16+
17+
- **No stored secrets**: Workflows request short-lived tokens directly from your CI/CD platform
18+
- **Automatic rotation**: Tokens are generated per-job and expire automatically
19+
- **Scoped access**: Tokens are bound to specific repositories, branches, and workflows
20+
21+
## How It Works
22+
23+
```
24+
┌─────────────────┐ 1. Request OIDC Token ┌─────────────────┐
25+
│ │ ──────────────────────────────► │ │
26+
│ CI/CD Platform │ │ OIDC Provider │
27+
│ (e.g. GitHub) │ ◄────────────────────────────── │ │
28+
│ │ 2. Return JWT Token └─────────────────┘
29+
└────────┬────────┘
30+
31+
│ 3. Exchange Token
32+
│ (JWT + Workload Identity Email)
33+
34+
┌─────────────────┐
35+
│ │ 4. Validate Token
36+
│ Bytebase │ - Verify issuer
37+
│ │ - Check audience
38+
└────────┬────────┘ - Match subject pattern
39+
40+
│ 5. Return Bytebase API Token
41+
42+
┌─────────────────┐
43+
│ CI/CD Workflow │ 6. Call Bytebase API
44+
│ continues... │ with API Token
45+
└─────────────────┘
46+
```
47+
48+
1. Your CI/CD workflow requests an OIDC token from the platform's token endpoint
49+
2. The platform returns a signed JWT containing claims about the workflow (repository, branch, etc.)
50+
3. Your workflow calls Bytebase's `exchangeToken` API with the JWT and Workload Identity email
51+
4. Bytebase validates the token against the configured Workload Identity settings
52+
5. Bytebase returns a short-lived API token (1-hour validity)
53+
6. Your workflow uses this token for subsequent Bytebase API calls
54+
55+
## Supported Platforms
56+
57+
<CardGroup cols={2}>
58+
<Card title="GitHub Actions" icon="github" href="/integrations/workload-identity/github-actions">
59+
Configure OIDC authentication for GitHub Actions workflows
60+
</Card>
61+
</CardGroup>
62+
63+
## Prerequisites
64+
65+
- Bytebase version 3.13.0 or later
66+
- A CI/CD platform that supports OIDC token issuance

0 commit comments

Comments
 (0)