Skip to content

Commit

Permalink
feat: refactoring, adding comments for clarity
Browse files Browse the repository at this point in the history
  • Loading branch information
tunacinsoy committed Aug 28, 2024
1 parent a5b783e commit 5f42020
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 23 deletions.
1 change: 1 addition & 0 deletions .github/workflows/attest-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
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 the attestation is not present, then attestation should be done
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
Expand Down
23 changes: 22 additions & 1 deletion scripts/convert-images-into-sha256-format.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
# This script is already included in .github/workflows/attest-images.yml file, however I wanted to explain it in more detail here.

# The aim of this script is to convert image names which were generated in format {IMAGE_NAME}:{GITHUB_SHA} (For instance -> sba-posts:83047ac) into
# a format that includes docker digest. (For instance _> docker.io/tunacinsoy/sba-frontend@sha256:466ef8f59a7ef5081334c0e4082a2c16f01e251eaa08c94d803aeb0ed9684fd6)
# If the image is retrieved from prebuilt images from dockerhub (like mongo) format becomes like this:
# docker.io/library/mongo@sha256:e64f27edef80b41715e5830312da25ea5e6874a2b62ed1adb3e8f74bde7475a6

# Get all image names from blog-app manifests, and store them in images file.
grep -ir "image:" ../manifests/blog-app |\
awk {'print $3'} | sort -t: -u -k1,1 > ./images

# For each image name in images file:
for image in $(cat ./images); do
# Get the count of slash character in each image name
no_of_slash=$(echo $image | tr -cd '/' | wc -c)
prefix=""
# If there is only one slash in image name, then it is built by user. (username/image_name)
if [ $no_of_slash -eq 1 ]; then
prefix="docker.io/"
fi
# If there is not any slash in image name, then it is already built, and was present in dockerhub (mongo:latest)
if [ $no_of_slash -eq 0 ]; then
prefix="docker.io/library/"
fi
image_to_attest=$image
if [[ $image =~ "@" ]]; then
# If image name which was in images file already has @ symbol, then it has sha256 digest in its name
echo "Image $image has DIGEST"
image_to_attest="${prefix}${image}"
else
# If it does not, then we need to pull its digest from dockerhub
DIGEST=$(docker pull $image | grep Digest | awk {'print $2'})
image_name=$(echo $image | awk -F ':' {'print $1'})
# image_to_attest becomes like this: docker.io/tunacinsoy/sba-frontend@sha256:466ef8f59a7ef5081334c0e4082a2c16f01e251eaa08c94d803aeb0ed9684fd6
image_to_attest="${prefix}${image_name}@${DIGEST}"
fi
# In image names, we need to add \ (backslash) before special characters such as "[]\/$*.^[]"
# Hence, sed will work correctly while replacing image names with the updated sha256 format.
escaped_image=$(printf '%s\n' "${image}" | sed -e 's/[]\/$*.^[]/\\&/g')
escaped_image_to_attest=$(printf '%s\n' "${image_to_attest}" |
sed -e 's/[]\/$*.^[]/\\&/g')
echo "Processing $image"
grep -rl $image ./manifests |
# Replace image names with the correct format _> (docker.io/tunacinsoy/sba-frontend@sha256:466ef8f59a7ef5081334c0e4082a2c16f01e251eaa08c94d803aeb0ed9684fd6)
grep -rl $image ../manifests/blog-app |
xargs sed -i "s/${escaped_image}/${escaped_image_to_attest}/g"
done
15 changes: 8 additions & 7 deletions terraform/app.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ resource "kubectl_manifest" "apps" {
# Needs to depend on argocd deployment, since we'll configure it after deployment finishes
depends_on = [kubectl_manifest.argocd]
# for_each iterates over each manifest in the namespace file
for_each = data.kubectl_file_documents.apps.manifests
for_each = data.kubectl_file_documents.apps.manifests
# Applies the content of each manifest to the Kubernetes cluster
yaml_body = each.value
yaml_body = each.value
# Forces the namespace to be set to argocd, ensuring that all resources are created in the correct namespace
override_namespace = "argocd"
}

# MANAGING SECRETS USING External Secrets
# Managing Secrets using ExternalSecrets Operator
# External-Secrets operator for the retrieval of secrets
data "kubectl_file_documents" "external-secrets" {
content = file("../manifests/argocd/external-secrets.yaml")
}

