Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/setup-github-token-gati-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"setup-github-token": minor
---

Support GATI v2 behind a staged rollout. Adds a `profile` input that selects
GATI v2, a `gati-version` input to force either flow, and a `gati-version`
output. `aws-role-arn` and `aws-lambda-url` are now optional, and required only
when `profile` is not set.
89 changes: 89 additions & 0 deletions actions/setup-github-token/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
# setup-github-token action

Gets a GitHub installation access token from GATI, and optionally configures git
to authenticate with it.

Requires `id-token: write` on the calling job — v1 uses it to assume the AWS
role, v2 to authenticate to GATI directly.

## Which flow runs

- **v1** ([`invoke-gati`](../invoke-gati/)) assumes `aws-role-arn`, then calls
the lambda at `aws-lambda-url`.
- **v2** ([`invoke-gati-v2`](../invoke-gati-v2/)) requests a `profile` over
GitHub OIDC, with no AWS credentials.

Passing `profile` selects v2. Otherwise a staged rollout decides: the repository
name is hashed into a bucket of 0–99, and uses v2 when that bucket is below the
rollout percentage. Since the bucket never changes, a repository moves to v2
once and stays there. The percentage and the two pin lists that override it are
the `ROLLOUT_PERCENTAGE`, `FORCE_V2_REPOSITORIES`, and `FORCE_V1_REPOSITORIES`
env values on the `Resolve GATI version` step in [`action.yml`](./action.yml);
changing them needs a release. `gati-version` overrides all of the above.

## Migrated v1 roles

GATI v1 role configuration was carried into v2 automatically, keyed by the
role's IAM ARN and addressable as the profile `v1/<iam-role-arn>`. So v2 needs
no new configuration to take over from v1: given no `profile`, it derives one
from the `aws-role-arn` the caller already passes and returns a token with the
same permissions v1 would have. A caller only needs a real `profile` to get
scopes its v1 role does not already have.

## Inputs

Either `profile` or the v1 inputs must be provided.

| Name | Description | Defaulted |
| ---------------- | --------------------------------------------------------- | -------------- |
| `set-git-config` | Configure git to use the token for `https://github.com/`. | ✅ - `"false"` |
| `gati-version` | Force `v1` or `v2`, bypassing the rollout. | |

### v2

| Name | Description |
| --------- | ---------------------------------------------- |
| `profile` | Token profile to request. Selects v2 when set. |

### v1

