diff --git a/fast/README.md b/fast/README.md index 7459c870..a2264cc9 100644 --- a/fast/README.md +++ b/fast/README.md @@ -24,7 +24,7 @@ Please refer to the [stages](./stages/) section for further details on each stag ### Security-first design -Security was, from the beginning, one of the most critical elements in the design of Fabric FAST. Many of FAST's design decisions aim to build the foundations of a secure organization. In fact, the first two stages deal mainly with the organization-wide security setup. +Security was, from the beginning, one of the most critical elements in the design of Fabric FAST. Many of FAST's design decisions aim to build the foundations of a secure organization. In fact, the first stage deals mainly with the organization-wide security setup, and the second stage partitions the organization hierarchy and puts guardrails in place for each hierarchy branch. FAST also aims to minimize the number of permissions granted to principals according to the security-first approach previously mentioned. We achieve this through the meticulous use of groups, service accounts, custom roles, and [Cloud IAM Conditions](https://cloud.google.com/iam/docs/conditions-overview), among other things. @@ -40,9 +40,14 @@ One of our objectives with FAST is to provide a lightweight reference design for ### Multitenant organizations -FAST has built-in support for complex multitenant organizations, where each tenant has complete control over a separate hierarchy rooted in a top-level folder. This approach is particularly suited for large enterprises or governments, where country-level subsidiaries or government agencies have a wide degree of autonomy within a shared GCP organization managed by a central entity. +FAST has built-in support for two types of multitenancy implemented in [stage 1](stages/1-resman/README.md): -FAST implements multitenancy via [dedicated stages](stages-multitenant) for tenant-level bootstrap and resource management, which configure separate hierarchies within the organization rooted in top-level folders, so that subsequent FAST stages (networking, security, data, etc.) can be used directly for each tenant. The diagram below shows the relationships between organization-level and tenant-level stages. +- lightweight tenants that need extensive control over part of the organizational hierarchy, including potential use of their own billing account +- complex tenants that need to behave like their own landing zone inside a shared GCP organization + +The first approach allows easy creation of branches with loose guardrails in place and wide control over their resources, and is suited where tenants implement highly specialized, custom architectures and don't need FAST compliance inside their own branch. + +The second approach is used when tenants need to behave as their own landing zone, and direct use of FAST stage 2s and 3s inside the tenant space is desired to achieve a full Landing Zone for each tenant. This approach leverages [dedicated stages](stages-multitenant) for tenant-level bootstrap and resource management that are run after organization-wide bootstrap and resource management. The diagram below shows the relationships between organization-level and tenant-level stages.

