diff --git a/.github/workflows/go-lint.yaml b/.github/workflows/go-lint.yaml index 681a149f0..a82e3ddb7 100644 --- a/.github/workflows/go-lint.yaml +++ b/.github/workflows/go-lint.yaml @@ -37,7 +37,7 @@ jobs: folder: [helpers/foundation-deployer] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version-file: ${{ matrix.folder }}/go.mod cache-dependency-path: ${{ matrix.folder }}/go.sum diff --git a/0-bootstrap/README.md b/0-bootstrap/README.md index bc105515b..576f87f33 100644 --- a/0-bootstrap/README.md +++ b/0-bootstrap/README.md @@ -288,6 +288,7 @@ Each step has instructions for this change. | billing\_account | The ID of the billing account to associate projects with. | `string` | n/a | yes | | bucket\_force\_destroy | When deleting a bucket, this boolean option will delete all contained objects. If false, Terraform will fail to delete buckets which contain objects. | `bool` | `false` | no | | bucket\_prefix | Name prefix to use for state bucket created. | `string` | `"bkt"` | no | +| bucket\_tfstate\_kms\_force\_destroy | When deleting a bucket, this boolean option will delete the KMS keys used for the Terraform state bucket. | `bool` | `false` | no | | default\_region | Default region to create resources where applicable. | `string` | `"us-central1"` | no | | folder\_prefix | Name prefix to use for folders created. Should be the same in all steps. | `string` | `"fldr"` | no | | group\_billing\_admins | Google Group for GCP Billing Administrators | `string` | n/a | yes | diff --git a/0-bootstrap/cb.tf b/0-bootstrap/cb.tf index 5c726b731..326677a5a 100644 --- a/0-bootstrap/cb.tf +++ b/0-bootstrap/cb.tf @@ -22,6 +22,8 @@ locals { cicd_project_id = module.tf_source.cloudbuild_project_id + state_bucket_kms_key = "projects/${module.seed_bootstrap.seed_project_id}/locations/${var.default_region}/keyRings/${var.project_prefix}-keyring/cryptoKeys/${var.project_prefix}-key" + bucket_self_link_prefix = "https://www.googleapis.com/storage/v1/b/" default_state_bucket_self_link = "${local.bucket_self_link_prefix}${module.seed_bootstrap.gcs_bucket_tfstate}" gcp_projects_state_bucket_self_link = module.gcp_projects_state_bucket.bucket.self_link @@ -74,6 +76,12 @@ module "gcp_projects_state_bucket" { project_id = module.seed_bootstrap.seed_project_id location = var.default_region force_destroy = var.bucket_force_destroy + + encryption = { + default_kms_key_name = local.state_bucket_kms_key + } + + depends_on = [module.seed_bootstrap.gcs_bucket_tfstate] } module "tf_source" { diff --git a/0-bootstrap/main.tf b/0-bootstrap/main.tf index a98cb582f..6ca031abf 100644 --- a/0-bootstrap/main.tf +++ b/0-bootstrap/main.tf @@ -61,6 +61,9 @@ module "seed_bootstrap" { parent_folder = var.parent_folder == "" ? "" : local.parent org_admins_org_iam_permissions = local.org_admins_org_iam_permissions project_prefix = var.project_prefix + encrypt_gcs_bucket_tfstate = true + key_rotation_period = "7776000s" + kms_prevent_destroy = !var.bucket_tfstate_kms_force_destroy project_labels = { environment = "bootstrap" diff --git a/0-bootstrap/sa.tf b/0-bootstrap/sa.tf index 40ba312b7..5af04fd12 100644 --- a/0-bootstrap/sa.tf +++ b/0-bootstrap/sa.tf @@ -98,6 +98,7 @@ locals { "roles/storage.admin", "roles/iam.serviceAccountAdmin", "roles/resourcemanager.projectDeleter", + "roles/cloudkms.admin", ], "org" = [ "roles/storage.objectAdmin", diff --git a/0-bootstrap/variables.tf b/0-bootstrap/variables.tf index cbfd0cf48..b89304e36 100644 --- a/0-bootstrap/variables.tf +++ b/0-bootstrap/variables.tf @@ -82,6 +82,12 @@ variable "bucket_force_destroy" { default = false } +variable "bucket_tfstate_kms_force_destroy" { + description = "When deleting a bucket, this boolean option will delete the KMS keys used for the Terraform state bucket." + type = bool + default = false +} + /* ---------------------------------------- Specific to Groups creation ---------------------------------------- */ diff --git a/3-networks-dual-svpc/README.md b/3-networks-dual-svpc/README.md index ed0be0b0b..d60c6656e 100644 --- a/3-networks-dual-svpc/README.md +++ b/3-networks-dual-svpc/README.md @@ -189,6 +189,7 @@ Run `terraform output cloudbuild_project_id` in the `0-bootstrap` folder to get sed -i "s/REMOTE_STATE_BUCKET/${backend_bucket}/" ./common.auto.tfvars ``` + **Note:** Make sure that you update the `perimeter_additional_members` variable with your e-mail in order to be able to view/access resources in the project protected by the VPC service controls. 1. Commit changes diff --git a/3-networks-dual-svpc/common.auto.example.tfvars b/3-networks-dual-svpc/common.auto.example.tfvars index 0eda0a987..441314aae 100644 --- a/3-networks-dual-svpc/common.auto.example.tfvars +++ b/3-networks-dual-svpc/common.auto.example.tfvars @@ -17,9 +17,9 @@ // The DNS name of peering managed zone. Must end with a period. domain = "example.com." -// Uncomment the following line and add you email in the perimeter_additional_members list. +// Update the following line and add you email in the perimeter_additional_members list. // You must be in this list to be able to view/access resources in the project protected by the VPC service controls. -//perimeter_additional_members = ["user:YOUR-USER-EMAIL@example.com"] +perimeter_additional_members = ["user:YOUR-USER-EMAIL@example.com"] remote_state_bucket = "REMOTE_STATE_BUCKET" diff --git a/3-networks-hub-and-spoke/README.md b/3-networks-hub-and-spoke/README.md index 9a37953a1..bcee1c7d0 100644 --- a/3-networks-hub-and-spoke/README.md +++ b/3-networks-hub-and-spoke/README.md @@ -193,6 +193,7 @@ Run `terraform output cloudbuild_project_id` in the `0-bootstrap` folder to get sed -i "s/REMOTE_STATE_BUCKET/${backend_bucket}/" ./common.auto.tfvars ``` + **Note:** Make sure that you update the `perimeter_additional_members` variable with your e-mail in order to be able to view/access resources in the project protected by the VPC service controls. 1. Commit changes diff --git a/3-networks-hub-and-spoke/common.auto.example.tfvars b/3-networks-hub-and-spoke/common.auto.example.tfvars index e6cb8edbd..9ce52ac7c 100644 --- a/3-networks-hub-and-spoke/common.auto.example.tfvars +++ b/3-networks-hub-and-spoke/common.auto.example.tfvars @@ -17,10 +17,10 @@ // The DNS name of peering managed zone. Must end with a period. domain = "example.com." -// Uncomment the following line and add you email in the perimeter_additional_members list. +// Update the following line and add you email in the perimeter_additional_members list. // You must be in this list to be able to view/access resources in the project protected by the VPC service controls. -//perimeter_additional_members = ["user:YOUR-USER-EMAIL@example.com"] +perimeter_additional_members = ["user:YOUR-USER-EMAIL@example.com"] remote_state_bucket = "REMOTE_STATE_BUCKET" diff --git a/helpers/foundation-deployer/global.tfvars.example b/helpers/foundation-deployer/global.tfvars.example index de2bfec27..168677658 100644 --- a/helpers/foundation-deployer/global.tfvars.example +++ b/helpers/foundation-deployer/global.tfvars.example @@ -39,9 +39,11 @@ group_org_admins = "REPLACE_ME" # "gcp-organization-admins@example.com" group_billing_admins = "REPLACE_ME" # "gcp-billing-admins@example.com" org_project_creators = [] -bucket_force_destroy = false -project_prefix = "prj" -folder_prefix = "fldr" +bucket_force_destroy = false +bucket_tfstate_kms_force_destroy = false + +project_prefix = "prj" +folder_prefix = "fldr" // Optional - for an organization with existing projects or for development/validation. // Uncomment this variable to place all the example foundation resources under diff --git a/helpers/foundation-deployer/stages/apply.go b/helpers/foundation-deployer/stages/apply.go index f2fd3da14..1afaf7cd4 100644 --- a/helpers/foundation-deployer/stages/apply.go +++ b/helpers/foundation-deployer/stages/apply.go @@ -32,18 +32,19 @@ import ( func DeployBootstrapStage(t testing.TB, s steps.Steps, tfvars GlobalTFVars, c CommonConf) error { bootstrapTfvars := BootstrapTfvars{ - OrgID: tfvars.OrgID, - DefaultRegion: tfvars.DefaultRegion, - BillingAccount: tfvars.BillingAccount, - GroupOrgAdmins: tfvars.GroupOrgAdmins, - GroupBillingAdmins: tfvars.GroupBillingAdmins, - OrgProjectCreators: tfvars.OrgProjectCreators, - ParentFolder: tfvars.ParentFolder, - ProjectPrefix: tfvars.ProjectPrefix, - FolderPrefix: tfvars.FolderPrefix, - BucketForceDestroy: tfvars.BucketForceDestroy, - Groups: tfvars.Groups, - InitialGroupConfig: tfvars.InitialGroupConfig, + OrgID: tfvars.OrgID, + DefaultRegion: tfvars.DefaultRegion, + BillingAccount: tfvars.BillingAccount, + GroupOrgAdmins: tfvars.GroupOrgAdmins, + GroupBillingAdmins: tfvars.GroupBillingAdmins, + OrgProjectCreators: tfvars.OrgProjectCreators, + ParentFolder: tfvars.ParentFolder, + ProjectPrefix: tfvars.ProjectPrefix, + FolderPrefix: tfvars.FolderPrefix, + BucketForceDestroy: tfvars.BucketForceDestroy, + BucketTfstateKmsForceDestroy: tfvars.BucketTfstateKmsForceDestroy, + Groups: tfvars.Groups, + InitialGroupConfig: tfvars.InitialGroupConfig, } err := utils.WriteTfvars(filepath.Join(c.FoundationPath, BootstrapStep, "terraform.tfvars"), bootstrapTfvars) diff --git a/helpers/foundation-deployer/stages/data.go b/helpers/foundation-deployer/stages/data.go index f76f3a008..67d4e7be3 100644 --- a/helpers/foundation-deployer/stages/data.go +++ b/helpers/foundation-deployer/stages/data.go @@ -156,6 +156,7 @@ type GlobalTFVars struct { FolderPrefix *string `hcl:"folder_prefix"` CaiMonitoringKmsForceDestroy *bool `hcl:"cai_monitoring_kms_force_destroy"` BucketForceDestroy *bool `hcl:"bucket_force_destroy"` + BucketTfstateKmsForceDestroy *bool `hcl:"bucket_tfstate_kms_force_destroy"` AuditLogsTableDeleteContentsOnDestroy *bool `hcl:"audit_logs_table_delete_contents_on_destroy"` LogExportStorageForceDestroy *bool `hcl:"log_export_storage_force_destroy"` LogExportStorageLocation string `hcl:"log_export_storage_location"` @@ -193,18 +194,19 @@ func (g GlobalTFVars) CheckString(s string) { } type BootstrapTfvars struct { - OrgID string `hcl:"org_id"` - BillingAccount string `hcl:"billing_account"` - GroupOrgAdmins string `hcl:"group_org_admins"` - GroupBillingAdmins string `hcl:"group_billing_admins"` - DefaultRegion string `hcl:"default_region"` - ParentFolder *string `hcl:"parent_folder"` - ProjectPrefix *string `hcl:"project_prefix"` - FolderPrefix *string `hcl:"folder_prefix"` - BucketForceDestroy *bool `hcl:"bucket_force_destroy"` - OrgProjectCreators []string `hcl:"org_project_creators"` - Groups *Groups `hcl:"groups"` - InitialGroupConfig *string `hcl:"initial_group_config"` + OrgID string `hcl:"org_id"` + BillingAccount string `hcl:"billing_account"` + GroupOrgAdmins string `hcl:"group_org_admins"` + GroupBillingAdmins string `hcl:"group_billing_admins"` + DefaultRegion string `hcl:"default_region"` + ParentFolder *string `hcl:"parent_folder"` + ProjectPrefix *string `hcl:"project_prefix"` + FolderPrefix *string `hcl:"folder_prefix"` + BucketForceDestroy *bool `hcl:"bucket_force_destroy"` + BucketTfstateKmsForceDestroy *bool `hcl:"bucket_tfstate_kms_force_destroy"` + OrgProjectCreators []string `hcl:"org_project_creators"` + Groups *Groups `hcl:"groups"` + InitialGroupConfig *string `hcl:"initial_group_config"` } type OrgTfvars struct { diff --git a/helpers/foundation-deployer/stages/validate.go b/helpers/foundation-deployer/stages/validate.go index 3bc9dc4b5..2a3aac52a 100644 --- a/helpers/foundation-deployer/stages/validate.go +++ b/helpers/foundation-deployer/stages/validate.go @@ -105,6 +105,9 @@ func ValidateDestroyFlags(t testing.TB, g GlobalTFVars) { if g.LogExportStorageForceDestroy == nil || !*g.LogExportStorageForceDestroy { flags = append(flags, "log_export_storage_force_destroy") } + if g.BucketTfstateKmsForceDestroy == nil || !*g.BucketTfstateKmsForceDestroy { + flags = append(flags, "bucket_tfstate_kms_force_destroy") + } if g.CaiMonitoringKmsForceDestroy == nil || !*g.CaiMonitoringKmsForceDestroy { flags = append(flags, "cai_monitoring_kms_force_destroy") } diff --git a/test/integration/bootstrap/bootstrap_test.go b/test/integration/bootstrap/bootstrap_test.go index e2c010bf6..3ae75956b 100644 --- a/test/integration/bootstrap/bootstrap_test.go +++ b/test/integration/bootstrap/bootstrap_test.go @@ -48,6 +48,7 @@ func TestBootstrap(t *testing.T) { vars := map[string]interface{}{ "bucket_force_destroy": true, + "bucket_tfstate_kms_force_destroy": true, } temp := tft.NewTFBlueprintTest(t,