resource "kubectl_manifest" "external-secrets" {
# It needs to depend on argocd creation, since we'll deploy external-secrets right after argocd gets created
#It needs to depend on the creation of ArgoCD, since we'll deploy external-secrets right after ArgoCD is created.
depends_on = [
kubectl_manifest.argocd,
]
Expand All @@ -35,9 +35,10 @@ resource "kubectl_manifest" "external-secrets" {
override_namespace = "argocd"
}

# # File that holds the secret resource that have service account credentials
# File that holds the secret resource that have service account credentials.
# It is used by ClusterSecretStore object to access GCP Secret Manager to retrieve application secrets.
data "kubectl_file_documents" "gcpsm-secret" {
content = file("../manifests/argocd/gcpsm-secret.yaml")
content = file("../manifests/argocd/gcpsm-secret.yaml")
}

resource "kubectl_manifest" "gcpsm-secret" {
Expand All @@ -47,7 +48,7 @@ resource "kubectl_manifest" "gcpsm-secret" {

# ClusterSecretStore resource uses k8s-secret resource to retrieve application secrets from google cloud secret manager
data "kubectl_file_documents" "cluster-secret-store" {
content = file("../manifests/argocd/cluster-secret-store.yaml")
content = file("../manifests/argocd/cluster-secret-store.yaml")
}

resource "kubectl_manifest" "cluster-secret-store" {
Expand Down
21 changes: 12 additions & 9 deletions terraform/argocd.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This hcl file is responsible for the deployment of argocd to the existing gke cluster.

# This ensures that the delay happens only after the GKE cluster has been created
resource "time_sleep" "wait_30_seconds" {
# This ensures that the delay happens only after the GKE cluster has been created
depends_on = [google_container_cluster.main]
create_duration = "30s"
}
Expand All @@ -18,30 +18,33 @@ module "gke_auth" {
use_private_endpoint = false
}

# Manifest file that creates argocd namespace
data "kubectl_file_documents" "namespace" {
content = file("../manifests/argocd/namespace.yaml")
}

data "kubectl_file_documents" "argocd" {
content = file("../manifests/argocd/install.yaml")
}

# Creates argocd namespace within our k8s cluster.
resource "kubectl_manifest" "namespace" {
# for_each iterates over each manifest in the namespace file
for_each = data.kubectl_file_documents.namespace.manifests
for_each = data.kubectl_file_documents.namespace.manifests
# Applies the content of each manifest to the Kubernetes cluster
yaml_body = each.value
yaml_body = each.value
# Forces the namespace to be set to argocd, ensuring that all resources are created in the correct namespace
override_namespace = "argocd"
}

# Installation script for argocd, retrieved from its repository.
data "kubectl_file_documents" "argocd" {
content = file("../manifests/argocd/install.yaml")
}

resource "kubectl_manifest" "argocd" {
# It needs to depend on namespace creation, since we'll deploy argocd into argocd namespace
depends_on = [kubectl_manifest.namespace]
# for_each iterates over each manifest in the namespace file
for_each = data.kubectl_file_documents.argocd.manifests
for_each = data.kubectl_file_documents.argocd.manifests
# Applies the content of each manifest to the Kubernetes cluster
yaml_body = each.value
yaml_body = each.value
# Forces the namespace to be set to argocd, ensuring that all resources are created in the correct namespace
override_namespace = "argocd"
}
23 changes: 18 additions & 5 deletions terraform/binaryauth.tf
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
# For each image we used in deployment.yaml file, we attest them using the keyring that we have created.
# To attest them, we need to retrieve the private key; and for the verification, we need public key.
resource "google_kms_key_ring" "qa-attestor-keyring" {
count = var.branch == "dev" ? 1 : 0
count = var.branch == "dev" ? 1 : 0
name = "qa-attestor-keyring"
location = var.region
lifecycle {
prevent_destroy = false
}
}

# qa-attestor uses the private key to sign images, so that they will be authorized to use in prod environment.
# Remember, we do not check if image is attested or not in dev env, we are actually doing attestation in dev environment in our demo.
module "qa-attestor" {
count = var.branch == "dev" ? 1 : 0
source = "terraform-google-modules/kubernetes-engine/google//modules/binary-authorization"
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
# Using [0] explicitly accesses the first (and possibly only) element of that list.
# This is needed because Terraform treats even a single resource created with a count as a list of resources.
keyring-id = google_kms_key_ring.qa-attestor-keyring[0].id
}

# These policies are applied only for dev environment.
#Following whitelists are ignored, since we can trust images which have these patterns associated with them.
resource "google_binary_authorization_policy" "policy" {
count = var.branch == "dev" ? 1 : 0
admission_whitelist_patterns {
Expand Down Expand Up @@ -44,11 +52,16 @@ resource "google_binary_authorization_policy" "policy" {
admission_whitelist_patterns {
name_pattern = "ghcr.io/external-secrets/*"
}
# This setting enables global policy evaluation, meaning the policy applies to all clusters unless explicitly overridden
global_policy_evaluation_mode = "ENABLE"
# This part specifies the default behavior for images that do not match any whitelist patterns
default_admission_rule {
evaluation_mode = "REQUIRE_ATTESTATION"
# Images must have an attestation, which is a signed statement that verifies the image meets certain criteria
evaluation_mode = "REQUIRE_ATTESTATION"
# This mode enforces the policy by blocking untrusted images and logging the action.
enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"
require_attestations_by = [
# Specifies which attestors (trusted authorities) are required for an image
module.qa-attestor[0].attestor
]
}
Expand Down
2 changes: 1 addition & 1 deletion terraform/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ provider "kubectl" {
token = module.gke_auth.token
# Ensures Terraform uses the connection details provided directly in the
# Terraform configuration (e.g., host, cluster_ca_certificate, token), rather than relying on the local Kubernetes config file (~/.kube/config).
load_config_file = false
load_config_file = false
}

terraform {
Expand Down

0 comments on commit 5f42020

Please sign in to comment.