Skip to content
Open
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
54 changes: 29 additions & 25 deletions tutorials/protect-github-ssh.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
updated_at: October 05, 2026
updated_at: October 13, 2026
title: Protect GitHub with SSH Certificates
html_title: Protect GitHub Repositories with Hardware-Bound SSH Certificates
description: Authenticate to GitHub with short-lived SSH certificates backed by your device's TPM or Secure Enclave, issued and managed by the Smallstep Agent.
Expand All @@ -22,7 +22,7 @@ sequenceDiagram
participant Git as git client
participant GH as GitHub
Agent->>CA: Request SSH user certificate (hardware-bound key)
CA->>Agent: Certificate with GitHub username principal
CA->>Agent: Certificate with GitHub username extension
Git->>GH: git push (certificate via SSH agent socket)
GH->>GH: Verify certificate against trusted CA
GH->>Git: Access granted
Expand All @@ -46,7 +46,7 @@ You will need:

## 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.
Smallstep issues each user an SSH certificate that carries the user's GitHub username in the `login@github.com` extension—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.

Expand Down Expand Up @@ -117,25 +117,21 @@ echo "Authorization: Bearer [your API token]" > api_headers
set -o history
```

Next, choose the SSH authority that should issue your SSH certificates.
In the Smallstep dashboard, go to **Certificate Manager → Authorities**, open the authority,
and copy its **Authority ID** from the **Authority Settings** panel.

Then create the credential with the [Create Credential](https://gateway.smallstep.com/v2025-01-01/operations/PostCredentials) endpoint:
Then create the credential with the [Create Endpoint Configuration](https://gateway.smallstep.com/v2025-01-01/operations/PostEndpointConfigurations) endpoint:

```bash
curl -sH @api_headers --request POST \
--url https://gateway.smallstep.com/api/credentials \
--url https://gateway.smallstep.com/api/endpoint-configurations \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'x-smallstep-api-version: 2025-01-01' \
--data @- <<'EOF' | jq
{
"slug": "github",
"certificate": {
"kind": "account",
"name": "GitHub",
"certificateInfo": {
"type": "SSH_USER",
"authorityID": "[your authority ID]",
"fields": {
"details": {
"keyId": {
"deviceMetadata": "smallstep:identity",
"static": "github"
Expand All @@ -145,10 +141,9 @@ curl -sH @api_headers --request POST \
}
}
},
"key": {
"keyInfo": {
"protection": "HARDWARE_ATTESTED"
},
"managementMode": "agent",
"policy": {
"assurance": ["high"],
"operatingSystem": ["macOS", "Windows", "Linux"]
Expand All @@ -159,17 +154,26 @@ EOF

Here's what the fields mean:

- **`certificate.type: "SSH_USER"`** issues SSH user certificates instead of X.509 certificates.
- **`fields.keyId`** sets the certificate's key ID, used for identification and audit logging.
- **`fields.principals`** populates the certificate's principals from device metadata.
`SSH.Principals` includes the user's SSH principals synced from your identity provider—including the `githubUsername` attribute you configured above—and `smallstep:identity` adds the email address of the user assigned to the device.
- **`key.protection: "HARDWARE_ATTESTED"`** generates the private key in the device's
- **`kind: "account"`** marks this as a credential that a person uses to access a protected resource—here, GitHub.
- **`certificateInfo.type: "SSH_USER"`** issues SSH user certificates instead of X.509 certificates.
- **`details.keyId`** sets the certificate's key ID, used for identification and audit logging.
- **`details.principals`** populates the certificate's principals from device metadata.
`SSH.Principals` adds the user's POSIX usernames and SSH group principals synced from your identity provider,
and `smallstep:identity` adds the email address of the user assigned to the device.
- **`keyInfo.protection: "HARDWARE_ATTESTED"`** generates the private key in the device's
secure hardware (TPM or Secure Enclave), where it can never be exported.
- **`policy`** selects the devices that receive this credential;
here, high-assurance macOS, Windows, and Linux devices.

The request doesn't name an authority.
Smallstep issues these certificates from your team's SSH authority—the same authority [Smallstep SSH](../ssh/README.mdx) uses—so this credential works alongside Smallstep SSH.

The GitHub username isn't one of the principals.
Smallstep adds it to each certificate automatically, in the `login@github.com` extension,
from the `githubUsername` attribute you configured above.

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.
and renews hardware-bound SSH certificates that carry each user's GitHub username.

# Step 2: Configure the enforcement point

Expand Down Expand Up @@ -203,7 +207,7 @@ ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK...
5. 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
and any SSH certificate signed by your Smallstep CA that carries a valid GitHub username
will be accepted for authentication to your organization's repositories.

For more information,
Expand Down Expand Up @@ -347,12 +351,13 @@ git config --global url."org-[your org ID]@github.com:[your org]/".insteadOf "gi
You can configure `git` to sign commits with the same hardware-protected SSH credential,
providing cryptographic proof of commit authorship.

1. Tell Git to use SSH for signing, and set your signing key:
1. Tell Git to use SSH for signing, and set your signing key.
The `key::` prefix tells Git that the value is a public key, not a file path:

```bash
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]"
git config --global user.signingkey "key::[your SSH public key]"
```

2. Configure allowed signers, so `git` can verify signatures locally:
Expand Down Expand Up @@ -393,4 +398,3 @@ For more, see [GitHub's documentation on commit signature verification](https://
If nothing is listed, confirm the device matches the credential's policy
(assurance level, operating system, and tags),
and see the [agent troubleshooting guide](../platform/troubleshooting-agent.mdx).
- In the Smallstep dashboard, confirm the user's device shows an issued SSH certificate.
Loading