Skip to content

Commit

Permalink
feat: Enable Essential Contacts (#783)
Browse files Browse the repository at this point in the history
* Essencial contacts first POC

* Remove essential contacs from step 0-bootstrap

* Essential Contacts docs

* Setup variables

* New feature essential contacts

* Fix integrated test var initialization

* Enable Essential Contacts API

* Essential Contacs IAM role

* Essencial contacts first POC

* Remove essential contacs from step 0-bootstrap

* Essential Contacts docs

* Setup variables

* New feature essential contacts

* Fix integrated test var initialization

* Enable Essential Contacts API

* Essential Contacs IAM role

* Fix Essential Contacts role after BYOSA

* Fix getting Org Admin group email

* Add project-factory essential_contacts submodule example

* Comment explaning why transpose categories map

* Fix method to get categories from gcloud command

* Undo changes

* New comments on code

* Remove Technical Incidents category as it is still in Pre GA

* Remove Technical Incidents option

* Added Essential Contacts API to list of valid APIs at the policy library constraint

* Fix readme lint
  • Loading branch information
felipecrescencio-cit committed Sep 7, 2022
1 parent a1d636e commit 86fcb2a
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 25 deletions.
2 changes: 2 additions & 0 deletions 0-bootstrap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ the following steps:
| environment\_step\_terraform\_service\_account\_email | Environment Step Terraform Account |
| gcs\_bucket\_cloudbuild\_artifacts | Bucket used to store Cloud/Build artifacts in CloudBuild 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 |
Expand Down
3 changes: 2 additions & 1 deletion 0-bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ module "seed_bootstrap" {
"pubsub.googleapis.com",
"securitycenter.googleapis.com",
"accesscontextmanager.googleapis.com",
"billingbudgets.googleapis.com"
"billingbudgets.googleapis.com",
"essentialcontacts.googleapis.com"
]

sa_org_iam_permissions = []
Expand Down
10 changes: 10 additions & 0 deletions 0-bootstrap/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ output "csr_repos" {
value = module.tf_source.csr_repos
}

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
}

