diff --git a/0-bootstrap/README-GitHub.md b/0-bootstrap/README-GitHub.md index 3f9c133ca..6b25c1abf 100644 --- a/0-bootstrap/README-GitHub.md +++ b/0-bootstrap/README-GitHub.md @@ -37,19 +37,12 @@ Also make sure that you have the following: - A Google Cloud [organization](https://cloud.google.com/resource-manager/docs/creating-managing-organization). - A Google Cloud [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account). - Cloud Identity or Google Workspace groups for organization and billing admins. -- Add the Identity (user or Service Account) who will run Terraform to the `group_org_admins` group. -They must be in this group, or they won't have `roles/resourcemanager.projectCreator` access. -- For the Identity who will run the procedures in this document, grant the following roles: - - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. - - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. - - The `roles/billing.admin` role on the billing account. - - The `roles/resourcemanager.folderCreator` role. - -If other users need to be able to run these procedures, add them to the group -represented by the `org_project_creators` variable. -For more information about the permissions that are required, and the resources -that are created, see the organization bootstrap module -[documentation.](https://github.com/terraform-google-modules/terraform-google-bootstrap) +- For the user who will run the procedures in this document, grant the following roles: + - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. + - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. + - The `roles/resourcemanager.projectCreator` role on the Google Cloud organization. + - The `roles/billing.admin` role on the billing account. + - The `roles/resourcemanager.folderCreator` role. ## Instructions diff --git a/0-bootstrap/README-GitLab.md b/0-bootstrap/README-GitLab.md index f9d9a1667..bccc35ba3 100644 --- a/0-bootstrap/README-GitLab.md +++ b/0-bootstrap/README-GitLab.md @@ -37,19 +37,12 @@ Also make sure that you have the following: - A Google Cloud [organization](https://cloud.google.com/resource-manager/docs/creating-managing-organization). - A Google Cloud [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account). - Cloud Identity or Google Workspace groups for organization and billing admins. -- Add the Identity (user or Service Account) who will run Terraform to the `group_org_admins` group. -They must be in this group, or they won't have `roles/resourcemanager.projectCreator` access. -- For the Identity who will run the procedures in this document, grant the following roles: - - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. - - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. - - The `roles/billing.admin` role on the billing account. - - The `roles/resourcemanager.folderCreator` role. - -If other users need to be able to run these procedures, add them to the group -represented by the `org_project_creators` variable. -For more information about the permissions that are required, and the resources -that are created, see the organization bootstrap module -[documentation.](https://github.com/terraform-google-modules/terraform-google-bootstrap) +- For the user who will run the procedures in this document, grant the following roles: + - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. + - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. + - The `roles/resourcemanager.projectCreator` role on the Google Cloud organization. + - The `roles/billing.admin` role on the billing account. + - The `roles/resourcemanager.folderCreator` role. ## Instructions diff --git a/0-bootstrap/README-Terraform-Cloud.md b/0-bootstrap/README-Terraform-Cloud.md index 83c3f8261..43c53f4b0 100644 --- a/0-bootstrap/README-Terraform-Cloud.md +++ b/0-bootstrap/README-Terraform-Cloud.md @@ -43,19 +43,12 @@ Also make sure that you have the following: - A Google Cloud [organization](https://cloud.google.com/resource-manager/docs/creating-managing-organization). - A Google Cloud [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account). - Cloud Identity or Google Workspace groups for organization and billing admins. -- Add the Identity (user or Service Account) who will run Terraform to the `group_org_admins` group. -They must be in this group, or they won't have `roles/resourcemanager.projectCreator` access. -- For the Identity who will run the procedures in this document, grant the following roles: - - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. - - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. - - The `roles/billing.admin` role on the billing account. - - The `roles/resourcemanager.folderCreator` role. - -If other users need to be able to run these procedures, add them to the group -represented by the `org_project_creators` variable. -For more information about the permissions that are required, and the resources -that are created, see the organization bootstrap module -[documentation.](https://github.com/terraform-google-modules/terraform-google-bootstrap) +- For the user who will run the procedures in this document, grant the following roles: + - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. + - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. + - The `roles/resourcemanager.projectCreator` role on the Google Cloud organization. + - The `roles/billing.admin` role on the billing account. + - The `roles/resourcemanager.folderCreator` role. ### Instructions diff --git a/0-bootstrap/README.md b/0-bootstrap/README.md index 29481b1ea..5cae19a76 100644 --- a/0-bootstrap/README.md +++ b/0-bootstrap/README.md @@ -75,37 +75,26 @@ Also make sure that you've done the following: [organization](https://cloud.google.com/resource-manager/docs/creating-managing-organization). 1. Set up a Google Cloud [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account). -1. Create Cloud Identity or Google Workspace groups for - organization and billing admins. -1. Add the user who will use Terraform to the `group_org_admins` group. - They must be in this group, or they won't have - `roles/resourcemanager.projectCreator` access. +1. Create Cloud Identity or Google Workspace groups as defined in [groups for access control](https://cloud.google.com/architecture/security-foundations/authentication-authorization#groups_for_access_control). +Set the variables in **terraform.tfvars** (`groups` block) to use the specific group names you create. 1. For the user who will run the procedures in this document, grant the following roles: - The `roles/resourcemanager.organizationAdmin` role on the Google Cloud organization. - The `roles/orgpolicy.policyAdmin` role on the Google Cloud organization. + - The `roles/resourcemanager.projectCreator` role on the Google Cloud organization. - The `roles/billing.admin` role on the billing account. - The `roles/resourcemanager.folderCreator` role. -If other users need to be able to run these procedures, add them to the group -represented by the `org_project_creators` variable. -For more information about the permissions that are required, and the resources -that are created, see the organization bootstrap module -[documentation.](https://github.com/terraform-google-modules/terraform-google-bootstrap) - ### Optional - Automatic creation of Google Cloud Identity groups In the foundation, Google Cloud Identity groups are used for [authentication and access management](https://cloud.google.com/architecture/security-foundations/authentication-authorization) . -To enable automatic creation of the [required groups](https://cloud.google.com/architecture/security-foundations/authentication-authorization#users_and_groups), complete the following actions: +To enable automatic creation of the [groups](https://cloud.google.com/architecture/security-foundations/authentication-authorization#groups_for_access_control), complete the following actions: - Have an existing project for Cloud Identity API billing. - Enable the Cloud Identity API (`cloudidentity.googleapis.com`) on the billing project. - Grant role `roles/serviceusage.serviceUsageConsumer` to the user running Terraform on the billing project. -- Provide values for the groups and billing project in the variable `groups`. - -All groups in the `groups.required_groups` are required. - -All groups in the `groups.optional_groups` are optional. +- Change the field `groups.create_required_groups` to **true** to create the required groups. +- Change the field `groups.create_optional_groups` to **true** and fill the `groups.optional_groups` with the emails to be created. ### Optional - Cloud Build access to on-prem @@ -305,13 +294,10 @@ Each step has instructions for this change. | bucket\_tfstate\_kms\_force\_destroy | When deleting a bucket, this boolean option will delete the KMS keys used for the Terraform state bucket. | `bool` | `false` | no | | default\_region | Default region to create resources where applicable. | `string` | `"us-central1"` | no | | folder\_prefix | Name prefix to use for folders created. Should be the same in all steps. | `string` | `"fldr"` | no | -| group\_billing\_admins | Google Group for GCP Billing Administrators | `string` | n/a | yes | -| group\_org\_admins | Google Group for GCP Organization Administrators | `string` | n/a | yes | -| groups | Contain the details of the Groups to be created. |
object({
create_groups = bool
billing_project = string
required_groups = object({
group_org_admins = string
group_billing_admins = string
billing_data_users = string
audit_data_users = string
monitoring_workspace_users = string
})
optional_groups = object({
gcp_platform_viewer = string
gcp_security_reviewer = string
gcp_network_viewer = string
gcp_scc_admin = string
gcp_global_secrets_admin = string
gcp_audit_viewer = string
})
}) | {
"billing_project": null,
"create_groups": false,
"optional_groups": {
"gcp_audit_viewer": "",
"gcp_global_secrets_admin": "",
"gcp_network_viewer": "",
"gcp_platform_viewer": "",
"gcp_scc_admin": "",
"gcp_security_reviewer": ""
},
"required_groups": {
"audit_data_users": "",
"billing_data_users": "",
"group_billing_admins": "",
"group_org_admins": "",
"monitoring_workspace_users": ""
}
} | no |
+| groups | Contain the details of the Groups to be created. | object({
create_required_groups = optional(bool, false)
create_optional_groups = optional(bool, false)
billing_project = optional(string, null)
required_groups = object({
group_org_admins = string
group_billing_admins = string
billing_data_users = string
audit_data_users = string
monitoring_workspace_users = string
})
optional_groups = optional(object({
gcp_security_reviewer = optional(string, "")
gcp_network_viewer = optional(string, "")
gcp_scc_admin = optional(string, "")
gcp_global_secrets_admin = optional(string, "")
gcp_kms_admin = optional(string, "")
}), {})
}) | n/a | yes |
| initial\_group\_config | Define the group configuration when it is initialized. Valid values are: WITH\_INITIAL\_OWNER, EMPTY and INITIAL\_GROUP\_CONFIG\_UNSPECIFIED. | `string` | `"WITH_INITIAL_OWNER"` | no |
| org\_id | GCP Organization ID | `string` | n/a | yes |
| org\_policy\_admin\_role | Additional Org Policy Admin role for admin group. You can use this for testing purposes. | `bool` | `false` | no |
-| org\_project\_creators | Additional list of members to have project creator role across the organization. Prefix of group: user: or serviceAccount: is required. | `list(string)` | `[]` | no |
| parent\_folder | Optional - for an organization with existing projects or for development/validation. It will place all the example foundation resources under the provided folder instead of the root organization. The value is the numeric folder ID. The folder must already exist. | `string` | `""` | no |
| project\_prefix | Name prefix to use for projects created. Should be the same in all steps. Max size is 3 characters. | `string` | `"prj"` | no |
@@ -332,8 +318,6 @@ Each step has instructions for this change.
| gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud Build artifacts in cicd project. |
| gcs\_bucket\_cloudbuild\_logs | Bucket used to store Cloud Build logs in cicd project. |
| gcs\_bucket\_tfstate | Bucket used for storing terraform state for Foundations Pipelines in Seed Project. |
-| group\_billing\_admins | Google Group for GCP Billing Administrators. |
-| group\_org\_admins | Google Group for GCP Organization Administrators. |
| networks\_step\_terraform\_service\_account\_email | Networks Step Terraform Account |
| optional\_groups | List of Google Groups created that are optional to the Example Foundation steps. |
| organization\_step\_terraform\_service\_account\_email | Organization Step Terraform Account |
diff --git a/0-bootstrap/cb.tf b/0-bootstrap/cb.tf
index 827f0d2a0..2b9829654 100644
--- a/0-bootstrap/cb.tf
+++ b/0-bootstrap/cb.tf
@@ -93,7 +93,7 @@ module "tf_source" {
project_id = "${var.project_prefix}-b-cicd-${random_string.suffix.result}"
location = var.default_region
billing_account = var.billing_account
- group_org_admins = local.group_org_admins
+ group_org_admins = var.groups.required_groups.group_org_admins
buckets_force_destroy = var.bucket_force_destroy
activate_apis = [
diff --git a/0-bootstrap/groups.tf b/0-bootstrap/groups.tf
index 8254c6aca..6f9a8d160 100644
--- a/0-bootstrap/groups.tf
+++ b/0-bootstrap/groups.tf
@@ -17,18 +17,18 @@
#Groups creation resources
locals {
- optional_groups_to_create = {
- for key, value in var.groups.optional_groups : key => value
- if value != "" && var.groups.create_groups == true
- }
required_groups_to_create = {
for key, value in var.groups.required_groups : key => value
- if var.groups.create_groups == true
+ if var.groups.create_required_groups == true
+ }
+ optional_groups_to_create = {
+ for key, value in var.groups.optional_groups : key => value
+ if value != "" && var.groups.create_optional_groups == true
}
}
data "google_organization" "org" {
- count = var.groups.create_groups ? 1 : 0
+ count = var.groups.create_required_groups || var.groups.create_optional_groups ? 1 : 0
organization = var.org_id
}
diff --git a/0-bootstrap/main.tf b/0-bootstrap/main.tf
index 0ed502ed4..353908f45 100644
--- a/0-bootstrap/main.tf
+++ b/0-bootstrap/main.tf
@@ -28,13 +28,10 @@ locals {
"serviceAccount:${google_service_account.terraform-env-sa["net"].email}",
"serviceAccount:${google_service_account.terraform-env-sa["proj"].email}",
]
- org_project_creators = distinct(concat(var.org_project_creators, local.step_terraform_sa))
- parent = var.parent_folder != "" ? "folders/${var.parent_folder}" : "organizations/${var.org_id}"
+ parent = var.parent_folder != "" ? "folders/${var.parent_folder}" : "organizations/${var.org_id}"
org_admins_org_iam_permissions = var.org_policy_admin_role == true ? [
"roles/orgpolicy.policyAdmin", "roles/resourcemanager.organizationAdmin", "roles/billing.user"
] : ["roles/resourcemanager.organizationAdmin", "roles/billing.user"]
- group_org_admins = var.groups.create_groups ? module.required_group["group_org_admins"].id : var.group_org_admins
- group_billing_admins = var.groups.create_groups ? module.required_group["group_billing_admins"].id : var.group_billing_admins
}
resource "google_folder" "bootstrap" {
@@ -52,10 +49,10 @@ module "seed_bootstrap" {
state_bucket_name = "${var.bucket_prefix}-${var.project_prefix}-b-seed-tfstate"
force_destroy = var.bucket_force_destroy
billing_account = var.billing_account
- group_org_admins = local.group_org_admins
- group_billing_admins = local.group_billing_admins
+ group_org_admins = var.groups.required_groups.group_org_admins
+ group_billing_admins = var.groups.required_groups.group_billing_admins
default_region = var.default_region
- org_project_creators = local.org_project_creators
+ org_project_creators = local.step_terraform_sa
sa_enable_impersonation = true
create_terraform_sa = false
parent_folder = var.parent_folder == "" ? "" : local.parent
diff --git a/0-bootstrap/outputs.tf b/0-bootstrap/outputs.tf
index fa3d51636..3b33ebe17 100644
--- a/0-bootstrap/outputs.tf
+++ b/0-bootstrap/outputs.tf
@@ -63,24 +63,14 @@ output "common_config" {
}
}
-output "group_org_admins" {
- description = "Google Group for GCP Organization Administrators."
- value = var.groups.create_groups == true ? module.required_group["group_org_admins"].id : var.group_org_admins
-}
-
-output "group_billing_admins" {
- description = "Google Group for GCP Billing Administrators."
- value = var.groups.create_groups == true ? module.required_group["group_billing_admins"].id : var.group_billing_admins
-}
-
output "required_groups" {
description = "List of Google Groups created that are required by the Example Foundation steps."
- value = var.groups.create_groups == true ? module.required_group : {}
+ value = var.groups.create_required_groups == true ? module.required_group : tomap(var.groups.required_groups)
}
output "optional_groups" {
description = "List of Google Groups created that are optional to the Example Foundation steps."
- value = var.groups.create_groups == true ? module.optional_group : {}
+ value = var.groups.create_optional_groups == true ? module.optional_group : tomap(var.groups.optional_groups)
}
/* ----------------------------------------
diff --git a/0-bootstrap/sa.tf b/0-bootstrap/sa.tf
index 205995603..e0ed0da54 100644
--- a/0-bootstrap/sa.tf
+++ b/0-bootstrap/sa.tf
@@ -227,3 +227,10 @@ resource "google_billing_account_iam_member" "billing_admin_user" {
google_billing_account_iam_member.tf_billing_user
]
}
+
+resource "google_billing_account_iam_member" "billing_account_sink" {
+ billing_account_id = var.billing_account
+ role = "roles/logging.configWriter"
+ member = "serviceAccount:${google_service_account.terraform-env-sa["org"].email}"
+}
+
diff --git a/0-bootstrap/terraform.example.tfvars b/0-bootstrap/terraform.example.tfvars
index 67bea0614..0ef591ca4 100644
--- a/0-bootstrap/terraform.example.tfvars
+++ b/0-bootstrap/terraform.example.tfvars
@@ -18,13 +18,27 @@ org_id = "REPLACE_ME" # format "000000000000"
billing_account = "REPLACE_ME" # format "000000-000000-000000"
-group_org_admins = "REPLACE_ME"
-
-group_billing_admins = "REPLACE_ME"
-
-# Example of values for the groups
-# group_org_admins = "gcp-organization-admins@example.com"
-# group_billing_admins = "gcp-billing-admins@example.com"
+// For enabling the automatic groups creation, uncoment the
+// variables and update the values with the group names
+groups = {
+ # create_required_groups = false # Change to true to create the required_groups
+ # create_optional_groups = false # Change to true to create the optional_groups
+ # billing_project = "REPLACE_ME" # Fill to create required or optional groups
+ required_groups = {
+ group_org_admins = "REPLACE_ME" # example "gcp-organization-admins@example.com"
+ group_billing_admins = "REPLACE_ME" # example "gcp-billing-admins@example.com"
+ }
+ # optional_groups = {
+ # billing_data_users = "" #"billing_data_users_local_test@example.com"
+ # audit_data_users = "" #"audit_data_users_local_test@example.com"
+ # monitoring_workspace_users = "" #"monitoring_workspace_users_local_test@example.com"
+ # gcp_security_reviewer = "" #"gcp_security_reviewer_local_test@example.com"
+ # gcp_network_viewer = "" #"gcp_network_viewer_local_test@example.com"
+ # gcp_scc_admin = "" #"gcp_scc_admin_local_test@example.com"
+ # gcp_global_secrets_admin = "" #"gcp_global_secrets_admin_local_test@example.com"
+ # gcp_kms_admin = "" #"gcp_kms_admin_local_test@example.com"
+ # }
+}
default_region = "us-central1"
@@ -35,29 +49,6 @@ default_region = "us-central1"
# The folder must already exist.
# parent_folder = "01234567890"
-# Optional - for enabling the automatic groups creation, uncoment the groups
-# variable and update the values with the desired group names
-# groups = {
-# create_groups = true,
-# billing_project = "billing-project",
-# required_groups = {
-# group_org_admins = "group_org_admins_local_test@example.com"
-# group_billing_admins = "group_billing_admins_local_test@example.com"
-# billing_data_users = "billing_data_users_local_test@example.com"
-# audit_data_users = "audit_data_users_local_test@example.com"
-# monitoring_workspace_users = "monitoring_workspace_users_local_test@example.com"
-# },
-# optional_groups = {
-# gcp_platform_viewer = "gcp_platform_viewer_local_test@example.com"
-# gcp_security_reviewer = "gcp_security_reviewer_local_test@example.com"
-# gcp_network_viewer = "gcp_network_viewer_local_test@example.com"
-# gcp_scc_admin = "gcp_scc_admin_local_test@example.com"
-# gcp_global_secrets_admin = "gcp_global_secrets_admin_local_test@example.com"
-# gcp_audit_viewer = "gcp_audit_viewer_local_test@example.com"
-# }
-# }
-
-
/* ----------------------------------------
Specific to github_bootstrap
diff --git a/0-bootstrap/variables.tf b/0-bootstrap/variables.tf
index a1598a3d1..58e70f730 100644
--- a/0-bootstrap/variables.tf
+++ b/0-bootstrap/variables.tf
@@ -24,16 +24,6 @@ variable "billing_account" {
type = string
}
-variable "group_org_admins" {
- description = "Google Group for GCP Organization Administrators"
- type = string
-}
-
-variable "group_billing_admins" {
- description = "Google Group for GCP Billing Administrators"
- type = string
-}
-
variable "default_region" {
description = "Default region to create resources where applicable."
type = string
@@ -46,12 +36,6 @@ variable "parent_folder" {
default = ""
}
-variable "org_project_creators" {
- description = "Additional list of members to have project creator role across the organization. Prefix of group: user: or serviceAccount: is required."
- type = list(string)
- default = []
-}
-
variable "org_policy_admin_role" {
description = "Additional Org Policy Admin role for admin group. You can use this for testing purposes."
type = bool
@@ -94,8 +78,9 @@ variable "bucket_tfstate_kms_force_destroy" {
variable "groups" {
description = "Contain the details of the Groups to be created."
type = object({
- create_groups = bool
- billing_project = string
+ create_required_groups = optional(bool, false)
+ create_optional_groups = optional(bool, false)
+ billing_project = optional(string, null)
required_groups = object({
group_org_admins = string
group_billing_admins = string
@@ -103,65 +88,44 @@ variable "groups" {
audit_data_users = string
monitoring_workspace_users = string
})
- optional_groups = object({
- gcp_platform_viewer = string
- gcp_security_reviewer = string
- gcp_network_viewer = string
- gcp_scc_admin = string
- gcp_global_secrets_admin = string
- gcp_audit_viewer = string
- })
+ optional_groups = optional(object({
+ gcp_security_reviewer = optional(string, "")
+ gcp_network_viewer = optional(string, "")
+ gcp_scc_admin = optional(string, "")
+ gcp_global_secrets_admin = optional(string, "")
+ gcp_kms_admin = optional(string, "")
+ }), {})
})
- default = {
- create_groups = false
- billing_project = null
- required_groups = {
- group_org_admins = ""
- group_billing_admins = ""
- billing_data_users = ""
- audit_data_users = ""
- monitoring_workspace_users = ""
- }
- optional_groups = {
- gcp_platform_viewer = ""
- gcp_security_reviewer = ""
- gcp_network_viewer = ""
- gcp_scc_admin = ""
- gcp_global_secrets_admin = ""
- gcp_audit_viewer = ""
- }
- }
validation {
- condition = var.groups.create_groups == true ? (var.groups.billing_project != "" ? true : false) : true
+ condition = var.groups.create_required_groups || var.groups.create_optional_groups ? (var.groups.billing_project != null ? true : false) : true
error_message = "A billing_project must be passed to use the automatic group creation."
}
validation {
- condition = var.groups.create_groups == true ? (var.groups.required_groups.group_org_admins != "" ? true : false) : true
- error_message = "The group group_org_admins is invalid, it must be a valid email."
+ condition = var.groups.required_groups.group_org_admins != ""
+ error_message = "The group group_org_admins is invalid, it must be a valid email"
}
validation {
- condition = var.groups.create_groups == true ? (var.groups.required_groups.group_billing_admins != "" ? true : false) : true
- error_message = "The group group_billing_admins is invalid, it must be a valid email."
+ condition = var.groups.required_groups.group_billing_admins != ""
+ error_message = "The group group_billing_admins is invalid, it must be a valid email"
}
validation {
- condition = var.groups.create_groups == true ? (var.groups.required_groups.billing_data_users != "" ? true : false) : true
- error_message = "The group billing_data_users is invalid, it must be a valid email."
+ condition = var.groups.required_groups.billing_data_users != ""
+ error_message = "The group billing_data_users is invalid, it must be a valid email"
}
validation {
- condition = var.groups.create_groups == true ? (var.groups.required_groups.audit_data_users != "" ? true : false) : true
- error_message = "The group audit_data_users is invalid, it must be a valid email."
+ condition = var.groups.required_groups.audit_data_users != ""
+ error_message = "The group audit_data_users is invalid, it must be a valid email"
}
validation {
- condition = var.groups.create_groups == true ? (var.groups.required_groups.monitoring_workspace_users != "" ? true : false) : true
- error_message = "The group monitoring_workspace_users is invalid, it must be a valid email."
+ condition = var.groups.required_groups.monitoring_workspace_users != ""
+ error_message = "The group monitoring_workspace_users is invalid, it must be a valid email"
}
-
}
variable "initial_group_config" {
diff --git a/1-org/envs/shared/README.md b/1-org/envs/shared/README.md
index 3ccdf7739..0d8c22779 100644
--- a/1-org/envs/shared/README.md
+++ b/1-org/envs/shared/README.md
@@ -3,8 +3,6 @@
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| audit\_data\_users | Google Workspace or Cloud Identity group that have access to audit logs. | `string` | n/a | yes |
-| billing\_data\_users | Google Workspace or Cloud Identity group that have access to billing data set. | `string` | n/a | yes |
| billing\_export\_dataset\_location | The location of the dataset for billing data export. | `string` | `"US"` | no |
| cai\_monitoring\_kms\_force\_destroy | If set to true, delete KMS keyring and keys when destroying the module; otherwise, destroying the module will fail if KMS keys are present. | `bool` | `false` | no |
| create\_access\_context\_manager\_access\_policy | Whether to create access context manager access policy. | `bool` | `true` | no |
@@ -15,8 +13,7 @@
| enforce\_allowed\_worker\_pools | Whether to enforce the organization policy restriction on allowed worker pools for Cloud Build. | `bool` | `false` | no |
| essential\_contacts\_domains\_to\_allow | The list of domains that email addresses added to Essential Contacts can have. | `list(string)` | n/a | yes |
| essential\_contacts\_language | Essential Contacts preferred language for notifications, as a ISO 639-1 language code. See [Supported languages](https://cloud.google.com/resource-manager/docs/managing-notification-contacts#supported-languages) for a list of supported languages. | `string` | `"en"` | no |
-| gcp\_groups | Groups to grant specific roles in the Organization.object({
platform_viewer = optional(string, null)
security_reviewer = optional(string, null)
network_viewer = optional(string, null)
scc_admin = optional(string, null)
audit_viewer = optional(string, null)
global_secrets_admin = optional(string, null)
}) | `{}` | no |
-| gcp\_user | Users to grant specific roles in the Organization.object({
org_admin = optional(string, null)
billing_creator = optional(string, null)
billing_admin = optional(string, null)
}) | `{}` | no |
+| gcp\_groups | Groups to grant specific roles in the Organization.object({
audit_viewer = optional(string, null)
security_reviewer = optional(string, null)
network_viewer = optional(string, null)
scc_admin = optional(string, null)
global_secrets_admin = optional(string, null)
kms_admin = optional(string, null)
}) | `{}` | no |
| log\_export\_storage\_force\_destroy | (Optional) If set to true, delete all contents when destroying the resource; otherwise, destroying the resource will fail if contents are present. | `bool` | `false` | no |
| log\_export\_storage\_location | The location of the storage bucket used to export logs. | `string` | `"US"` | no |
| log\_export\_storage\_retention\_policy | Configuration of the bucket's data retention policy for how long objects in the bucket should be retained. | object({
is_locked = bool
retention_period_days = number
}) | `null` | no |
@@ -32,6 +29,7 @@
| Name | Description |
|------|-------------|
| base\_net\_hub\_project\_id | The Base Network hub project ID |
+| billing\_sink\_names | The name of the sinks under billing account level. |
| cai\_monitoring\_artifact\_registry | CAI Monitoring Cloud Function Artifact Registry name. |
| cai\_monitoring\_asset\_feed | CAI Monitoring Cloud Function Organization Asset Feed name. |
| cai\_monitoring\_bucket | CAI Monitoring Cloud Function Source Bucket name. |
diff --git a/1-org/envs/shared/essential_contacts.tf b/1-org/envs/shared/essential_contacts.tf
index 2b43fc1a1..1aee708d4 100644
--- a/1-org/envs/shared/essential_contacts.tf
+++ b/1-org/envs/shared/essential_contacts.tf
@@ -15,19 +15,20 @@
*/
locals {
+ group_org_admins = local.required_groups["group_org_admins"]
+ group_billing_admins = local.required_groups["group_billing_admins"]
gcp_scc_admin = var.gcp_groups.scc_admin == null ? local.group_org_admins : var.gcp_groups.scc_admin
- gcp_platform_viewer = var.gcp_groups.platform_viewer == null ? local.group_org_admins : var.gcp_groups.platform_viewer
gcp_security_reviewer = var.gcp_groups.security_reviewer == null ? local.group_org_admins : var.gcp_groups.security_reviewer
gcp_network_viewer = var.gcp_groups.network_viewer == null ? local.group_org_admins : var.gcp_groups.network_viewer
# Notification categories details: https://cloud.google.com/resource-manager/docs/managing-notification-contacts#notification-categories
categories_map = {
- "BILLING" = setunion([local.group_billing_admins, var.billing_data_users])
- "LEGAL" = setunion([local.group_org_admins, var.audit_data_users])
- "PRODUCT_UPDATES" = setunion([local.gcp_scc_admin, local.gcp_platform_viewer])
+ "BILLING" = setunion([local.group_billing_admins, local.required_groups["billing_data_users"]])
+ "LEGAL" = setunion([local.group_org_admins, local.required_groups["audit_data_users"]])
+ "PRODUCT_UPDATES" = [local.group_org_admins]
"SECURITY" = setunion([local.gcp_scc_admin, local.gcp_security_reviewer])
"SUSPENSION" = [local.group_org_admins]
- "TECHNICAL" = setunion([local.gcp_platform_viewer, local.gcp_security_reviewer, local.gcp_network_viewer])
+ "TECHNICAL" = setunion([local.gcp_security_reviewer, local.gcp_network_viewer])
}
# Convert a map indexed by category to a map indexed by email
diff --git a/1-org/envs/shared/iam.tf b/1-org/envs/shared/iam.tf
index d30c1c414..9398a4c98 100644
--- a/1-org/envs/shared/iam.tf
+++ b/1-org/envs/shared/iam.tf
@@ -64,16 +64,22 @@ resource "google_folder_iam_audit_config" "folder_config" {
}
}
+resource "google_project_iam_member" "audit_log_logging_viewer" {
+ project = module.org_audit_logs.project_id
+ role = "roles/logging.viewer"
+ member = "group:${local.required_groups["audit_data_users"]}"
+}
+
resource "google_project_iam_member" "audit_log_bq_user" {
project = module.org_audit_logs.project_id
role = "roles/bigquery.user"
- member = "group:${var.audit_data_users}"
+ member = "group:${local.required_groups["audit_data_users"]}"
}
resource "google_project_iam_member" "audit_log_bq_data_viewer" {
project = module.org_audit_logs.project_id
role = "roles/bigquery.dataViewer"
- member = "group:${var.audit_data_users}"
+ member = "group:${local.required_groups["audit_data_users"]}"
}
/******************************************
@@ -83,13 +89,13 @@ resource "google_project_iam_member" "audit_log_bq_data_viewer" {
resource "google_project_iam_member" "billing_bq_user" {
project = module.org_billing_logs.project_id
role = "roles/bigquery.user"
- member = "group:${var.billing_data_users}"
+ member = "group:${local.required_groups["billing_data_users"]}"
}
resource "google_project_iam_member" "billing_bq_viewer" {
project = module.org_billing_logs.project_id
role = "roles/bigquery.dataViewer"
- member = "group:${var.billing_data_users}"
+ member = "group:${local.required_groups["billing_data_users"]}"
}
/******************************************
@@ -99,27 +105,13 @@ resource "google_project_iam_member" "billing_bq_viewer" {
resource "google_organization_iam_member" "billing_viewer" {
org_id = local.org_id
role = "roles/billing.viewer"
- member = "group:${var.billing_data_users}"
+ member = "group:${local.required_groups["billing_data_users"]}"
}
/******************************************
- Groups permissions according to SFB (Section 6.2 - Users and groups) - IAM
+ Groups permissions
*****************************************/
-resource "google_organization_iam_member" "organization_viewer" {
- count = var.gcp_groups.platform_viewer != null && local.parent_folder == "" ? 1 : 0
- org_id = local.org_id
- role = "roles/viewer"
- member = "group:${var.gcp_groups.platform_viewer}"
-}
-
-resource "google_folder_iam_member" "organization_viewer" {
- count = var.gcp_groups.platform_viewer != null && local.parent_folder != "" ? 1 : 0
- folder = "folders/${local.parent_folder}"
- role = "roles/viewer"
- member = "group:${var.gcp_groups.platform_viewer}"
-}
-
resource "google_organization_iam_member" "security_reviewer" {
count = var.gcp_groups.security_reviewer != null && local.parent_folder == "" ? 1 : 0
org_id = local.org_id
@@ -169,7 +161,14 @@ resource "google_project_iam_member" "audit_bq_data_viewer" {
member = "group:${var.gcp_groups.audit_viewer}"
}
-resource "google_project_iam_member" "scc_admin" {
+resource "google_organization_iam_member" "org_scc_admin" {
+ count = var.gcp_groups.scc_admin != null && local.parent_folder == "" ? 1 : 0
+ org_id = local.org_id
+ role = "roles/securitycenter.adminEditor"
+ member = "group:${var.gcp_groups.scc_admin}"
+}
+
+resource "google_project_iam_member" "project_scc_admin" {
count = var.gcp_groups.scc_admin != null ? 1 : 0
project = module.scc_notifications.project_id
role = "roles/securitycenter.adminEditor"
@@ -183,34 +182,9 @@ resource "google_project_iam_member" "global_secrets_admin" {
member = "group:${var.gcp_groups.global_secrets_admin}"
}
-/******************************************
- Privileged accounts permissions according to SFB (Section 6.3 - Privileged identities)
-*****************************************/
-
-resource "google_organization_iam_member" "org_admin_user" {
- count = var.gcp_user.org_admin != null && local.parent_folder == "" ? 1 : 0
- org_id = local.org_id
- role = "roles/resourcemanager.organizationAdmin"
- member = "user:${var.gcp_user.org_admin}"
-}
-
-resource "google_folder_iam_member" "org_admin_user" {
- count = var.gcp_user.org_admin != null && local.parent_folder != "" ? 1 : 0
- folder = "folders/${local.parent_folder}"
- role = "roles/resourcemanager.folderAdmin"
- member = "user:${var.gcp_user.org_admin}"
-}
-
-resource "google_organization_iam_member" "billing_creator_user" {
- count = var.gcp_user.billing_creator != null && local.parent_folder == "" ? 1 : 0
- org_id = local.org_id
- role = "roles/billing.creator"
- member = "user:${var.gcp_user.billing_creator}"
-}
-
-resource "google_billing_account_iam_member" "billing_admin_user" {
- count = var.gcp_user.billing_admin != null ? 1 : 0
- billing_account_id = local.billing_account
- role = "roles/billing.admin"
- member = "user:${var.gcp_user.billing_admin}"
+resource "google_project_iam_member" "kms_admin" {
+ count = var.gcp_groups.kms_admin != null ? 1 : 0
+ project = module.org_kms.project_id
+ role = "roles/cloudkms.viewer"
+ member = "group:${var.gcp_groups.kms_admin}"
}
diff --git a/1-org/envs/shared/log_sinks.tf b/1-org/envs/shared/log_sinks.tf
index a884e6db2..88c5e51f4 100644
--- a/1-org/envs/shared/log_sinks.tf
+++ b/1-org/envs/shared/log_sinks.tf
@@ -42,6 +42,9 @@ module "logs_export" {
resources = local.parent_resources
resource_type = local.parent_resource_type
logging_destination_project_id = module.org_audit_logs.project_id
+ billing_account = local.billing_account
+ enable_billing_account_sink = true
+
/******************************************
Send logs to Storage
diff --git a/1-org/envs/shared/org_policy.tf b/1-org/envs/shared/org_policy.tf
index 6d391e85d..5c43dbbc1 100644
--- a/1-org/envs/shared/org_policy.tf
+++ b/1-org/envs/shared/org_policy.tf
@@ -90,6 +90,13 @@ module "restrict_protocol_fowarding" {
IAM
*******************************************/
+resource "time_sleep" "wait_logs_export" {
+ create_duration = "30s"
+ depends_on = [
+ module.logs_export
+ ]
+}
+
module "org_domain_restricted_sharing" {
source = "terraform-google-modules/org-policy/google//modules/domain_restricted_sharing"
version = "~> 5.1"
@@ -98,6 +105,10 @@ module "org_domain_restricted_sharing" {
folder_id = local.folder_id
policy_for = local.policy_for
domains_to_allow = var.domains_to_allow
+
+ depends_on = [
+ time_sleep.wait_logs_export
+ ]
}
/******************************************
diff --git a/1-org/envs/shared/outputs.tf b/1-org/envs/shared/outputs.tf
index 32c298728..171315e2d 100644
--- a/1-org/envs/shared/outputs.tf
+++ b/1-org/envs/shared/outputs.tf
@@ -119,6 +119,11 @@ output "logs_export_logbucket_name" {
description = "The log bucket for destination of log exports. See https://cloud.google.com/logging/docs/routing/overview#buckets ."
}
+output "billing_sink_names" {
+ value = module.logs_export.billing_sink_names
+ description = "The name of the sinks under billing account level."
+}
+
output "logs_export_logbucket_linked_dataset_name" {
value = module.logs_export.logbucket_linked_dataset_name
description = "The resource name of the Log Bucket linked BigQuery dataset created for Log Analytics. See https://cloud.google.com/logging/docs/log-analytics ."
diff --git a/1-org/envs/shared/remote.tf b/1-org/envs/shared/remote.tf
index a32fc99aa..9b54670c8 100644
--- a/1-org/envs/shared/remote.tf
+++ b/1-org/envs/shared/remote.tf
@@ -26,12 +26,11 @@ locals {
default_region = data.terraform_remote_state.bootstrap.outputs.common_config.default_region
project_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.project_prefix
folder_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.folder_prefix
- group_billing_admins = data.terraform_remote_state.bootstrap.outputs.group_billing_admins
- group_org_admins = data.terraform_remote_state.bootstrap.outputs.group_org_admins
networks_step_terraform_service_account_email = data.terraform_remote_state.bootstrap.outputs.networks_step_terraform_service_account_email
org_step_terraform_service_account_email = data.terraform_remote_state.bootstrap.outputs.organization_step_terraform_service_account_email
bootstrap_folder_name = data.terraform_remote_state.bootstrap.outputs.common_config.bootstrap_folder_name
cloud_build_private_worker_pool_id = try(data.terraform_remote_state.bootstrap.outputs.cloud_build_private_worker_pool_id, "")
+ required_groups = data.terraform_remote_state.bootstrap.outputs.required_groups
}
data "terraform_remote_state" "bootstrap" {
diff --git a/1-org/envs/shared/variables.tf b/1-org/envs/shared/variables.tf
index bb439163d..75645deda 100644
--- a/1-org/envs/shared/variables.tf
+++ b/1-org/envs/shared/variables.tf
@@ -20,16 +20,6 @@ variable "enable_hub_and_spoke" {
default = false
}
-variable "billing_data_users" {
- description = "Google Workspace or Cloud Identity group that have access to billing data set."
- type = string
-}
-
-variable "audit_data_users" {
- description = "Google Workspace or Cloud Identity group that have access to audit logs."
- type = string
-}
-
variable "domains_to_allow" {
description = "The list of domains to allow users from in IAM. Used by Domain Restricted Sharing Organization Policy. Must include the domain of the organization you are deploying the foundation. To add other domains you must also grant access to these domains to the Terraform Service Account used in the deploy."
type = list(string)
@@ -165,27 +155,12 @@ variable "gcp_groups" {
global_secrets_admin: Google Workspace or Cloud Identity group that members are responsible for putting secrets into Secrets Manage
EOT
type = object({
- platform_viewer = optional(string, null)
+ audit_viewer = optional(string, null)
security_reviewer = optional(string, null)
network_viewer = optional(string, null)
scc_admin = optional(string, null)
- audit_viewer = optional(string, null)
global_secrets_admin = optional(string, null)
- })
- default = {}
-}
-
-variable "gcp_user" {
- description = <object({
name = optional(string, null)
logging_sink_name = optional(string, null)
logging_sink_filter = optional(string, "")
location = optional(string, "global")
enable_analytics = optional(bool, true)
linked_dataset_id = optional(string, null)
linked_dataset_description = optional(string, null)
retention_days = optional(number, 30)
}) | `null` | no |
| logging\_destination\_project\_id | The ID of the project that will have the resources where the logs will be created. | `string` | n/a | yes |
| logging\_project\_key | (Optional) The key of logging destination project if it is inside resources map. It is mandatory when resource\_type = project and logging\_target\_type = logbucket. | `string` | `""` | no |
@@ -71,6 +73,7 @@ module "logging_logbucket" {
| Name | Description |
|------|-------------|
+| billing\_sink\_names | Map of log sink names with billing suffix |
| logbucket\_destination\_name | The resource name for the destination Log Bucket. |
| logbucket\_linked\_dataset\_name | The resource name of the Log Bucket linked BigQuery dataset. |
| pubsub\_destination\_name | The resource name for the destination Pub/Sub. |
diff --git a/1-org/modules/centralized-logging/main.tf b/1-org/modules/centralized-logging/main.tf
index 5a7a974f6..f15acc3be 100644
--- a/1-org/modules/centralized-logging/main.tf
+++ b/1-org/modules/centralized-logging/main.tf
@@ -62,6 +62,12 @@ locals {
lbk = try(module.destination_logbucket[0].destination_uri, "")
}
+ destination_resource_name = merge(
+ var.pubsub_options != null ? { pub = module.destination_pubsub[0].resource_name } : {},
+ var.storage_options != null ? { sto = module.destination_storage[0].resource_name } : {},
+ var.logbucket_options != null ? { lbk = module.destination_logbucket[0].resource_name } : {}
+ )
+
logging_tgt_prefix = {
pub = "tp-logs-"
sto = try("bkt-logs-${var.logging_destination_project_id}-", "bkt-logs-")
@@ -90,6 +96,28 @@ module "log_export" {
include_children = local.include_children
}
+
+module "log_export_billing" {
+ source = "terraform-google-modules/log-export/google"
+ version = "~> 7.4"
+
+ for_each = var.enable_billing_account_sink ? local.destination_resource_name : {}
+
+ destination_uri = local.destination_uri_map[each.key]
+ filter = ""
+ log_sink_name = "${coalesce(local.destinations_options[each.key].logging_sink_name, local.logging_sink_name_map[each.key])}-billing-${random_string.suffix.result}"
+ parent_resource_id = var.billing_account
+ parent_resource_type = "billing_account"
+ unique_writer_identity = true
+}
+
+resource "time_sleep" "wait_sa_iam_membership" {
+ create_duration = "30s"
+ depends_on = [
+ module.log_export_billing
+ ]
+}
+
#-------------------------#
# Send logs to Log Bucket #
#-------------------------#
@@ -124,6 +152,25 @@ resource "google_project_iam_member" "logbucket_sink_member" {
member = module.log_export["${each.value}_lbk"].writer_identity
}
+#------------------------------------------------------------------#
+# Log Bucket Service account IAM membership for log_export_billing #
+#------------------------------------------------------------------#
+resource "google_project_iam_member" "logbucket_sink_member_billing" {
+ count = var.enable_billing_account_sink == true && var.logbucket_options != null ? 1 : 0
+
+ project = var.logging_destination_project_id
+ role = "roles/logging.bucketWriter"
+
+ # Set permission only on sinks for this destination using
+ # module.log_export_billing key "object({
enabled = optional(bool, false)
location = optional(string, "us-central1")
display_name = optional(string, "FEDRAMP-MODERATE")
compliance_regime = optional(string, "FEDRAMP_MODERATE")
resource_type = optional(string, "CONSUMER_FOLDER")
}) | `{}` | no |
| env | The environment to prepare (ex. development) | `string` | n/a | yes |
| environment\_code | A short form of the folder level resources (environment) within the Google Cloud organization (ex. d). | `string` | n/a | yes |
-| monitoring\_workspace\_users | Google Workspace or Cloud Identity group that have access to Monitoring Workspaces. | `string` | n/a | yes |
| project\_budget | Budget configuration for projects.object({
base_network_budget_amount = optional(number, 1000)
base_network_alert_spent_percents = optional(list(number), [1.2])
base_network_alert_pubsub_topic = optional(string, null)
base_network_budget_alert_spend_basis = optional(string, "FORECASTED_SPEND")
restricted_network_budget_amount = optional(number, 1000)
restricted_network_alert_spent_percents = optional(list(number), [1.2])
restricted_network_alert_pubsub_topic = optional(string, null)
restricted_network_budget_alert_spend_basis = optional(string, "FORECASTED_SPEND")
monitoring_budget_amount = optional(number, 1000)
monitoring_alert_spent_percents = optional(list(number), [1.2])
monitoring_alert_pubsub_topic = optional(string, null)
monitoring_budget_alert_spend_basis = optional(string, "FORECASTED_SPEND")
secret_budget_amount = optional(number, 1000)
secret_alert_spent_percents = optional(list(number), [1.2])
secret_alert_pubsub_topic = optional(string, null)
secret_budget_alert_spend_basis = optional(string, "FORECASTED_SPEND")
kms_budget_amount = optional(number, 1000)
kms_alert_spent_percents = optional(list(number), [1.2])
kms_alert_pubsub_topic = optional(string, null)
kms_budget_alert_spend_basis = optional(string, "FORECASTED_SPEND")
}) | `{}` | no |
| remote\_state\_bucket | Backend bucket to load Terraform Remote State Data from previous steps. | `string` | n/a | yes |
| tfc\_org\_name | Name of the TFC organization | `string` | n/a | yes |
diff --git a/2-environments/modules/env_baseline/iam.tf b/2-environments/modules/env_baseline/iam.tf
index d602f4485..ffe9acbc3 100644
--- a/2-environments/modules/env_baseline/iam.tf
+++ b/2-environments/modules/env_baseline/iam.tf
@@ -18,8 +18,8 @@
Monitoring - IAM
*****************************************/
-resource "google_project_iam_member" "monitoring_editor" {
+resource "google_project_iam_member" "monitoring_viewer" {
project = module.monitoring_project.project_id
- role = "roles/monitoring.editor"
- member = "group:${var.monitoring_workspace_users}"
+ role = "roles/monitoring.viewer"
+ member = "group:${local.required_groups["monitoring_workspace_users"]}"
}
diff --git a/2-environments/modules/env_baseline/remote.tf b/2-environments/modules/env_baseline/remote.tf
index 9475329c7..521bab9cc 100644
--- a/2-environments/modules/env_baseline/remote.tf
+++ b/2-environments/modules/env_baseline/remote.tf
@@ -21,6 +21,7 @@ locals {
project_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.project_prefix
folder_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.folder_prefix
tags = data.terraform_remote_state.org.outputs.tags
+ required_groups = data.terraform_remote_state.bootstrap.outputs.required_groups
}
data "terraform_remote_state" "bootstrap" {
diff --git a/2-environments/modules/env_baseline/variables.tf b/2-environments/modules/env_baseline/variables.tf
index 7a93fb2a9..5c459a0cb 100644
--- a/2-environments/modules/env_baseline/variables.tf
+++ b/2-environments/modules/env_baseline/variables.tf
@@ -34,11 +34,6 @@ variable "tfc_org_name" {
type = string
}
-variable "monitoring_workspace_users" {
- description = "Google Workspace or Cloud Identity group that have access to Monitoring Workspaces."
- type = string
-}
-
variable "project_budget" {
description = <