Support domainless orgs in FAST (#2086)

* bootstrap

* align org policies to domainless enforced ones

* fix #2073

* fix tests

* fix team admin attribute in resman stage
This commit is contained in:
Ludovico Magnocavallo 2024-02-19 11:29:37 +03:00 committed by GitHub
parent bee3072568
commit eb23bb62d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 83 additions and 65 deletions

View File

@ -626,7 +626,7 @@ The `fast_features` variable consists of 4 toggles:
| 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`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [organization](variables.tf#L229) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [organization](variables.tf#L229) | Organization details. | <code title="object&#40;&#123;&#10; id &#61; number&#10; domain &#61; optional&#40;string&#41;&#10; customer_id &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [prefix](variables.tf#L244) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
| [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | |
| [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. | <code title="object&#40;&#123;&#10; bootstrap &#61; optional&#40;object&#40;&#123;&#10; name &#61; string&#10; type &#61; string&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; resman &#61; optional&#40;object&#40;&#123;&#10; name &#61; string&#10; type &#61; string&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |

View File

@ -34,22 +34,25 @@ compute.trustedImageProjects:
rules:
- allow:
values:
- "projects/centos-cloud"
- "projects/cos-cloud"
- "projects/debian-cloud"
- "projects/fedora-cloud"
- "projects/fedora-coreos-cloud"
- "projects/opensuse-cloud"
- "projects/rhel-cloud"
- "projects/rhel-sap-cloud"
- "projects/rocky-linux-cloud"
- "projects/suse-cloud"
- "projects/suse-byos-cloud"
- "projects/suse-sap-cloud"
- "projects/ubuntu-os-cloud"
- "projects/ubuntu-os-pro-cloud"
- "projects/windows-cloud"
- "projects/windows-sql-cloud"
- "is:projects/centos-cloud"
- "is:projects/cos-cloud"
- "is:projects/debian-cloud"
- "is:projects/fedora-cloud"
- "is:projects/fedora-coreos-cloud"
- "is:projects/opensuse-cloud"
- "is:projects/rhel-cloud"
- "is:projects/rhel-sap-cloud"
- "is:projects/rocky-linux-cloud"
- "is:projects/suse-cloud"
- "is:projects/suse-sap-cloud"
- "is:projects/ubuntu-os-cloud"
- "is:projects/ubuntu-os-pro-cloud"
- "is:projects/windows-cloud"
- "is:projects/windows-sql-cloud"
- "is:projects/confidential-vm-images"
- "is:projects/backupdr-images"
- "is:projects/deeplearning-platform-release"
- "is:projects/serverless-vpc-access-images"
# compute.disableInternetNetworkEndpointGroup:
# rules:

View File

@ -10,7 +10,6 @@ run.allowedIngress:
rules:
- allow:
values:
- is:internal
- is:internal-and-cloud-load-balancing
# run.allowedVPCEgress:
# rules:

View File

@ -22,7 +22,7 @@ locals {
"roles/billing.creator"
]
# domain IAM bindings
iam_domain_bindings = {
iam_domain_bindings = var.organization.domain == null ? {} : {
"domain:${var.organization.domain}" = {
authoritative = ["roles/browser"]
additive = []

View File

@ -46,7 +46,7 @@ locals {
]
])
drs_domains = concat(
[var.organization.customer_id],
var.organization.customer_id == null ? [] : [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}"
@ -104,26 +104,28 @@ locals {
iam_roles_additive = distinct([
for k, v in local._iam_bindings_additive : v.role
])
# import org policies only when not using bootstrap user
import_org_policies = var.org_policies_config.import_defaults && var.bootstrap_user != null
}
# TODO: add a check block to ensure our custom roles exist in the factory files
# import org policy constraints enabled by default in new orgs since February 2024
import {
for_each = !local.import_org_policies ? toset([]) : toset([
"compute.requireOsLogin",
"compute.skipDefaultNetworkCreation",
"compute.vmExternalIpAccess",
"iam.allowedPolicyMemberDomains",
"iam.automaticIamGrantsForDefaultServiceAccounts",
"iam.disableServiceAccountKeyCreation",
"iam.disableServiceAccountKeyUpload",
"sql.restrictAuthorizedNetworks",
"sql.restrictPublicIp",
"storage.uniformBucketLevelAccess",
])
for_each = (
!var.org_policies_config.import_defaults || var.bootstrap_user != null
? toset([])
: toset([
"compute.requireOsLogin",
"compute.skipDefaultNetworkCreation",
"compute.vmExternalIpAccess",
"iam.allowedPolicyMemberDomains",
"iam.automaticIamGrantsForDefaultServiceAccounts",
"iam.disableServiceAccountKeyCreation",
"iam.disableServiceAccountKeyUpload",
"sql.restrictAuthorizedNetworks",
"sql.restrictPublicIp",
"storage.uniformBucketLevelAccess",
])
)
id = "organizations/${var.organization.id}/policies/${each.key}"
to = module.organization.google_org_policy_policy.default[each.key]
}
@ -151,34 +153,48 @@ module "organization" {
var.iam_bindings_additive
)
# delegated role grant for resource manager service account
iam_bindings = {
organization_iam_admin_conditional = {
members = [module.automation-tf-resman-sa.iam_email]
role = module.organization.custom_role_id["organization_iam_admin"]
condition = {
expression = format(
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
join(",", formatlist("'%s'", concat(
[
iam_bindings = merge(
{
organization_iam_admin_conditional = {
members = [module.automation-tf-resman-sa.iam_email]
role = module.organization.custom_role_id["organization_iam_admin"]
condition = {
expression = format(
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
join(",", formatlist("'%s'", [
"roles/accesscontextmanager.policyAdmin",
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin",
"roles/orgpolicy.policyAdmin",
"roles/orgpolicy.policyViewer",
"roles/resourcemanager.organizationViewer",
module.organization.custom_role_id["tenant_network_admin"]
],
local.billing_mode == "org" ? [
]))
)
title = "automation_sa_delegated_grants"
description = "Automation service account delegated grants."
}
}
},
local.billing_mode != "org" ? {} : {
organization_billing_conditional = {
members = [module.automation-tf-resman-sa.iam_email]
role = module.organization.custom_role_id["organization_iam_admin"]
condition = {
expression = format(
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
join(",", formatlist("'%s'", [
"roles/billing.admin",
"roles/billing.costsManager",
"roles/billing.user",
] : []
)))
)
title = "automation_sa_delegated_grants"
description = "Automation service account delegated grants."
]))
)
title = "automation_sa_delegated_grants"
description = "Automation service account delegated grants."
}
}
}
}
)
custom_roles = var.custom_roles
factories_config = {
custom_roles = var.factories_config.custom_roles

View File

@ -229,9 +229,9 @@ variable "org_policies_config" {
variable "organization" {
description = "Organization details."
type = object({
domain = string
id = number
customer_id = string
domain = optional(string)
customer_id = optional(string)
})
}

View File

@ -254,7 +254,7 @@ This is an example that shows how to populate the relevant variables.
```tfvars
tenants = {
tn0 = {
admin_group_email = "tn-0-admins@tenant.example.org"
admin_principal = "group:tn-0-admins@tenant.example.org"
descriptive_name = "Tenant 0"
# an optional billing account and org can be specified for the tenant
organization = {
@ -264,7 +264,7 @@ tenants = {
}
}
tnq = {
admin_group_email = "tn-1-admins@example.org"
admin_principal = "group:tn-1-admins@example.org"
descriptive_name = "Tenant 1"
}
}

View File

@ -34,8 +34,8 @@ module "branch-security-folder" {
"roles/resourcemanager.folderAdmin" = [module.branch-security-sa.iam_email]
"roles/resourcemanager.projectCreator" = [module.branch-security-sa.iam_email]
# read-only (plan) automation service account
"roles/viewer" = [module.branch-network-r-sa.iam_email]
"roles/resourcemanager.folderViewer" = [module.branch-network-r-sa.iam_email]
"roles/viewer" = [module.branch-security-r-sa.iam_email]
"roles/resourcemanager.folderViewer" = [module.branch-security-r-sa.iam_email]
}
tag_bindings = {
context = try(

View File

@ -21,7 +21,7 @@
locals {
tenant_iam = {
for k, v in var.tenants : k => [
"group:${v.admin_group_email}",
v.admin_principal,
module.tenant-self-iac-sa[k].iam_email
]
}

View File

@ -127,13 +127,13 @@ locals {
},
{
for k, v in var.tenants : "org-viewer-tenant_${k}_admin" => {
member = "group:${v.admin_group_email}"
member = v.admin_principal
role = "roles/resourcemanager.organizationViewer"
}
},
local.billing_mode != "org" ? {} : {
for k, v in var.tenants : "billing_user-tenant_${k}_billing_admin" => {
member = "group:${v.admin_group_email}"
member = v.admin_principal
role = "roles/billing.user"
}
},

View File

@ -194,7 +194,7 @@ values:
module.organization.google_organization_iam_binding.bindings["organization_iam_admin_conditional"]:
condition:
- description: Automation service account delegated grants.
expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/resourcemanager.organizationViewer','organizations/123456789012/roles/tenantNetworkAdmin','roles/billing.admin','roles/billing.costsManager','roles/billing.user'])
expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/orgpolicy.policyViewer','roles/resourcemanager.organizationViewer','organizations/123456789012/roles/tenantNetworkAdmin'])
title: automation_sa_delegated_grants
members:
- serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com
@ -363,7 +363,7 @@ counts:
google_logging_organization_sink: 3
google_logging_project_bucket_config: 3
google_org_policy_policy: 20
google_organization_iam_binding: 26
google_organization_iam_binding: 27
google_organization_iam_custom_role: 6
google_organization_iam_member: 35
google_project: 3
@ -381,4 +381,4 @@ counts:
google_tags_tag_key: 1
google_tags_tag_value: 1
modules: 16
resources: 190
resources: 191

View File

@ -42,7 +42,7 @@ counts:
google_logging_organization_sink: 3
google_logging_project_bucket_config: 3
google_org_policy_policy: 20
google_organization_iam_binding: 26
google_organization_iam_binding: 27
google_organization_iam_custom_role: 6
google_organization_iam_member: 22
google_project: 3
@ -61,7 +61,7 @@ counts:
google_tags_tag_value: 1
local_file: 7
modules: 15
resources: 181
resources: 182
outputs:
custom_roles: