# GitHub Actions

> Publish from a GitHub Actions workflow with a trust policy and the job identity token.

Source: https://docs.telesphorepo.com/publishing/github-actions/

A GitHub Actions job can publish without a stored secret. The job gets an identity token from GitHub. Telesphorepo trades it for a [publish session](/publishing/overview/).

## Set up

1. An admin opens the **Trust policies** page and picks the GitHub Actions preset. The issuer is `https://token.actions.githubusercontent.com`.

2. Add claims that name your workflow. Every claim needs at least one allowed value. A value is exact text or a pattern with `*`.

   | Claim          | Example                                    |
   | -------------- | ------------------------------------------ |
   | `repository`   | `acme/app`                                 |
   | `ref`          | `refs/tags/v*`                             |
   | `workflow_ref` | `acme/app/.github/workflows/release.yml@*` |

3. List the repositories that the policy may publish to.

4. Give the job `permissions: id-token: write`.

Leave the audience empty. It then means the `base_url` of the server. See [Trust policies](/security/trust-policies/) for all fields.

## Publish with the plugin or extension

With the [Gradle plugin](/publishing/gradle-plugin/) or the [Maven extension](/publishing/maven-extension/), you add no step. They find the GitHub token on their own.

```yaml title=".github/workflows/release.yml"
name: release
on:
  push:
    tags: ["v*"]

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - run: ./gradlew publish
```

## Publish with curl

Without a client, use two `curl` calls. The examples need `jq`.

```yaml title=".github/workflows/release.yml"
permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Publish
        run: |
          OIDC=$(curl -sSf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://repo.acme.com" | jq -r .value)
          SESSION=$(curl -sSf https://repo.acme.com/api/publish/sessions/oidc \
            -H 'Content-Type: application/json' \
            -d "{\"token\":\"$OIDC\",\"repo\":\"releases\"}")
          TOKEN=$(echo "$SESSION" | jq -r .token)

          curl -sSf -u "ci:$TOKEN" -T app-1.0.jar \
            https://repo.acme.com/maven/releases/com/acme/app/1.0/app-1.0.jar

          # End the session when the publish is done.
          curl -sSf -X DELETE -u "ci:$TOKEN" \
            "https://repo.acme.com/api/publish/sessions/$(echo "$SESSION" | jq -r .id)"
```

The first call asks GitHub for a token. The `audience` must equal the audience of the trust policy. The second call trades the token for a session. The session answer has `id`, `token`, `repo` and `expires_at`. Use `token` as the Basic password with any user name.

## Gotchas

- Without `id-token: write`, GitHub gives the job no token request URL, and the exchange fails.
- The server checks signature, issuer, audience and expiry. The token must be at most 10 minutes old.
- The server accepts each identity token once. Do not retry the exchange with the same token. Ask GitHub for a new one.
- If no enabled policy matches the issuer, audience and claims, or the policy does not list the repository, the exchange fails.
- A `*` in a claim value matches any text, `/` included. `refs/tags/v*` matches `refs/tags/v1/x`.
- Narrow the `ref` and `workflow_ref` claims so that only your release workflow matches.
- If you edit or disable a policy, the server ends its open sessions.
