diff --git a/modules/gh-runner-mig-vm/main.tf b/modules/gh-runner-mig-vm/main.tf index b03e92b..d7868d6 100644 --- a/modules/gh-runner-mig-vm/main.tf +++ b/modules/gh-runner-mig-vm/main.tf @@ -89,18 +89,23 @@ resource "google_secret_manager_secret" "gh-secret" { } } } + resource "google_secret_manager_secret_version" "gh-secret-version" { provider = google-beta secret = google_secret_manager_secret.gh-secret.id secret_data = jsonencode({ - "REPO_NAME" = var.repo_name - "REPO_OWNER" = var.repo_owner - "GITHUB_TOKEN" = var.gh_token - "LABELS" = join(",", var.gh_runner_labels) + "REPO_NAME" = var.repo_name + "REPO_OWNER" = var.repo_owner + "RUNNER_VERSION" = var.gh_runner_version + "RUNNER_GROUP" = var.gh_runner_group + "GITHUB_TOKEN" = var.gh_token + "GHA_INSTALLATION_ID" = var.gha_installation_id + "GHA_CLIENT_ID" = var.gha_client_id + "GHA_PRIVATE_KEY" = base64encode(file(var.gha_private_key)) + "LABELS" = join(",", var.gh_runner_labels) }) } - resource "google_secret_manager_secret_iam_member" "gh-secret-member" { provider = google-beta project = var.project_id @@ -119,7 +124,7 @@ locals { module "mig_template" { source = "terraform-google-modules/vm/google//modules/instance_template" - version = "~> 7.0" + version = "~> 11.0" project_id = var.project_id machine_type = var.machine_type network = local.network_name @@ -153,13 +158,12 @@ module "mig_template" { Runner MIG *****************************************/ module "mig" { - source = "terraform-google-modules/vm/google//modules/mig" - version = "~> 7.0" - project_id = var.project_id - subnetwork_project = var.project_id - hostname = local.instance_name - region = var.region - instance_template = module.mig_template.self_link + source = "terraform-google-modules/vm/google//modules/mig" + version = "~> 11.0" + project_id = var.project_id + hostname = local.instance_name + region = var.region + instance_template = module.mig_template.self_link /* autoscaler */ autoscaling_enabled = true diff --git a/modules/gh-runner-mig-vm/scripts/shutdown.sh b/modules/gh-runner-mig-vm/scripts/shutdown.sh index 420ca36..2a5c184 100644 --- a/modules/gh-runner-mig-vm/scripts/shutdown.sh +++ b/modules/gh-runner-mig-vm/scripts/shutdown.sh @@ -13,6 +13,52 @@ # See the License for the specific language governing permissions and # limitations under the License. +generate_gha_jwt () { + #################### + # Generate JWT token + # Doc: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app + #################### + + client_id="$GHA_CLIENT_ID" # Client ID as first argument + + pem=$(echo -n "$GHA_PRIVATE_KEY" | base64 --decode) # file path of the private key as second argument + + now=$(date +%s) + iat=$((now - 60)) # Issues 60 seconds in the past + exp=$((now + 600)) # Expires 10 minutes in the future + + #b64enc() { tr -d '\n' | tr -d '\r' | base64 | tr '+/' '-_' | tr -d '='; } + b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; } + + header_json='{ + "typ":"JWT", + "alg":"RS256" + }' + # Header encode + header=$( echo -n "${header_json}" | b64enc ) + + payload_json='{ + "iat":'"${iat}"', + "exp":'"${exp}"', + "iss":"'"${client_id}"'" + }' + # Payload encode + payload=$( echo -n "${payload_json}" | b64enc ) + + # Signature + header_payload="${header}"."${payload}" + signature=$( + openssl dgst -sha256 -sign <(echo -n "${pem}") \ + <(echo -n "${header_payload}") | b64enc + ) + + # Create JWT + JWT="${header_payload}"."${signature}" + + #printf "%s\n" "$JWT" +} + + secretUri=$(curl -sS "http://metadata.google.internal/computeMetadata/v1/instance/attributes/secret-id" -H "Metadata-Flavor: Google") #secrets URI is of the form projects/$PROJECT_NUMBER/secrets/$SECRET_NAME/versions/$SECRET_VERSION #split into array based on `/` delimeter @@ -37,5 +83,21 @@ else # Remove action runner from the repo POST_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/actions/runners/remove-token" fi + +#only execute when client_id and private_key are not empty +#use as check to see if we want to use github app for authentication +#because we are overwriting the GITHUB_TOKEN variable +if [[ -n $GHA_CLIENT_ID ]] && [[ -n $GHA_PRIVATE_KEY ]]; then + generate_gha_jwt + + #Get access token + #Docs: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app + GITHUB_TOKEN=$(curl --request POST \ + --url "https://api.github.com/app/installations/${GHA_INSTALLATION_ID}/access_tokens" \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${JWT}" \ + --header "X-GitHub-Api-Version: 2022-11-28" | jq -r .token) +fi + #remove the runner configuration -RUNNER_ALLOW_RUNASROOT=1 /runner/config.sh remove --unattended --token "$(curl -sS --request POST --url "$POST_URL" --header "authorization: Bearer ${GITHUB_TOKEN}" --header "content-type: application/json" | jq -r .token)" +RUNNER_ALLOW_RUNASROOT=1 /runner/config.sh remove --unattended --token "$(curl -sS --request POST --url "$POST_URL" --header "authorization: Bearer ${GITHUB_TOKEN}" --header "content-type: application/json" | jq -r .token)" --runnergroup "${RUNNER_GROUP}" diff --git a/modules/gh-runner-mig-vm/scripts/startup.sh b/modules/gh-runner-mig-vm/scripts/startup.sh index 2f3c52d..d99dd71 100644 --- a/modules/gh-runner-mig-vm/scripts/startup.sh +++ b/modules/gh-runner-mig-vm/scripts/startup.sh @@ -17,6 +17,51 @@ apt-get update apt-get -y install jq +generate_gha_jwt () { + #################### + # Generate JWT token + # Doc: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app + #################### + + client_id="$GHA_CLIENT_ID" # Client ID as first argument + + pem=$(echo -n "$GHA_PRIVATE_KEY" | base64 --decode) # file path of the private key as second argument + + now=$(date +%s) + iat=$((now - 60)) # Issues 60 seconds in the past + exp=$((now + 600)) # Expires 10 minutes in the future + + #b64enc() { tr -d '\n' | tr -d '\r' | base64 | tr '+/' '-_' | tr -d '='; } + b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; } + + header_json='{ + "typ":"JWT", + "alg":"RS256" + }' + # Header encode + header=$( echo -n "${header_json}" | b64enc ) + + payload_json='{ + "iat":'"${iat}"', + "exp":'"${exp}"', + "iss":"'"${client_id}"'" + }' + # Payload encode + payload=$( echo -n "${payload_json}" | b64enc ) + + # Signature + header_payload="${header}"."${payload}" + signature=$( + openssl dgst -sha256 -sign <(echo -n "${pem}") \ + <(echo -n "${header_payload}") | b64enc + ) + + # Create JWT + JWT="${header_payload}"."${signature}" + + #printf "%s\n" "$JWT" +} + secretUri=$(curl -sS "http://metadata.google.internal/computeMetadata/v1/instance/attributes/secret-id" -H "Metadata-Flavor: Google") #secrets URI is of the form projects/$PROJECT_NUMBER/secrets/$SECRET_NAME/versions/$SECRET_VERSION #split into array based on `/` delimeter @@ -31,7 +76,23 @@ secrets=$(gcloud secrets versions access "$SECRET_VERSION" --secret="$SECRET_NAM # we want to use wordsplitting export $(echo "$secrets" | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]") #github runner version -GH_RUNNER_VERSION="2.283.2" +GH_RUNNER_VERSION=${RUNNER_VERSION:-2.283.3} + +#only execute when client_id and private_key are not empty +#use as check to see if we want to use github app for authentication +#because we are overwriting the GITHUB_TOKEN variable +if [[ -n $GHA_CLIENT_ID ]] && [[ -n $GHA_PRIVATE_KEY ]]; then + generate_gha_jwt + + #Get access token + #Docs: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app + GITHUB_TOKEN=$(curl --request POST \ + --url "https://api.github.com/app/installations/${GHA_INSTALLATION_ID}/access_tokens" \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${JWT}" \ + --header "X-GitHub-Api-Version: 2022-11-28" | jq -r .token) +fi + #get actions binary curl -o actions.tar.gz --location "https://github.com/actions/runner/releases/download/v${GH_RUNNER_VERSION}/actions-runner-linux-x64-${GH_RUNNER_VERSION}.tar.gz" mkdir /runner @@ -56,7 +117,7 @@ fi # Register runner ACTIONS_RUNNER_INPUT_TOKEN="$(curl -sS --request POST --url "$POST_URL" --header "authorization: Bearer ${GITHUB_TOKEN}" --header 'content-type: application/json' | jq -r .token)" #configure runner -RUNNER_ALLOW_RUNASROOT=1 /runner/config.sh --unattended --replace --work "/runner-tmp" --url "$GH_URL" --token "$ACTIONS_RUNNER_INPUT_TOKEN" --labels "$LABELS" +RUNNER_ALLOW_RUNASROOT=1 /runner/config.sh --unattended --replace --work "/runner-tmp" --url "$GH_URL" --token "$ACTIONS_RUNNER_INPUT_TOKEN" --labels "$LABELS" --runnergroup "${RUNNER_GROUP}" #install and start runner service cd /runner || exit diff --git a/modules/gh-runner-mig-vm/variables.tf b/modules/gh-runner-mig-vm/variables.tf index 5641c98..1f30126 100644 --- a/modules/gh-runner-mig-vm/variables.tf +++ b/modules/gh-runner-mig-vm/variables.tf @@ -71,12 +71,24 @@ variable "repo_owner" { description = "Owner of the repo for the Github Action" } +variable "gh_runner_version" { + type = string + description = "Version of the runner to deploy" + default = "2.283.2" +} + variable "gh_runner_labels" { type = set(string) description = "GitHub runner labels to attach to the runners. Docs: https://docs.github.com/en/actions/hosting-your-own-runners/using-labels-with-self-hosted-runners" default = [] } +variable "gh_runner_group" { + type = string + description = "GitHub runner group to attach to the runners. Docs: https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-more-self-hosted-runners" + default = "" +} + variable "min_replicas" { type = number description = "Minimum number of runner instances" @@ -94,6 +106,24 @@ variable "gh_token" { description = "Github token that is used for generating Self Hosted Runner Token" } +variable "gha_client_id" { + type = string + description = "Github App ID" + default = "" +} + +variable "gha_installation_id" { + type = string + description = "Github Installation ID" + default = "" +} + +variable "gha_private_key" { + type = string + description = "Github App private key" + default = "./gha_private-key.pem" +} + variable "service_account" { description = "Service account email address" type = string