> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Repo Deployment Keys Rotated

### More Info:

Deploy keys can have significant access to a repository and should be rotated on a regular basis.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        To remediate a “Repo Deployment Keys Rotated” finding in GitHub, you essentially need to **create a new SSH deploy key pair and replace the old deploy key in the repository via the GitHub web console**.

        Below are the minimal, step‑by‑step instructions.

        ***

        ## 1. Generate a New SSH Key Pair (Deploy Key)

        Run this on a secure machine that will hold the private key (e.g., your CI server or a secure admin machine):

        ```bash theme={null}
        ssh-keygen -t ed25519 -C "deploy-key-for-<repo-name>" -f ~/.ssh/<repo-name>-deploy-key
        ```

        * Press Enter for no passphrase (deploy keys usually must be non‑interactive).
        * This creates:
          * Private key: `~/.ssh/<repo-name>-deploy-key`
          * Public key: `~/.ssh/<repo-name>-deploy-key.pub`

        Copy the **public key**:

        ```bash theme={null}
        cat ~/.ssh/<repo-name>-deploy-key.pub
        ```

        Keep the **private key** secure; you’ll configure it wherever the deployment/CI runs.

        ***

        ## 2. Add the New Deploy Key in GitHub Console

        1. Go to the repository in GitHub.
        2. Click **Settings** (top of repo).
        3. In the left menu, click **Deploy keys**.
        4. Click **Add deploy key**.
        5. Fill in:
           * **Title**: Something clear (e.g., `CI Deploy Key (rotated 2026-07-25)`).
           * **Key**: Paste the public key you copied.
           * **Allow write access**:
             * Enable only if this key must push to the repo; otherwise leave unchecked.
        6. Click **Add key**.

        ***

        ## 3. Update the System That Uses the Deploy Key

        On the system that actually performs the deployment (CI/CD tool, build server, etc.):

        1. Replace the **old private key** with the **new private key** (`<repo-name>-deploy-key`).

        2. Ensure SSH config (if used) points to this new key, for example in `~/.ssh/config`:

           ```text theme={null}
           Host github.com
             HostName github.com
             User git
             IdentityFile ~/.ssh/<repo-name>-deploy-key
           ```

        3. Test connectivity:

           ```bash theme={null}
           ssh -T git@github.com
           ```

           You should see a message like “Hi `<user>`! You've successfully authenticated…”

        ***

        ## 4. Remove the Old Deploy Key (Complete the Rotation)

        Once you confirm your deployments/CI work with the new key:

        1. In the **same repo**, go to **Settings → Deploy keys**.
        2. Find the **old deploy key** entry.
        3. Click **Delete** (or the trash icon) next to the old key.
        4. Confirm deletion.

        ***

        ## 5. (Optional) Enforce Better Practices

        * Use **read-only** keys unless write is strictly needed.
        * Rotate keys on a regular schedule.
        * Track which system each deploy key belongs to via clear titles.

        If you tell me your CI/CD tool (e.g., GitHub Actions, Jenkins, GitLab CI, etc.), I can give the exact steps to plug the new private key into that system.
      </Accordion>

      <Accordion title="Using CLI">
        Here’s how to remediate a “Repo Deployment Keys Rotated” issue by rotating GitHub deploy keys using the GitHub CLI (`gh`).

        Assumptions:

        * You have `gh` installed and authenticated (`gh auth login`).
        * You have `ssh-keygen` available.

        ***

        ## 1. Identify the repo and existing deploy keys

        ```bash theme={null}
        # Set variables
        OWNER="your-org-or-username"
        REPO="your-repo-name"

        # List current deploy keys
        gh api repos/$OWNER/$REPO/keys --jq '.[] | {id, title, read_only, created_at}'
        ```

        Note the `id` and `title` of the key(s) you want to rotate.

        ***

        ## 2. Generate a new SSH key pair

        ```bash theme={null}
        # Create a new key pair specifically for this repo
        ssh-keygen -t ed25519 -C "deploy-key-$OWNER-$REPO-$(date +%Y%m%d)" -f ~/.ssh/deploy_key_$OWNER_$REPO -N ""
        ```

        Files created:

        * Private: `~/.ssh/deploy_key_$OWNER_$REPO`
        * Public: `~/.ssh/deploy_key_$OWNER_$REPO.pub`

        ***

        ## 3. Add the new deploy key via GitHub CLI

        ```bash theme={null}
        NEW_KEY_TITLE="deploy-key-$OWNER-$REPO-$(date +%Y%m%d)"
        NEW_PUB_KEY=$(cat ~/.ssh/deploy_key_$OWNER_$REPO.pub)

        # Add as read-only (set to false if you truly need write access)
        gh api repos/$OWNER/$REPO/keys \
          -X POST \
          -f title="$NEW_KEY_TITLE" \
          -f key="$NEW_PUB_KEY" \
          -F read_only=true
        ```

        Verify it was added:

        ```bash theme={null}
        gh api repos/$OWNER/$REPO/keys --jq '.[] | {id, title, read_only, created_at}'
        ```

        ***

        ## 4. Update automation/CI to use the new private key

        Wherever the old deploy key was used (CI/CD tool, server, etc.):

        1. Replace the old private key with:\
           `~/.ssh/deploy_key_$OWNER_$REPO`

        2. Ensure permissions are strict:

           ```bash theme={null}
           chmod 600 ~/.ssh/deploy_key_$OWNER_$REPO
           ```

        3. Test connectivity:

           ```bash theme={null}
           GIT_SSH_COMMAND="ssh -i ~/.ssh/deploy_key_$OWNER_$REPO -o IdentitiesOnly=yes" \
           git ls-remote git@github.com:$OWNER/$REPO.git
           ```

        ***

        ## 5. Remove the old deploy key via GitHub CLI

        Once the new key is confirmed working:

        1. Get the ID of the old key:

           ```bash theme={null}
           gh api repos/$OWNER/$REPO/keys --jq '.[] | {id, title}'
           ```

        2. Delete the old key (replace `OLD_KEY_ID`):

           ```bash theme={null}
           OLD_KEY_ID=12345678

           gh api repos/$OWNER/$REPO/keys/$OLD_KEY_ID -X DELETE
           ```

        3. Confirm removal:

           ```bash theme={null}
           gh api repos/$OWNER/$REPO/keys --jq '.[] | {id, title}'
           ```

        ***

        ## 6. (Optional) Enforce a rotation practice

        * Document:
          * Key name pattern
          * Rotation interval (e.g., 90 days)
          * Procedure above as standard runbook.
        * If “GitHub IAM” is a scanner/policy engine, re-run it to confirm the misconfiguration is cleared.
      </Accordion>

      <Accordion title="Using Python">
        Below is a concrete, step‑by‑step way to “remediate” a **Repo Deployment Keys Rotated** finding for GitHub using Python – i.e., automatically rotate (replace) deploy keys on a repo via the GitHub API.

        ***

        ## 1. Prerequisites

        1. **Personal Access Token (PAT)** (or GitHub App token) with:
           * `repo` scope (for private repos)
           * Or at least `public_repo` (for public repos)
        2. **Python 3.8+** installed.
        3. `requests` and `cryptography` (for generating SSH keys):

        ```bash theme={null}
        pip install requests cryptography
        ```

        Set your GitHub token as an environment variable (safer than hard‑coding):

        ```bash theme={null}
        export GITHUB_TOKEN="ghp_XXXXXXXXXXXXXXXXXXXX"
        ```

        ***

        ## 2. High‑Level Rotation Flow

        For each affected repository:

        1. Generate a **new SSH keypair**.
        2. Add the **new public key** as a deploy key via GitHub API.
        3. Update any systems/CI/CD pipelines that use the private key.
        4. Remove the **old deploy key** from the repo via GitHub API.

        ***

        ## 3. Generate a New SSH Keypair in Python

        ```python theme={null}
        from cryptography.hazmat.primitives import serialization
        from cryptography.hazmat.primitives.asymmetric import rsa
        from cryptography.hazmat.backends import default_backend

        def generate_ssh_keypair():
            # Generate private key
            private_key = rsa.generate_private_key(
                public_exponent=65537,
                key_size=4096,
                backend=default_backend()
            )

            # Private key PEM
            private_pem = private_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption(),
            ).decode("utf-8")

            # Public key in OpenSSH format
            public_key = private_key.public_key()
            public_openssh = public_key.public_bytes(
                encoding=serialization.Encoding.OpenSSH,
                format=serialization.PublicFormat.OpenSSH,
            ).decode("utf-8")

            return private_pem, public_openssh

        # Example usage
        private_key_pem, public_key_ssh = generate_ssh_keypair()
        print("Public key:", public_key_ssh)
        # Store/print private_key_pem securely for your CI/CD or deployment system
        ```

        ***

        ## 4. Add the New Deploy Key via GitHub API

        GitHub API endpoint:\
        `POST /repos/{owner}/{repo}/keys`

        ```python theme={null}
        import os
        import requests

        GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
        REPO_OWNER = "your-org-or-user"
        REPO_NAME = "your-repo"

        def add_deploy_key(title, public_key, read_only=True):
            url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/keys"
            headers = {
                "Authorization": f"token {GITHUB_TOKEN}",
                "Accept": "application/vnd.github+json",
            }
            payload = {
                "title": title,
                "key": public_key,
                "read_only": read_only,  # False if you need write
            }

            resp = requests.post(url, json=payload, headers=headers)
            resp.raise_for_status()
            return resp.json()

        # Example: add newly-generated key
        private_key_pem, public_key_ssh = generate_ssh_keypair()
        new_key = add_deploy_key("rotated-deploy-key-2026-07-25", public_key_ssh)
        print("New deploy key ID:", new_key["id"])
        ```

        **Important:**\
        Save `private_key_pem` securely (secret manager, CI/CD secret, etc.) and update any service that uses the old deploy key to now use this new private key.

        ***

        ## 5. List Existing Deploy Keys (Find Old Key to Remove)

        GitHub API endpoint:\
        `GET /repos/{owner}/{repo}/keys`

        ```python theme={null}
        def list_deploy_keys():
            url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/keys"
            headers = {
                "Authorization": f"token {GITHUB_TOKEN}",
                "Accept": "application/vnd.github+json",
            }
            resp = requests.get(url, headers=headers)
            resp.raise_for_status()
            return resp.json()

        keys = list_deploy_keys()
        for k in keys:
            print(k["id"], k["title"], k["key"])
        ```

        Use this to identify the old key either by `title`, a known substring in the key, or manually.

        ***

        ## 6. Remove the Old Deploy Key

        GitHub API endpoint:\
        `DELETE /repos/{owner}/{repo}/keys/{key_id}`

        ```python theme={null}
        def delete_deploy_key(key_id: int):
            url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/keys/{key_id}"
            headers = {
                "Authorization": f"token {GITHUB_TOKEN}",
                "Accept": "application/vnd.github+json",
            }
            resp = requests.delete(url, headers=headers)
            if resp.status_code not in (204, 404):
                # 204 = success; 404 = already gone
                resp.raise_for_status()

        # Example: delete all keys with a specific old title pattern
        OLD_TITLE = "old-deploy-key"

        for k in list_deploy_keys():
            if OLD_TITLE in k["title"]:
                print(f"Deleting key {k['id']} ({k['title']})")
                delete_deploy_key(k["id"])
        ```

        ***

        ## 7. Putting It All Together (Rotation Script)

        1. Generate new keypair.
        2. Add new public key as deploy key.
        3. Update your CI/CD/deployment system with the **new private key**.
        4. Confirm it can clone/push.
        5. Delete the old deploy key.

        You can wrap the above steps into a single script that:

        * Accepts `owner`, `repo`, and the `old_key_title` (or `old_key_id`) as input.
        * Outputs the new private key so you can inject it into your secrets store.

        ***

        ## 8. Optional: Automate Regular Rotation

        To make this a recurring remediation:

        * Put the rotation script into a secure automation environment (e.g., GitHub Actions, Jenkins, or a separate runner).
        * Store the PAT or GitHub App token in a secret manager.
        * Schedule the job (e.g., run monthly/quarterly).
        * After each run, automatically update the target systems’ SSH private key secrets.

        ***

        If you share the name pattern of the old keys and how your CI/CD currently stores the private key (GitHub Actions secret, Jenkins credential, etc.), I can tailor the Python script to fully automate the update on that side as well.
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        # GitHub provider configuration
        provider "github" {
          owner = "GITHUB_ORG_OR_USER" # e.g. "my-org"
          token = "GITHUB_TOKEN_WITH_REPO_SCOPE" # or use environment variable GITHUB_TOKEN
        }

        # Existing repository
        resource "github_repository" "repo" {
          name        = "REPOSITORY_NAME" # e.g. "my-service"
          visibility  = "private"
          description = "REPOSITORY_DESCRIPTION"
        }

        # NEW deploy key (rotation target)
        # Generate a new SSH key pair outside Terraform; put the PUBLIC key here.
        resource "github_repository_deploy_key" "new_deploy_key" {
          repository = github_repository.repo.name

          title      = "NEW_DEPLOY_KEY_TITLE"   # e.g. "ci-deploy-key-2024-07"
          key        = "NEW_PUBLIC_SSH_KEY"     # e.g. "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
          read_only  = false                    # set to true if write is not required

          # rotation note: changing `key` forces replacement of this resource
        }

        # Remove the OLD deploy key from Terraform state/config
        # 1. Replace the old resource block in your code with the new one above, OR
        # 2. If you still have it declared, delete the old block:
        #
        # resource "github_repository_deploy_key" "old_deploy_key" { ... }  # <-- DELETE this block
        #
        # Any applications using the old key must be reconfigured to use the
        # NEW private key that matches NEW_PUBLIC_SSH_KEY before applying.

        ```

        Rotating the key is done by generating a new SSH key pair, updating the applications to use the new private key, then removing the old `github_repository_deploy_key` resource from Terraform so `terraform plan` shows the old key being destroyed and the new key created.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://developer.github.com/v3/guides/managing-deploy-keys](https://developer.github.com/v3/guides/managing-deploy-keys)
