diff --git a/docs/docs.json b/docs/docs.json index c1ee9880..8ec37c2b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -272,6 +272,13 @@ { "tab": "Integrations", "groups": [ + { + "group": "Workload Identity", + "pages": [ + "integrations/workload-identity/overview", + "integrations/workload-identity/github-actions" + ] + }, { "group": "API", "pages": [ diff --git a/docs/integrations/workload-identity/github-actions.mdx b/docs/integrations/workload-identity/github-actions.mdx new file mode 100644 index 00000000..4d9b7dfb --- /dev/null +++ b/docs/integrations/workload-identity/github-actions.mdx @@ -0,0 +1,217 @@ +--- +title: GitHub Actions +--- + +This guide explains how to configure Workload Identity for GitHub Actions to authenticate with Bytebase without storing long-lived credentials. + +## Step 1: Create a Workload Identity in Bytebase + +1. Go to **IAM & Admin** > **Users & Groups**. +2. Click **Add User** in the upper-right corner. +3. Select **Workload Identity** as the Type. +4. Fill in the configuration: + +| Field | Description | Example | +|-------|-------------|---------| +| **Name** | Display name for this identity | `GitHub Actions Deploy` | +| **Email** | Unique email for this identity (must end with `@workload.bytebase.com`) | `github-actions-deploy@workload.bytebase.com` | +| **Platform** | Select GitHub Actions | `GitHub Actions` | +| **Owner** | GitHub organization or username | `my-org` | +| **Repository** | Repository name | `my-repo` | +| **Branch** | Branch name (use `*` for all branches) | `main` | + +5. Click **Confirm** to create the Workload Identity. + +## Step 2: Assign Roles + +After creating the Workload Identity, assign the `GitOps Service Agent` role to enable automated CI/CD workflows: + +1. Go to your project's **Settings** > **Members**. +2. Click **Grant Access**. +3. Enter the Workload Identity email (e.g., `github-actions-deploy@workload.bytebase.com`). +4. Select the **GitOps Service Agent** role. +5. Click **Confirm**. + + + +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. + + + +## Step 3: Configure GitHub Actions Workflow + +In your GitHub Actions workflow, add the following configuration: + +### Request OIDC Token + +Add `id-token: write` permission and use the `actions/github-script` action to get the token: + +```yaml +name: Deploy Database Changes + +on: + push: + branches: [main] + +permissions: + id-token: write # Required for OIDC token + contents: read + +env: + BYTEBASE_URL: https://bytebase.example.com + WORKLOAD_IDENTITY_EMAIL: github-actions-deploy@workload.bytebase.com + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get Bytebase Token + id: bytebase-token + uses: actions/github-script@v7 + with: + script: | + const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}'); + core.setSecret(token); + core.setOutput('token', token); + + - name: Exchange for Bytebase API Token + id: exchange + run: | + RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \ + -H "Content-Type: application/json" \ + -d "{\"token\": \"${{ steps.bytebase-token.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}") + + ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken') + echo "::add-mask::$ACCESS_TOKEN" + echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT + + - name: Call Bytebase API + run: | + curl -s "${BYTEBASE_URL}/v1/projects" \ + -H "Authorization: Bearer ${{ steps.exchange.outputs.access_token }}" +``` + +## Complete Example + +Here's a complete workflow that creates a database change using Workload Identity: + +```yaml +name: Database Schema Change + +on: + push: + branches: [main] + paths: + - 'migrations/**' + +permissions: + id-token: write + contents: read + +env: + BYTEBASE_URL: https://bytebase.example.com + WORKLOAD_IDENTITY_EMAIL: github-deploy@workload.bytebase.com + PROJECT: projects/my-project + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get OIDC Token + id: oidc + uses: actions/github-script@v7 + with: + script: | + const token = await core.getIDToken('https://github.com/${{ github.repository_owner }}'); + core.setSecret(token); + core.setOutput('token', token); + + - name: Exchange Token + id: auth + run: | + RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/auth:exchangeToken" \ + -H "Content-Type: application/json" \ + -d "{\"token\": \"${{ steps.oidc.outputs.token }}\", \"email\": \"${WORKLOAD_IDENTITY_EMAIL}\"}") + + ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.accessToken') + if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then + echo "Failed to get access token" + echo $RESPONSE + exit 1 + fi + + echo "::add-mask::$ACCESS_TOKEN" + echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT + + - name: Create Plan + id: plan + run: | + # Read migration SQL file + SQL_CONTENT=$(cat migrations/latest.sql | jq -Rs .) + + RESPONSE=$(curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/plans" \ + -H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"Migration from GitHub Actions\", + \"steps\": [{ + \"specs\": [{ + \"changeDatabaseConfig\": { + \"target\": \"instances/prod/databases/mydb\", + \"type\": \"MIGRATE\", + \"sheet\": \"${SQL_CONTENT}\" + } + }] + }] + }") + + PLAN_NAME=$(echo $RESPONSE | jq -r '.name') + echo "plan_name=$PLAN_NAME" >> $GITHUB_OUTPUT + + - name: Create Issue and Rollout + run: | + # Create issue + curl -s -X POST "${BYTEBASE_URL}/v1/${PROJECT}/issues" \ + -H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"Schema migration\", + \"type\": \"DATABASE_CHANGE\", + \"plan\": \"${{ steps.plan.outputs.plan_name }}\" + }" +``` + +## Troubleshooting + +### Token Exchange Fails + +If the token exchange returns an error: + +1. **Verify the repository and branch**: Check that your workflow's repository, branch match the configured values in Bytebase. + +2. **Check the audience**: Ensure the audience in your `getIDToken()` call matches `https://github.com/{owner}`. + +### Permission Denied + +If API calls return permission errors: + +1. Verify the Workload Identity has the `GitOps Service Agent` role assigned. +2. Check that the Workload Identity is a member of the target project. + +### Debug Token Claims + +To inspect the OIDC token claims, decode the JWT: + +```yaml +- name: Debug Token + run: | + echo "${{ steps.oidc.outputs.token }}" | cut -d. -f2 | base64 -d | jq . +``` + +This shows the token's claims including `sub`, `aud`, and `iss` that Bytebase validates. diff --git a/docs/integrations/workload-identity/overview.mdx b/docs/integrations/workload-identity/overview.mdx new file mode 100644 index 00000000..51c8d44b --- /dev/null +++ b/docs/integrations/workload-identity/overview.mdx @@ -0,0 +1,66 @@ +--- +title: Overview +--- + +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. + +## Why Workload Identity? + +Traditional CI/CD integration requires storing Service Account keys as secrets in your CI/CD platform. This approach has security risks: + +- **Secret sprawl**: Credentials stored in multiple locations increase attack surface +- **No automatic rotation**: Long-lived keys require manual rotation +- **Broad access**: Leaked credentials can be used from anywhere + +Workload Identity solves these problems by: + +- **No stored secrets**: Workflows request short-lived tokens directly from your CI/CD platform +- **Automatic rotation**: Tokens are generated per-job and expire automatically +- **Scoped access**: Tokens are bound to specific repositories, branches, and workflows + +## How It Works + +``` +┌─────────────────┐ 1. Request OIDC Token ┌─────────────────┐ +│ │ ──────────────────────────────► │ │ +│ CI/CD Platform │ │ OIDC Provider │ +│ (e.g. GitHub) │ ◄────────────────────────────── │ │ +│ │ 2. Return JWT Token └─────────────────┘ +└────────┬────────┘ + │ + │ 3. Exchange Token + │ (JWT + Workload Identity Email) + ▼ +┌─────────────────┐ +│ │ 4. Validate Token +│ Bytebase │ - Verify issuer +│ │ - Check audience +└────────┬────────┘ - Match subject pattern + │ + │ 5. Return Bytebase API Token + ▼ +┌─────────────────┐ +│ CI/CD Workflow │ 6. Call Bytebase API +│ continues... │ with API Token +└─────────────────┘ +``` + +1. Your CI/CD workflow requests an OIDC token from the platform's token endpoint +2. The platform returns a signed JWT containing claims about the workflow (repository, branch, etc.) +3. Your workflow calls Bytebase's `exchangeToken` API with the JWT and Workload Identity email +4. Bytebase validates the token against the configured Workload Identity settings +5. Bytebase returns a short-lived API token (1-hour validity) +6. Your workflow uses this token for subsequent Bytebase API calls + +## Supported Platforms + + + + Configure OIDC authentication for GitHub Actions workflows + + + +## Prerequisites + +- Bytebase version 3.13.0 or later +- A CI/CD platform that supports OIDC token issuance