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 }}