Also read by v2 to derive a profile, see
[Migrated v1 roles](#migrated-v1-roles). Ignored when `profile` is set.

| Name | Description | Defaulted |
| --------------------------- | ----------------------------------------------- | -------------------------------- |
| `aws-role-arn` | ARN of the role that can get a token from GATI. | |
| `aws-lambda-url` | URL of the GATI lambda function. | |
| `aws-region` | AWS region. | |
| `aws-role-duration-seconds` | Duration of the assumed role, in seconds. | ✅ - `"900"` |
| `role-session-name` | Session name, truncated to 64 characters. | ✅ - run id, run number, and job |

## Outputs

| Name | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------- |
| `access-token` | The token. A v2 token is revoked when its job ends, so request one per job rather than passing it between jobs. |
| `gati-version` | The GATI version that issued the token, `v1` or `v2`. |

## Example

```yaml
jobs:
example:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Setup GitHub token
id: setup-github-token
uses: smartcontractkit/.github/actions/setup-github-token@setup-github-token/v1
with:
aws-role-arn: ${{ secrets.AWS_OIDC_IAM_ROLE_ARN_GATI }}
aws-lambda-url: ${{ secrets.AWS_LAMBDA_URL_GATI }}
aws-region: us-west-2

- name: Use the token
env:
GH_TOKEN: ${{ steps.setup-github-token.outputs.access-token }}
run: gh repo view smartcontractkit/.github
```
67 changes: 58 additions & 9 deletions actions/setup-github-token/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
description: Setup a GitHub Token from GATI

inputs:
profile:
description:
GATI v2 token profile to request. When set, GATI v2 is used and the aws-*
inputs are ignored.
required: false
gati-version:
description:
Force "v1" or "v2", bypassing the staged rollout. Leave unset to let the
rollout decide.
required: false
aws-role-arn:
description: ARN of role capable of getting token from GATI
required: true
description:
ARN of role capable of getting token from GATI. Required unless profile is
set.
required: false
aws-lambda-url:
description: URL of GATI lambda function
required: true
description: URL of GATI lambda function. Required unless profile is set.
required: false
aws-region:
description: AWS region
required: true
description: AWS region. Required unless profile is set.
required: false
default: "us-west-2"
aws-role-duration-seconds:
description: Duration of role in seconds
required: false
Expand All @@ -26,15 +39,38 @@

outputs:
access-token:
value: ${{ steps.get-gh-token.outputs.access-token }}
value:
${{ steps.get-gh-token.outputs.access-token ||
steps.get-gh-token-v2.outputs.access-token }}
description:
The github access token that has permissions reflecting the current AWS
role value
gati-version:
value: ${{ steps.resolve-gati-version.outputs.version }}
description: The GATI version that issued the token, "v1" or "v2"

runs:
using: composite
steps:
- name: Resolve GATI version
id: resolve-gati-version
shell: bash
env:
PROFILE: ${{ inputs.profile }}
GATI_VERSION: ${{ inputs.gati-version }}
AWS_ROLE_ARN: ${{ inputs.aws-role-arn }}
AWS_LAMBDA_URL: ${{ inputs.aws-lambda-url }}
# Rollout configuration. Repositories not named in either list are
# bucketed by the hash of their name, and use v2 when their bucket is
# below the percentage.
ROLLOUT_PERCENTAGE: "0"
FORCE_V2_REPOSITORIES: |
smartcontractkit/.github
FORCE_V1_REPOSITORIES: ""
run: ${{ github.action_path }}/scripts/resolve-gati-version.sh

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIll fix in follow-up


- name: Check the role session name lengths and truncate if needed
if: steps.resolve-gati-version.outputs.version == 'v1'
shell: bash
id: role-session-name
env:
Expand All @@ -47,6 +83,7 @@
fi

- name: Assume role capable of getting token from gati
if: steps.resolve-gati-version.outputs.version == 'v1'
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
aws-region: ${{ inputs.aws-region }}
Expand All @@ -56,16 +93,28 @@
${{ steps.role-session-name.outputs.role-session-name }}
role-to-assume: ${{ inputs.aws-role-arn }}

- name: Get github token from gati
- name: Get github token from gati (v1)
id: get-gh-token
if: steps.resolve-gati-version.outputs.version == 'v1'
uses: smartcontractkit/.github/actions/invoke-gati@invoke-gati/0.3.0
with:
url: ${{ inputs.aws-lambda-url }}

- name: Get github token from gati (v2)
id: get-gh-token-v2
if: steps.resolve-gati-version.outputs.version == 'v2'
uses: smartcontractkit/.github/actions/invoke-gati-v2@invoke-gati-v2/0.1.0
with:
profile: ${{ steps.resolve-gati-version.outputs.profile }}

- name: Configure github token
if: inputs.set-git-config == 'true'
shell: bash
env:
ACCESS_TOKEN:
${{ steps.get-gh-token.outputs.access-token ||
steps.get-gh-token-v2.outputs.access-token }}
run: |
git config --global \
url."https://x-access-token:${{ steps.get-gh-token.outputs.access-token }}@github.com/".insteadOf \
url."https://x-access-token:${ACCESS_TOKEN}@github.com/".insteadOf \
"https://github.com/"
80 changes: 80 additions & 0 deletions actions/setup-github-token/scripts/resolve-gati-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash

# Selects between GATI v1 (assume an AWS role, then call the v1 lambda) and GATI
# v2 (GitHub OIDC, no AWS credentials), then emits the version and, for v2, the
# profile to request.

set -euo pipefail

PROFILE="${PROFILE:-}"
GATI_VERSION="${GATI_VERSION:-}"
AWS_ROLE_ARN="${AWS_ROLE_ARN:-}"
AWS_LAMBDA_URL="${AWS_LAMBDA_URL:-}"

VERSION=""
REASON=""

resolve_version() {
if [[ -n "${GATI_VERSION}" ]]; then
if [[ "${GATI_VERSION}" != "v1" && "${GATI_VERSION}" != "v2" ]]; then
echo "::error::gati-version must be 'v1' or 'v2', got '${GATI_VERSION}'."
exit 1
fi
VERSION="${GATI_VERSION}"
REASON="gati-version input"
return
fi

if [[ -n "${PROFILE}" ]]; then
VERSION="v2"
REASON="profile input"
return
fi

# A repository pinned to both lists stays on v1, so that pinning a broken
# repository back to v1 does not require also editing the v2 list.
if grep -qxF "${GITHUB_REPOSITORY}" <<<"${FORCE_V1_REPOSITORIES}"; then
VERSION="v1"
REASON="repository is pinned to v1"
return
fi

if grep -qxF "${GITHUB_REPOSITORY}" <<<"${FORCE_V2_REPOSITORIES}"; then
VERSION="v2"
REASON="repository is pinned to v2"
return
fi

local bucket
bucket=$(($(printf '%s' "${GITHUB_REPOSITORY}" | cksum | cut -d ' ' -f 1) % 100))
REASON="bucket ${bucket}, rollout at ${ROLLOUT_PERCENTAGE}%"
if ((bucket < ROLLOUT_PERCENTAGE)); then
VERSION="v2"
else
VERSION="v1"
fi
}

resolve_version

if [[ "${VERSION}" == "v1" && -n "${PROFILE}" ]]; then
echo "::error::profile is only supported by GATI v2, but v1 was selected (${REASON}). Remove the profile input, or set gati-version to 'v2'."
exit 1
fi

if [[ -z "${PROFILE}" ]]; then
if [[ -z "${AWS_ROLE_ARN}" || -z "${AWS_LAMBDA_URL}" ]]; then
echo "::error::aws-role-arn and aws-lambda-url are required when profile is not set."
exit 1
fi
# Only read by the v2 flow, which migrates a v1 role ARN as a v1 profile.
PROFILE="v1/${AWS_ROLE_ARN}"
fi

echo "Using GATI ${VERSION} (${REASON})."
echo "version=${VERSION}" >>"$GITHUB_OUTPUT"

if [[ "${VERSION}" == "v2" ]]; then
# Written without tee: the profile can embed an IAM role ARN.
echo "profile=${PROFILE}" >>"$GITHUB_OUTPUT"
fi
Loading