Shared reusable workflows for GitHub Actions
This workflow plans and applies Terraform config to deploy to an environment.
- Logs in to GCP, Kubernetes and Vault automatically with Workload Identity Federation
- Posts comments with relevant information to Pull Requests
- Runs validate, format and plan to verify deployment follows conventions before running apply
- Posts summaries on relevant steps on the Action pipeline view to improve visibility
- Supports manual approval and delay periods in deploy using GitHub Environments protection rules
- Re-uses plan from plan step in apply step to ensure apply always executes the diff generated by plan no matter when it is run
- Prevents deploys running in parallel against the same environment crashing due to failing to aquire state lock
- Allows for the choice of deploying and/or destroying terraform config
- Performs binary attestation on the image if proivided. Note that for an image to be used in the test and prod environments (i.e. test and prod kubernetes clusters) it will need to be attested in this manner.
jobs:
build:
# ...
post-build-attest:
# call to post-build-attest.yml with build image
dev:
name: Deploy to dev
permissions:
# For logging on to Vault, GCP
id-token: write
# For writing comments on PR
pull-requests: write
# For fetching git repo
contents: read
# For accessing repository
packages: write
uses: kartverket/github-workflows/.github/workflows/run-terraform.yml@<release tag>
with:
runner: atkv1-dev
environment: dev
kubernetes_cluster: atkv1-dev
terraform_workspace: dev
terraform_option_1: -var-file=dev.tfvars
terraform_init_option_1: -backend-config=dev.gcs.tfbackend
working_directory: terraform
auth_project_number: "123456789123"
service_account: X
project_id: X
image_url: <registry>/<repository>:<tag> or <registry>/<repository>@<digest>
destroy: <optional boolean>
Passing environment variables to reusable workflows is not supported by GitHub. This is a requested feature. The current workaround for this is to use a setup-job to consume env vars and provide an output that can be mapped to the arguments of the job.
Click here to see an example of this
env:
AUTH_PROJECT_NUMBER: X
WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT: X
PROJECT_ID: X
jobs:
setup-env:
runs-on: ubuntu-latest
outputs:
auth_project_number: ${{ steps.set-output.outputs.auth_project_number }}
service_account: ${{ steps.set-output.outputs.service_account }}
project_id: ${{ steps.set-output.outputs.project_id }}
steps:
- name: set outputs with default values
id: set-output
run: |
echo "auth_project_number=${{ env.AUTH_PROJECT_NUMBER }}" >> $GITHUB_OUTPUT
echo "service_account=${{ env.WORKLOAD_IDENTITY_FEDERATION_SERVICE_ACCOUNT }}" >> $GITHUB_OUTPUT
echo "project_id=${{ env.PROJECT_ID }}" >> $GITHUB_OUTPUT
dev:
name: Deploy to dev
needs: setup-env
permissions:
id-token: write
contents: read
pull-requests: write
packages: write
uses: kartverket/github-workflows/.github/workflows/run-terraform.yml@v2.1
with:
runner: atkv1-dev
environment: dev
kubernetes_cluster: atkv1-dev
terraform_workspace: dev
terraform_option_1: -var-file=dev.tfvars
working_directory: terraform
auth_project_number: ${{ needs.setup-env.outputs.auth_project_number }}
service_account: ${{ needs.setup-env.outputs.service_account }}
project_id: ${{ needs.setup-env.outputs.project_id }}
Secrets should be administered through vault and thus there is no explicit
support for taking GitHub secrets into the action. GitHub will also stop you
from sending secrets as arguments using the with
argument, so this will not
work either.
Use the vault_generic_secret
Terraform data source along with the vault_role
argument to run-terraform to
fetch secrets required at deploy-time.
Each repo needs its own unique vault_role
. Contact SKIP if you do not have
this role.
Key | Type | Required | Description |
---|---|---|---|
service_account | string | X | The GCP service account connected to the identity pool that will be used by Terraform. Service account and auth_project_number environment must coincide. |
auth_project_number | string | X | The GCP Project Number used as the active project. A 12-digit number used as a unique identifier for the project. Used to find workload identity pool. Project number and SA environment must coincide |
workload_identity_provider_override | string | The ID of the provider to use for authentication. Only used for overriding the default workload identity provider based on project number. It should be in the format of projects/{{project_number}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}} . |
|
runner | string | X | The GitHub runner to use when running the deploy. This can for example be atkv1-dev . |
deploy_on | string | Which branch will be the only branch allowed to deploy. This defaults to the main branch so that other branches only run check and plan. Defaults to refs/heads/main . |
|
working_directory | string | The directory in which to run terraform, i.e. where the Terraform files are placed. The path is relative to the root of the repository. | |
project_id | string | The GCP Project ID to use as the "active project" when running Terraform. When deploying to Kubernetes, this must match the project in which the Kubernetes cluster is registered. | |
kubernetes_cluster | string | An optional kubernetes cluster to authenticate to. Note that the project_id must match where the cluster is registered. | |
environment | string | The GitHub environment to use when deploying. See using environments for deployment for more info on this. | |
vault_role | string | Required when using vault in terraform. Enables fetching jwt and logging in to vault for the terraform provider to work. | |
terraform_workspace | string | When provided will set a workspace as the active workspace when planning and deploying. | |
terraform_option_X | string | An additional terraform option to be passed to plan and apply. For example -var-file=dev.tfvars and -var=<variableName>=<variableValue> . X may be an integer between 1-3, which allows at most 3 options. |
|
terraform_init_option_Y | string | An additional config to be passed to terraform init. For example -backend-config=dev.gcs.tfbackend . Y may be an integer between 1-3, which allows at most 3 init options. |
|
add_comment_on_pr | boolean | Setting this to false disables the creation of comments with info of the Terraform run on Pull Requests. When true the pull-request permission is required to be set to write . Defaults to true . |
|
image_url | string | An optional parameter; however, it is required for binary attestation. The Docker image url must be of the form registry/repository:tag or registry/repository@digest |
|
destroy | boolean | An optional boolean that determines whether terraform will be destroyed. Defaults to 'false'. |
This workflow performs binary attestation on a built image. Note the format of the image_url parameter.
- Logs in to GCP automatically with Workload Identity Federation
- Performs binary attestation on the image. Attests (1) that the image was built in context of Kartverket and (2) that the image is on main/master branch.
jobs:
build:
# ...
post-build-attest:
needs: [build]
name: Authentication and Attestation of Build
permissions:
contents: read
packages: write
# required for authentication to GCP
id-token: write
actions: read
security-events: write
statuses: write
uses: kartverket/github-workflows/.github/workflows/post-build-attest.yml@<release tag>
with:
auth_project_number: "123456789321"
service_account: x
image_url: ${{ needs.build.outputs.image_url }} # the image created by build job
Key | Type | Required | Description |
---|---|---|---|
auth_project_number | string | X | The GCP Project Number used as the active project. A 12-digit number used as a unique identifier for the project. Used to find workload identity pool. This project should be your dev environment project, as this is the environment where the attestors are located |
workload_identity_provider_override | string | The ID of the provider to use for authentication. Only used for overriding the default workload identity provider based on project number. It should be in the format of projects/{{project_number}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}} . |
|
service_account | string | X | The GCP service account connected to the identity pool that will be used by Terraform. Should be the dev environment deploy service account |
image_url | string | X | The Docker image url must be of the form registry/repository:tag or registry/repository@digest |
This workflow runs security scans and performs binary attestation if no high or critical vulnerabilities are found. Note, in order to not limit/interfere with the developement process, the scans do not run on draft pull requests. Additionally, if image_url is not supplied neither Trivy nor Binary Attestation will be performed (i.e. only TFSec scan will run).
- Runs TFSec, a static analysis security scanner for your Terraform code. Does not run on draft pull requests.
- Runs Trivy, a comprehensive security scanner. Does not run on draft pull requests.
- Creates a binary attestation on the supplied image if trivy is run.
- Calls the Github Security Code Scanning API and fails with exit code 1 if there are any high or critical errors.
- Creates a binary attestation on the supplied image if both TFsec and Trivy scans are run and there are no high or critical errors in Github Security Code Scanning.
- Code Scanning must be appropriatly set up in the Github Security tab.
- Note that the image built during your build-job must be pushed to the registry on all but draft PRs for the workflow to work as intended (see example build job below, paying extra attention to lines following
# Note: ...
)
jobs:
build:
# Example of how to build an image and supply appropriate inputs to run-security-scans reusable workflow
# Note outputs derived in the setOutput step and 'tags' set in the meta step
name: Build Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_url: ${{ steps.setOutput.outputs.image_url }}
image_tag_url: ${{ steps.setOutput.outputs.image_tag_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Sets tag 'latest' for images built on main/master branch and tag 'prebuild-temp' on all other image builds
- name: Set tag
id: set-tag
env:
BRANCH: ${{ github.ref_name }}
run: |
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
echo "image_tag=latest" >> $GITHUB_OUTPUT
else
echo "image_tag=prebuild-temp" >> $GITHUB_OUTPUT
fi
# Login against a Docker registry except on draft-PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event.pull_request.draft == false
uses: docker/login-action@<version>
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@<version>
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Note: checkout https://github.com/docker/metadata-action#tags-input for tag format options
tags: |
type=sha,format=long
type=raw,value=${{ steps.set-tag.outputs.image_tag }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-docker
uses: docker/build-push-action@<version>
with:
context: .
# Note: The image must be pushed to registry in all cases except for draft PRs
push: ${{ !github.event.pull_request.draft }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Set output with build values
id: setOutput
# Note: The image url is output in both `registry/repository:tag` and `registry/repository@digest` formats
run: |
echo "image_url=${{ env.REGISTRY }}/${{ github.repository }}@${{ steps.build-docker.outputs.digest }}" >> $GITHUB_OUTPUT
echo "image_tag_url=${{ env.REGISTRY }}/${{ github.repository }}:${{ steps.meta.outputs.version }}" >> $GITHUB_OUTPUT
post-build-attest:
# call to post-build-attest.yml with build image
dev:
# call to run-terraform.yml for dev environment
test:
# call to run-terraform.yml for test environment
security-scans:
needs: [build]
name: Security Scans
permissions:
contents: read
packages: write
# required for authentication to GCP
id-token: write
actions: read
security-events: write
uses: kartverket/github-workflows/.github/workflows/run-security-scans.yml@<release tag>
with:
auth_project_number: "123456789123"
service_account: x
image_url: ${{ needs.build.outputs.image_tag_url}} # optional, must have format <registry>/<repository>:<tag>
trivy: <optional>
tfsec: <optional>
prod:
dev:
# call to run-terraform.yml for prod environment only after security-scans job
Key | Type | Required | Description |
---|---|---|---|
auth_project_number | string | X | The GCP Project Number used as the active project. A 12-digit number used as a unique identifier for the project. Used to find workload identity pool. This project should be your dev environment project, as this is the environment where the attestors are located |
workload_identity_provider_override | string | The ID of the provider to use for authentication. Only used for overriding the default workload identity provider based on project number. It should be in the format of projects/{{project_number}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}} . |
|
service_account | string | X | The GCP service account connected to the identity pool that will be used by Terraform. Should be the dev environment deploy service account |
image_url | string | The Docker image url must be of the form registry/repository:tag for run-security-scans. It is not required; however, in order to run Trivy and aquire attestations an image_url must be supplied. |
|
trivy | boolean | An optional boolean that determines whether trivy-scan will be run. Defaults to 'true'. | |
tfsec | boolean | An optional boolean that determines whether tfsec-scan will be run. Defaults to 'true'. |