From f457ce825f1e4d29c1262a9ceca5a54401791075 Mon Sep 17 00:00:00 2001 From: Karteek <120569182+karteekiitg@users.noreply.github.com> Date: Fri, 2 May 2025 18:18:03 +0530 Subject: [PATCH] feat: create GCS bucket to store remote state feat: Add gcloud tofu code to automatically create gcs buckets to use as remote backend. Also setup workload identity federation, and store it to infisical, to use it in github actions. Signed-off-by: Karteek <120569182+karteekiitg@users.noreply.github.com> feat(tofu): modularise gcs-state Create a Google Cloud Storage (GCS) bucket and store the state for doing it in the bucket itself Signed-off-by: Vegard Hagen feat(wif): enable workload identify federation Signed-off-by: Vegard Hagen --- tofu/state/README.md | 34 ++++++++++++++++++++++++ tofu/state/gcs/gcs.tofu | 44 ++++++++++++++++++++++++++++++++ tofu/state/gcs/output.tofu | 7 +++++ tofu/state/gcs/providers.tofu | 24 +++++++++++++++++ tofu/state/gcs/variables.tofu | 12 +++++++++ tofu/state/main.tofu | 6 +++++ tofu/state/output.tofu | 4 +++ tofu/state/providers.tofu | 33 ++++++++++++++++++++++++ tofu/state/variables.auto.tfvars | 4 +++ tofu/state/variables.tofu | 24 +++++++++++++++++ 10 files changed, 192 insertions(+) create mode 100644 tofu/state/README.md create mode 100644 tofu/state/gcs/gcs.tofu create mode 100644 tofu/state/gcs/output.tofu create mode 100644 tofu/state/gcs/providers.tofu create mode 100644 tofu/state/gcs/variables.tofu create mode 100644 tofu/state/main.tofu create mode 100644 tofu/state/output.tofu create mode 100644 tofu/state/providers.tofu create mode 100644 tofu/state/variables.auto.tfvars create mode 100644 tofu/state/variables.tofu diff --git a/tofu/state/README.md b/tofu/state/README.md new file mode 100644 index 0000000..c1a1c5b --- /dev/null +++ b/tofu/state/README.md @@ -0,0 +1,34 @@ +# Google Cloud Storage + +Example configuration for creating [Google Cloud Storage](https://cloud.google.com/storage) bucket for storing an +encrypted Tofu state in. + +## Prerequisites + +Google Cloud Platform project with an active Billing Account. + +## Getting started + +1. Install `gcloud` — Instructions: https://cloud.google.com/sdk/docs/install-sdk +2. Run `gcloud init` +3. Authenticate using `gcloud auth application-default login` + +Before initialising, disable the GCS backend in `providers.tofu` (by e.g. commenting it out) since we can't store the +state in something that doesn't exist yet. + +After the initial `tofy apply` to create the bucket, the state can be migrated by adding + +```terraform +backend "gcs" { + bucket = var.bucket_name + prefix = var.state_prefix +} +``` + +back in and running + +```shell +tofu init -migrate-state +``` + +to migrate the state to the remote GCS bucket. \ No newline at end of file diff --git a/tofu/state/gcs/gcs.tofu b/tofu/state/gcs/gcs.tofu new file mode 100644 index 0000000..596edfc --- /dev/null +++ b/tofu/state/gcs/gcs.tofu @@ -0,0 +1,44 @@ +resource "google_service_account" "tofu_dev_sa" { + account_id = "tofu-dev-sa" + display_name = "Tofu Dev Service Account" + description = "Service account for development tasks using Tofu" +} + +# Bucket names must be globally unique. +resource "google_storage_bucket" "tofu_remote_state" { + name = var.bucket_name + location = var.gcp.region + storage_class = "STANDARD" + + uniform_bucket_level_access = true + public_access_prevention = "enforced" + + versioning { + enabled = true + } + + # `force_destroy = true` must be applied before removing bucket + force_destroy = true + + # Add a lifecycle rule to delete noncurrent versions after 90 days + lifecycle_rule { + action { + type = "Delete" + } + condition { + # Delete noncurrent versions older than 90 days + age = 30 + # Keep up to 100 newer versions + num_newer_versions = 100 + # Apply this rule to noncurrent versions + with_state = "ARCHIVED" + } + } +} + +# This grants the tofu-dev_sa service account administrative permissions over objects in the bucket. +resource "google_storage_bucket_iam_member" "tofu_remote_state_object_admin" { + bucket = google_storage_bucket.tofu_remote_state.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.tofu_dev_sa.email}" +} diff --git a/tofu/state/gcs/output.tofu b/tofu/state/gcs/output.tofu new file mode 100644 index 0000000..9bfaf19 --- /dev/null +++ b/tofu/state/gcs/output.tofu @@ -0,0 +1,7 @@ +output "tofu_service_account" { + value = { + id = google_service_account.tofu_dev_sa.id + name = google_service_account.tofu_dev_sa.name + email = google_service_account.tofu_dev_sa.email + } +} diff --git a/tofu/state/gcs/providers.tofu b/tofu/state/gcs/providers.tofu new file mode 100644 index 0000000..84f3063 --- /dev/null +++ b/tofu/state/gcs/providers.tofu @@ -0,0 +1,24 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.44.0" + } + } + encryption { + key_provider "pbkdf2" "my_passphrase" { + passphrase = var.tofu_encryption_passphrase + } + method "aes_gcm" "my_method" { + keys = key_provider.pbkdf2.my_passphrase + } + state { + method = method.aes_gcm.my_method + enforced = true + } + plan { + method = method.aes_gcm.my_method + enforced = true + } + } +} diff --git a/tofu/state/gcs/variables.tofu b/tofu/state/gcs/variables.tofu new file mode 100644 index 0000000..2d7be9f --- /dev/null +++ b/tofu/state/gcs/variables.tofu @@ -0,0 +1,12 @@ +variable "bucket_name" { + description = "Globally unique name for the GCS bucket" + type = string +} + +variable "gcp" { + description = "Google Cloud project ID and region to deploy resources into" + type = object({ + project_id = string, + region = string + }) +} diff --git a/tofu/state/main.tofu b/tofu/state/main.tofu new file mode 100644 index 0000000..9ef47fd --- /dev/null +++ b/tofu/state/main.tofu @@ -0,0 +1,6 @@ +module "gcs" { + source = "./gcs" + + gcp = var.gcp + bucket_name = var.bucket_name +} diff --git a/tofu/state/output.tofu b/tofu/state/output.tofu new file mode 100644 index 0000000..a87abfe --- /dev/null +++ b/tofu/state/output.tofu @@ -0,0 +1,4 @@ +output "tofu_dev_service_account_email" { + description = "The email of the service account that GitHub Actions will impersonate. Use this for the GCP_SERVICE_ACCOUNT_EMAIL GitHub secret." + value = module.gcs.tofu_service_account.email +} diff --git a/tofu/state/providers.tofu b/tofu/state/providers.tofu new file mode 100644 index 0000000..a39fc6b --- /dev/null +++ b/tofu/state/providers.tofu @@ -0,0 +1,33 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.44.0" + } + } + encryption { + key_provider "pbkdf2" "encryption_passphrase" { + passphrase = var.encryption_passphrase + } + method "aes_gcm" "encryption_method" { + keys = key_provider.pbkdf2.encryption_passphrase + } + state { + method = method.aes_gcm.encryption_method + enforced = true + } + plan { + method = method.aes_gcm.encryption_method + enforced = true + } + } + backend "gcs" { + bucket = var.bucket_name + prefix = var.state_prefix + } +} + +provider "google" { + project = var.gcp.project_id + region = var.gcp.region +} diff --git a/tofu/state/variables.auto.tfvars b/tofu/state/variables.auto.tfvars new file mode 100644 index 0000000..a9cfd32 --- /dev/null +++ b/tofu/state/variables.auto.tfvars @@ -0,0 +1,4 @@ +gcp = { + project_id = "homelab-359819", + region = "europe-west1" +} diff --git a/tofu/state/variables.tofu b/tofu/state/variables.tofu new file mode 100644 index 0000000..342eadc --- /dev/null +++ b/tofu/state/variables.tofu @@ -0,0 +1,24 @@ +variable "encryption_passphrase" { + description = "Encryption passphrase for Tofu state encryption" + type = string + sensitive = true +} + +variable "gcp" { + description = "Google Cloud project ID and region to deploy resources into" + type = object({ + project_id = string, + region = string + }) +} + +variable "bucket_name" { + description = "Globally unique name for GCS bucket" + type = string +} + +variable "state_prefix" { + description = "State prefix in GCS bucket" + type = string + default = "tofu/gcs-state" +}