Universal Passkeys for Developers
All devices. Any protocol. Zero shared secrets.
GitHub Action for authorizing a Beyond Identity user to sign git commits and verifying those signatures. The action enforces that all commits are signed by authorized users in your organization's Beyond Identity directory.
Additionally, an allowlist can be configured for
- select committer email addresses to bypass signature verification
- select third party keys used for signature verification
See section on Allowlist.
The recommended usage is to add a workflow to your repository that runs on all
pull requests targeting the default branch (e.g. main
).
name: Authorize Commit Signing
on:
pull_request:
# Optionally limit to only pull requests targeting the default branch.
# Remove this to run on all pull requests.
branches: [main]
jobs:
auth-commit-sig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# Critical: check out the head commit on the branch. By default,
# actions/checkout will check out a merge commit built for the pull
# request and signed by Github itself. Using the pull-request HEAD
# allows the action to check the latest commit on the pull request,
# which must be signed by an authorized user before it can be merged.
ref: ${{ github.event.pull_request.head.sha }}
- name: Authorize with Beyond Identity
id: signature-verification
uses: gobeyondidentity/auth-commit-sig@v1
with:
# API Token to call Beyond Identity cloud.
# The token should be stored as an actions secret or fetched from a secrets manager.
# https://docs.github.com/en/actions/security-guides/encrypted-secrets.
api_token: ${{ secrets.BYNDID_KEY_MGMT_API_TOKEN }}
# Repository which the signature verification action is performed on.
# This is also used to match against repositories listed on the allowlist.
repository: "gobeyondidentity/auth-commit-sig"
The BYNDID_KEY_MGMT_API_TOKEN
should be set as an
actions secret
in the repository or fetched from a secrets manager.
An allowlist can be configured for the github action to pass for users meeting certain criteria.
Currently two types of allowlists are supported, merge_commit_allowlist
and non_merge_commit_allowlist
, which are
based on merge
and non-merge
commits, respectively. The merge
commits are the commits which have two or more parents.
For example, when merging a feature branch into the main (default) branch, git creates a merge commit on the main
branch, which is the result of the latest commits in both the main and feature branches. For more information, see
git merge.
Within each allowlist, the configuration contains the following two sublists:
- Email addresses and the repositories the email address can be used to bypass signature verification.
- Third party keys and the repositories that the third party key can be used for signature verification.
If the email address of the committer is on the allowlist, the action will bypass signature verification. Otherwise, it will continue with the regular signature verification process.
If there are third party keys on the allowlist, the action will attempt to verify the signature using all those keys. If verification succeeds, the action will pass. If verification fails, the action continues with the regular signature verification process.
The repositories
parameter attached to the email addresses and third party keys is optional. If not provided,
the email address or third party key will be used on ALL repositories the action is run on.
Some additional steps added to the action workflow.
-
In a repository, create an allowlist YAML file. The repository can be the same repository where the action is registered, or it can be a separate repository. We recommend it be stored in seperate repo that has strict access controls.
An example allowlist file called
allowlist_example.yaml
can be found within this repository and below.
# Allowlist that is used when the commit is a merge commit.
# A merge commit is defined as a commit containing two or more parents.
# e.g. `git merge`
merge_commit_allowlist:
email_addresses:
# `repositories` not defined, can bypass signature verification for _any_ repository.
- email_address: [email protected]
# `repositories` defined, can bypass signature verification _only_ for repository_A and repository_B.
- email_address: [email protected]
repositories:
- repository_A
- repository_B
third_party_keys:
# `repositories` not defined, can be used for signature verification for _any_ repository.
- key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
m3IEYr73lBMIKoZIzj01AQcCAwT1gCXHMjKP6EWgtzJNxpkfFWhpK4dsV1dfbzRz
...truncated
-----END PGP PUBLIC KEY BLOCK-----
# `repositories` defined, can be used for signature verification _only_ for repository_C and repository_D.
- key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
t3IEYr738sG78d7SD01AQcCAwT1gCXHMjKP6EWgtzJNxpkfF01AQcCAwT11dfbzRx
...truncated
-----END PGP PUBLIC KEY BLOCK-----
repositories:
- repository_C
- repository_D
# Allowlist that is used when the commit is not a merge commit.
# Please see https://www.atlassian.com/git/tutorials/using-branches/git-merge for more info.
non_merge_commit_allowlist:
email_addresses:
# `repositories` not defined, can bypass signature verification for _any_ repository.
- email_address: [email protected]
# `repositories` defined, can bypass signature verification _only_ for repository_A and repository_B.
- email_address: [email protected]
repositories:
- repository_A
- repository_B
- repository_X
- repository_Y
# `repositories` defined, can bypass signature verification _only_ for repository_A and repository_B.
- email_address: [email protected]
repositories:
- repository_A
- repository_B
third_party_keys:
# `repositories` not defined, can be used for signature verification for _any_ repository.
- key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
m3IEYr73lBMIKoZIzj01AQcCAwT1gCXHMjKP6EWgtzJNxpkfFWhpK4dsV1dfbzRz
...truncated
-----END PGP PUBLIC KEY BLOCK-----
# `repositories` defined, can be used for signature verification _only_ for repository_C and repository_D.
- key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
t3IEYr738sG78d7SD01AQcCAwT1gCXHMjKP6EWgtzJNxpkfF01AQcCAwT11dfbzRx
...truncated
-----END PGP PUBLIC KEY BLOCK-----
repositories:
- repository_C
- repository_D
In the example above, the email [email protected] will be allowed to commit to any repository and bypass signature verification, regardless of the type of the commit as the email is listed in both allowlists. However, the email [email protected] can only bypass signature verification in repositories repository_A and repository_B when making merge or non-merge commits. [email protected] will also be able to make unverified non-merge commits to repository_X and repository_Y. The email user3@company is allowed to bypass signature verification when making non-merge commits to repository_A and repository_B only.
-
Add an additional
Checkout Allowlist repository
step the workflow configuration. This step checks out the repo where the allowlist YAML file is stored and stores the files from that repo locally in the action workspace underpath
. If your allowlist is in the same repository where the action is registered, this step can be skipped.If the allowlist repository is private, you should set up an SSH Key for cloning. See Deploy Keys.
This SSH Key should be set as an actions secret called
ALLOWLIST_REPO_SSH_PRIV_KEY
in the repository where the action is run or fetched from a secrets manager. -
Add additional variables to the
Authorize with Beyond Identity
step.
allowlist_config_file_path
: File path to the allowlist configuration file from actions workspace.- If it is from a different repository, this variable should be prefixed with
./
and thepath
from Step 2.- Ex.
./{path}/{path_to_allowlist}
- Ex.
- If it is the same repository as the action, this variable should be prefiexed with
./
and the path to the allowlist configuration file.- Ex.
./{path_to_allowlist}
- Ex.
- If it is from a different repository, this variable should be prefixed with
name: Authorize Commit Signing
on:
pull_request:
branches: [main]
jobs:
auth-commit-sig:
runs-on: ubuntu-latest
steps:
- name: Checkout commit
uses: actions/checkout@v3
with:
# Critical: check out the head commit on the branch. By default,
# actions/checkout will check out a merge commit built for the pull
# request and signed by Github itself. Using the pull-request HEAD
# allows the action to check the latest commit on the pull request,
# which must be signed by an authorized user before it can be merged.
ref: ${{ github.event.pull_request.head.sha }}
# If allowlist is configured, checkout allowlist repository. Otherwise, do
# not include this step.
# The allowlist _can_ be in the same repository as the step above, or can be
# configured in a separate repository. See README for details on how to set
# up the allowlist configuration.
- name: Checkout Allowlist repository
uses: actions/checkout@v3
with:
# Path to allowlist repository in the format (owner/repo_name).
# If it is the same repository as above, can skip this step.
repository: "gobeyondidentity/commit-signing-allowlist"
# SSH Key for private repositories. If repo is public, can omit.
# See https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys.
# The key should be stored as an actions secret or fetched from a secrets manager.
# See https://docs.github.com/en/actions/security-guides/encrypted-secrets.
ssh-key: ${{ secrets.ALLOWLIST_REPO_SSH_PRIV_KEY }}
# Relative path under $GITHUB_WORKSPACE to place the repository.
# NOTE: the repo_name will not be cloned as a folder. Only the files will
# be checked out at this path.
path: "allowlist-dir"
- name: Authorize with Beyond Identity
id: signature-verification
uses: gobeyondidentity/auth-commit-sig@v1
env:
# If API_BASE_URL is omitted, defaults to our production API server
# at https://api.byndid.com/key-mgmt.
API_BASE_URL: "https://api.byndid.com/key-mgmt"
with:
# API Token to call Beyond Identity cloud.
# The token should be stored as an actions secret or fetched from a secrets manager.
# https://docs.github.com/en/actions/security-guides/encrypted-secrets.
api_token: ${{ secrets.BYNDID_KEY_MGMT_API_TOKEN }}
# Repository which the signature verification action is performed on.
# This is also used to match against repositories listed on the allowlist.
repository: "gobeyondidentity/auth-commit-sig"
# If allowlist is configured, set this to the file path to the allowlist
# configuration file starting from $GITHUB_WORKSPACE.
# If it is from a different repository, this variable should be prefixed
# with `./` and the `path` from Step 2.
# Ex. `./{path}/{path_to_allowlist}`
# If it is the same repository as the action, it is just the path to the
# allowlist configuration file.
# Ex. `./{path_to_allowlist}`
# See README for details on how to format this file.
allowlist_config_file_path: "./allowlist-dir/allowlist.yaml"
When the action is complete, the job prints an output that is a JSON blob containing information about the results of the action. It contains information about the commit, if signature verification was successful, if an allowlist email address or third party key was used, errors, and other details etc.
{
"version": "1.0.0",
"repository": "gobeyondidentity/auth-commit-sig",
"commit": {
"commit_hash": "7f1927513d02f546ed50579de7192c181836ea23",
"tree_hash": "d742502776eb874cba1605b768ad438810ecf911",
"parent_hashes": ["12cf190ced71c3d23107d90641b361b9edcc41f6"],
"author": {
"name": "John Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"committer": {
"name": "John Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"signed": true,
"signature_key_id": "87A2691085B4544E"
},
"result": "PASS",
"desc": "Signature verified by a Beyond Identity managed key.",
"verification_details": {
"verified_by": "BI_MANAGED_KEY",
"bi_managed_key": {
"key_id": "87A2691085B4544E",
"email_address": "[email protected]"
}
},
"errors": []
}
{
"version": "1.0.0",
"repository": "gobeyondidentity/auth-commit-sig",
"commit": {
"commit_hash": "7f1927513d02f546ed50579de7192c181836ea23",
"tree_hash": "d742502776eb874cba1605b768ad438810ecf911",
"parent_hashes": ["12cf190ced71c3d23107d90641b361b9edcc41f6"],
"author": {
"name": "Jane Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"committer": {
"name": "Jane Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"signed": true,
"signature_key_id": "7723AD85B1221B3B"
},
"result": "PASS",
"desc": "Signature verified by a third party key from the allowlist.",
"verification_details": {
"verified_by": "THIRD_PARTY_KEY",
"third_party_key": {
"key_id": "7723AD85B1221B3B",
"fingerprint": "VOcYYhfmV98gAMVoh6JpEIW0VE4=",
"user_id": "Jane Doe <[email protected]>"
}
},
"errors": []
}
{
"version": "1.0.0",
"repository": "gobeyondidentity/auth-commit-sig",
"commit": {
"commit_hash": "7f1927513d02f546ed50579de7192c181836ea23",
"tree_hash": "d742502776eb874cba1605b768ad438810ecf911",
"parent_hashes": ["12cf190ced71c3d23107d90641b361b9edcc41f6"],
"author": {
"name": "Jane Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"committer": {
"name": "Jane Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"signed": true,
"signature_key_id": "7723AD85B1221B3B"
},
"result": "PASS",
"desc": "Bypassed signature verification with an email address from the allowlist.",
"verification_details": {
"verified_by": "EMAIL_ADDRESS",
"email_address": "[email protected]"
},
"errors": []
}
{
"version": "1.0.0",
"repository": "gobeyondidentity/auth-commit-sig",
"commit": {
"commit_hash": "7f1927513d02f546ed50579de7192c181836ea23",
"tree_hash": "d742502776eb874cba1605b768ad438810ecf911",
"parent_hashes": ["12cf190ced71c3d23107d90641b361b9edcc41f6"],
"author": {
"name": "Jackie Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"committer": {
"name": "Jackie Doe",
"email_address": "[email protected]",
"timestamp": "2022-09-05T13:58:12-04:00"
},
"signed": false
},
"result": "FAIL",
"desc": "Commit is not signed. See errors for details.",
"errors": [
{
"desc": "commit is not signed"
}
]
}