README ¶
GitHub Token Minter
Service that acts as a GitHub App that can generate tokens with elevated privileges to allow a GitHub workflow to act upon other private repositories in an organization or on organization level resources such GitHub teams.
Architecture
Setup
Create a GitHub App and Install it
Follow the steps to create a new GitHub App. You'll want to capture the application id
and private key
that are generated during the creation process.
Once created it needs to be installed in your organziation. When installing you will grant the app the level of access you deem that it needs. At this stage you need to capture your installation id
.
Deploy the service
You can use the provided Terraform module to setup the basic infrastructure needed for this service. You can refer to the provided module to see how to build your own Terraform from scratch.
module "github_token_minter" {
source = "git::https://github.com/abcxyz/github-token-minter.git//terraform?ref=main" # Should pin this to a SHA
project_id = "YOUR PROJECT ID"
domain = "YOUR DOMAIN NAME"
dataset_id = "NAME OF YOUR AUDIT DATASET"
service_iam = {
admins = []
developers = [
"serviceAccount:<YOUR CI SERVICE ACCOUNT>@<YOUR PROJECT ID>.iam.gserviceaccount.com",
]
invokers = [
"serviceAccount:<YOUR WIF SERVICE ACCOUNT>@<YOUR PROJECT ID>.iam.gserviceaccount.com",
]
}
dataset_iam = {
owners = ["group:<some group>@<your org>.com"]
editors = [
"serviceAccount:<YOUR WIF SERVICE ACCOUNT>@<YOUR PROJECT ID>.iam.gserviceaccount.com",
]
viewers = ["group:<some other group>@<your org>.com"]
}
}
Service Configuration
The service relies on a number of environment variables. Most of them have defaults that map to the public GitHub environment and are exposed only to allow customers who use private GitHub Enterprise environments to configure them.
The three that are not optional are the GITHUB_APP_ID
and the GITHUB_INSTALL_ID
and the GITHUB_PRIVATE_KEY
.
All three of those values come from creating and installing your GitHub app within your organization. These values are sensitive and must be treated as secrets. The default Terraform module stores them in Secret Manager and they are mapped into the Cloud Run service from there.
ENV VAR name | Description |
---|---|
GITHUB_APP_ID | GitHub App Id created following the app creation instructions at https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app |
GITHUB_PRIVATE_KEY | Private key generated as part of the GitHub App creation |
GITHUB_INSTALL_ID | GitHub Installation Id generated by installing the app via https://docs.github.com/en/developers/apps/managing-github-apps/installing-github-apps |
GITHUB_JKWS_URL | JWKS endpoint to use to verify JWTs from GitHub. Defaults to https://token.actions.githubusercontent.com/.well-known/jwks |
GITHUB_ACCESS_TOKEN_URL_PATTERN | Access token url to be used for calls to https://docs.github.com/en/rest/apps/installations. *Must contain a single %s representing the installation id . Defaults to https://api.github.com/app/installations/%s/access_tokens |
PORT | Port to run the service on. Defaults to 8080 |
CONFIGS_DIR | Location of repository configuration files. Defaults to configs |
Configuring Repository Access
Each repository that needs to mint a token must be configured with an allowed set of permissions. Any request from a repository that is not configured will default to denying all access.
Each configuration file can have 1 or more rules that it can match against and it will evaluate them in order, top down, until it finds a matching condition.
The repository configuration file looks like below and is made up of three primary sections:
# abcxyz/github-token-minter.yaml
-
if: 'assertion.workflow_ref.startsWith("abcxyz/github-token-minter/.github/workflows/ci.yml")'
repositories:
- 'github-token-minter'
permissions:
issues: 'read'
The if
clause uses the Google Common Expression Language to match an inbound OIDC token against a series of rules. Any attributes from the assertion object are available to match against and you can make this expression as simple or complicated as required.
The object mirrors what is available in an OIDC token for a GitHub Workflow, below is an example token.
{
"jti": "example-id",
"sub": "repo:octo-org/octo-repo:environment:prod",
"environment": "prod",
"aud": "https://github.com/octo-org",
"ref": "refs/heads/main",
"sha": "example-sha",
"repository": "octo-org/octo-repo",
"repository_owner": "octo-org",
"actor_id": "12",
"repository_visibility": "private",
"repository_id": "74",
"repository_owner_id": "65",
"run_id": "example-run-id",
"run_number": "10",
"run_attempt": "2",
"actor": "octocat",
"workflow": "example-workflow",
"head_ref": "",
"base_ref": "",
"event_name": "workflow_dispatch",
"ref_type": "branch",
"job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com",
"nbf": 1632492967,
"exp": 1632493867,
"iat": 1632493567
}
The repositories
and permissions
attributes mirror the schema defined for requesting a GitHub app installation access token.
-
Repositories is an array of strings representing the name of the repository. Since the GitHub app only services a single organization per installation you do not need to specify the organization name as part of the repository name. E.g.
github-token-minter
instead ofabcxyz/github-token-minter
. The repository attribute supports a prefix wildcard match so you can select multiple repositories at once. A single*
would capture all repositories for the organization but something likegithub-*
would only capture repositories that started withgithub-
. -
Permissions is a map of permission name to access level. For example, to generate a token that can read issues you would specify
"issues": "read"
.
Deploying configuration to your service
The container built in this repository contains only the bare bones configuration required for the integration tests in our CI environment. All other repository configuration is stored outside of this repository and needs to be added into the container at deployment time.
We recommend that a GitHub workflow builds a new container from the released image produced by this repository that layers in your configuration files before it is deployed.
Given a repository setup like
<my repo root>
-- configs
-- repo1_config.yml
-- repo2_config.yml
Dockerfile
A Dockerfile paired with a short GitHub workflow is sufficient.
Examples:
ARG VERSION=v0.0.8-amd64
FROM us-docker.pkg.dev/abcxyz-artifacts/docker-images/github-token-minter-server:$VERSION
COPY configs /configs
env:
NEW_IMAGE_NAME: 'my-token-minter:v3.0.0'
jobs:
image-release:
runs-on: 'ubuntu-latest'
permissions:
contents: 'read'
id-token: 'write'
steps:
- name: 'Checkout'
uses: 'actions/checkout@v3'
- name: 'Build release image and push to Release registry'
uses: 'docker/build-push-action@v2'
with:
push: true
tags: '${{ env.NEW_IMAGE_NAME }}'
Using the service
The service provides a composite GitHub action that can be used to access the service from a GitHub workflow.
It is located in the .github/actions/mint-token directory of this repository.
The action requires a handful of parameters
wif_provider:
description: 'Workload identity federation provider.'
required: true
wif_service_account:
description: 'Workload identity federation service account.'
required: true
service_audience:
description: 'Cloud Run audience for the production github-token-minter service'
required: true
service_url:
description: 'URL for the production github-token-minter service'
required: true
requested_permissions:
description: 'Permission request information in the form {"repositories":["github-token-minter"],"permissions":{"issues":"read"}}'
required: true
Example usage of the action in a workflow
use-a-token-example:
runs-on: 'ubuntu-latest'
needs:
- 'deployment'
permissions:
contents: 'write'
packages: 'write'
id-token: 'write'
steps:
- name: 'Checkout'
uses: 'actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c' # ratchet:actions/checkout@v3
- id: 'mint-token'
uses: './.github/actions/mint-token'
with:
wif_provider: '${{ env.WIF_PROVIDER }}'
wif_service_account: '${{ env.WIF_SERVICE_ACCOUNT }}'
service_audience: '${{ env.SERVICE_AUDIENCE }}'
service_url: '${{ env.SERVICE_URL }}'
requested_permissions: '{"repositories":["github-token-minter"],"permissions":{"issues":"read"}}'
- name: 'list-issues'
run: |
curl --fail \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ steps.mint-token.outputs.token }}"\
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/abcxyz/github-token-minter/issues/events
Directories ¶
Path | Synopsis |
---|---|
cmd
|
|
github-token-minter
The github-token-minter command is an http server which receives requests containing OIDC tokens from GitHub and produces a GitHub application level token with elevated privlidges.
|
The github-token-minter command is an http server which receives requests containing OIDC tokens from GitHub and produces a GitHub application level token with elevated privlidges. |
pkg
|
|