---
title: Protecting the GitHub private key
description: Keep your GitHub app private key safe from compromise via AWS KMS.
---
GitHub Apps make authenticated API calls by generating a JWT from a private key
issued for the app. **This private key is an extremely sensitive credential**, as it
can be used to access the full scope of actions assigned to the GitHub App.

Chinmina Bridge supports signing JWTs using AWS KMS, ensuring that key material
cannot be extracted from the executing process or from the account
configuration.

> \[!TIP]
>
> It is more complicated to use KMS compared to a simple environment variable.
> Given the power of this credential however, it is **strongly** recommended that
> any production implementation of Chinmina (running on AWS) uses this strategy.

## Uploading the private key to AWS KMS

> \[!CAUTION]
>
> It is possible to use IaC to import private keys into KMS. Note carefully that
> this will generally leave your private key unencrypted in your state file, so
> please consider your risk exposure when doing so, or consult your company's
> security specialists. The following guide documents the manual procedure.

1. Follow GitHub's instructions to [generate a private key][github-key-generate]
   for the GitHub application in use.

2. The key spec for your GitHub key *should* be RSA 2048. If you're curious,
   this can be checked by examining the output of the following `openssl`
   command.

   ```shell title="Show details of private key"
   openssl rsa -text -noout -in yourkey.pem
   ```

3. Convert the downloaded GitHub private key file from PEM format to DER format.
   DER is required by AWS KMS when uploading key material.

   ```shell title="Convert from PEM to DER"
   openssl rsa -inform PEM -outform DER -in ./private-key.pem -out private-key.cer
   ```

4. Follow the [AWS KMS key import instructions][aws-import-key-material] for importing the
   application private key into KMS.

   The instructions include how to create an RSA 2048 key of type `EXTERNAL`,
   how to encrypt the key material so that it can be imported, and how to upload
   it.

   The steps of this process individually are not difficult, but it does have a
   number of them and the details matter. **Follow the instructions carefully.**

5. Create an alias for the KMS key to allow for easy [manual key
   rotation][aws-manual-key-rotation].

   This might seem like an optional step, but a key alias is **essential to
   allow for key rotation**. Unless you're stopped
   by your organizational policy, use the alias. The private key will be able
   to be rotated without any service downtime.

6. Ensure that the key policy has a statement allowing Chinmina to access the key. The specified role should be the role that the Chinmina process has access to at runtime.

   ```json
   {
     "Sid": "Allow Chinmina to sign using the key",
     "Effect": "Allow",
     "Principal": {
       "AWS": ["arn:aws:iam::226140413739:role/full-task-role-name"]
     },
     "Action": ["kms:Sign"],
     "Resource": "*"
   }
   ```

> \[!TIP]
>
> Chinmina does not assume a role to access the key: it assumes valid
> credentials are present for the AWS SDK to use. Typically these are available
> to the container via the credentials supplied via the IMDS (instance metadata
> service).

## Configuring the Chinmina service

1. Set the environment variable `GITHUB_APP_PRIVATE_KEY_ARN` to the ARN of the
   **alias** that has just been created.

2. Update the key resource policy. The KMS key resource policy needs to allow
   the service to use the key *for signing only*.

   Allowing the Chinmina service to use the key can be done by specifying the
   ARN of the role principal (see highlight below), or by using [AWS condition
   keys][kms-condition-keys] in a `Condition` element. The [global condition
   key][aws-global-condition-keys] [`aws:PrincipalArn`][aws-principal-arn] can
   be useful if it's necessary to employ a wildcard on the role name.

   ```json title="example-resource-policy.json" /arn:.*-role/
   {
     "Sid": "ChinminaServiceUsage",
     "Effect": "Allow",
     "Principal": {
       "AWS": ["arn:aws:iam::123456789012:role/chinmina-process-role"]
     },
     "Action": "kms:Sign",
     "Resource": "*"
   }
   ```

3. The IAM policy for the Chinmina process (i.e. the AWS role available to
   Chinmina when it runs) needs to be able to use the **alias** created for
   the private key. This is done with a condition in the policy element:

   ```json title="example-iam-policy.json" /"kms:RequestAlias.*$/
   {
     "Action": "kms:Sign",
     "Effect": "Allow",
     "Resource": "*",
     "Condition": {
       "StringEquals": {
         "kms:RequestAlias": "alias/chinmina-signing"
       }
     }
   }
   ```

   Using the `kms:RequestAlias` condition instead of the fully qualified
   key ARN in the `resource` attribute allows for transparent key rotation
   without service interruption.

[github-key-generate]: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps#generating-private-keys

[aws-import-key-material]: https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys.html

[aws-manual-key-rotation]: https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html#rotate-keys-manually

[kms-condition-keys]: https://docs.aws.amazon.com/kms/latest/developerguide/conditions-kms.html

[aws-global-condition-keys]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html

[aws-principal-arn]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalarn
