Repo Collaborators Gpg Keys Rotated Remediation
Triage and Remediation
- Remediation
Remediation
Using Console
Below are step‑by‑step instructions to rotate (replace) a GPG key used for commit signing in GitHub, using the GitHub web console. The process is:
- Generate a new GPG key locally
- Add the new key to your GitHub account (console)
- Update your local Git configuration to use the new key
- Remove/disable the old key in GitHub (and locally)
1. Generate a new GPG key (locally)
On your workstation (not in GitHub):
gpg --full-generate-key
Recommended answers:
- Key type:
RSA and RSA(or modern ECC if you know you need it) - Key size:
4096(for RSA) - Expiration: set an appropriate expiry date (e.g., 1 year)
- Name: your real name
- Email: the same email you use on GitHub
- Comment: something like
GitHub signing key - Passphrase: strong passphrase
List your keys:
gpg --list-secret-keys --keyid-format=long
Identify the new key’s ID (e.g., ABCD1234EFGH5678).
Export the public key in ASCII:
gpg --armor --export ABCD1234EFGH5678
Copy the entire block including -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK-----.
2. Add the new GPG key in the GitHub web console
- In your browser, sign in to GitHub.
- Click your profile picture (top‑right) → Settings.
- In the left sidebar, select SSH and GPG keys.
- Under GPG keys, click New GPG key.
- Title: something meaningful, e.g.,
Work laptop GPG key (2026-07). - Key: paste the ASCII‑armored public key you copied.
- Click Add GPG key.
- Confirm with your password or 2FA if prompted.
Now GitHub trusts this new key for verifying your signed commits.
3. Configure Git locally to use the new key
Tell Git to sign commits by default and use the new key:
git config --global user.signingkey ABCD1234EFGH5678
git config --global commit.gpgsign true
(Replace ABCD1234EFGH5678 with your real key ID.)
If using GPG v2, you may also need:
git config --global gpg.program gpg
Test signing a commit in any repo:
echo "test" > testfile
git add testfile
git commit -S -m "Test commit with new GPG key"
git push
On GitHub, open the commit and confirm it shows as Verified and linked to your account.
4. Remove or disable the old GPG key in GitHub
Once you confirm new signed commits are verified:
- In GitHub, go to Settings → SSH and GPG keys.
- Under GPG keys, locate the old key.
- Click Delete next to the old key.
- Confirm deletion.
This prevents the old key from being used to produce commits GitHub will trust going forward.
5. (Optional) Revoke / remove the old key locally
To fully complete rotation:
-
Revoke old key (if you have or generate a revocation certificate):
gpg --output revoked-old-key.asc --gen-revoke OLDKEYIDgpg --import revoked-old-key.asc -
Or delete old secret key from your keyring (only if you are sure you don’t need it):
gpg --delete-secret-key OLDKEYIDgpg --delete-key OLDKEYID
If you share more about your current state (number of keys, whether they’re already in GitHub, etc.), I can narrow this to just the exact actions you need.
Using CLI
Below is a minimal, step‑by‑step rotation process for GitHub GPG keys using the GitHub CLI (gh).
Assumptions:
- You already have
ghinstalled and authenticated (gh auth login). - You’re rotating a personal GPG key used to sign commits/tags that GitHub verifies.
1. List Existing GPG Keys in GitHub
gh gpg-key list
Note the ID or email associated with the key you want to rotate.
2. Generate a New GPG Key Locally
gpg --full-generate-key
Recommended options:
- Key type:
RSA and RSA(or Ed25519 if supported via newer GPG) - Key size: at least
4096for RSA - Expiration: set a future expiration date (e.g. 1y)
- Use the same email as your GitHub account
List keys to confirm:
gpg --list-secret-keys --keyid-format=long
Copy the new key ID, e.g. ABCD1234EF567890.
3. Export the New Public Key
gpg --armor --export ABCD1234EF567890 > new-gpg-key.asc
4. Upload the New GPG Key to GitHub via gh
gh gpg-key add new-gpg-key.asc --title "Rotated GPG key $(date +%Y-%m-%d)"
Confirm it appears:
gh gpg-key list
5. Update Local Git to Use the New Key
Set the new key as the signing key:
git config --global user.signingkey ABCD1234EF567890
git config --global commit.gpgsign true
Test signing:
echo "test" > /tmp/test-gpg.txt
git commit -am "test gpg" # or commit in a repo
Push a signed commit and verify it shows as “Verified” in GitHub.
6. Remove the Old GPG Key from GitHub
After verifying the new key works, remove the old one by ID:
-
Get the GitHub key ID (not GPG key ID) from:
gh gpg-key list -
Delete the old key:
gh gpg-key delete <github_gpg_key_id>
(You’ll be prompted to confirm.)
7. (Optional) Revoke Old Key Locally
If the old key should no longer be trusted anywhere:
gpg --edit-key OLDKEYID
# In the prompt:
# revkey
# save
Then optionally delete it:
gpg --delete-secret-and-public-key OLDKEYID
This completes GPG key rotation for GitHub using the GitHub CLI.
Using Python
Below is a minimal, practical rotation approach: detect old GPG keys in GitHub, generate a new key, upload it, and remove the old key using Python and the GitHub API.
1. Prerequisites
-
GitHub Personal Access Token (PAT)
- Create one (Classic) with scopes:
read:gpg_keywrite:gpg_key
- Store it as an environment variable, e.g.
GITHUB_TOKEN.
- Create one (Classic) with scopes:
-
Python packages
pip install requests python-gnupg -
GnuPG installed on the machine (needed by python-gnupg):
- Linux:
sudo apt-get install gnupg(or equivalent) - macOS:
brew install gnupg - Windows: install Gpg4win
- Linux:
2. Define Rotation Policy
Example policy (adapt as needed):
- Rotate any GPG key:
- older than 90 days, or
- expiring within 7 days.
3. Script: List Existing GPG Keys and Identify “Stale” Ones
import os
import requests
from datetime import datetime, timezone
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
GITHUB_API = "https://api.github.com"
HEADERS = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json"
}
ROTATE_AGE_DAYS = 90
EXPIRING_SOON_DAYS = 7
def list_gpg_keys():
resp = requests.get(f"{GITHUB_API}/user/gpg_keys", headers=HEADERS)
resp.raise_for_status()
return resp.json()
def parse_github_time(ts):
# e.g. "2021-03-31T17:49:39Z"
return datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
def find_keys_to_rotate(keys):
now = datetime.now(timezone.utc)
to_rotate = []
for k in keys:
created_at = parse_github_time(k["created_at"])
age_days = (now - created_at).days
# GitHub API for gpg_keys doesn't expose expiration directly; you may rely on age only
if age_days >= ROTATE_AGE_DAYS:
to_rotate.append(k)
return to_rotate
if __name__ == "__main__":
keys = list_gpg_keys()
stale = find_keys_to_rotate(keys)
print("Existing keys:")
for k in keys:
print(f"- id={k['id']} key_id={k['key_id']} created_at={k['created_at']}")
print("\nKeys to rotate:")
for k in stale:
print(f"- id={k['id']} key_id={k['key_id']} created_at={k['created_at']}")
Adjust the criteria as you like.
4. Generate a New GPG Key Using Python
This example generates a new key for a given user/email, with an explicit expiration.
import gnupg
from pathlib import Path
GPG_HOME = Path.home() / ".gnupg-rotation" # separate home to avoid polluting default
GPG_HOME.mkdir(exist_ok=True)
gpg = gnupg.GPG(gnupghome=str(GPG_HOME))
def generate_gpg_key(name, email, passphrase=None, expire="1y"):
input_data = gpg.gen_key_input(
name_real=name,
name_email=email,
key_type="RSA",
key_length=4096,
expire_date=expire,
passphrase=passphrase or ""
)
key = gpg.gen_key(input_data)
if not key.fingerprint:
raise RuntimeError("Failed to generate GPG key")
return key.fingerprint
def export_public_key( fingerprint ):
ascii_armored_public_keys = gpg.export_keys(fingerprint)
return ascii_armored_public_keys
if __name__ == "__main__":
fingerprint = generate_gpg_key("GitHub User", "user@example.com", passphrase=None)
pub_key = export_public_key(fingerprint)
print(pub_key)
Note:
- Use the same name/email as your GitHub user email.
- Keep private key + passphrase secure (recommended: hardware token or OS keystore).
5. Upload the New Public GPG Key to GitHub
import os
import requests
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
GITHUB_API = "https://api.github.com"
HEADERS = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json"
}
def upload_gpg_key(armored_public_key):
payload = {"armored_public_key": armored_public_key}
resp = requests.post(f"{GITHUB_API}/user/gpg_keys", headers=HEADERS, json=payload)
resp.raise_for_status()
return resp.json()
if __name__ == "__main__":
# Suppose you've generated and exported the public key as `pub_key`
from pathlib import Path
pub_key = Path("new_gpg_pub.asc").read_text() # or pass directly from export_public_key()
res = upload_gpg_key(pub_key)
print("Uploaded new GPG key:", res)
You can integrate step 4 and 5 into a single workflow: generate, export, upload.
6. Remove Old (Rotated) GPG Keys from GitHub
Once the new key is uploaded and you’ve validated it works (by signing and pushing a test commit), remove the old keys:
import os
import requests
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
GITHUB_API = "https://api.github.com"
HEADERS = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json"
}
def delete_gpg_key(key_id):
resp = requests.delete(f"{GITHUB_API}/user/gpg_keys/{key_id}", headers=HEADERS)
if resp.status_code not in (204, 404):
resp.raise_for_status()
if __name__ == "__main__":
# Assume you already identified keys_to_rotate from step 3
from previous_script import list_gpg_keys, find_keys_to_rotate # or copy functions
keys = list_gpg_keys()
stale = find_keys_to_rotate(keys)
for k in stale:
print(f"Deleting key id={k['id']} key_id={k['key_id']}")
delete_gpg_key(k["id"])
7. Hook This Into Your IAM / Security Process
- Run the rotation script:
- Periodically via CI (GitHub Actions, Jenkins, etc.).
- Or as part of an internal identity lifecycle tool.
- Store:
- Mapping of GitHub username → GPG fingerprint.
- Key creation and (optional) expiration dates for compliance/audit.
8. Optional: Sign Commits with the New Key in Git
On developer machines (not via Python but for completeness):
git config --global user.signingkey <NEW_KEY_FINGERPRINT>
git config --global commit.gpgsign true
If you specify:
- how you store/manage private keys (local, HSM, KMS, Vault),
- and whether you want org-wide or per-user automation,
I can adjust this to a full rotation workflow (including per-user key policy and CI integration).
Using Terraform
Terraform cannot manage or rotate GitHub GPG signing keys.
The official integrations/github (hashicorp/github) provider does not expose any resource or argument for user GPG keys; they are per‑user credentials managed via the GitHub UI or API, not repository or org configuration. Because of that, there is no github_* Terraform resource that can invalidate, delete, or create GPG keys, so this finding cannot be remediated on github-applicationintegration-scm-repository via Terraform.
To remediate:
-
Each affected GitHub user must:
- Generate a new GPG key locally.
- Upload it under: GitHub → Settings → SSH and GPG keys → New GPG key.
- Configure
git commit -Swith the new key. - Delete the old GPG key from the same page.
-
Optionally, automate rotation outside Terraform via:
gh apior GitHub REST API for GPG keys.- A scheduled job (e.g., GitHub Actions, external CI) that:
- Lists keys older than 180 days.
- Creates/upload new keys and deletes old ones.
There is no terraform plan change to verify, since no Terraform-managed resource can represent or rotate these GPG keys.