/* ----------------------------------------
Specific to jenkins_bootstrap module
---------------------------------------- */
Expand Down
1 change: 1 addition & 0 deletions 0-bootstrap/sa.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ locals {
"roles/securitycenter.notificationConfigEditor",
"roles/resourcemanager.organizationViewer",
"roles/accesscontextmanager.policyAdmin",
"roles/essentialcontacts.admin",
],
"net" = [
"roles/accesscontextmanager.policyAdmin",
Expand Down
9 changes: 9 additions & 0 deletions 1-org/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ To check if it already exists run:
gcloud scc notifications describe <scc_notification_name> --organization=<org_id>
```

**Note:** This module manages contacts for notifications using [Essential Contacts](https://cloud.google.com/resource-manager/docs/managing-notification-contacts) API. This is assigned at the Parent Level (Organization or Folder) you configured to be inherited by all child resources. There is also possible to assign Essential Contacts directly to projects using project-factory [essential_contacts submodule](https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/13.1.0/submodules/essential_contacts#example-usage). Billing notifications are assigned to be sent to `group_billing_admins` mandatory group. Legal and Suspension notifications are assigned to `group_org_admins` mandatory group. If you provide all other groups notifications will be configured like the table below:

| Group | Notification Category | Fallback Group |
|-------|-----------------------|----------------|
| gcp_network_viewer | Technical | Org Admins |
| gcp_platform_viewer | Product Updates and Technical | Org Admins |
| gcp_scc_admin | Product Updates and Security | Org Admins |
| gcp_security_reviewer | Security and Technical | Org Admins |

### Deploying with Cloud Build

1. Clone the policy repo based on the Terraform output from the previous section.
Expand Down
17 changes: 9 additions & 8 deletions 1-org/envs/shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,24 @@
| base\_net\_hub\_project\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded for the base net hub project. | `list(number)` | <pre>[<br> 0.5,<br> 0.75,<br> 0.9,<br> 0.95<br>]</pre> | no |
| base\_net\_hub\_project\_budget\_amount | The amount to use as the budget for the base net hub project. | `number` | `1000` | no |
| billing\_data\_users | Google Workspace or Cloud Identity group that have access to billing data set. | `string` | n/a | yes |
| create\_access\_context\_manager\_access\_policy | Whether to create access context manager access policy | `bool` | `true` | no |
| create\_access\_context\_manager\_access\_policy | Whether to create access context manager access policy. | `bool` | `true` | no |
| data\_access\_logs\_enabled | Enable Data Access logs of types DATA\_READ, DATA\_WRITE for all GCP services. Enabling Data Access logs might result in your organization being charged for the additional logs usage. See https://cloud.google.com/logging/docs/audit#data-access The ADMIN\_READ logs are enabled by default. | `bool` | `false` | no |
| dns\_hub\_project\_alert\_pubsub\_topic | The name of the Cloud Pub/Sub topic where budget related messages will be published, in the form of `projects/{project_id}/topics/{topic_id}` for the DNS hub project. | `string` | `null` | no |
| dns\_hub\_project\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded for the DNS hub project. | `list(number)` | <pre>[<br> 0.5,<br> 0.75,<br> 0.9,<br> 0.95<br>]</pre> | no |
| dns\_hub\_project\_budget\_amount | The amount to use as the budget for the DNS hub project. | `number` | `1000` | no |
| domains\_to\_allow | 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. | `list(string)` | n/a | yes |
| enable\_hub\_and\_spoke | Enable Hub-and-Spoke architecture. | `bool` | `false` | no |
| enable\_os\_login\_policy | Enable OS Login Organization Policy. | `bool` | `false` | no |
| gcp\_audit\_viewer | Members are part of an audit team and view audit logs in the logging project. | `string` | `null` | no |
| gcp\_billing\_admin\_user | Identity that has billing administrator permissions | `string` | `null` | no |
| 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\_audit\_viewer | Google Workspace or Cloud Identity group that members are part of an audit team and view audit logs in the logging project. | `string` | `null` | no |
| gcp\_billing\_admin\_user | Identity that has billing administrator permissions. | `string` | `null` | no |
| gcp\_billing\_creator\_user | Identity that can create billing accounts. | `string` | `null` | no |
| gcp\_global\_secrets\_admin | G Suite or Cloud Identity group that members are responsible for putting secrets into Secrets Manager. | `string` | `null` | no |
| gcp\_network\_viewer | G Suite or Cloud Identity group that members are part of the networking team and review network configurations | `string` | `null` | no |
| gcp\_global\_secrets\_admin | Google Workspace or Cloud Identity group that members are responsible for putting secrets into Secrets Manager. | `string` | `null` | no |
| gcp\_network\_viewer | Google Workspace or Cloud Identity group that members are part of the networking team and review network configurations. | `string` | `null` | no |
| gcp\_org\_admin\_user | Identity that has organization administrator permissions. | `string` | `null` | no |
| gcp\_platform\_viewer | G Suite or Cloud Identity group that have the ability to view resource information across the Google Cloud organization. | `string` | `null` | no |
| gcp\_scc\_admin | G Suite or Cloud Identity group that can administer Security Command Center. | `string` | `null` | no |
| gcp\_security\_reviewer | G Suite or Cloud Identity group that members are part of the security team responsible for reviewing cloud security. | `string` | `null` | no |
| gcp\_platform\_viewer | Google Workspace or Cloud Identity group that have the ability to view resource information across the Google Cloud organization. | `string` | `null` | no |
| gcp\_scc\_admin | Google Workspace or Cloud Identity group that can administer Security Command Center. | `string` | `null` | no |
| gcp\_security\_reviewer | Google Workspace or Cloud Identity group that members are part of the security team responsible for reviewing cloud security. | `string` | `null` | no |
| interconnect\_project\_alert\_pubsub\_topic | The name of the Cloud Pub/Sub topic where budget related messages will be published, in the form of `projects/{project_id}/topics/{topic_id}` for the Dedicated Interconnect project. | `string` | `null` | no |
| interconnect\_project\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded for the Dedicated Interconnect project. | `list(number)` | <pre>[<br> 0.5,<br> 0.75,<br> 0.9,<br> 0.95<br>]</pre> | no |
| interconnect\_project\_budget\_amount | The amount to use as the budget for the Dedicated Interconnect project. | `number` | `1000` | no |
Expand Down
45 changes: 45 additions & 0 deletions 1-org/envs/shared/essential_contacts.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

locals {
gcp_scc_admin = var.gcp_scc_admin == null ? local.group_org_admins : var.gcp_scc_admin
gcp_platform_viewer = var.gcp_platform_viewer == null ? local.group_org_admins : var.gcp_platform_viewer
gcp_security_reviewer = var.gcp_security_reviewer == null ? local.group_org_admins : var.gcp_security_reviewer
gcp_network_viewer = var.gcp_network_viewer == null ? local.group_org_admins : var.gcp_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])
"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])
}

# Convert a map indexed by category to a map indexed by email
# this way is simpler to understand and maintain than the opposite
# google_essential_contacts_contact resource needs one email with a list of categories
contacts_list = transpose(local.categories_map)
}

resource "google_essential_contacts_contact" "essential_contacts" {
for_each = local.contacts_list
parent = local.parent
email = each.key
language_tag = var.essential_contacts_language
notification_category_subscriptions = each.value
}
16 changes: 9 additions & 7 deletions 1-org/envs/shared/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
// These values can be overridden here if needed.
// Some values, like org_id, parent_folder, and parent, must be consistent in all steps.
locals {
org_id = data.terraform_remote_state.bootstrap.outputs.common_config.org_id
parent_folder = data.terraform_remote_state.bootstrap.outputs.common_config.parent_folder
parent = data.terraform_remote_state.bootstrap.outputs.common_config.parent_id
billing_account = data.terraform_remote_state.bootstrap.outputs.common_config.billing_account
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
org_id = data.terraform_remote_state.bootstrap.outputs.common_config.org_id
parent_folder = data.terraform_remote_state.bootstrap.outputs.common_config.parent_folder
parent = data.terraform_remote_state.bootstrap.outputs.common_config.parent_id
billing_account = data.terraform_remote_state.bootstrap.outputs.common_config.billing_account
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
}

data "terraform_remote_state" "bootstrap" {
Expand Down
6 changes: 5 additions & 1 deletion 1-org/envs/shared/terraform.example.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
// Must include the domain of the organization you are deploying the foundation.
domains_to_allow = ["example.com"]

billing_data_users = "gcp-billing-admins@example.com"
group_org_admins = "gcp-organization-admins@example.com"

group_billing_admins = "gcp-billing-admins@example.com"

billing_data_users = "gcp-billing-data-users@example.com"

audit_data_users = "gcp-security-admins@example.com"

Expand Down
22 changes: 14 additions & 8 deletions 1-org/envs/shared/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ variable "skip_gcloud_download" {
}

variable "create_access_context_manager_access_policy" {
description = "Whether to create access context manager access policy"
description = "Whether to create access context manager access policy."
type = bool
default = true
}
Expand Down Expand Up @@ -260,37 +260,37 @@ variable "scc_notifications_project_budget_amount" {
}

variable "gcp_platform_viewer" {
description = "G Suite or Cloud Identity group that have the ability to view resource information across the Google Cloud organization."
description = "Google Workspace or Cloud Identity group that have the ability to view resource information across the Google Cloud organization."
type = string
default = null
}

variable "gcp_security_reviewer" {
description = "G Suite or Cloud Identity group that members are part of the security team responsible for reviewing cloud security."
description = "Google Workspace or Cloud Identity group that members are part of the security team responsible for reviewing cloud security."
type = string
default = null
}

variable "gcp_network_viewer" {
description = "G Suite or Cloud Identity group that members are part of the networking team and review network configurations"
description = "Google Workspace or Cloud Identity group that members are part of the networking team and review network configurations."
type = string
default = null
}

variable "gcp_scc_admin" {
description = "G Suite or Cloud Identity group that can administer Security Command Center."
description = "Google Workspace or Cloud Identity group that can administer Security Command Center."
type = string
default = null
}

variable "gcp_audit_viewer" {
description = "Members are part of an audit team and view audit logs in the logging project."
description = "Google Workspace or Cloud Identity group that members are part of an audit team and view audit logs in the logging project."
type = string
default = null
}

variable "gcp_global_secrets_admin" {
description = "G Suite or Cloud Identity group that members are responsible for putting secrets into Secrets Manager."
description = "Google Workspace or Cloud Identity group that members are responsible for putting secrets into Secrets Manager."
type = string
default = null
}
Expand All @@ -308,11 +308,17 @@ variable "gcp_billing_creator_user" {
}

variable "gcp_billing_admin_user" {
description = "Identity that has billing administrator permissions"
description = "Identity that has billing administrator permissions."
type = string
default = null
}

variable "essential_contacts_language" {
description = "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."
type = string
default = "en"
}

variable "backend_bucket" {
description = "Backend bucket to load remote state information from previous steps."
type = string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spec:
- "container.googleapis.com"
- "datastore.googleapis.com"
- "dns.googleapis.com"
- "essentialcontacts.googleapis.com"
- "iam.googleapis.com"
- "iamcredentials.googleapis.com"
- "logging.googleapis.com"
Expand Down
14 changes: 14 additions & 0 deletions test/integration/org/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestOrg(t *testing.T) {
"securitycenter.googleapis.com",
"accesscontextmanager.googleapis.com",
"billingbudgets.googleapis.com",
"essentialcontacts.googleapis.com",
} {
utils.Poll(t, func() (bool, error) { return testutils.CheckAPIEnabled(t, projectID, api) }, 5, 2*time.Minute)
}
Expand Down Expand Up @@ -144,6 +145,19 @@ func TestOrg(t *testing.T) {
notification := gcloud.Runf(t, "scc notifications describe %s --organization %s", notificationName, orgID)
assert.Equal(topicFullName, notification.Get("pubsubTopic").String(), fmt.Sprintf("notification %s should use topic %s", notificationName, topicName))

//essential contacts
//test case considers that just the Org Admin group exists and will subscribe for all categories
essentialContacts := gcloud.Runf(t, "essential-contacts list --folder=%s", parentFolder).Array()
assert.Len(essentialContacts, 1, "only one essential contact email should be created")

groupOrgAdmins := utils.ValFromEnv(t, "TF_VAR_group_email")
assert.Equal(groupOrgAdmins, essentialContacts[0].Get("email").String(), "essential contact email should be group org admin")
assert.Equal("VALID", essentialContacts[0].Get("validationState").String(), "state of essential contact should be valid")

listCategories := utils.GetResultStrSlice(essentialContacts[0].Get("notificationCategorySubscriptions").Array())
expectedCategories := []string{"BILLING", "LEGAL", "PRODUCT_UPDATES", "SECURITY", "SUSPENSION", "TECHNICAL"}
assert.Subset(listCategories, expectedCategories, "notification category subscriptions should be the same")

//logging
billingLogsProjectID := org.GetStringOutput("org_billing_logs_project_id")
billingDatasetName := "billing_data"
Expand Down
1 change: 1 addition & 0 deletions test/setup/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ module "project" {
"securitycenter.googleapis.com",
"servicenetworking.googleapis.com",
"billingbudgets.googleapis.com",
"essentialcontacts.googleapis.com",
]
}

0 comments on commit 86fcb2a

Please sign in to comment.