> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Actions

> Let GitHub Actions obtain temporary Basaltic deployment credentials with its job identity.

Use a GitHub Actions job identity to obtain a temporary Basaltic deployment session. No long-lived Basaltic access key needs to be stored in GitHub.

The job requests a GitHub OIDC JWT, then exchanges it at Basaltic’s `AssumeRoleWithWebIdentity` endpoint. The resulting bearer token authorizes deployment API calls according to the Basaltic role’s policies.

## Register GitHub in the deployment account

Follow [provider registration](https://docs.basaltic.sh/iam/roles.md#trusting-an-oidc-provider) in the same account as the deployment role. Use these values:

> [tab] API
>
> ```json theme={null}
> {
>   "name": "github",
>   "issuer": "https://token.actions.githubusercontent.com",
>   "audiences": ["urn:example:basaltic:production"]
> }
> ```

The audience is your chosen identifier for this exchange. Use exactly the same value in the provider, trust policy, and workflow. The AWS guide uses `sts.amazonaws.com` as its example audience.

## Restrict the role to your workflow

Create an [IAM role](https://docs.basaltic.sh/iam/roles.md) named `github-deployer` in account `production`. Its trust policy should name your provider and the exact GitHub subject. This example uses a protected environment named `production`:

```json theme={null}
{
  "principals": ["crn:iam::production:oidc-provider/github"],
  "conditions": [
    {
      "operator": "equals",
      "key": "basalt:webidentity:Subject",
      "values": ["repo:acme@123456/app@789012:environment:production"]
    },
    {
      "operator": "equals",
      "set_operator": "for_any_value",
      "key": "basalt:webidentity:Audience",
      "values": ["urn:example:basaltic:production"]
    }
  ]
}
```

Replace the sample owner, repository, and IDs with your token’s exact `sub`. GitHub supports both name-based and immutable-ID subject formats. Renames, transfers, or subject customization can change the value. See [GitHub’s subject reference](https://docs.github.com/en/actions/reference/security/oidc#example-subject-claims).

| Job scope | Example subject |
| - | - |
| Environment | `repo:acme@123456/app@789012:environment:production` |
| Branch without an environment | `repo:acme@123456/app@789012:ref:refs/heads/main` |
| Older name-based environment | `repo:acme/app:environment:production` |
| Older name-based branch | `repo:acme/app:ref:refs/heads/main` |

An environment subject does not also bind the branch. Configure [GitHub environment protection](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) to restrict deployment branches and approvals. The example workflow uses an environment, so its trust must use the environment subject.

Basaltic evaluates the verified issuer, subject, and audience. Claims such as `repository_id`, `ref`, and `job_workflow_ref` are not separate Basaltic policy keys. Put required restrictions in the supported subject and GitHub environment configuration.

## Grant deployment permissions

Attach [permission policies](https://docs.basaltic.sh/iam/policies.md) for the specific resources your deployment changes. Trust permits login; it does not grant deployment access. To run the verification step below, add this small policy, then add your application’s deployment permissions separately:

```json theme={null}
{
  "version": "2024-01-01",
  "statements": [{
    "effect": "allow",
    "actions": ["iam:GetRole"],
    "resources": ["crn:iam::production:role/github-deployer"]
  }]
}
```

## Obtain a session in GitHub Actions

Add the following workflow after replacing the account, role, audience, and environment values. GitHub’s `id-token: write` permission allows the job to request its identity token; Basaltic permissions still come from the role. The exchange uses no pre-existing Basaltic credential.

Set `BASALTIC_ROLE_ID` to the role UUID returned when you create the role; the verification endpoint takes a UUID.

```yaml .github/workflows/deploy.yml theme={null}
name: Deploy to Basaltic
on:
  push:
    branches: [main]
  workflow_dispatch:
permissions:
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
      id-token: write
    env:
      BASALTIC_ACCOUNT: production
      BASALTIC_ROLE: crn:iam::production:role/github-deployer
      BASALTIC_ROLE_ID: REPLACE_WITH_ROLE_UUID
      BASALTIC_AUDIENCE: urn:example:basaltic:production
    steps:
      - name: Exchange GitHub identity
        shell: bash
        run: |
          python3 - <<'PYTHON'
          import json, os, urllib.parse, urllib.request

          url = os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"]
          separator = "&" if "?" in url else "?"
          url += separator + urllib.parse.urlencode({"audience": os.environ["BASALTIC_AUDIENCE"]})
          request = urllib.request.Request(url, headers={
              "Authorization": "Bearer " + os.environ["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
          })
          with urllib.request.urlopen(request, timeout=30) as response:
              proof = json.load(response)["value"]
          print("::add-mask::" + proof)
          body = json.dumps({
              "role": os.environ["BASALTIC_ROLE"],
              "web_identity_token": proof,
              "session_name": "github-" + os.environ["GITHUB_RUN_ID"],
              "duration_seconds": 900
          }).encode()
          request = urllib.request.Request(
              "https://iam.basaltic.sh/v1/assume-role-with-web-identity",
              data=body, headers={"Content-Type": "application/json"}
          )
          with urllib.request.urlopen(request, timeout=30) as response:
              session = json.load(response)
          token = session["access_token"]
          print("::add-mask::" + token)
          with open(os.environ["GITHUB_ENV"], "a") as output:
              output.write("BASALTIC_TOKEN=" + token + "\n")
          PYTHON
      - name: Verify deployment role
        shell: bash
        run: |
          curl --fail --silent --show-error \
            -H "Authorization: Bearer $BASALTIC_TOKEN" \
            -H "X-Account-Id: $BASALTIC_ACCOUNT" \
            "https://iam.basaltic.sh/v1/roles/$BASALTIC_ROLE_ID" > /dev/null
```

Use `BASALTIC_TOKEN` in subsequent deployment steps as the HTTP bearer token. Replace or follow the verification step with your deployment commands, granting only their required IAM actions. This variable is set by the example; tools need their own documented token configuration. Do not print the token or upload the exchange response as an artifact.

The sample requests a 15-minute session. For longer jobs, obtain a fresh GitHub proof and exchange it again before the Basaltic session expires. There is no refresh token in this flow. See [GitHub’s OIDC workflow guidance](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-cloud-providers).

## Troubleshooting

A `401` during exchange usually points to issuer registration, audience, signature, or expiration. A `403` points to role trust: compare the exact subject, including immutable IDs, branch versus environment, and the audience set operator. A `403` after login indicates missing role permissions or a permission boundary. See the [federation overview](https://docs.basaltic.sh/iam/oidc.md#troubleshooting).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.