As we’re using GitOps right from the beginning, we will use Terraform to set up binary authorization for us. We’ll start by setting up some GitHub Actions secrets. Go to https://github.com//mdo-environments/settings/secrets/actions and create the following secrets:
ATTESTOR_NAME=quality-assurance-attestor
KMS_KEY_LOCATION=us-central1
KMS_KEYRING_NAME=qa-attestor-keyring
KMS_KEY_NAME=quality-assurance-attestor-key
KMS_KEY_VERSION=1
We’ll then create a binaryauth.tf file with the following resources.
We’ll begin by creating a Google KMS key ring. Since binary authorization utilizes PKI for creating and verifying attestations, this key ring will enable our attestor to digitally sign attestations for images. Please note the count attribute defined in the following code. This ensures that it is created exclusively in the dev environment, where we intend to use the attestor for attesting images after testing our app:
resource “google_kms_key_ring” “qa-attestor-keyring” { count = var.branch == “dev” ? 1 : 0
name = “qa-attestor-keyring”
location = var.region
lifecycle {
prevent_destroy = false
}
}
We will then use a Google-provided binary-authorization Terraform module to create our quality-assurance attestor. That attestor uses the Google KMS key ring we created before:
module “qa-attestor” {
count = var.branch == “dev” ? 1 : 0
source = “terraform-google-modules/kubernetes-engine/google//modules/binary-
authorization”
attestor-name = “quality-assurance”
project_id = var.project_id
keyring-id = google_kms_key_ring.qa-attestor-keyring[0].id
}
Finally, we will create a binary authorization policy that specifies the cluster’s behavior when deploying a container. In this scenario, our objective is to deploy only attested images. However, we will make a few exceptions, allowing Google-provided system images, Argo CD, and External Secrets Operator images. We will set the global_policy_evaluation_mode attribute to ENABLE to avoid enforcing the policy on system images managed by Google.
The admission_whitelist_patterns section defines container image patterns permitted to be deployed without attestations. This includes patterns for Google-managed system images, the Argo CD registry, the External Secrets registry, and the Redis container used by Argo CD.
The defaultAdmissionRule section mandates attestation using the attestor we created. Therefore, any other images would require attestation to run on the cluster:
resource “google_binary_authorization_policy” “policy” {
count = var.branch == “dev” ? 1 : 0
admission_whitelist_patterns {
name_pattern = “gcr.io/google_containers/“… name_pattern = “gcr.io/google-containers/“…
name_pattern = “k8s.gcr.io/“… name_pattern = “gke.gcr.io/“…
name_pattern = “gcr.io/stackdriver-agents/“… name_pattern = “quay.io/argoproj/“…
name_pattern = “ghcr.io/dexidp/“… name_pattern = “docker.io/redis[@:]“…
name_pattern = “ghcr.io/external-secrets/*”
}
global_policy_evaluation_mode = “ENABLE”
default_admission_rule {
evaluation_mode = “REQUIRE_ATTESTATION”
enforcement_mode = “ENFORCED_BLOCK_AND_AUDIT_LOG”
require_attestations_by = [
module.qa-attestor[0].attestor
]
}
}
To enforce the binary authorization policy within a cluster, we must also enable binary authorization.
To do so, we add the following block within the cluster.tf file:
resource “google_container_cluster” “main” {
…
dynamic “binary_authorization” {
for_each = var.branch == “prod” ? [1] : []
content {
evaluation_mode = “PROJECT_SINGLETON_POLICY_ENFORCE”
}
}
…
}
This dynamic block is created exclusively when the branch name isprod. The reason for this approach is our intention to deploy our code to the Dev environment without image attestation, conduct testing, and then attest the images if the tests succeed. Therefore, only the Prod cluster should disallow unattested images. To achieve this, we will include the following steps in the Dev CD workflow:
binary-auth:
name: Attest Images
needs: [run-tests]
uses: ./.github/workflows/attest-images.yml
secrets: inherit
As you can see, this calls the attest-images.yml workflow. Let’s look at that now:
steps:
uses: actions/checkout@v2
id: gcloud-auth …
name: Set up Cloud SDK …
name: Install gcloud beta
id: install-gcloud-beta
run: gcloud components install beta
name: Attest Images
run: |
for image in $(cat ./images); do no_of_slash=$(echo $image | tr -cd ‘/’ | wc -c) prefix=””
if [ $no_of_slash -eq 1 ]; then prefix=”docker.io/”
fi
if [ $no_of_slash -eq 0 ]; then
prefix=”docker.io/library/”
fi
image_to_attest=$image
if [[ $image =~ “@” ]]; then echo “Image $image has DIGEST” image_to_attest=”${prefix}${image}”
else
echo “All images should be in the SHA256 digest format” exit 1
fi
echo “Processing $image”
attestation_present=$(gcloud beta container binauthz attestations list
–attestor-project=”${{ secrets.PROJECT_ID }}” –attestor=”${{ secrets.ATTESTOR_NAME }}” –artifact-url=”${image_to_attest}”)
if [ -z “${attestation_present// }” ]; then
gcloud beta container binauthz attestations sign-and-create –artifact-
url=”${image_to_attest}” –attestor=”${{ secrets.ATTESTOR_NAME }}” –attestor-project=”${{
secrets.PROJECT_ID }}” –keyversion-project=”${{ secrets.PROJECT_ID }}” –keyversion-
location=”${{ secrets.KMS_KEY_LOCATION }}” –keyversion-keyring=”${{ secrets.KMS_KEYRING_
NAME }}” –keyversion-key=”${{ secrets.KMS_KEY_NAME }}” –keyversion=”${{ secrets.KMS_KEY_
VERSION }}”
fi
done