Protect GitHub with SSH Certificates
This guide shows how to protect access to your GitHub organization's repositories with Smallstep, using SSH certificates issued to hardware-bound keys.
By configuring GitHub to trust an SSH Certificate Authority (CA),
you can use the Smallstep Agent to manage SSH credentials
that are protected by your device's native security hardware—a TPM on Windows and Linux systems, or the Secure Enclave on macOS devices.
Private keys never leave the secure hardware,
and the agent integrates with your git client through a standard SSH agent socket,
so developers authenticate without managing SSH keys at all.
SSH certificate authorities are a GitHub Enterprise feature. You'll need a GitHub Enterprise Cloud organization (or GitHub Enterprise Server) to complete this guide.
Before you begin
You will need:
- A GitHub Enterprise Cloud organization where you are an owner.
- Devices enrolled in Smallstep with the Smallstep Agent installed, each with a user assigned.
- Users synced from your identity provider, with a GitHub username attribute (next section).
Sync users and their GitHub usernames
Smallstep issues each user an SSH certificate whose principals must include the user's GitHub username—that's how GitHub maps a certificate to a GitHub account.
To make the username available, configure your identity provider to sync users to Smallstep
with a custom githubUsername attribute.
Okta
First, sync Okta users to Smallstep via SCIM provisioning, if you haven't already.
Then add the GitHub username attribute:
- Add GitHub username to the Okta user profile:
- In Okta, go to Directory → Profile Editor
- Find and click on the User (default) profile
- Click Add Attribute and configure it:
- Data type: string
- Display name: GitHub Username
- Variable name:
githubUsername - Description: User's GitHub username for SSH certificate authentication
- Save the attribute
- Add GitHub username to the Smallstep application profile:
- Open the Smallstep application in Okta
- Go to the Provisioning tab → To App
- Scroll to Smallstep Attribute Mappings and click Go to Profile Editor
- Click Add Attribute and fill the form with the following details:
- Data type: string
- Display name: GitHub Username
- Variable name:
githubUsername - External name:
githubUsername - External namespace:
urn:scim:smallstep:ssh:schema - Attribute type:
personal
- Save the attribute
- Map the attribute:
- Return to the Smallstep application's Provisioning tab → To App
- Scroll to Smallstep Attribute Mappings and click Show Unmapped Attributes
- Find the GitHub Username attribute and edit the mapping:
- Attribute value type: Map from Okta Profile
- Attribute value:
githubUsername - Apply on: Create and Update
- Save the mapping
- Populate GitHub usernames:
- Go to Directory → People, open each user's profile, and enter their GitHub username in the GitHub Username field
- Assign the user to the Smallstep application if not already assigned
- Verify synchronization:
- In your Smallstep dashboard, go to Users and verify that users are syncing from Okta with their GitHub usernames populated. It may take a few minutes for changes to sync.
Google Workspace and Microsoft Entra ID
User sync is available for Google Workspace and Entra ID. For help mapping a GitHub username attribute from these providers, contact us.
Step 1: Configure credential issuance
Create a credential that tells the Smallstep Agent to issue a hardware-bound SSH user certificate to each of your devices.
You will need an API token. See Smallstep API for details.
Store your API token in a headers file for curl:
set +o history
echo "Authorization: Bearer [your API token]" > api_headers
set -o history
Find the authority that should issue your SSH certificates with the List Authorities endpoint:
curl -sH @api_headers --request GET \
--url https://gateway.smallstep.com/api/authorities \
--header 'Accept: application/json' \
--header 'x-smallstep-api-version: 2025-01-01' | jq '.[] | {id, name, domain}'
Then create the credential with the Create Credential endpoint:
curl -sH @api_headers --request POST \
--url https://gateway.smallstep.com/api/credentials \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'x-smallstep-api-version: 2025-01-01' \
--data @- <<'EOF' | jq
{
"slug": "github",
"certificate": {
"type": "SSH_USER",
"authorityID": "[your authority ID]",
"fields": {
"keyId": {
"deviceMetadata": "smallstep:identity",
"static": "github"
},
"principals": {
"deviceMetadata": ["SSH.Principals", "smallstep:identity"]
}
}
},
"key": {
"protection": "HARDWARE_ATTESTED"
},
"managementMode": "agent",
"policy": {
"assurance": ["high"],
"operatingSystem": ["macOS", "Windows", "Linux"]
}
}
EOF
Here's what the fields mean:
certificate.type: "SSH_USER"issues SSH user certificates instead of X.509 certificates.fields.keyIdsets the certificate's key ID, used for identification and audit logging.fields.principalspopulates the certificate's principals from device metadata.SSH.Principalsincludes the user's SSH principals synced from your identity provider—including thegithubUsernameattribute you configured above—andsmallstep:identityadds the email address of the user assigned to the device.key.protection: "HARDWARE_ATTESTED"generates the private key in the device's secure hardware (TPM or Secure Enclave), where it can never be exported.policyselects the devices that receive this credential; here, high-assurance macOS, Windows, and Linux devices.
Once this credential is created, the Smallstep Agent on matching devices automatically requests and renews hardware-bound SSH certificates with the appropriate GitHub username as a principal.
Step 2: Configure the enforcement point
The enforcement point is GitHub itself: your GitHub Enterprise organization is configured to trust SSH certificates signed by your Smallstep SSH CA.
Retrieve your SSH CA public key
On a machine with the step CLI installed,
bootstrap with your team and print your SSH user CA public key:
step ssh config --team [your team slug]
step ssh config --roots
The output is your SSH CA's public key. Copy the entire key; it will look similar to:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK...
Add the CA to your GitHub organization
- On GitHub, navigate to your organization's Settings
- Under Authentication security, find SSH certificate authorities
- Click New CA
- Paste the public key from
step ssh config --rootsinto the Key field - Click Add CA
The CA now appears in your list of SSH certificate authorities, and any SSH certificate signed by your Smallstep CA with a valid GitHub username principal will be accepted for authentication to your organization's repositories.
For more information, see GitHub's documentation on SSH certificate authorities.
Optional: Require SSH certificates
GitHub Enterprise lets you require SSH certificates for all Git access to your organization's repositories, which blocks personal SSH keys entirely.
Only enable this setting once you have fully migrated all of your clients, remotes, and CI systems to SSH certificates.
Certificate authentication uses a special SSH login of the form org-[your org ID]@github.com—the usual git@github.com login is not compatible.
See Migrate your remotes and submodules below.
Step 3: Configure clients
Client configuration for SSH is handled by the Smallstep Agent—there is no MDM-managed path for SSH credentials.
The agent exposes an SSH agent socket that your git and ssh clients use for authentication,
so the only client-side setup is pointing SSH_AUTH_SOCK at it.
The agent's SSH socket location per platform:
| Platform | SSH agent socket |
|---|---|
| macOS | ~/Library/Application Support/Smallstep/step-agent-ssh.sock |
| Linux | /run/step-agent/step-agent-ssh.sock |
| Windows | \\.\pipe\step-agent-ssh |
The steps below make the Smallstep Agent's keys your only SSH agent keys.
For gradual adoption, you can instead scope the agent to specific projects:
tools like direnv can set SSH_AUTH_SOCK per directory,
and git supports conditional configuration with
[includeIf "env:SSH_AUTH_SOCK=..."] directives.
macOS
Add the following to your shell configuration file (~/.zshrc for zsh, or ~/.bash_profile for bash):
export SSH_AUTH_SOCK="$HOME/Library/Application Support/Smallstep/step-agent-ssh.sock"
Reload your shell configuration with source ~/.zshrc (or source ~/.bash_profile).
Linux
When the agent runs under systemd, add the following to your ~/.bashrc or ~/.zshrc:
export SSH_AUTH_SOCK="/run/step-agent/step-agent-ssh.sock"
Reload your shell configuration with source ~/.bashrc (or source ~/.zshrc).
Windows
Set the environment variable for your user:
- Press
Win + Xand select System - Click Advanced system settings → Environment Variables
- Under User variables, click New
- Variable name:
SSH_AUTH_SOCK - Variable value:
\\.\pipe\step-agent-ssh
- Variable name:
- Click OK, and restart any open terminal windows
Verify SSH authentication
Confirm the agent's certificate is available:
ssh-add -l
You should see your hardware-bound SSH certificate listed.
Next, find your GitHub organization's database ID—certificate authentication uses an org-[ID]@github.com login instead of git@github.com:
gh api /orgs/[your org name] --jq '.id'
Then test authentication:
ssh -T org-[your org ID]@github.com
You should receive a success message confirming your authentication.
Migrate your remotes and submodules
The usual git@github.com SSH login is not compatible with SSH certificate authentication.
Update each clone's remotes (and any submodule URLs) to use the org-[ID]@github.com login:
git remote set-url origin org-[your org ID]@github.com:[your org]/[repo].git
To rewrite all GitHub remotes for your organization without touching each repository, you can add a global rewrite rule instead:
git config --global url."org-[your org ID]@github.com:[your org]/".insteadOf "git@github.com:[your org]/"
Optional: Sign commits with your hardware-bound key
You can configure git to sign commits with the same hardware-protected SSH credential,
providing cryptographic proof of commit authorship.
-
Tell Git to use SSH for signing, and set your signing key:
git config --global gpg.format ssh step ssh list --raw | head -1 | step crypto key format --ssh git config --global user.signingkey "[your SSH public key]"
-
Configure allowed signers, so
gitcan verify signatures locally:echo '[certificate principal] namespaces="git" [key type] [base64 public key]' > ~/.ssh/allowed_signers git config --global gpg.ssh.allowedSignersFile "~/.ssh/allowed_signers"
-
Sign commits with
git commit -S, or enable signing for all commits:git config --global commit.gpgsign true
-
Verify with a test commit:
git commit --allow-empty -S -m "Test signed commit" git log --show-signature -1
-
To get the “Verified” badge on GitHub, add your SSH public key to your GitHub profile as a signing key:
gh ssh-key add [key file] --type signing --title "Smallstep"
For more, see GitHub's documentation on commit signature verification.
Verify and troubleshoot
- Clone, push, and pull from a repository in your organization using an
org-[ID]@github.comremote. - Run
ssh -vT org-[your org ID]@github.comand look for the certificate being offered during authentication. - Run
ssh-add -lto confirm the agent socket is reachable and a certificate is loaded. If nothing is listed, confirm the device matches the credential's policy (assurance level, operating system, and tags), and see the agent troubleshooting guide. - In the Smallstep dashboard, confirm the user's device shows an issued SSH certificate.
Last updated on July 8, 2026
Introducing
Device Identity
Ensure that only company-owned devices can access your enterprise's most sensitive resources.