From e0a3a3c7bbc1113ad0a1e5fdbe32f92aa02e2db3 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Fri, 31 Jan 2025 07:03:29 +0100 Subject: [PATCH] Expose custom constraint factory in bootstrap (#2854) * Expose custom constraint factory in bootstrap * Silence linter * Fix tests --- fast/stages/0-bootstrap/README.md | 32 +++++++------- .../data/custom-constraints/gke.yaml | 26 ++++++++++++ .../0-bootstrap/data/org-policies/gke.yaml | 23 ++++++++++ fast/stages/0-bootstrap/organization.tf | 5 ++- .../org-policy-custom-constraint.schema.json | 42 +++++++++++++++++++ fast/stages/0-bootstrap/variables.tf | 7 ++-- tests/fast/stages/s0_bootstrap/cicd.yaml | 5 ++- tests/fast/stages/s0_bootstrap/simple.yaml | 5 ++- 8 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 fast/stages/0-bootstrap/data/custom-constraints/gke.yaml create mode 100644 fast/stages/0-bootstrap/data/org-policies/gke.yaml create mode 100644 fast/stages/0-bootstrap/schemas/org-policy-custom-constraint.schema.json diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 8e4349e4a..2e94d9c85 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -668,28 +668,28 @@ FAST defines a simple mechanism to extend stage functionality via the use of [ad | name | description | type | required | default | producer | |---|---|:---:|:---:|:---:|:---:| | [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | | -| [organization](variables.tf#L277) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L292) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [organization](variables.tf#L278) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L293) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | | [bootstrap_user](variables.tf#L38) | Email of the nominal user running this stage for the first time. | string | | null | | | [cicd_config](variables.tf#L44) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | {} | | | [custom_roles](variables.tf#L85) | Map of role names => list of permissions to additionally create at the organization level. | map(list(string)) | | {} | | | [environments](variables.tf#L92) | Environment names. When not defined, short name is set to the key and tag name to lower(name). | map(object({…})) | | {…} | | | [essential_contacts](variables.tf#L126) | Email used for essential contacts, unset if null. | string | | null | | -| [factories_config](variables.tf#L132) | Configuration for the resource factories or external data. | object({…}) | | {} | | +| [factories_config](variables.tf#L132) | Configuration for the resource factories or external data. | object({…}) | | {} | | | [fast_addon](variables-addons.tf#L17) | FAST addons configurations for stages 1. Keys are used as short names for the add-on resources. | map(object({…})) | | {} | | -| [groups](variables.tf#L143) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | -| [iam](variables.tf#L159) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_bindings_additive](variables.tf#L166) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | -| [iam_by_principals](variables.tf#L181) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | -| [locations](variables.tf#L188) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | -| [log_sinks](variables.tf#L202) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [org_policies_config](variables.tf#L258) | Organization policies customization. | object({…}) | | {} | | -| [outputs_location](variables.tf#L286) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L301) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | -| [resource_names](variables.tf#L312) | Resource names overrides for specific resources. Prefix is always set via code, except where noted in the variable type. | object({…}) | | {} | | -| [universe](variables.tf#L344) | Target GCP universe. | object({…}) | | null | | -| [workforce_identity_providers](variables.tf#L354) | Workforce Identity Federation pools. | map(object({…})) | | {} | | -| [workload_identity_providers](variables.tf#L370) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [groups](variables.tf#L144) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | | +| [iam](variables.tf#L160) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_bindings_additive](variables.tf#L167) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | +| [iam_by_principals](variables.tf#L182) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | | +| [locations](variables.tf#L189) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | +| [log_sinks](variables.tf#L203) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | +| [org_policies_config](variables.tf#L259) | Organization policies customization. | object({…}) | | {} | | +| [outputs_location](variables.tf#L287) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L302) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {} | | +| [resource_names](variables.tf#L313) | Resource names overrides for specific resources. Prefix is always set via code, except where noted in the variable type. | object({…}) | | {} | | +| [universe](variables.tf#L345) | Target GCP universe. | object({…}) | | null | | +| [workforce_identity_providers](variables.tf#L355) | Workforce Identity Federation pools. | map(object({…})) | | {} | | +| [workload_identity_providers](variables.tf#L371) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | ## Outputs diff --git a/fast/stages/0-bootstrap/data/custom-constraints/gke.yaml b/fast/stages/0-bootstrap/data/custom-constraints/gke.yaml new file mode 100644 index 000000000..05792ca21 --- /dev/null +++ b/fast/stages/0-bootstrap/data/custom-constraints/gke.yaml @@ -0,0 +1,26 @@ +# 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. + +# yaml-language-server: $schema=../../schemas/org-policy-custom-constraint.schema.json + +custom.disableKubeletReadOnlyPort: + resource_types: + - container.googleapis.com/Cluster + method_types: + - CREATE + - UPDATE + condition: resource.nodeConfig.kubeletConfig.insecureKubeletReadonlyPortEnabled == true + action_type: DENY + display_name: Disable Kubelet Read-Only Port 10255 + description: Disallows the use of Kubelet read-only port 10255 to enhance security diff --git a/fast/stages/0-bootstrap/data/org-policies/gke.yaml b/fast/stages/0-bootstrap/data/org-policies/gke.yaml new file mode 100644 index 000000000..8991d327f --- /dev/null +++ b/fast/stages/0-bootstrap/data/org-policies/gke.yaml @@ -0,0 +1,23 @@ +# 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. + +--- +# sample subset of useful organization policies, edit to suit requirements +# start of document (---) avoids errors if the file only contains comments + +# yaml-language-server: $schema=../../schemas/org-policies.schema.json + +custom.disableKubeletReadOnlyPort: + rules: + - enforce: true diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index 8a701692f..ecf44ee63 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -1,5 +1,5 @@ /** - * Copyright 2024 Google LLC + * 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. @@ -198,6 +198,9 @@ module "organization" { custom_roles = var.custom_roles factories_config = { custom_roles = var.factories_config.custom_roles + org_policy_custom_constraints = ( + var.bootstrap_user != null ? null : var.factories_config.custom_constraints + ) org_policies = ( var.bootstrap_user != null ? null : var.factories_config.org_policies ) diff --git a/fast/stages/0-bootstrap/schemas/org-policy-custom-constraint.schema.json b/fast/stages/0-bootstrap/schemas/org-policy-custom-constraint.schema.json new file mode 100644 index 000000000..fd7fc5c7f --- /dev/null +++ b/fast/stages/0-bootstrap/schemas/org-policy-custom-constraint.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Organization Policy Custom Constraints", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^[a-z-]+\\.[a-zA-Z]+$": { + "type": "object", + "additionalProperties": false, + "required": [ + "action_type", + "condition" + ], + "properties": { + "display_name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "condition": { + "type": "string" + }, + "method_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "resource_types": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 8c7201795..f306ab3bd 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -132,9 +132,10 @@ variable "essential_contacts" { variable "factories_config" { description = "Configuration for the resource factories or external data." type = object({ - custom_roles = optional(string, "data/custom-roles") - org_policies = optional(string, "data/org-policies") - org_policies_iac = optional(string, "data/org-policies-iac") + custom_constraints = optional(string, "data/custom-constraints") + custom_roles = optional(string, "data/custom-roles") + org_policies = optional(string, "data/org-policies") + org_policies_iac = optional(string, "data/org-policies-iac") }) nullable = false default = {} diff --git a/tests/fast/stages/s0_bootstrap/cicd.yaml b/tests/fast/stages/s0_bootstrap/cicd.yaml index 186cd1ec0..c4935cb99 100644 --- a/tests/fast/stages/s0_bootstrap/cicd.yaml +++ b/tests/fast/stages/s0_bootstrap/cicd.yaml @@ -334,7 +334,8 @@ counts: google_logging_organization_settings: 1 google_logging_organization_sink: 4 google_logging_project_bucket_config: 4 - google_org_policy_policy: 24 + google_org_policy_custom_constraint: 1 + google_org_policy_policy: 25 google_organization_iam_binding: 27 google_organization_iam_custom_role: 13 google_organization_iam_member: 29 @@ -355,4 +356,4 @@ counts: google_tags_tag_value: 2 local_file: 13 modules: 26 - resources: 271 + resources: 273 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index 8ad9f8cfc..23f12f0a0 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -19,7 +19,8 @@ counts: google_logging_organization_settings: 1 google_logging_organization_sink: 4 google_logging_project_bucket_config: 4 - google_org_policy_policy: 24 + google_org_policy_custom_constraint: 1 + google_org_policy_policy: 25 google_organization_iam_binding: 27 google_organization_iam_custom_role: 13 google_organization_iam_member: 29 @@ -40,7 +41,7 @@ counts: google_tags_tag_value: 2 local_file: 8 modules: 20 - resources: 234 + resources: 236 outputs: automation: __missing__