From c051a06b1e8af24330409adee8d2ff1f003552d7 Mon Sep 17 00:00:00 2001 From: cpanato Date: Thu, 28 Mar 2024 13:31:21 +0100 Subject: [PATCH] add verify prod and refactor terraform check/deploy --- .github/workflows/.terraform.yaml | 216 +++++++++++++++++++++++++++++ .github/workflows/deploy.yaml | 68 +++------ .github/workflows/verify-prod.yaml | 29 ++++ 3 files changed, 267 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/.terraform.yaml create mode 100644 .github/workflows/verify-prod.yaml diff --git a/.github/workflows/.terraform.yaml b/.github/workflows/.terraform.yaml new file mode 100644 index 0000000..937b630 --- /dev/null +++ b/.github/workflows/.terraform.yaml @@ -0,0 +1,216 @@ +# Copyright 2024 Chainguard, Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Reusable Terraform Workflow + +on: + workflow_call: + inputs: + workload_identity_provider: + type: string + required: true + description: "Workload identity provider for GCP Workload Federation." + service_account: + type: string + required: true + description: "GCP Service Account to impersonate." + checkout_sha: + required: false + type: string + description: 'Commit SHA to run at.' + working_directory: + required: true + type: string + description: 'Location for the terraform.' + project_id: + required: false + type: string + description: 'GCP project ID.' + octo-sts-identity: + required: false + default: '' + type: string + description: 'The Octo STS identity name' + +jobs: + terraform: + name: Terraform + runs-on: 'ubuntu-latest' + permissions: + contents: read # clone the repository contents + id-token: write # federates with GCP + + env: + SHA: ${{ inputs.checkout_sha || github.sha }} + PROJECT_ID: ${{ inputs.project_id }} + TF_PLAN_BIN: 'plan.tmp' + TF_PLAN_OUT: 'plan.out' + + defaults: + run: + working-directory: "${{ inputs.working_directory }}" + + steps: + - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + with: + ref: ${{ env.SHA }} + + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version-file: './go.mod' + check-latest: true + + - name: Authenticate to Google Cloud + uses: 'google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c' # v2.1.2 + id: auth + with: + token_format: 'access_token' + project_id: '${{ inputs.project_id }}' + workload_identity_provider: '${{ inputs.workload_identity_provider }}' + service_account: '${{ inputs.service_account }}' + + - name: 'Set up Cloud SDK' + uses: google-github-actions/setup-gcloud@98ddc00a17442e89a24bbf282954a3b65ce6d200 # v2.1.0 + + - uses: 'docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20' # v2 + with: + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.access_token }}' + registry: 'gcr.io' + + - name: Setup terraform + uses: 'hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36' # v3.0.0 + with: + terraform_version: 1.6 + + - name: Terraform fmt + id: fmt + run: terraform fmt -check -recursive + + - name: Terraform init + id: init + run: terraform init -reconfigure + + - name: Terraform Validate + id: validate + run: terraform validate -no-color + + - name: Terraform Plan + id: plan + continue-on-error: true + shell: bash + run: | + set +e + + terraform plan -input=false -lock=false -detailed-exitcode -no-color -out=${{ env.TF_PLAN_BIN }} + exitcode=$? + case "$exitcode" in + 0) + # 0 = Succeeded with empty diff (no changes) + echo "Succeeded with empty diff (no changes)" + echo "TF_PLAN_OUTCOME=same" >> "$GITHUB_OUTPUT" + ;; + 1) + # 1 = Error + echo "Error" + echo "TF_PLAN_OUTCOME=error" >> "$GITHUB_OUTPUT" + exit 1 + ;; + 2) + # 2 = Succeeded with non-empty diff (changes present) + echo "Succeeded with non-empty diff (changes present)" + echo "TF_PLAN_OUTCOME=diff" >> "$GITHUB_OUTPUT" + ;; + *) + echo "unknown error" + echo "TF_PLAN_OUTCOME=unknown" >> "$GITHUB_OUTPUT" + exit 1 + ;; + esac + terraform show -no-color ${{ env.TF_PLAN_BIN }} > "${GITHUB_WORKSPACE}"/${{ env.TF_PLAN_OUT }} + exit 0 + + - uses: octo-sts/action@6177b4481c00308b3839969c3eca88c96a91775f # v1.0.0 + if: github.event_name == 'pull_request' + id: octo-sts + with: + scope: ${{ github.repository }} + identity: ${{ inputs.octo-sts-identity }} + + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + if: github.event_name == 'pull_request' + with: + github-token: ${{ steps.octo-sts.outputs.token }} + script: | + // 1. Retrieve existing bot comments for the PR + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }) + const botComment = comments.find(comment => { + return comment.user.type === 'Bot' && comment.body.includes('Terraform checks for "${{ inputs.working_directory }}"') + }) + + // adapted from: https://github.com/actions/github-script/issues/266#issuecomment-1159681385 + const run_url = process.env.GITHUB_SERVER_URL + '/' + process.env.GITHUB_REPOSITORY + '/actions/runs/' + process.env.GITHUB_RUN_ID + const run_link = 'Actions.' + const fs = require('fs') + const plan_file = fs.readFileSync('${{ env.TF_PLAN_OUT }}', 'utf8') + const plan = plan_file.length > 64000 ? plan_file.toString().substring(0, 64000) + " ..." : plan_file + const truncated_message = plan_file.length > 64000 ? "Output is too long and was truncated. You can read full Plan in " + run_link + "

