From 2eb2b383205a13b08aa05f4c73def48ca64ea76a Mon Sep 17 00:00:00 2001 From: jmpugh Date: Fri, 24 Jul 2026 18:11:59 +0000 Subject: [PATCH 1/4] Add support for FedRAMP Moderate Assured Workloads deployments --- README.md | 2 +- .../data/custom-org-policies/lb_policy.yaml | 64 +++++ .../networking_policy.yaml | 12 + .../custom-org-policies/platform_policy.yaml | 39 ++- .../data/org-policies/cloudkms_policy.yaml | 2 +- .../data/org-policies/lb_policy.yaml | 47 ---- .../data/org-policies/networking_policy.yaml | 16 -- .../data/org-policies/platform_policy.yaml | 25 -- fast/stages-aw/0-bootstrap/kms.tf | 4 +- fast/stages-aw/0-bootstrap/organization.tf | 1 + fast/stages-aw/1-resman/kms.tf | 8 +- .../2-networking-a-fedramp-high/nva.tf | 2 +- .../2-networking-a-fedramp-high/variables.tf | 11 +- fast/stages-aw/3-security/core-dev.tf | 2 +- fast/stages-aw/3-security/core-prod.tf | 2 +- fast/stages-aw/3-security/main.tf | 1 + fast/stages-aw/README.md | 7 +- scripts/clean.sh | 2 +- scripts/deploy.sh | 245 +++++++---------- scripts/destroy.sh | 260 ++++++------------ scripts/restore.sh | 8 +- 21 files changed, 330 insertions(+), 430 deletions(-) create mode 100644 fast/stages-aw/0-bootstrap/data/custom-org-policies/lb_policy.yaml diff --git a/README.md b/README.md index 18464abc9..c0e7af10d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The target audience for Stellar Engine is organizations and teams that operate i Stellar Engine offers several significant benefits, particularly for organizations operating in regulated environments or requiring high levels of compliance and security. Here are the key advantages based on the summary: -- **Pre-Built Compliance Mappings:** The inclusion of documentation mapping NIST 800-53r5 controls for FRH, IL4, and IL5 simplifies the process of achieving compliance. This allows organizations to fast-track their ATO processes by leveraging pre-validated configurations.
+- **Pre-Built Compliance Mappings:** The inclusion of documentation mapping NIST 800-53r5 controls for FRH, FRM, IL4, and IL5 simplifies the process of achieving compliance. This allows organizations to fast-track their ATO processes by leveraging pre-validated configurations.
IaC for Compliance: By embedding compliance requirements into IaC, Stellar Engine ensures that key controls are implemented consistently and automatically.
- **Consistency and Scalability:** Utilizing IaC enables repeatable and reliable deployment of landing zones, ensuring that infrastructure adheres to best practices and compliance standards.
- **Flexibility:** While tailored for Assured Workload overlays, Stellar Engine serves as a foundation for other compliance regimes, making it adaptable to various regulatory requirements.
diff --git a/fast/stages-aw/0-bootstrap/data/custom-org-policies/lb_policy.yaml b/fast/stages-aw/0-bootstrap/data/custom-org-policies/lb_policy.yaml new file mode 100644 index 000000000..159b60ba1 --- /dev/null +++ b/fast/stages-aw/0-bootstrap/data/custom-org-policies/lb_policy.yaml @@ -0,0 +1,64 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +%{ if !contains(["FEDRAMP_MODERATE"], regime) ~} +# This boolean constraint, when enforced, disables creating Cloud Armor security +# policies. By default, you can create Cloud Armor security policies in any +# organization, folder, or project. +# COULD HAVE +compute.disableGlobalCloudArmorPolicy: + rules: + - enforce: true + +# This boolean constraint disables creation of global load balancing products. +# When enforced, only regional load balancing products without global dependencies +# can be created. By default, creation of global load balancing is allowed. +# COULD HAVE +compute.disableGlobalLoadBalancing: + rules: + - enforce: true + +# This boolean constraint, when enforced, disables creation of global +# self-managed SSL Certificates. Creation of google-managed or regional +# self-managed certificates is not disabled by this constraint. By default, +# you can create global self-managed SSL Certificates in any organization, +# folder, or project. +# COULD HAVE +compute.disableGlobalSelfManagedSslCertificate: + rules: + - enforce: true + +# This list constraint defines the set of load balancer types which can be +# created for an organization, folder, or project. Every load balancer type +# to be allowed or denied must be listed explicitly. By default, creation of +# all types of load balancers is allowed. The list of allowed or denied values +# must be identified as the string name of a load balancer, and can only +# include values from the list below: +# [ INTERNAL_TCP_UDP, INTERNAL_HTTP_HTTPS, EXTERNAL_NETWORK_TCP_UDP, +# EXTERNAL_TCP_PROXY, EXTERNAL_SSL_PROXY, EXTERNAL_HTTP_HTTPS, +# EXTERNAL_MANAGED_HTTP_HTTPS, REGIONAL_INTERNAL_MANAGED_TCP_PROXY, +# REGIONAL_EXTERNAL_MANAGED_TCP_PROXY, +# GLOBAL_EXTERNAL_MANAGED_HTTP_HTTPS]. +# To include all internal or all external load balancer types, use the +# in: prefix followed by INTERNAL or EXTERNAL. For example, allowing +# in:INTERNAL will allow all load balancer types from the above list that +# include INTERNAL. +# COULD HAVE +compute.restrictLoadBalancerCreationForTypes: + rules: + - allow: + values: + - in:INTERNAL +%{ endif ~} diff --git a/fast/stages-aw/0-bootstrap/data/custom-org-policies/networking_policy.yaml b/fast/stages-aw/0-bootstrap/data/custom-org-policies/networking_policy.yaml index 01d8f6293..4097e6284 100644 --- a/fast/stages-aw/0-bootstrap/data/custom-org-policies/networking_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/custom-org-policies/networking_policy.yaml @@ -73,3 +73,15 @@ compute.restrictVpcPeering: values: - under:organizations/${organization_id} - under:folders/832634261155 # https://autonomousthingz-life.medium.com/google-cloud-organisation-policies-get-the-restrict-vpc-peering-usage-organisation-policy-d87dce84a5e7 + +%{ if !contains(["FEDRAMP_MODERATE"], regime) ~} +# This list constraint defines the type of protocol forwarding rule objects +# with target instance that a user can create. +# COULD HAVE +compute.restrictProtocolForwardingCreationForTypes: + rules: + - allow: + values: + - INTERNAL +%{ endif ~} + diff --git a/fast/stages-aw/0-bootstrap/data/custom-org-policies/platform_policy.yaml b/fast/stages-aw/0-bootstrap/data/custom-org-policies/platform_policy.yaml index 408b6a316..56db3489e 100644 --- a/fast/stages-aw/0-bootstrap/data/custom-org-policies/platform_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/custom-org-policies/platform_policy.yaml @@ -100,6 +100,7 @@ essentialcontacts.managed.allowedContactDomains: - enforce: true +%{ if !contains(["FEDRAMP_MODERATE", "FRM"], regime) ~} # Policy: compute.managed.restrictProtocolForwardingCreationForTypes # Description: Restricts the creation of protocol forwarding. # You might need to adjust rules based on your exact requirements, @@ -107,6 +108,7 @@ essentialcontacts.managed.allowedContactDomains: compute.managed.restrictProtocolForwardingCreationForTypes: rules: - enforce: true +%{ endif ~} # Policy: iam.managed.preventPrivilegedBasicRolesForDefaultServiceAccounts # Description: Prevents default service accounts from being granted privileged basic roles. @@ -124,4 +126,39 @@ iam.managed.disableServiceAccountKeyCreation: # Description: Disables the upload of service account keys. iam.managed.disableServiceAccountKeyUpload: rules: - - enforce: true \ No newline at end of file + - enforce: true + +# This list constraint defines the set of locations where location-based Google +# Cloud resources can be created. By default, resources can be created in any +# location. Policies for this constraint can specify multi-regions such as asia +# and europe, regions such as us-east1 or europe-west1 as allowed or denied +# locations. Allowing or denying a multi-region does not imply that all included +# sub-locations should also be allowed or denied. For example, if the policy +# denies the us multi-region (which refers to multi-region resources, like some +# storage services), resources can still be created in the regional location +# us-east1. On the other hand, the in:us-locations group contains all locations +# within the us region, and can be used to block every region. We recommend +# using value groups to define your policy. You can specify value groups, +# collections of locations that are curated by Google to provide a simple way +# to define your resource locations. To use value groups in your organization +# policy, prefix your entries with the string in:, followed by the value group. +# For example, to create resources that will only be physically located within +# the US, set in:us-locations in the list of allowed values. If the +# suggested_value field is used in a location policy, it should be a region. +# If the value specified is a region, a UI for a zonal resource may pre-populate +# any zone in that region. +# SHOULD HAVE +%{ if !contains(["FEDRAMP_MODERATE"], regime) ~} +gcp.resourceLocations: + rules: + - allow: + values: + - in:us-locations +%{ else ~} +gcp.resourceLocations: + rules: + - deny: + values: + - in:asia-east2-locations + - in:global +%{ endif ~} diff --git a/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml b/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml index eec9081c6..97f75be3b 100644 --- a/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml @@ -88,4 +88,4 @@ gcp.restrictNonCmekServices: custom.kmsRotation: rules: - - enforce: true \ No newline at end of file + - enforce: true diff --git a/fast/stages-aw/0-bootstrap/data/org-policies/lb_policy.yaml b/fast/stages-aw/0-bootstrap/data/org-policies/lb_policy.yaml index 268a672c3..fe34a79aa 100644 --- a/fast/stages-aw/0-bootstrap/data/org-policies/lb_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/org-policies/lb_policy.yaml @@ -13,53 +13,6 @@ # limitations under the License. --- -# This boolean constraint, when enforced, disables creating Cloud Armor security -# policies. By default, you can create Cloud Armor security policies in any -# organization, folder, or project. -# COULD HAVE -compute.disableGlobalCloudArmorPolicy: - rules: - - enforce: true - -# This boolean constraint disables creation of global load balancing products. -# When enforced, only regional load balancing products without global dependencies -# can be created. By default, creation of global load balancing is allowed. -# COULD HAVE -compute.disableGlobalLoadBalancing: - rules: - - enforce: true - -# This boolean constraint, when enforced, disables creation of global -# self-managed SSL Certificates. Creation of google-managed or regional -# self-managed certificates is not disabled by this constraint. By default, -# you can create global self-managed SSL Certificates in any organization, -# folder, or project. -# COULD HAVE -compute.disableGlobalSelfManagedSslCertificate: - rules: - - enforce: true - -# This list constraint defines the set of load balancer types which can be -# created for an organization, folder, or project. Every load balancer type -# to be allowed or denied must be listed explicitly. By default, creation of -# all types of load balancers is allowed. The list of allowed or denied values -# must be identified as the string name of a load balancer, and can only -# include values from the list below: -# [ INTERNAL_TCP_UDP, INTERNAL_HTTP_HTTPS, EXTERNAL_NETWORK_TCP_UDP, -# EXTERNAL_TCP_PROXY, EXTERNAL_SSL_PROXY, EXTERNAL_HTTP_HTTPS, -# EXTERNAL_MANAGED_HTTP_HTTPS, REGIONAL_INTERNAL_MANAGED_TCP_PROXY, -# REGIONAL_EXTERNAL_MANAGED_TCP_PROXY, -# GLOBAL_EXTERNAL_MANAGED_HTTP_HTTPS]. -# To include all internal or all external load balancer types, use the -# in: prefix followed by INTERNAL or EXTERNAL. For example, allowing -# in:INTERNAL will allow all load balancer types from the above list that -# include INTERNAL. -# COULD HAVE -compute.restrictLoadBalancerCreationForTypes: - rules: - - allow: - values: - - in:INTERNAL # This boolean constraint, when enforced, disables turning on Identity-Aware # Proxy on global resources. Enabling IAP on regional resources is not diff --git a/fast/stages-aw/0-bootstrap/data/org-policies/networking_policy.yaml b/fast/stages-aw/0-bootstrap/data/org-policies/networking_policy.yaml index 9580fca86..80e254a52 100644 --- a/fast/stages-aw/0-bootstrap/data/org-policies/networking_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/org-policies/networking_policy.yaml @@ -81,22 +81,6 @@ compute.requireVpcFlowLogs: values: - COMPREHENSIVE -# This list constraint defines the type of protocol forwarding rule objects -# with target instance that a user can create. When this constraint is enforced, -# new forwarding rule objects with target instance will be limited to internal -# and/or external IP addresses, based on the types specified. The types to be -# allowed or denied must be listed explicitly. By default, creation of both -# internal and external protocol forwarding rule objects with target instance -# are allowed. -# The list of allowed or denied values can only include values from the list -# below: [INTERNAL, EXTERNAL]. . -# COULD HAVE -compute.restrictProtocolForwardingCreationForTypes: - rules: - - allow: - values: - - INTERNAL - # This list constraint defines the set of VPC networks that are allowed to be # peered with the VPC networks belonging to this project, folder, or # organization. By default, a Network Admin for one network can peer with any diff --git a/fast/stages-aw/0-bootstrap/data/org-policies/platform_policy.yaml b/fast/stages-aw/0-bootstrap/data/org-policies/platform_policy.yaml index 8dc07bf2a..02b758513 100644 --- a/fast/stages-aw/0-bootstrap/data/org-policies/platform_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/org-policies/platform_policy.yaml @@ -36,31 +36,6 @@ gcp.disableCloudLogging: rules: - enforce: false -# This list constraint defines the set of locations where location-based Google -# Cloud resources can be created. By default, resources can be created in any -# location. Policies for this constraint can specify multi-regions such as asia -# and europe, regions such as us-east1 or europe-west1 as allowed or denied -# locations. Allowing or denying a multi-region does not imply that all included -# sub-locations should also be allowed or denied. For example, if the policy -# denies the us multi-region (which refers to multi-region resources, like some -# storage services), resources can still be created in the regional location -# us-east1. On the other hand, the in:us-locations group contains all locations -# within the us region, and can be used to block every region. We recommend -# using value groups to define your policy. You can specify value groups, -# collections of locations that are curated by Google to provide a simple way -# to define your resource locations. To use value groups in your organization -# policy, prefix your entries with the string in:, followed by the value group. -# For example, to create resources that will only be physically located within -# the US, set in:us-locations in the list of allowed values.If the -# suggested_value field is used in a location policy, it should be a region. -# If the value specified is a region, a UI for a zonal resource may pre-populate -# any zone in that region. -# SHOULD HAVE -gcp.resourceLocations: - rules: - - allow: - values: - - in:us-locations # This constraint defines the set of Google Cloud resource services that can be # used within an organization, folder, or project, such as compute.googleapis.com diff --git a/fast/stages-aw/0-bootstrap/kms.tf b/fast/stages-aw/0-bootstrap/kms.tf index 569804eee..10632529e 100644 --- a/fast/stages-aw/0-bootstrap/kms.tf +++ b/fast/stages-aw/0-bootstrap/kms.tf @@ -15,7 +15,7 @@ locals { version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" } } module "logging-kms" { @@ -75,4 +75,4 @@ module "gcs-kms" { "serviceAccount:${var.prefix}-prod-resman-0r@${var.prefix}-prod-iac-core-0.iam.gserviceaccount.com" ] } -} \ No newline at end of file +} diff --git a/fast/stages-aw/0-bootstrap/organization.tf b/fast/stages-aw/0-bootstrap/organization.tf index 87165c43a..fd497f4a0 100644 --- a/fast/stages-aw/0-bootstrap/organization.tf +++ b/fast/stages-aw/0-bootstrap/organization.tf @@ -115,6 +115,7 @@ locals { customer_id : var.organization.customer_id drs_tag_name : local.drs_tag_name allowed_domains : var.org_policies_config.constraints.allowed_policy_member_domains + regime : var.assured_workloads.regime })) ] )...) diff --git a/fast/stages-aw/1-resman/kms.tf b/fast/stages-aw/1-resman/kms.tf index 59d4d322c..b67435d04 100644 --- a/fast/stages-aw/1-resman/kms.tf +++ b/fast/stages-aw/1-resman/kms.tf @@ -12,6 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +locals { + protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" +} + module "tenant-project-keys" { source = "../../../modules/kms" project_id = module.tenant-self-iac-projects[each.key].project_id @@ -35,7 +39,7 @@ module "tenant-project-keys" { rotation_period = "7776000s" # CIS Compliance Benchmark 1.10 version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = local.protection_level } }, default = { @@ -45,7 +49,7 @@ module "tenant-project-keys" { rotation_period = "7776000s" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = local.protection_level } } } diff --git a/fast/stages-aw/2-networking-a-fedramp-high/nva.tf b/fast/stages-aw/2-networking-a-fedramp-high/nva.tf index d16e6a5b0..65ddea54f 100644 --- a/fast/stages-aw/2-networking-a-fedramp-high/nva.tf +++ b/fast/stages-aw/2-networking-a-fedramp-high/nva.tf @@ -200,7 +200,7 @@ module "kms" { purpose = "ENCRYPT_DECRYPT" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" } } } diff --git a/fast/stages-aw/2-networking-a-fedramp-high/variables.tf b/fast/stages-aw/2-networking-a-fedramp-high/variables.tf index 40aaf03f8..eb7c22cf8 100644 --- a/fast/stages-aw/2-networking-a-fedramp-high/variables.tf +++ b/fast/stages-aw/2-networking-a-fedramp-high/variables.tf @@ -202,9 +202,14 @@ variable "billing_override" { } variable "assured_workloads" { - description = "Assured Workloads configuration." - type = any - default = null + description = "Configuration for Assured Workloads." + type = object({ + regime = optional(string) + location = optional(string) + folder = optional(string) + }) + nullable = false + default = {} } variable "common_services_folder" { diff --git a/fast/stages-aw/3-security/core-dev.tf b/fast/stages-aw/3-security/core-dev.tf index d51e9b6fb..fed3448a7 100644 --- a/fast/stages-aw/3-security/core-dev.tf +++ b/fast/stages-aw/3-security/core-dev.tf @@ -56,7 +56,7 @@ module "dev-sec-kms" { name = "dev-${each.key}" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = local.kms_protection_level } } keys = local.kms_locations_keys[each.key] diff --git a/fast/stages-aw/3-security/core-prod.tf b/fast/stages-aw/3-security/core-prod.tf index f4d7cb156..20466021e 100644 --- a/fast/stages-aw/3-security/core-prod.tf +++ b/fast/stages-aw/3-security/core-prod.tf @@ -55,7 +55,7 @@ module "prod-sec-kms" { name = "prod-${each.key}" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = "HSM" + protection_level = local.kms_protection_level } } keys = local.kms_locations_keys[each.key] diff --git a/fast/stages-aw/3-security/main.tf b/fast/stages-aw/3-security/main.tf index a72bd7b78..11f63aa1b 100644 --- a/fast/stages-aw/3-security/main.tf +++ b/fast/stages-aw/3-security/main.tf @@ -47,6 +47,7 @@ locals { if contains(v.locations, loc) } } + kms_protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" project_services = [ "cloudkms.googleapis.com", "secretmanager.googleapis.com", diff --git a/fast/stages-aw/README.md b/fast/stages-aw/README.md index 0f4f58ba0..05e1230c4 100644 --- a/fast/stages-aw/README.md +++ b/fast/stages-aw/README.md @@ -33,9 +33,10 @@ To destroy a previous FAST deployment follow the instructions detailed in [clean ## Networking (2) -- [IL5 Compliant](2-networking-b-il5-ngfw/README.md) -- [FedRAMP High Compliant](2-networking-a-fedramp-high/README.md) - Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. Currently, two networking options (IL5 and FedRAMP High Compliant) are available, with a third lightweight networking option currently being developed, with recommended usage in IL2 and FedRAMP Moderate environments. +- [FedRAMP High/Moderate Compliant](2-networking-a-fedramp-high/README.md) +- [IL5/IL4 Compliant](2-networking-b-il5-ngfw/README.md) + +Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. Currently, two networking options (IL5 and FedRAMP High Compliant) are available, with a third lightweight networking option currently being developed, with recommended usage in IL2 and FedRAMP Moderate environments. ## Security (3) diff --git a/scripts/clean.sh b/scripts/clean.sh index 099c0316f..d925938d8 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -170,4 +170,4 @@ if promptUser "Would you like to also clean up all local Terraform state and con log_info "Local cleanup completed." fi -log_info "Clean.sh completed." \ No newline at end of file +log_info "Clean.sh completed." diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 058c04172..8ee8149fa 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -116,6 +116,92 @@ validate_env_vars() { return 0 } +deploy_networking() { + if handle_prompt_if "Would you like to pull the remote tfvars files created in Stages 0 and 1?"; then + gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/providers/2-networking-providers.tf ./ + gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ + gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json ./ + gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ + fi + + handle_prompt "Would you like to perform the terraform init?" "terraform init" || true + + # Check for existing projects to import + if gcloud projects describe "${PREFIX}-net-vdss-host" >/dev/null 2>&1; then + log_info "Project ${PREFIX}-net-vdss-host already exists." + if handle_prompt_if "Would you like to import it into Terraform state?"; then + if terraform state list | grep -q "module.vdss-host-project.google_project.project\[0\]"; then + log_info "Removing module.vdss-host-project.google_project.project[0] from state to force re-import..." + terraform state rm module.vdss-host-project.google_project.project[0] || true + fi + log_info "Importing ${PREFIX}-net-vdss-host..." + terraform import module.vdss-host-project.google_project.project[0] "${PREFIX}-net-vdss-host" || true + fi + fi + + # Check for existing VPCs + for vpc in "vdss-dmz-0" "vdss-landing-0"; do + if gcloud compute networks describe "$vpc" --project="${PREFIX}-net-vdss-host" >/dev/null 2>&1; then + log_info "VPC $vpc already exists." + if handle_prompt_if "Would you like to import $vpc?"; then + # Map VPC name to module name + module_name="dmz-vpc" + if [[ "$vpc" == "vdss-landing-0" ]]; then module_name="vdss-vpc"; fi + + if terraform state list | grep -q "module.${module_name}.google_compute_network.network\[0\]"; then + log_info "Removing module.${module_name}.google_compute_network.network[0] from state to force re-import..." + terraform state rm "module.${module_name}.google_compute_network.network[0]" || true + fi + terraform import module.${module_name}.google_compute_network.network[0] "${PREFIX}-net-vdss-host/${vpc}" || true + fi + fi + done + + if handle_prompt_if "Would you like to perform the terraform apply?"; then + apply_success=false + max_attempts=3 + attempt=1 + + while [[ $attempt -le $max_attempts ]] && [[ "$apply_success" == "false" ]]; do + log_info "Terraform apply attempt $attempt/$max_attempts" + + if terraform apply -auto-approve; then + apply_success=true + log_info "Terraform apply completed successfully" + else + if [[ $attempt -lt $max_attempts ]]; then + # Check if it's a peering error (common timing issue) + if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then + log_warn "Terraform apply failed, likely due to network peering timing issues" + log_info "Waiting 60 seconds for network operations to settle..." + sleep 60 + ((attempt++)) + else + log_warn "Terraform apply failed with non-peering error" + echo 'If you receive an error relating to a service account not existing, please click "Settings" in gcs within the project, and it will generate the service account for you.' + if handle_prompt_if "Would you like to retry the apply (attempt $((attempt+1))/$max_attempts)?"; then + ((attempt++)) + else + break + fi + fi + else + log_error "Terraform apply failed after $max_attempts attempts" + if handle_prompt_if "Would you like to try one more manual attempt?"; then + terraform apply -auto-approve && apply_success=true + fi + break + fi + fi + done + + if [[ "$apply_success" == "false" ]]; then + log_error "Unable to complete terraform apply. You may need to investigate manually." + exit 1 + fi + fi +} + ############ Prerequisites ############ log_info "Welcome to the Stellar Engine automated deployment!" log_info "This is designed for the initial deployment." @@ -591,7 +677,7 @@ fast_features = { } assured_workloads = { - regime = "${COMPLIANCE_REGIME}" # "IL4, IL5, FEDRAMP_HIGH, etc... if you wish to not use assured_workloads, set this value to COMPLIANCE_REGIME_UNSPECIFIED" + regime = "${COMPLIANCE_REGIME}" # "IL4, IL5, FEDRAMP_HIGH, FEDRAMP_MODERATE, etc... if you wish to not use assured_workloads, set this value to COMPLIANCE_REGIME_UNSPECIFIED" location = "${REGION}" # Uses the same region as other resources for consistency - change to match your regions.primary if different } @@ -965,169 +1051,26 @@ if promptUser "Stage 2 - Networking -"; then cd "${SCRIPT_DIR}"/../fast/stages-aw/1-resman || exit echo "Please type \"1\", \"2\", or \"3\" below that corresponds to the network paradigm you want: " - echo "1) IL2/FedRAMP Moderate" - echo "2) FedRAMP High" + echo "1) IL2" + echo "2) FedRAMP High/Moderate" echo "3) IL4/IL5" read -r choice - ########### IL2/FedRAMP Moderate ########### + ########### IL2 ########### if [ "$choice" == 1 ]; then echo "This stage is still under development. Goodbye!" - ########### FedRAMP High ########### + ########### FedRAMP High/Moderate ########### elif [ "$choice" == 2 ]; then - echo "You have selected FedRAMP High" + echo "You have selected FedRAMP High/Moderate" cd "${SCRIPT_DIR}"/../fast/stages-aw/2-networking-a-fedramp-high || exit - - if handle_prompt_if "Would you like to pull the remote tfvars files created in Stages 0 and 1?"; then - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/providers/2-networking-providers.tf ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ - fi - - handle_prompt "Would you like to perform the terraform init?" "terraform init" || true - - # Check for existing projects to import - if gcloud projects describe "${PREFIX}-net-vdss-host" >/dev/null 2>&1; then - log_info "Project ${PREFIX}-net-vdss-host already exists." - if handle_prompt_if "Would you like to import it into Terraform state?"; then - if terraform state list | grep -q "module.vdss-host-project.google_project.project\[0\]"; then - log_info "Removing module.vdss-host-project.google_project.project[0] from state to force re-import..." - terraform state rm module.vdss-host-project.google_project.project[0] || true - fi - log_info "Importing ${PREFIX}-net-vdss-host..." - terraform import module.vdss-host-project.google_project.project[0] "${PREFIX}-net-vdss-host" || true - fi - fi - - # Check for existing VPCs - for vpc in "vdss-dmz-0" "vdss-landing-0"; do - if gcloud compute networks describe "$vpc" --project="${PREFIX}-net-vdss-host" >/dev/null 2>&1; then - log_info "VPC $vpc already exists." - if handle_prompt_if "Would you like to import $vpc?"; then - # Map VPC name to module name - module_name="dmz-vpc" - if [[ "$vpc" == "vdss-landing-0" ]]; then module_name="vdss-vpc"; fi - - if terraform state list | grep -q "module.${module_name}.google_compute_network.network\[0\]"; then - log_info "Removing module.${module_name}.google_compute_network.network[0] from state to force re-import..." - terraform state rm "module.${module_name}.google_compute_network.network[0]" || true - fi - terraform import module.${module_name}.google_compute_network.network[0] "${PREFIX}-net-vdss-host/${vpc}" || true - fi - fi - done - - if handle_prompt_if "Would you like to perform the terraform apply?"; then - apply_success=false - max_attempts=3 - attempt=1 - - while [[ $attempt -le $max_attempts ]] && [[ "$apply_success" == "false" ]]; do - log_info "Terraform apply attempt $attempt/$max_attempts" - - if terraform apply -auto-approve; then - apply_success=true - log_info "Terraform apply completed successfully" - else - if [[ $attempt -lt $max_attempts ]]; then - # Check if it's a peering error (common timing issue) - if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then - log_warn "Terraform apply failed, likely due to network peering timing issues" - log_info "Waiting 60 seconds for network operations to settle..." - sleep 60 - ((attempt++)) - else - log_warn "Terraform apply failed with non-peering error" - # Potential Service Account/KMS bugs - echo 'If you receive an error relating to a service account not existing, please click "Settings" in gcs within the project, and it will generate the service account for you.' - if handle_prompt_if "Would you like to retry the apply (attempt $((attempt+1))/$max_attempts)?"; then - ((attempt++)) - else - break - fi - fi - else - log_error "Terraform apply failed after $max_attempts attempts" - if handle_prompt_if "Would you like to try one more manual attempt?"; then - terraform apply -auto-approve && apply_success=true - fi - break - fi - fi - done - - if [[ "$apply_success" == "false" ]]; then - log_error "Unable to complete terraform apply. You may need to investigate manually." - exit 1 - fi - fi + deploy_networking ########### IL4/IL5 ########### elif [ "$choice" == 3 ]; then echo "You have selected IL5" cd "${SCRIPT_DIR}"/../fast/stages-aw/2-networking-b-il5-ngfw || exit - - # cmd=("./pre-redeploy.sh") - # handle_prompt "If this is a redeployment (<30 days), would you like to run the redeploy script?" "${cmd[@]}" - - if handle_prompt_if "Would you like to pull the remote tfvars files created in Stages 0 and 1?"; then - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/providers/2-networking-providers.tf ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json ./ - gcloud storage cp gs://"${PREFIX}"-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ - fi - - handle_prompt "Would you like to perform the terraform init?" "terraform init" || true - - cmd=("terraform apply -auto-approve -target google_project_iam_custom_role.ngfw-custom-role") - handle_prompt "Would you like to perform the targeted terraform apply?" "${cmd[@]}" || true - - if handle_prompt_if "Would you like to perform the full terraform apply?"; then - apply_success=false - max_attempts=3 - attempt=1 - - while [[ $attempt -le $max_attempts ]] && [[ "$apply_success" == "false" ]]; do - log_info "Terraform apply attempt $attempt/$max_attempts" - - if terraform apply -auto-approve; then - apply_success=true - log_info "Terraform apply completed successfully" - else - if [[ $attempt -lt $max_attempts ]]; then - # Check if it's a peering error (common timing issue) - if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then - log_warn "Terraform apply failed, likely due to network peering timing issues" - log_info "Waiting 60 seconds for network operations to settle..." - sleep 60 - ((attempt++)) - else - log_warn "Terraform apply failed with non-peering error" - # Potential Service Account/KMS bugs - echo 'If you receive an error relating to a service account not existing, please click "Settings" in gcs within the project, and it will generate the service account for you.' - if handle_prompt_if "Would you like to retry the apply (attempt $((attempt+1))/$max_attempts)?"; then - ((attempt++)) - else - break - fi - fi - else - log_error "Terraform apply failed after $max_attempts attempts" - if handle_prompt_if "Would you like to try one more manual attempt?"; then - terraform apply -auto-approve && apply_success=true - fi - break - fi - fi - done - - if [[ "$apply_success" == "false" ]]; then - log_error "Unable to complete terraform apply. You may need to investigate manually." - exit 1 - fi - fi + deploy_networking fi echo "Congratulations, you have completed Stage 2!" diff --git a/scripts/destroy.sh b/scripts/destroy.sh index 4c6cf6ecf..4f7b0dea9 100755 --- a/scripts/destroy.sh +++ b/scripts/destroy.sh @@ -265,6 +265,92 @@ promptUser() { done } +destroy_networking() { + missing_files=() + [[ ! -f "0-globals.auto.tfvars.json" ]] && missing_files+=("0-globals.auto.tfvars.json") + [[ ! -f "2-networking-providers.tf" ]] && missing_files+=("2-networking-providers.tf") + + if [[ ${#missing_files[@]} -gt 0 ]]; then + log_warn "The following config files are missing: ${missing_files[*]}" + if promptUser "Attempt to pull from GCS?"; then + gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/providers/2-networking-providers.tf" ./ 2>/dev/null || log_warn "Failed to pull providers" + gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull globals" + gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull bootstrap vars" + gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull resman vars" + fi + fi + + if promptUser "Would you like to run terraform destroy?"; then + # Ensure backend is initialized before destroy + log_info "Initializing Terraform for Stage 2..." + terraform init -reconfigure + destroy_success=false + max_attempts=3 + attempt=1 + + while [[ $attempt -le $max_attempts ]] && [[ "$destroy_success" == "false" ]]; do + log_info "Terraform destroy attempt $attempt/$max_attempts" + + if terraform destroy -auto-approve; then + destroy_success=true + log_info "Terraform destroy completed successfully" + else + if [[ $attempt -lt $max_attempts ]]; then + log_warn "Terraform destroy failed, checking for common issues..." + + # Check for peering errors (common during network destruction) + if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then + log_warn "Likely network peering timing issues, waiting for resources to settle..." + log_info "Waiting 60 seconds for network resource deletion..."; sleep 60 + ((attempt++)) + else + log_warn "Terraform destroy failed with non-peering error" + if promptUser "Would you like to retry destroy (attempt $((attempt+1))/$max_attempts)?"; then + ((attempt++)) + else + break + fi + fi + else + log_error "Terraform destroy failed after $max_attempts attempts" + if promptUser "Would you like to try one more manual attempt?"; then + terraform destroy -auto-approve && destroy_success=true + fi + break + fi + fi + done + + if [[ "$destroy_success" == "false" ]]; then + log_error "Unable to complete terraform destroy. Manual cleanup may be required." + fi + fi + + if promptUser "Would you like to delete your .terraform dir and related files?"; then + # Comprehensive terraform cleanup for stage 2 + if [[ -d ".terraform" ]]; then + rm -rf .terraform + log_info "Deleted .terraform directory" + else + log_warn ".terraform directory does not exist" + fi + + # Remove terraform lock file + if [[ -f ".terraform.lock.hcl" ]]; then + rm -f .terraform.lock.hcl + log_info "Deleted .terraform.lock.hcl" + else + log_warn ".terraform.lock.hcl does not exist" + fi + + # Remove any backup state files + if [[ -f "terraform.tfstate.backup" ]]; then + rm -f terraform.tfstate.backup + log_info "Deleted terraform.tfstate.backup" + fi + fi +} + ########### DANGER: DESTRUCTIVE OPERATIONS ############ log_warn "=== WARNING: DESTRUCTIVE SCRIPT ===" log_warn "This script will DELETE your ENTIRE environment!" @@ -565,8 +651,8 @@ fi if [[ "${skip_stage_2:-}" != "true" ]] && promptUser "Stage 2 - Networking"; then # Choose networking paradigm echo "Please type \"1\", \"2\", or \"3\" below that corresponds to the network paradigm you want: " - echo "1) IL2/FedRAMP Moderate" - echo "2) FedRAMP High" + echo "1) IL2" + echo "2) FedRAMP High/Moderate" echo "3) IL4/IL5" read -r choice @@ -577,178 +663,12 @@ if [[ "${skip_stage_2:-}" != "true" ]] && promptUser "Stage 2 - Networking"; the ########### FedRAMP High ########### elif [ "$choice" == 2 ]; then cd "${SCRIPT_DIR}"/../fast/stages-aw/2-networking-a-fedramp-high || exit - - # Pull config if missing - missing_files=() - [[ ! -f "0-globals.auto.tfvars.json" ]] && missing_files+=("0-globals.auto.tfvars.json") - [[ ! -f "2-networking-providers.tf" ]] && missing_files+=("2-networking-providers.tf") - - if [[ ${#missing_files[@]} -gt 0 ]]; then - log_warn "The following config files are missing: ${missing_files[*]}" - if promptUser "Attempt to pull from GCS?"; then - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/providers/2-networking-providers.tf" ./ 2>/dev/null || log_warn "Failed to pull providers" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull globals" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull bootstrap vars" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull resman vars" - fi - fi - - if promptUser "Would you like to run terraform destroy?"; then - # Ensure backend is initialized before destroy - log_info "Initializing Terraform for Stage 2..." - terraform init -reconfigure - destroy_success=false - max_attempts=3 - attempt=1 - - while [[ $attempt -le $max_attempts ]] && [[ "$destroy_success" == "false" ]]; do - log_info "Terraform destroy attempt $attempt/$max_attempts" - - if terraform destroy -auto-approve; then - destroy_success=true - log_info "Terraform destroy completed successfully" - else - if [[ $attempt -lt $max_attempts ]]; then - log_warn "Terraform destroy failed, checking for common issues..." - - # Check for peering errors (common during network destruction) - if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then - log_warn "Likely network peering timing issues, waiting for resources to settle..." - log_info "Waiting 60 seconds for network resource deletion..."; sleep 60 - ((attempt++)) - else - log_warn "Terraform destroy failed with non-peering error" - if promptUser "Would you like to retry destroy (attempt $((attempt+1))/$max_attempts)?"; then - ((attempt++)) - else - break - fi - fi - else - log_error "Terraform destroy failed after $max_attempts attempts" - if promptUser "Would you like to try one more manual attempt?"; then - terraform destroy -auto-approve && destroy_success=true - fi - break - fi - fi - done - - if [[ "$destroy_success" == "false" ]]; then - log_error "Unable to complete terraform destroy. Manual cleanup may be required." - fi - fi - - if promptUser "Would you like to delete your .terraform dir and related files?"; then - # Comprehensive terraform cleanup for stage 2 - FedRAMP High - if [[ -d ".terraform" ]]; then - rm -rf .terraform - log_info "Deleted .terraform directory" - else - log_warn ".terraform directory does not exist" - fi - - # Remove terraform lock file - if [[ -f ".terraform.lock.hcl" ]]; then - rm -f .terraform.lock.hcl - log_info "Deleted .terraform.lock.hcl" - else - log_warn ".terraform.lock.hcl does not exist" - fi - - # Remove any backup state files - if [[ -f "terraform.tfstate.backup" ]]; then - rm -f terraform.tfstate.backup - log_info "Deleted terraform.tfstate.backup" - fi - fi + destroy_networking ########### IL4/IL5 ########### elif [ "$choice" == 3 ]; then cd "${SCRIPT_DIR}"/../fast/stages-aw/2-networking-b-il5-ngfw || exit - - # Pull config if missing - missing_files=() - [[ ! -f "0-globals.auto.tfvars.json" ]] && missing_files+=("0-globals.auto.tfvars.json") - [[ ! -f "2-networking-providers.tf" ]] && missing_files+=("2-networking-providers.tf") - - if [[ ${#missing_files[@]} -gt 0 ]]; then - log_warn "The following config files are missing: ${missing_files[*]}" - if promptUser "Attempt to pull from GCS?"; then - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/providers/2-networking-providers.tf" ./ 2>/dev/null || log_warn "Failed to pull providers" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull globals" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull bootstrap vars" - gcloud storage cp "gs://${PREFIX}-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json" ./ 2>/dev/null || log_warn "Failed to pull resman vars" - fi - fi - - if promptUser "Would you like to run terraform destroy?"; then - destroy_success=false - max_attempts=3 - attempt=1 - - while [[ $attempt -le $max_attempts ]] && [[ "$destroy_success" == "false" ]]; do - log_info "Terraform destroy attempt $attempt/$max_attempts" - - if terraform destroy; then - destroy_success=true - log_info "Terraform destroy completed successfully" - else - if [[ $attempt -lt $max_attempts ]]; then - log_warn "Terraform destroy failed, checking for common issues..." - - # Check for peering errors (common during network destruction) - if terraform show 2>/dev/null | grep -q "peering\|network" || [[ -f .terraform.tfstate.backup ]]; then - log_warn "Likely network peering timing issues, waiting for resources to settle..." - log_info "Waiting 60 seconds for network resource deletion..."; sleep 60 - ((attempt++)) - else - log_warn "Terraform destroy failed with non-peering error" - if promptUser "Would you like to retry destroy (attempt $((attempt+1))/$max_attempts)?"; then - ((attempt++)) - else - break - fi - fi - else - log_error "Terraform destroy failed after $max_attempts attempts" - if promptUser "Would you like to try one more manual attempt?"; then - terraform destroy && destroy_success=true - fi - break - fi - fi - done - - if [[ "$destroy_success" == "false" ]]; then - log_error "Unable to complete terraform destroy. Manual cleanup may be required." - fi - fi - - if promptUser "Would you like to delete your .terraform dir and related files?"; then - # Comprehensive terraform cleanup for stage 2 - IL4/IL5 - if [[ -d ".terraform" ]]; then - rm -rf .terraform - log_info "Deleted .terraform directory" - else - log_warn ".terraform directory does not exist" - fi - - # Remove terraform lock file - if [[ -f ".terraform.lock.hcl" ]]; then - rm -f .terraform.lock.hcl - log_info "Deleted .terraform.lock.hcl" - else - log_warn ".terraform.lock.hcl does not exist" - fi - - # Remove any backup state files - if [[ -f "terraform.tfstate.backup" ]]; then - rm -f terraform.tfstate.backup - log_info "Deleted terraform.tfstate.backup" - fi - fi - fi + destroy_networking if promptUser "Would you like to remove billing account admin permissions for the ${PREFIX}-prod-resman-net-0@${PREFIX}-prod-iac-core-0.iam.gserviceaccount.com?"; then if ! gcloud billing accounts remove-iam-policy-binding "${BILLING_ACCOUNT}" --member=serviceAccount:"${PREFIX}"-prod-resman-net-0@"${PREFIX}"-prod-iac-core-0.iam.gserviceaccount.com --role=roles/billing.admin; then diff --git a/scripts/restore.sh b/scripts/restore.sh index b5a9bfb7f..8ad2935b6 100644 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -36,16 +36,16 @@ gcloud alpha billing projects link "${PREFIX}"-net-vdss-host --billing-account " if promptUser "Stage 2 - Networking"; then # Choose networking paradigm echo "Please type \"1\", \"2\", or \"3\" below that corresponds to the network paradigm you want: " - echo "1) IL2/FedRAMP Moderate" - echo "2) FedRAMP High" + echo "1) IL2" + echo "2) FedRAMP High/Moderate" echo "3) IL4/IL5" read -r choice - ########### IL2/FedRAMP Moderate ########### + ########### IL2 ########### if [ "$choice" == 1 ]; then echo "This stage is still under development." - ########### FedRAMP High ########### + ########### FedRAMP High/Moderate ########### elif [ "$choice" == 2 ]; then cd "${SCRIPT_DIR}"/../fast/stages-aw/2-networking-a-fedramp-high || exit From dcdf3535a337081e1c353decd729beef7ba1f33e Mon Sep 17 00:00:00 2001 From: jmpugh Date: Fri, 24 Jul 2026 18:28:58 +0000 Subject: [PATCH 2/4] Add support for FedRAMP Moderate Assured Workloads deployments --- .../0-bootstrap/data/org-policies/cloudkms_policy.yaml | 2 +- scripts/clean.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml b/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml index 97f75be3b..eec9081c6 100644 --- a/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml +++ b/fast/stages-aw/0-bootstrap/data/org-policies/cloudkms_policy.yaml @@ -88,4 +88,4 @@ gcp.restrictNonCmekServices: custom.kmsRotation: rules: - - enforce: true + - enforce: true \ No newline at end of file diff --git a/scripts/clean.sh b/scripts/clean.sh index d925938d8..099c0316f 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -170,4 +170,4 @@ if promptUser "Would you like to also clean up all local Terraform state and con log_info "Local cleanup completed." fi -log_info "Clean.sh completed." +log_info "Clean.sh completed." \ No newline at end of file From f04e2c0faf777917879403421a769fd4b84008d7 Mon Sep 17 00:00:00 2001 From: jmpugh Date: Fri, 24 Jul 2026 18:43:00 +0000 Subject: [PATCH 3/4] configure KMS protection level via variable --- fast/stages-aw/0-bootstrap/kms.tf | 2 +- fast/stages-aw/0-bootstrap/main.tf | 1 + fast/stages-aw/0-bootstrap/outputs.tf | 5 +++++ fast/stages-aw/0-bootstrap/variables.tf | 6 ++++++ fast/stages-aw/1-resman/kms.tf | 8 ++------ fast/stages-aw/1-resman/variables.tf | 6 ++++++ fast/stages-aw/2-networking-a-fedramp-high/nva.tf | 2 +- fast/stages-aw/2-networking-a-fedramp-high/variables.tf | 6 ++++++ fast/stages-aw/3-security/core-dev.tf | 2 +- fast/stages-aw/3-security/core-prod.tf | 2 +- fast/stages-aw/3-security/main.tf | 1 - fast/stages-aw/3-security/variables.tf | 6 ++++++ 12 files changed, 36 insertions(+), 11 deletions(-) diff --git a/fast/stages-aw/0-bootstrap/kms.tf b/fast/stages-aw/0-bootstrap/kms.tf index 10632529e..4bad09faa 100644 --- a/fast/stages-aw/0-bootstrap/kms.tf +++ b/fast/stages-aw/0-bootstrap/kms.tf @@ -15,7 +15,7 @@ locals { version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" + protection_level = var.kms_protection_level } } module "logging-kms" { diff --git a/fast/stages-aw/0-bootstrap/main.tf b/fast/stages-aw/0-bootstrap/main.tf index c8bf8c25f..d046c74e7 100644 --- a/fast/stages-aw/0-bootstrap/main.tf +++ b/fast/stages-aw/0-bootstrap/main.tf @@ -36,4 +36,5 @@ locals { } # naming: environment used in most resource names prefix = join("-", compact([var.prefix, "prod"])) + kms_protection_level = (var.kms_protection_level == null ? var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" : var.kms_protection_level) } diff --git a/fast/stages-aw/0-bootstrap/outputs.tf b/fast/stages-aw/0-bootstrap/outputs.tf index 65e78a98c..d6608960b 100644 --- a/fast/stages-aw/0-bootstrap/outputs.tf +++ b/fast/stages-aw/0-bootstrap/outputs.tf @@ -163,6 +163,11 @@ output "custom_roles" { value = module.organization.custom_role_id } +output "kms_protection_level" { + description = "KMS protection level." + value = var.kms_protection_level +} + output "outputs_bucket" { description = "GCS bucket where generated output files are stored." value = module.automation-tf-output-gcs.name diff --git a/fast/stages-aw/0-bootstrap/variables.tf b/fast/stages-aw/0-bootstrap/variables.tf index d1c279608..54769f26e 100644 --- a/fast/stages-aw/0-bootstrap/variables.tf +++ b/fast/stages-aw/0-bootstrap/variables.tf @@ -192,6 +192,12 @@ variable "iam_by_principals" { nullable = false } +variable "kms_protection_level" { + description = "KMS protection level." + type = string + nullable = true +} + variable "locations" { description = "Optional locations for GCS, BigQuery, and logging buckets created here." type = object({ diff --git a/fast/stages-aw/1-resman/kms.tf b/fast/stages-aw/1-resman/kms.tf index b67435d04..59771d066 100644 --- a/fast/stages-aw/1-resman/kms.tf +++ b/fast/stages-aw/1-resman/kms.tf @@ -12,10 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -locals { - protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" -} - module "tenant-project-keys" { source = "../../../modules/kms" project_id = module.tenant-self-iac-projects[each.key].project_id @@ -39,7 +35,7 @@ module "tenant-project-keys" { rotation_period = "7776000s" # CIS Compliance Benchmark 1.10 version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = local.protection_level + protection_level = var.kms_protection_level } }, default = { @@ -49,7 +45,7 @@ module "tenant-project-keys" { rotation_period = "7776000s" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = local.protection_level + protection_level = var.kms_protection_level } } } diff --git a/fast/stages-aw/1-resman/variables.tf b/fast/stages-aw/1-resman/variables.tf index 8dce6d8c9..c9e38b22b 100644 --- a/fast/stages-aw/1-resman/variables.tf +++ b/fast/stages-aw/1-resman/variables.tf @@ -240,6 +240,12 @@ variable "groups" { default = {} } +variable "kms_protection_level" { + description = "KMS protection level." + type = string + nullable = true +} + variable "regions" { # tfdoc:variable:source 0-bootstrap description = "Region definitions. Inherited from 0-bootstrap outputs. Must be specified in bootstrap terraform.tfvars." diff --git a/fast/stages-aw/2-networking-a-fedramp-high/nva.tf b/fast/stages-aw/2-networking-a-fedramp-high/nva.tf index 65ddea54f..bf021afc7 100644 --- a/fast/stages-aw/2-networking-a-fedramp-high/nva.tf +++ b/fast/stages-aw/2-networking-a-fedramp-high/nva.tf @@ -200,7 +200,7 @@ module "kms" { purpose = "ENCRYPT_DECRYPT" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" + protection_level = var.kms_protection_level } } } diff --git a/fast/stages-aw/2-networking-a-fedramp-high/variables.tf b/fast/stages-aw/2-networking-a-fedramp-high/variables.tf index eb7c22cf8..d73ab21a9 100644 --- a/fast/stages-aw/2-networking-a-fedramp-high/variables.tf +++ b/fast/stages-aw/2-networking-a-fedramp-high/variables.tf @@ -236,6 +236,12 @@ variable "groups" { default = null } +variable "kms_protection_level" { + description = "KMS protection level." + type = string + nullable = true +} + variable "regime_mapping" { description = "Compliance regime shorthand mapping." type = any diff --git a/fast/stages-aw/3-security/core-dev.tf b/fast/stages-aw/3-security/core-dev.tf index fed3448a7..0a34b6eb2 100644 --- a/fast/stages-aw/3-security/core-dev.tf +++ b/fast/stages-aw/3-security/core-dev.tf @@ -56,7 +56,7 @@ module "dev-sec-kms" { name = "dev-${each.key}" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = local.kms_protection_level + protection_level = var.kms_protection_level } } keys = local.kms_locations_keys[each.key] diff --git a/fast/stages-aw/3-security/core-prod.tf b/fast/stages-aw/3-security/core-prod.tf index 20466021e..318525eb2 100644 --- a/fast/stages-aw/3-security/core-prod.tf +++ b/fast/stages-aw/3-security/core-prod.tf @@ -55,7 +55,7 @@ module "prod-sec-kms" { name = "prod-${each.key}" version_template = { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" - protection_level = local.kms_protection_level + protection_level = var.kms_protection_level } } keys = local.kms_locations_keys[each.key] diff --git a/fast/stages-aw/3-security/main.tf b/fast/stages-aw/3-security/main.tf index 11f63aa1b..a72bd7b78 100644 --- a/fast/stages-aw/3-security/main.tf +++ b/fast/stages-aw/3-security/main.tf @@ -47,7 +47,6 @@ locals { if contains(v.locations, loc) } } - kms_protection_level = var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" project_services = [ "cloudkms.googleapis.com", "secretmanager.googleapis.com", diff --git a/fast/stages-aw/3-security/variables.tf b/fast/stages-aw/3-security/variables.tf index 92fc01acf..7206afa28 100644 --- a/fast/stages-aw/3-security/variables.tf +++ b/fast/stages-aw/3-security/variables.tf @@ -110,6 +110,12 @@ variable "kms_keys" { nullable = false } +variable "kms_protection_level" { + description = "KMS protection level." + type = string + nullable = true +} + variable "logging" { # tfdoc:variable:source 0-bootstrap description = "Log writer identities for organization / folders." From 44cd278fedd048e6a47085862f7b9f55f7d2c571 Mon Sep 17 00:00:00 2001 From: jmpugh Date: Fri, 24 Jul 2026 18:45:29 +0000 Subject: [PATCH 4/4] configure KMS protection level via variable --- fast/stages-aw/0-bootstrap/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fast/stages-aw/0-bootstrap/main.tf b/fast/stages-aw/0-bootstrap/main.tf index d046c74e7..6d902d667 100644 --- a/fast/stages-aw/0-bootstrap/main.tf +++ b/fast/stages-aw/0-bootstrap/main.tf @@ -35,6 +35,6 @@ locals { kms = var.regions.primary } # naming: environment used in most resource names - prefix = join("-", compact([var.prefix, "prod"])) - kms_protection_level = (var.kms_protection_level == null ? var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM" : var.kms_protection_level) + prefix = join("-", compact([var.prefix, "prod"])) + kms_protection_level = coalesce(var.kms_protection_level, var.assured_workloads.regime == "FEDRAMP_MODERATE" ? "SOFTWARE" : "HSM") }