From 8e678b0196e8266a9f027473cd0f8c89fe243030 Mon Sep 17 00:00:00 2001 From: James Farr Gomez Date: Wed, 2 Oct 2024 12:47:54 -0700 Subject: [PATCH] Migrate Terraform to use Github App (#19) --- .github/workflows/terraform_plan.yaml | 15 +++-- data.tf | 2 - encode_jwt.py | 80 +++++++++++++++++++++++++++ image-pipeline.tf | 6 +- main.tf | 8 +-- morpheus.tf | 2 +- repolist.tf | 18 ++++++ sandbox.tf | 2 +- varfiles/default | 0 varfiles/default.tfvars | 14 +++++ variables.tf | 12 ++++ versions.tf | 2 +- 12 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 encode_jwt.py create mode 100644 repolist.tf delete mode 100644 varfiles/default diff --git a/.github/workflows/terraform_plan.yaml b/.github/workflows/terraform_plan.yaml index 02841cf..d57aa70 100644 --- a/.github/workflows/terraform_plan.yaml +++ b/.github/workflows/terraform_plan.yaml @@ -16,10 +16,10 @@ jobs: runs-on: [ "229685449397" ] env: -# GITHUB_APP_ID: ${{ vars.GH_APP_ID }} -# GITHUB_APP_INSTALLATION_ID: ${{ vars.GH_APP_INSTALLATION_ID }} -# GITHUB_APP_PEM_FILE: ${{ secrets.GH_APP_PEM_FILE }} - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} +# GITHUB_APP_ID: ${{ vars.GH_APP_ID }} + GITHUB_APP_INSTALLATION_ID: ${{ vars.GH_APP_INSTALLATION_ID }} + GITHUB_APP_PEM_FILE: ${{ secrets.GH_APP_PEM_FILE }} +# GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_OWNER: CSVD GITHUB_BASE_URL: https://github.e.it.census.gov/ TF_WORKSPACE: ${{ vars.terraform_workspace }} @@ -48,7 +48,12 @@ jobs: echo AWS_SECRET_ACCESS_KEY=`jq -r '.SecretAccessKey' aws_credentials.json` >> $GITHUB_ENV aws configure set aws_session_token `jq -r '.Token' aws_credentials.json` echo AWS_SESSION_TOKEN=`jq -r '.Token' aws_credentials.json` >> $GITHUB_ENV - + + - name: Setup GITHUB Credentials + id: github_credentials + run: | + echo GITHUB_TOKEN=$(python encode_jwt.py "$GITHUB_APP_PEM_FILE" "$GITHUB_APP_INSTALLATION_ID" "$GITHUB_BASE_URL") >> $GITHUB_ENV + - name: Terraform Init id: init run: /opt/tfenv/bin/terraform init -upgrade diff --git a/data.tf b/data.tf index 6692404..ff319be 100644 --- a/data.tf +++ b/data.tf @@ -1,3 +1 @@ -data "aws_region" "current" {} - data "github_organization_teams" "teams" {} diff --git a/encode_jwt.py b/encode_jwt.py new file mode 100644 index 0000000..a777670 --- /dev/null +++ b/encode_jwt.py @@ -0,0 +1,80 @@ +## Run this script set the private key as github_app_private_key and installation_id as the installation id of the app + +#export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt +#export github_app_private_key="-----BEGIN" +#export github_app_installation_id=11 +#export github_app_url=https://github.e.it.census.gov +#export GITHUB_TOKEN=$(python encode_jwt.py "$github_app_private_key" "$github_app_installation_id" "$github_app_url") + +import time +import json +import base64 +import argparse +import requests +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import load_pem_private_key +import sys + +# Set up argument parser +parser = argparse.ArgumentParser(description='Encode JWT with RS256 and get GitHub Enterprise installation access token') +parser.add_argument('private_key', type=str, help='PEM formatted private key string') +parser.add_argument('installation_id', type=str, help='GitHub App Installation ID') +parser.add_argument('enterprise_url', type=str, help='GitHub Enterprise API URL (e.g., https://github.e.it.census.gov)') +args = parser.parse_args() + +# Load the PEM private key +private_key = load_pem_private_key(args.private_key.encode(), password=None) + +# JWT Header +header = { + "alg": "RS256", + "typ": "JWT" +} + +# JWT Payload +payload = { + "iat": int(time.time()), + "exp": int(time.time()) + (10 * 60), + "iss": "6" # Replace with your actual GitHub App ID +} + +# Encode Header and Payload as Base64 +header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip("=") +payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") + +# Create the message (header + payload) +message = f"{header_encoded}.{payload_encoded}".encode() + +# Sign the message using RS256 +signature = private_key.sign( + message, + padding.PKCS1v15(), + hashes.SHA256() +) + +# Encode the signature in Base64 +signature_encoded = base64.urlsafe_b64encode(signature).decode().rstrip("=") + +# Construct the full JWT +jwt_token = f"{header_encoded}.{payload_encoded}.{signature_encoded}" + +# Prepare the request to get the installation access token +headers = { + "Authorization": f"Bearer {jwt_token}", + "Accept": "application/vnd.github+json" +} + +# Make the request to the GitHub Enterprise API to get the installation access token +url = f"{args.enterprise_url}api/v3/app/installations/{args.installation_id}/access_tokens" +response = requests.post(url, headers=headers) + +# Check if the request was successful +if response.status_code == 201: + installation_access_token = response.json().get('token') + print(installation_access_token) # Output the token only +else: + # Raise an error with a message + sys.stderr.write(f"Error: Failed to get installation access token. Status code: {response.status_code}\n") + sys.stderr.write(f"{response.text}\n") + sys.exit(1) # Exit with an error code diff --git a/image-pipeline.tf b/image-pipeline.tf index a60ba30..dcc5065 100644 --- a/image-pipeline.tf +++ b/image-pipeline.tf @@ -50,7 +50,7 @@ module "asset_releases" { github_repo_description = "Terraform Workspace for publishing image-pipeline-assets" repo_org = "CSVD" name = "image-pipeline-asset-releases" - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams github_repo_topics = [ "terraform" ] @@ -68,7 +68,7 @@ module "aws_image_pipeline" { github_repo_description = "Terraform Workspace for creating and managing AWS Image Pipelines" repo_org = "CSVD" name = "aws-image-pipeline" - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams github_repo_topics = [ "terraform" ] @@ -112,7 +112,7 @@ module "terraform_aws_image_pipeline" { github_repo_description = "Terraform Module that creates codepipeline and codebuild jobs and other resources for building and deploying images" repo_org = "CSVD" name = "terraform-aws-image-pipeline" - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams github_repo_topics = [ "terraform" ] diff --git a/main.tf b/main.tf index e0457de..a10c620 100644 --- a/main.tf +++ b/main.tf @@ -27,7 +27,7 @@ module "elastic_beanstalk" { enforce_prs = false collaborators = local.collaborators pull_request_bypassers = local.pull_request_bypassers - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams } @@ -273,7 +273,7 @@ module "setup_terraform" { create_codeowners = false enforce_prs = false collaborators = local.collaborators - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams } module "setup_node" { @@ -289,7 +289,7 @@ module "setup_node" { create_codeowners = false enforce_prs = false collaborators = local.collaborators - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams } # ghe-runner @@ -306,7 +306,7 @@ module "ghe_runners" { create_codeowners = false enforce_prs = false collaborators = local.collaborators - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams } module "vpc_services" { diff --git a/morpheus.tf b/morpheus.tf index 132b969..4a82a28 100644 --- a/morpheus.tf +++ b/morpheus.tf @@ -10,7 +10,7 @@ module "morpheus_repos" { source = "HappyPathway/repo/github" #github_codeowners_team = "CSVD" github_repo_description = "Repo for morpheus cloud" - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams repo_org = "CSVD" name = each.value github_repo_topics = [ diff --git a/repolist.tf b/repolist.tf new file mode 100644 index 0000000..4c11376 --- /dev/null +++ b/repolist.tf @@ -0,0 +1,18 @@ +module "repo_list" { + source = "HappyPathway/repo/github" + for_each = tomap({ for repo in var.repolist : repo.name => repo }) + #github_codeowners_team = "CSVD" + github_repo_description = each.value.description + repo_org = each.value.repo_org + name = each.value.name + github_repo_topics = [ + "terraform" + ] + is_template = each.value.is_template + force_name = true + create_codeowners = false + enforce_prs = each.value.enforce_prs + collaborators = local.collaborators + pull_request_bypassers = local.pull_request_bypassers + github_org_teams = local.github_organization_teams +} diff --git a/sandbox.tf b/sandbox.tf index e80f158..38e5611 100644 --- a/sandbox.tf +++ b/sandbox.tf @@ -19,7 +19,7 @@ module "sandbox" { create_codeowners = false enforce_prs = false collaborators = { "arnol377" : "admin" } - github_org_teams = local.github_organization_teams + github_org_teams = local.github_organization_teams managed_extra_files = [ { path = ".github/workflows/terraform-plan.yaml" diff --git a/varfiles/default b/varfiles/default deleted file mode 100644 index e69de29..0000000 diff --git a/varfiles/default.tfvars b/varfiles/default.tfvars index fa1a342..6ad4a1d 100644 --- a/varfiles/default.tfvars +++ b/varfiles/default.tfvars @@ -1,3 +1,17 @@ image_pipeline_workflows = { "image-pipeline-goss-testing" = "./workflows/goss-testing.yaml" } + +repolist = [ + { + description = "Managing AWS CSVD Secrets" + repo_org = "CSVD" + name = "aws-secrets" + }, + { + description = "Tools for managing Terraform" + repo_org = "CSVD" + name = "tf-tools" + } +] + diff --git a/variables.tf b/variables.tf index 0f1b652..82faa87 100644 --- a/variables.tf +++ b/variables.tf @@ -1,3 +1,15 @@ variable "image_pipeline_workflows" { type = map(string) } + +variable "repolist" { + type = list(object({ + description = string + repo_org = string + name = string + is_template = optional(bool, false) + create_codeowners = optional(bool, false) + enforce_prs = optional(bool, false) + })) + default = [] +} diff --git a/versions.tf b/versions.tf index 187b843..1030799 100644 --- a/versions.tf +++ b/versions.tf @@ -2,7 +2,7 @@ terraform { required_providers { random = { source = "integrations/github" - version = ">= 6.2.2" + version = ">= 6.3.0" } aws = { source = "hashicorp/aws"