" : "" + + // 2. Prepare format of the comment + const output = `Terraform checks for "${{ inputs.working_directory }}" + #### Terraform Format and Style 🖌 \`${{ steps.fmt.outcome }}\` + #### Terraform Initialization ⚙️ \`${{ steps.init.outcome }}\` + #### Terraform Validation 🤖 \`${{ steps.validate.outcome }}\` +
Validation Output + + \`\`\`\n + ${{ steps.validate.outputs.stdout }} + \`\`\` + +
+ + #### Terraform Plan 📖 \`${{ steps.plan.outcome }}\` + +
Show Plan + + \`\`\`\n + ${plan} + \`\`\` + +
+ ${truncated_message} + + *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ inputs.working_directory }}\`, Workflow: \`${{ github.workflow }}\`*`; + // 3. If we have a comment, update it, otherwise create a new one + if (botComment) { + github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: output + }) + } else { + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + } + + - name: Terraform Plan Status + if: ${{ steps.plan.outputs.TF_PLAN_OUTCOME == 'error' || steps.plan.outputs.TF_PLAN_OUTCOME == 'unknown' }} + run: exit 1 + + - name: Terraform Apply + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + run: terraform apply -auto-approve -input=false "${{ env.TF_PLAN_BIN }}" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 7f1d432..11fdbf5 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -9,7 +9,9 @@ on: - "main" workflow_dispatch: -concurrency: deploy +concurrency: + group: deploy + cancel-in-progress: false jobs: deploy: @@ -20,48 +22,22 @@ jobs: id-token: write # federates with GCP steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version-file: './go.mod' - check-latest: true - - - uses: google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c # v2.1.2 - id: auth - with: - token_format: 'access_token' - project_id: 'octo-sts' - workload_identity_provider: 'projects/96355665038/locations/global/workloadIdentityPools/github-pool/providers/github-provider' - service_account: 'github-identity@octo-sts.iam.gserviceaccount.com' - - - uses: 'docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20' # v2 - with: - username: 'oauth2accesstoken' - password: '${{ steps.auth.outputs.access_token }}' - registry: 'gcr.io' - - # Attempt to deploy the terraform configuration - - uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v2.0.0 - with: - terraform_version: 1.6 - - - working-directory: ./iac - run: | - terraform init - - terraform plan - - terraform apply -auto-approve - - - uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 # v2.3.0 - if: ${{ failure() }} - env: - SLACK_ICON: http://github.com/chainguard-dev.png?size=48 - SLACK_USERNAME: guardian - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - SLACK_CHANNEL: 'octo-sts-alerts' # Use a channel - SLACK_COLOR: '#8E1600' - MSG_MINIMAL: 'true' - SLACK_TITLE: Deploying OctoSTS to Cloud Run failed - SLACK_MESSAGE: | - For detailed logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + - uses: ./.github/workflows/.terraform.yaml + with: + project_id: 'octo-sts' + workload_identity_provider: 'projects/96355665038/locations/global/workloadIdentityPools/github-pool/providers/github-provider' + service_account: 'github-identity@octo-sts.iam.gserviceaccount.com' + working_directory: ./iac + + - uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 # v2.3.0 + if: ${{ failure() }} + env: + SLACK_ICON: http://github.com/chainguard-dev.png?size=48 + SLACK_USERNAME: guardian + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_CHANNEL: 'octo-sts-alerts' # Use a channel + SLACK_COLOR: '#8E1600' + MSG_MINIMAL: 'true' + SLACK_TITLE: Deploying OctoSTS to Cloud Run failed + SLACK_MESSAGE: | + For detailed logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/verify-prod.yaml b/.github/workflows/verify-prod.yaml new file mode 100644 index 0000000..9297ab6 --- /dev/null +++ b/.github/workflows/verify-prod.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 Chainguard, Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Verify prod Octo-sts + +on: + pull_request_target: + branches: + - 'main' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + verify-prod-octo-sts: + + permissions: + contents: read # clone the repository contents + id-token: write # federates with GCP + + uses: ./.github/workflows/.terraform.yaml + with: + project_id: 'octo-sts' + workload_identity_provider: 'projects/96355665038/locations/global/workloadIdentityPools/github-pool/providers/github-provider' + service_account: 'github-pull-requests@octo-sts.iam.gserviceaccount.com' + working_directory: ./iac + octo-sts-identity: verify-prod + checkout_sha: ${{ github.event.pull_request.head.sha }}