DEV Community

Jarrett Meyer
Jarrett Meyer

Posted on

Deploying AWS Credentials to GitHub Secrets

Objective

Deploy arbitrary projects using the AWS CDK from a GitHub action.

GitHub recommends using OIDC to allow communication between GitHub actions and AWS. But it wouldn't be an adventure if the instructions worked, would it? My company has blocked creating AWS OpenID providers, so we need a new way around this problem.

Create a Lambda function for rotating credentials

GH Personal Access Token

The first thing you're going to need is a personal access token from GitHub. We are going to save this PAT as a secret in AWS. This assumes you have saved your secret as a key-value pair that looks like

{
  "token": "thisismytokentherearemanylikeitbutthisoneismine"
}
Enter fullscreen mode Exit fullscreen mode
const SECRET_NAME = "my-github-pat";

export async function getPersonalAccessToken(): Promise<string> {
  const secretsManager = new AWS.SecretsManager();
  const value = await secretsManager.getSecretValue({ SecretId: SECRET_NAME }).promise();
  const obj = JSON.parse(value || "{}");
  return obj.token;
}
Enter fullscreen mode Exit fullscreen mode

Assume role

Assuming a role will allow us to generate temporary credentials for that role.

async function assumeGitHubRole() {
  const sts = new AWS.STS();

  const assumeRoleParams: AWS.STS.AssumeRoleRequest = {
    RoleArn: process.env.ROLE_ARN,
    RoleSessionName: process.env.ROLE_SESSION_NAME,
  };

  return await sts.assumeRole(assumeRoleParams).promise();
}
Enter fullscreen mode Exit fullscreen mode

Save secrets to GitHub

Secrets must be encrypted using the public key for the repository. We are using libsodium crypto_box_seal function to encrypt the values.

interface SealFunction {
  (message: Uint8Array, publicKey: Uint8Array): Uint8Array;
}

function encryptMessage(sealer: SealFunction, message: string, publicKey: Buffer): string {
  const messageBuffer = Buffer.from(message);
  const encryptedMessage = sealer(messageBuffer, publicKey);
  return Buffer.from(encryptedMessage).toString("base64");
}
Enter fullscreen mode Exit fullscreen mode

Update credentials

You now have all of the pieces necessary to update credentials in your GitHub repo.

export async function updateCredentials(): Promise<void> {
  try {
    const token = await getPersonalAccessToken();

    const repos = await request("GET /orgs/{org}/teams/{team_slug}/repos", {
      org: "my-org",
      team_slug: "my-team",
      headers: {
        authorization: `token ${token}`,
      },
    });

    const assumeRole = await assumeGitHubRole();

    // Ensure that the libsodium library has loaded and is ready to be called.
    await sodium.ready;

    // Loop through each repository, updating secrets as you go.
    const promises = [] as Promise<unknown>[];
    for (const repo of repos.data) {
      // Get the public key for the repo.
      const publicKeyResponse = await request("GET /repos/{owner}/{repo}/actions/secrets/public-key", {
        owner: "my-org",
        repo: repo.name,
        headers: {
          authorization: `token ${token}`,
        }
      };

      const publicKey = Buffer.from(publicKeyResponse.data.key, "base64");

      const encryptedAccessKeyId = encryptMessage(sodium.crypto_box_seal, assumeRole.Credentials.AccessKeyId, publicKey);
      const encryptedSecretAccessKey = encryptMessage(sodium.crypto_box_seal, assumeRole.Credentials.SecretAccessKey, publicKey);
      const encryptedSessionToken = encryptMessage(sodium.crypto_box_seal, assumeRole.Credentials.SessionToken, publicKey);

      promises.push(updateRepositorySecret(repo.name, "AWS_ACCESS_KEY_ID", getPublicKeyResponse.data.key_id, encryptedAccessKeyId));
      promises.push(updateRepositorySecret(repo.name, "AWS_SECRET_ACCESS_KEY", getPublicKeyResponse.data.key_id, encryptedSecretAccessKey));
      promises.push(updateRepositorySecret(repo.name, "AWS_SESSION_TOKEN", getPublicKeyResponse.data.key_id, encryptedSessionToken));
    }

    await Promise.all(promises);
    process.exit(0);
  } catch (error) {
    console.error("Error updating credentials:", JSON.stringify(error));
    process.exit(1);
  }
}
Enter fullscreen mode Exit fullscreen mode

To be continued...

Top comments (0)