Stages diagram diff --git a/fast/docs/0-org-policies.md b/fast/docs/0-org-policies.md index d4ebd2dc..e7d58ae5 100644 --- a/fast/docs/0-org-policies.md +++ b/fast/docs/0-org-policies.md @@ -1,29 +1,54 @@ -# Support initial org policies in bootstrap stage +# Move organization policies to bootstrap stage -**authors:** [Ludo](https://github.com/ludoo), [Roberto](https://github.com/drebes) \ -**date:** July 15, 2023 +**authors:** [Julio](https://github.com/juliocc), [Ludo](https://github.com/ludoo), [Roberto](https://github.com/drebes) \ +**date:** September 13, 2023 ## Status -Under discussion. +Implemented. ## Context -Some organization policies might need to be applied right from the start in stage 0, to better configure defaults for organization resources or to improve security. Two examples are skipping default network creation for projects (which would avoid creating a network in the IaC project), and deprivileging service accounts. +Three different requirements drive this proposal. -There are essentially two ways of achieving this +### Organization policies deployed at bootstrap time -- moving organization policies from stage 1 to stage 0, which would also mean moving tags or at least the `org-policies` key and its values -- turning on the organization policy factory in stage 0 +Many organizations take security seriously, and would like to have organization policies (for example `constraints/iam.automaticIamGrantsForDefaultServiceAccounts`) deployed right from the beginning at bootstrap time. This is currently extremely cumbersome, as organization policies are managed in stage 1. -The first approach is complex and results in a "fat" stage 0, which then would need to be applied every time an organization policy or even worse a tag is changed. This is counter to our initial approach where stage 0 is a "decoupling" (from org admin permissions) stage, only taking care of org-level IAM and logging, and the initial IaC resources. +As an additional benefit, managing some or all organization policies in stage 0 will enable to turn off undesired resource configuration for the initial projects (for example `constraints/compute.skipDefaultNetworkCreation`). -The second approach is lightweight and its only impact is in the need to avoid duplication between the organization policies managed in stage 0, and those managed in stage 1. +### Simplify and limit delegation of Organization Policy Administrator role + +Automation service accounts are currently assigned the Organization Policy Administrator role at the organization level, scoped via resource management tags. This is cumbersome as bindings are distributed between stage 0 that delegates role control to the stage 1 service account, and stage 1 that creates the automation service accounts, tags and folder bindings used for scoping. + +A more secure way of doing this is via a dedicated resource management tag value hierarchy, and conditions on the organization policies that alter behaviour based on tags. This would allow centrally defining allowed exceptions to organization policies, and selectively granting access to specific exceptions to individual automation service accounts via tag values. + +The project factory will need to retain scoped grants, to set policies that enforce lists of resources which would be too cumbersome to maintain in stage 0. + +### Reduce stage 1 complexity to allow simpler creation of hierarchy templates + +Stage 1 is currently too complex to allow easy cloning into different resource hierarchy templates, which are needed to account for all landing zone designs. + +Removing complexity from stage 1 by moving organization policy and its related IAM to stage 0 will be an initial step towards stage 1 simplification. + +## Proposal + +The proposal is to + +- move management of organization policies to stage 0 +- move management of the `org_policies` tag key and associated values to stage 0 +- remove delegated/conditional grants for the Organization Policy Administrator role from stage 0 and 1 + +The approach fattens stage 0 and lessens its decoupling role in the overall FAST design, but looks preferable compared to the complexity of splitting organization policy management between stage 0 and 1, or worse delegating control of specific policies to external commands run before stage 0. ## Decision -No decision yet, this will need to be discussed. +Decision is to implement this. ## Consequences -TBD +Organization policies and related tags will need to be moved from stage 1 to stage 0 state. One approach is to + +- switch both states to local state +- use `terraform state mv -state-out` to temporarily move resources from stage 1 to stage 0 +- push stage 0 and stage 1 state diff --git a/fast/stages/0-bootstrap/README.md b/fast/stages/0-bootstrap/README.md index 9bfc7506..d6741172 100644 --- a/fast/stages/0-bootstrap/README.md +++ b/fast/stages/0-bootstrap/README.md @@ -60,6 +60,22 @@ One consequence of the above setup is the need to configure IAM bindings that ca A full reference of IAM roles managed by this stage [is available here](./IAM.md). +### Organization policies and tag-based conditions + +It's often desirable to have organization policies deployed before any other resource in the org, so as to ensure compliance with specific requirements (e.g. location restrictions), or control the configuration of specific resources (e.g. default network at project creation or service account grants). + +To cover this use case, organization policies have been moved from the resource management to the bootstrap stage in FAST versions after 26.0.0. They are managed via the usual factory aopproach, and a [sample set of data files](./data/org-policies/) is included with this stage. + +The only current exception to the factory approach is the `iam.allowedPolicyMemberDomains` constraint, which is managed in code so as to be able to auto-allow the organization's domain. More domains can be added via the `org_policies_config` variable, which also serves as an umbrella for future policies that will need to be managed in code. + +Organization policy exceptions are managed via a dedicated resource management tag hierarchy, rooted in the `org-policies` tag key. A default condition is already present for the the `iam.allowedPolicyMemberDomains` constraint, that relaxes the policy on resources that have the `org-policies/allowed-policy-member-domains-all` tag value bound or inherited. + +Further tag values can be defined via the `org_policies_config.tag_values` variable, and IAM access can be granted on them via the same variable. Once a tag value has been created, its id can be used in constraint rule conditions. + +Management of the rest of the tag hierarchy is delegated to the resource management stage, as that is often intimately tied to the folder hierarchy design. + +The organization policy tag key and values managed by this stage have been added to the `0-bootstrap.auto.tfvars` stage, so that IAM can be delegated to the resource management or successive stages via their ids. + ### Automation project and resources One other design choice worth mentioning here is using a single automation project for all foundational stages. We trade off some complexity on the API side (single source for usage quota, multiple service activation) for increased flexibility and simpler operations, while still effectively providing the same degree of separation via resource-level IAM. @@ -139,6 +155,7 @@ The roles that the Organization Admin used in the first `apply` needs to self-gr - Organization Role Administrator (`roles/iam.organizationRoleAdmin`) - Organization Administrator (`roles/resourcemanager.organizationAdmin`) - Project Creator (`roles/resourcemanager.projectCreator`) +- Tag Admin (`roles/resourcemanager.tagAdmin`) To quickly self-grant the above roles, run the following code snippet as the initial Organization Admin: @@ -152,7 +169,8 @@ export FAST_ORG_ID=123456 # set needed roles export FAST_ROLES="roles/billing.admin roles/logging.admin \ - roles/iam.organizationRoleAdmin roles/resourcemanager.projectCreator" + roles/iam.organizationRoleAdmin roles/resourcemanager.projectCreator \ + roles/resourcemanager.tagAdmin" for role in $FAST_ROLES; do gcloud organizations add-iam-policy-binding $FAST_ORG_ID \ @@ -519,35 +537,37 @@ The remaining configuration is manual, as it regards the repositories themselves | 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#L219) | Organization details. | object({…}) | ✓ | | | -| [prefix](variables.tf#L234) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | +| [organization](variables.tf#L243) | Organization details. | object({…}) | ✓ | | | +| [prefix](variables.tf#L258) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | | | [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | string | | null | | | [cicd_repositories](variables.tf#L33) | 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({…}) | | null | | | [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | object({…}) | | {…} | | | [custom_roles](variables.tf#L93) | Map of role names => list of permissions to additionally create at the organization level. | map(list(string)) | | {} | | -| [fast_features](variables.tf#L100) | Selective control for top-level FAST features. | object({…}) | | {} | | -| [federated_identity_providers](variables.tf#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | -| [group_iam](variables.tf#L132) | Organization-level authoritative IAM binding for groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | -| [groups](variables.tf#L140) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | map(string) | | {…} | | -| [iam](variables.tf#L158) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | -| [iam_bindings_additive](variables.tf#L165) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | -| [locations](variables.tf#L180) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | | -| [log_sinks](variables.tf#L194) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | | -| [outputs_location](variables.tf#L228) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [project_parent_ids](variables.tf#L243) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | +| [factories_config](variables.tf#L100) | Configuration for the organization policies factory. | object({…}) | | {} | | +| [fast_features](variables.tf#L109) | Selective control for top-level FAST features. | object({…}) | | {} | | +| [federated_identity_providers](variables.tf#L122) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | | +| [group_iam](variables.tf#L141) | Organization-level authoritative IAM binding for groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | +| [groups](variables.tf#L148) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | map(string) | | {…} | | +| [iam](variables.tf#L166) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | | +| [iam_bindings_additive](variables.tf#L173) | Organization-level custom additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | | +| [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#L227) | Organization policies customization. | object({…}) | | {} | | +| [outputs_location](variables.tf#L252) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [project_parent_ids](variables.tf#L267) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [automation](outputs.tf#L102) | Automation resources. | | | -| [billing_dataset](outputs.tf#L107) | BigQuery dataset prepared for billing export. | | | -| [cicd_repositories](outputs.tf#L112) | CI/CD repository configurations. | | | -| [custom_roles](outputs.tf#L124) | Organization-level custom roles. | | | -| [federated_identity](outputs.tf#L129) | Workload Identity Federation pool and providers. | | | -| [outputs_bucket](outputs.tf#L139) | GCS bucket where generated output files are stored. | | | -| [project_ids](outputs.tf#L144) | Projects created by this stage. | | | -| [providers](outputs.tf#L154) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | -| [service_accounts](outputs.tf#L161) | Automation service accounts created by this stage. | | | -| [tfvars](outputs.tf#L170) | Terraform variable files for the following stages. | ✓ | | +| [automation](outputs.tf#L112) | Automation resources. | | | +| [billing_dataset](outputs.tf#L117) | BigQuery dataset prepared for billing export. | | | +| [cicd_repositories](outputs.tf#L122) | CI/CD repository configurations. | | | +| [custom_roles](outputs.tf#L134) | Organization-level custom roles. | | | +| [federated_identity](outputs.tf#L139) | Workload Identity Federation pool and providers. | | | +| [outputs_bucket](outputs.tf#L149) | GCS bucket where generated output files are stored. | | | +| [project_ids](outputs.tf#L154) | Projects created by this stage. | | | +| [providers](outputs.tf#L164) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 | +| [service_accounts](outputs.tf#L171) | Automation service accounts created by this stage. | | | +| [tfvars](outputs.tf#L180) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/1-resman/data/org-policies/compute.yaml b/fast/stages/0-bootstrap/data/org-policies/compute.yaml similarity index 100% rename from fast/stages/1-resman/data/org-policies/compute.yaml rename to fast/stages/0-bootstrap/data/org-policies/compute.yaml diff --git a/fast/stages/1-resman/data/org-policies/iam.yaml b/fast/stages/0-bootstrap/data/org-policies/iam.yaml similarity index 100% rename from fast/stages/1-resman/data/org-policies/iam.yaml rename to fast/stages/0-bootstrap/data/org-policies/iam.yaml diff --git a/fast/stages/1-resman/data/org-policies/serverless.yaml b/fast/stages/0-bootstrap/data/org-policies/serverless.yaml similarity index 100% rename from fast/stages/1-resman/data/org-policies/serverless.yaml rename to fast/stages/0-bootstrap/data/org-policies/serverless.yaml diff --git a/fast/stages/1-resman/data/org-policies/sql.yaml b/fast/stages/0-bootstrap/data/org-policies/sql.yaml similarity index 100% rename from fast/stages/1-resman/data/org-policies/sql.yaml rename to fast/stages/0-bootstrap/data/org-policies/sql.yaml diff --git a/fast/stages/1-resman/data/org-policies/storage.yaml b/fast/stages/0-bootstrap/data/org-policies/storage.yaml similarity index 100% rename from fast/stages/1-resman/data/org-policies/storage.yaml rename to fast/stages/0-bootstrap/data/org-policies/storage.yaml diff --git a/fast/stages/0-bootstrap/organization-iam.tf b/fast/stages/0-bootstrap/organization-iam.tf index c93bd540..b8abc00f 100644 --- a/fast/stages/0-bootstrap/organization-iam.tf +++ b/fast/stages/0-bootstrap/organization-iam.tf @@ -59,6 +59,7 @@ locals { "roles/resourcemanager.folderAdmin", "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator", + "roles/resourcemanager.tagAdmin" ] additive = concat( [ @@ -102,6 +103,7 @@ locals { "roles/resourcemanager.organizationAdmin", "roles/resourcemanager.projectCreator", "roles/resourcemanager.projectMover", + "roles/resourcemanager.tagAdmin" ] additive = concat( [ diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index d9f62221..8f47caf3 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -45,6 +45,11 @@ locals { } ] ]) + drs_domains = concat( + [var.organization.customer_id], + var.org_policies_config.constraints.allowed_policy_member_domains + ) + drs_tag_name = "${var.organization.id}/${var.org_policies_config.tag_name}" group_iam = { for k, v in local.iam_group_bindings : k => v.authoritative } @@ -151,4 +156,40 @@ module "organization" { type = attrs.type } } + org_policies_data_path = var.factories_config.org_policy_data_path + org_policies = { + "iam.allowedPolicyMemberDomains" = { + rules = [ + { + allow = { values = local.drs_domains } + condition = { + expression = ( + "!resource.matchTag('${local.drs_tag_name}', 'allowed-policy-member-domains-all')" + ) + } + }, + { + allow = { all = true } + condition = { + expression = ( + "resource.matchTag('${local.drs_tag_name}', 'allowed-policy-member-domains-all')" + ) + title = "allow-all" + } + }, + ] + } + # "gcp.resourceLocations" = {} + # "iam.workloadIdentityPoolProviders" = {} + } + tags = { + (var.org_policies_config.tag_name) = { + description = "Organization policy conditions." + iam = {} + values = merge( + { allowed-policy-member-domains-all = {} }, + var.org_policies_config.tag_values + ) + } + } } diff --git a/fast/stages/0-bootstrap/outputs.tf b/fast/stages/0-bootstrap/outputs.tf index 70d84449..58b82179 100644 --- a/fast/stages/0-bootstrap/outputs.tf +++ b/fast/stages/0-bootstrap/outputs.tf @@ -88,6 +88,16 @@ locals { project_number = module.log-export-project.number writer_identities = module.organization.sink_writer_identities } + org_policy_tags = { + key_id = ( + module.organization.tag_keys[var.org_policies_config.tag_name].id + ) + key_name = var.org_policies_config.tag_name + values = { + for k, v in module.organization.tag_values : + split("/", k)[1] => v.id + } + } } tfvars_globals = { billing_account = var.billing_account diff --git a/fast/stages/0-bootstrap/variables.tf b/fast/stages/0-bootstrap/variables.tf index 93997979..8446b4dd 100644 --- a/fast/stages/0-bootstrap/variables.tf +++ b/fast/stages/0-bootstrap/variables.tf @@ -97,6 +97,15 @@ variable "custom_roles" { default = {} } +variable "factories_config" { + description = "Configuration for the organization policies factory." + type = object({ + org_policy_data_path = optional(string, "data/org-policies") + }) + nullable = false + default = {} +} + variable "fast_features" { description = "Selective control for top-level FAST features." type = object({ @@ -136,7 +145,6 @@ variable "group_iam" { nullable = false } - variable "groups" { # https://cloud.google.com/docs/enterprise/setup-checklist description = "Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed." @@ -216,6 +224,22 @@ variable "log_sinks" { } } +variable "org_policies_config" { + description = "Organization policies customization." + type = object({ + constraints = optional(object({ + allowed_policy_member_domains = optional(list(string), []) + }), {}) + tag_name = optional(string, "org-policies") + tag_values = optional(map(object({ + description = optional(string, "Managed by the Terraform organization module.") + iam = optional(map(list(string)), {}) + id = optional(string) + })), {}) + }) + default = {} +} + variable "organization" { description = "Organization details." type = object({ diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index cc2896f8..8cbaa7f5 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -140,7 +140,6 @@ The first set of default tags cannot be overridden and defines the following key - `context` to identify parts of the resource hierarchy, with `data`, `gke`, `networking`, `sandbox`, `security` and `teams` values - `environment` to identify folders and projects belonging to specific environments, with `development` and `production` values -- `org-policies` that holds values designed to be used in organization policy conditions, with a single value `allowed-policy-member-domains-all` used to allow all in the Domain Restricted Sharing policy via tag bindings; more values can be added by using the same tag key and specifying new values via the custom variable described below - `tenant` for FAST multitenant, with one value for each defined tenant that identifies their specific set of resources The second set is optional and allows defining a custom tag hierarchy, including IAM bindings that can refer to specific identities, or to the internally defined automation service accounts via their names, like in the following example: @@ -150,14 +149,8 @@ tags = { my-custom-tag = { values = { eggs = {} - spam = {} - } - } - org-policies = { - description = "Tag values used in organization policy conditions." - values = { - allowed-policy-member-domains-all = { - description = "Allow all in Domain Restricted Sharing." + spam = { + description = "Example tag value." iam = { "roles/resourcemanager.tagUser" = ["sandbox"] } @@ -316,12 +309,6 @@ This will result in This allows to centralize the minimum set of resources to delegate control of each team's folder to a pipeline, and/or to the team group. This can be used as a starting point for scenarios that implement more complex requirements (e.g. environment folders per team, etc.). -### Organization policies - -Organization policies leverage -- with one exception -- the built-in factory implemented in the organization module, and configured via the yaml files in the `data` folder. To edit organization policies, check and edit the files there. - -The one exception is [Domain Restricted Sharing](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains), which is made dynamic and implemented in code so as to auto-add the current organization's customer id. The `organization_policy_configs` variable allow to easily add ids from third party organizations if needed. - ### IAM IAM roles can be easily edited in the relevant `branch-xxx.tf` file, following the best practice outlined in the [bootstrap stage](../0-bootstrap#customizations) documentation of separating user-level and service-account level IAM policies in modules' `iam_groups`, `iam`, and `iam_additive` variables. @@ -339,11 +326,11 @@ Due to its simplicity, this stage lends itself easily to customizations: adding | name | description | modules | resources | |---|---|---|---| | [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member | -| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | google_organization_iam_member | +| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | | | [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | | -| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs · iam-service-account | google_organization_iam_member | +| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs · iam-service-account | | | [branch-security.tf](./branch-security.tf) | Security stage resources. | folder · gcs · iam-service-account | | | [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder · gcs · iam-service-account | | | [branch-tenants.tf](./branch-tenants.tf) | Lightweight tenant resources. | folder · gcs · iam-service-account · project | | @@ -368,21 +355,20 @@ Due to its simplicity, this stage lends itself easily to customizations: adding |---|---|:---:|:---:|:---:|:---:| | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables.tf#L39) | 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({…}) | ✓ | | 0-bootstrap | -| [organization](variables.tf#L192) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables.tf#L216) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [organization](variables.tf#L198) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables.tf#L214) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | | [cicd_repositories](variables.tf#L50) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | | [custom_roles](variables.tf#L132) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [data_dir](variables.tf#L141) | Relative path for the folder storing configuration data. | string | | "data" | | -| [fast_features](variables.tf#L147) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | -| [groups](variables.tf#L161) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | object({…}) | | {} | 0-bootstrap | -| [locations](variables.tf#L174) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | -| [organization_policy_configs](variables.tf#L202) | Organization policies customization. | object({…}) | | null | | -| [outputs_location](variables.tf#L210) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [tag_names](variables.tf#L227) | Customized names for resource management tags. | object({…}) | | {…} | | -| [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | -| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | -| [tenants](variables.tf#L285) | Lightweight tenant definitions. | map(object({…})) | | {} | | -| [tenants_config](variables.tf#L301) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | +| [fast_features](variables.tf#L141) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap | +| [groups](variables.tf#L155) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | object({…}) | | {} | 0-bootstrap | +| [locations](variables.tf#L168) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap | +| [org_policy_tags](variables.tf#L186) | Resource management tags for organization policy exceptions. | object({…}) | | {} | 0-bootstrap | +| [outputs_location](variables.tf#L208) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [tag_names](variables.tf#L225) | Customized names for resource management tags. | object({…}) | | {} | | +| [tags](variables.tf#L240) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | +| [team_folders](variables.tf#L261) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [tenants](variables.tf#L277) | Lightweight tenant definitions. | map(object({…})) | | {} | | +| [tenants_config](variables.tf#L293) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | ## Outputs diff --git a/fast/stages/1-resman/branch-data-platform.tf b/fast/stages/1-resman/branch-data-platform.tf index 48509b8c..219c3c65 100644 --- a/fast/stages/1-resman/branch-data-platform.tf +++ b/fast/stages/1-resman/branch-data-platform.tf @@ -137,22 +137,3 @@ module "branch-dp-prod-gcs" { "roles/storage.objectAdmin" = [module.branch-dp-prod-sa.0.iam_email] } } - -resource "google_organization_iam_member" "org_policy_admin_dp" { - for_each = !var.fast_features.data_platform ? {} : { - data-dev = ["data", "development", module.branch-dp-dev-sa.0.iam_email] - data-prod = ["data", "production", module.branch-dp-prod-sa.0.iam_email] - } - org_id = var.organization.id - role = "roles/orgpolicy.policyAdmin" - member = each.value.2 - condition { - title = "org_policy_tag_dp_scoped" - description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}." - expression = <<-END - resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}') - && - resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}') - END - } -} diff --git a/fast/stages/1-resman/branch-sandbox.tf b/fast/stages/1-resman/branch-sandbox.tf index 89b6b2dc..33eae4f0 100644 --- a/fast/stages/1-resman/branch-sandbox.tf +++ b/fast/stages/1-resman/branch-sandbox.tf @@ -60,17 +60,3 @@ module "branch-sandbox-sa" { display_name = "Terraform resman sandbox service account." prefix = var.prefix } - -resource "google_organization_iam_member" "org_policy_admin_sandbox" { - count = var.fast_features.sandbox ? 1 : 0 - org_id = var.organization.id - role = "roles/orgpolicy.policyAdmin" - member = module.branch-sandbox-sa.0.iam_email - condition { - title = "org_policy_tag_sandbox_scoped" - description = "Org policy tag scoped grant for sandbox." - expression = <<-END - resource.matchTag('${var.organization.id}/${var.tag_names.context}', 'sandbox') - END - } -} diff --git a/fast/stages/1-resman/organization.tf b/fast/stages/1-resman/organization.tf index c6860ad6..11200035 100644 --- a/fast/stages/1-resman/organization.tf +++ b/fast/stages/1-resman/organization.tf @@ -17,16 +17,6 @@ # tfdoc:file:description Organization policies. locals { - all_drs_domains = concat( - [var.organization.customer_id], - try(local.policy_configs.allowed_policy_member_domains, []), - compact([for k, v in var.tenants : try(v.organization.customer_id, "")]) - ) - policy_configs = ( - var.organization_policy_configs == null - ? {} - : var.organization_policy_configs - ) tags = { for k, v in var.tags : k => merge(v, { values = { @@ -51,38 +41,6 @@ module "organization" { organization_id = "organizations/${var.organization.id}" # additive bindings via delegated IAM grant set in stage 0 iam_bindings_additive = local.iam_bindings_additive - # sample subset of useful organization policies, edit to suit requirements - org_policies = { - "iam.allowedPolicyMemberDomains" = { - rules = [ - { - allow = { values = local.all_drs_domains } - condition = { - expression = "!resource.matchTag('${var.organization.id}/${var.tag_names.org-policies}', 'allowed-policy-member-domains-all')" - } - }, - { - allow = { all = true } - condition = { - expression = "resource.matchTag('${var.organization.id}/${var.tag_names.org-policies}', 'allowed-policy-member-domains-all')" - title = "allow-all" - } - }, - ] - } - #"gcp.resourceLocations" = { - # allow = { values = local.allowed_regions } - # } - # "iam.workloadIdentityPoolProviders" = { - # allow = { - # values = [ - # for k, v in coalesce(var.automation.federated_identity_providers, {}) : - # v.issuer_uri - # ] - # } - # } - } - org_policies_data_path = "${var.data_dir}/org-policies" # do not assign tagViewer or tagUser roles here on tag keys and values as # they are managed authoritatively and will break multitenant stages tags = merge(local.tags, { @@ -107,16 +65,6 @@ module "organization" { production = null } } - (var.tag_names.org-policies) = { - description = "Organization policy conditions." - iam = {} - values = { - allowed-policy-member-domains-all = merge({}, try( - local.tags[var.tag_names.org-policies].values.allowed-policy-member-domains-all, - {} - )) - } - } (var.tag_names.tenant) = { description = "Organization tenant." values = { diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index f255423a..849fc6ef 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -138,12 +138,6 @@ variable "custom_roles" { default = null } -variable "data_dir" { - description = "Relative path for the folder storing configuration data." - type = string - default = "data" -} - variable "fast_features" { # tfdoc:variable:source 0-0-bootstrap description = "Selective control for top-level FAST features." @@ -189,6 +183,18 @@ variable "locations" { nullable = false } +variable "org_policy_tags" { + # tfdoc:variable:source 0-bootstrap + description = "Resource management tags for organization policy exceptions." + type = object({ + key_id = optional(string) + key_name = optional(string) + values = optional(map(string), {}) + }) + nullable = false + default = {} +} + variable "organization" { # tfdoc:variable:source 0-bootstrap description = "Organization details." @@ -199,14 +205,6 @@ variable "organization" { }) } -variable "organization_policy_configs" { - description = "Organization policies customization." - type = object({ - allowed_policy_member_domains = list(string) - }) - default = null -} - variable "outputs_location" { description = "Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable." type = string @@ -227,17 +225,11 @@ variable "prefix" { variable "tag_names" { description = "Customized names for resource management tags." type = object({ - context = string - environment = string - org-policies = string - tenant = string + context = optional(string, "context") + environment = optional(string, "environment") + tenant = optional(string, "tenant") }) - default = { - context = "context" - environment = "environment" - org-policies = "org-policies" - tenant = "tenant" - } + default = {} nullable = false validation { condition = alltrue([for k, v in var.tag_names : v != null]) diff --git a/fast/stages/README.md b/fast/stages/README.md index 4ec02854..6a5bfd29 100644 --- a/fast/stages/README.md +++ b/fast/stages/README.md @@ -22,15 +22,15 @@ To destroy a previous FAST deployment follow the instructions detailed in [clean ## Organization (0 and 1) - [Bootstrap](0-bootstrap/README.md) - Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level, to avoid the need of broad permissions later on, and to implement a minimum of security features like sinks and exports from the start.\ + Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level to avoid the need of broad permissions later on, and to implement from the start critical auditing or security features like organization policies, sinks and exports.\ Exports: automation variables, organization-level custom roles - [Resource Management](1-resman/README.md) - Creates the base resource hierarchy (folders) and the automation resources required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures organization-level policies and any exceptions needed by different branches of the resource hierarchy.\ - Exports: folder ids, automation service account emails + Creates the base resource hierarchy (folders) and the automation resources that will be required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures resource management tags used in scoping specific IAM roles on the resource hierarchy.\ + Exports: folder ids, automation service account emails, tags ## Multitenancy -Implemented via separate stages that configure separate FAST-enabled hierarchies for each tenant, check the [multitenant stages folder](../stages-multitenant/). +Implemented directly in stage 1 for lightweight tenants, and for complex tenancy via separate FAST-enabled hierarchies for each tenant available in the [multitenant stages folder](../stages-multitenant/). ## Shared resources (2)