Draft README

This commit is contained in:
lcaggio 2023-01-30 23:32:13 +01:00
parent 9a46f06dd0
commit a808ea3293
7 changed files with 166 additions and 35 deletions

View File

@ -1,16 +1,108 @@
# Shielded folder
This module implements an opinionated Folder configuration to implement GCP best practices. Configurations implemented on the folder would be beneficial to host Workloads hineriting contrains from the folder they belong to.
This blueprint implements an opinionated Folder configuration to implement GCP best practices. Configurations implemented on the folder would be beneficial to host Workloads hineriting contrains from the folder they belong to.
In this blueprint, a folder will be created implementing the following features:
- Organizational policies
- Hirarckical firewall rules
- Cloud KMS
- VPC-SC
Withing the folder the following projects will be created:
- '
Within the folder the following projects will be created:
- 'audit-logs' where Audit Logs sink will be created
- 'sec-core' where Cloud KMS and Cloud Secret manager will be configured
The following diagram is a high-level reference of the resources created and managed here:
![Shielded architecture overview](./images/overview_diagram.png "Shielded architecture overview")
# Design overview and choices
Despite its simplicity, this blueprint implements the basics of a design that we've seen working well for various customers.
The approach adapts to different high-level requirements:
- IAM roles inheritance
- Organizational policies
- Audit log sink
- VPC Service Control
- Cloud KMS
# Project structure
The Shielded Folder blueprint is designed to rely on several projects:
- `audit-log`: to host Audit logging buckets and Audit log sync to GCS, BigQuery or PubSub
- `sec-core`: to host security related resources such as Cloud KMS and Cloud Secrets Manager
This separation into projects allows adhering to the least-privilege principle by using project-level roles.
# User groups
User groups provide a stable frame of reference that allows decoupling the final set of permissions from the stage where entities and resources are created, and their IAM bindings defined.
We use three groups to control access to resources:
- `data-engineers`: They handle and run workloads on the `wokload` subfolder. They have owner access to all resources in the `workload` folder in order to troubleshoot possible issues with pipelines. This team can also impersonate any service account.
- `data-security`: They handle security configurations for the shielded folder. They have owner access to the `audit-log` and `sec-core` projects.
# Encryption
The blueprint support the configuration of an instance of Cloud KMS to handle encryption on the resources. The encryption is disabled by default, but you can enble it configuring the `enable_features.kms` variable.
The script will create keys to encrypt log sink bucket/dataset/topic in the specified regions. Configuring the `kms_keys` variable, you can create additional KMS keys needed by your workload.
# How to run this script
To deploy this blueprint on your GCP organization, you will need
- a folder or organization where resources will be created
- a billing account that will be associated with the new projects
The Shielded Folder blueprint is meant to be executed by a Service Account (or a regular user) having this minimal set of permission:
- Billing account
- `roles/billing.user`
- Folder level
- `roles/resourcemanager.folderAdmin`
- `roles/resourcemanager.projectCreator`
The shielded Folfer blueprint assumes [groups described](#groups) are created in your GCP organization.
## Variable configuration
There are three sets of variables you will need to fill in:
```
organization = {
domain = "example.com"
}
prefix = "prefix"
```
## Deploying the blueprint
Once the configuration is complete, run the project factory by running
```bash
terraform init
terraform apply
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [organization](variables.tf#L128) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [prefix](variables.tf#L136) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | |
| [access_policy](variables.tf#L17) | Access Policy name, set to null if creating one. | <code>string</code> | | <code>null</code> |
| [access_policy_create](variables.tf#L23) | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format. | <code title="object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10; scopes &#61; optional&#40;list&#40;string&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [data_dir](variables.tf#L33) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> |
| [enable_features](variables.tf#L39) | Flag to enable features on the solution. | <code title="object&#40;&#123;&#10; kms &#61; bool&#10; log_sink &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; kms &#61; true&#10; log_sink &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> |
| [folder_create](variables.tf#L50) | Provide values if folder creation is needed, uses existing folder if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; display_name &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [folder_id](variables.tf#L59) | Folder ID in case you use folder_create=null. | <code>string</code> | | <code>null</code> |
| [groups](variables.tf#L65) | User groups. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; data-engineers &#61; &#34;gcp-data-engineers&#34;&#10; data-security &#61; &#34;gcp-data-security&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map&#40;object&#40;&#123;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;list&#40;string&#41;, &#91;&#34;global&#34;, &#34;europe&#34;, &#34;europe-west1&#34;&#93;&#41;&#10; rotation_period &#61; optional&#40;string, &#34;7776000s&#34;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; storage &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; logging &#61; optional&#40;string, &#34;global&#34;&#41;&#10; pubsub &#61; optional&#40;string, &#34;global&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;europe&#34;&#10; storage &#61; &#34;europe&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10; vpc-sc &#61; &#123;&#10; filter &#61; &#34;protoPayload.metadata.&#64;type&#61;&#92;&#34;type.googleapis.com&#47;google.cloud.audit.VpcServiceControlAuditMetadata&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [projects_create](variables.tf#L146) | Provide values if projects creation is needed, uses existing project if null. Projects will be created in the shielded folder. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [projects_id](variables.tf#L154) | Project id, references existing projects if `project_create` is null. Projects will be moved into the shielded folder. | <code title="object&#40;&#123;&#10; sec-core &#61; string&#10; audit-logs &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [vpc_sc_access_levels](variables.tf#L163) | VPC SC access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_sc_egress_policies](variables.tf#L192) | VPC SC egress policy defnitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; identity_type &#61; optional&#40;string, &#34;ANY_IDENTITY&#34;&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; resource_type_external &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_sc_ingress_policies](variables.tf#L212) | VPC SC ingress policy defnitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_sc_perimeters](variables.tf#L233) | VPC SC regular perimeter definitions for shielded folder. All projects in the perimeter will be added. | <code title="object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; egress_policies &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; ingress_policies &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
<!-- END TFDOC -->
#TODO Proper README (after deciding if this is a blueprint or a FAST stage)
# Implemented

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
# tfdoc:file:description Security project, Cloud KMS and Secret Manager resources.
locals {
kms_locations = distinct(flatten([
for k, v in var.kms_keys : v.locations
@ -56,7 +58,7 @@ locals {
module "sec-project" {
count = var.enable_features.kms ? 1 : 0
source = "../../../modules/project"
name = "sec-core"
name = var.projects_create != null ? "sec-core" : var.projects_id["sec-core"]
parent = module.folder.id
billing_account = try(var.projects_create.billing_account_id, null)
project_create = var.projects_create != null && var.enable_features.kms
@ -75,7 +77,7 @@ module "sec-project" {
}
module "sec-kms" {
for_each = var.enable_features.log_sink ? toset(local.kms_locations) : toset([])
for_each = var.enable_features.kms ? toset(local.kms_locations) : toset([])
source = "../../../modules/kms"
project_id = module.sec-project[0].project_id
keyring = {
@ -90,7 +92,7 @@ module "sec-kms" {
}
module "log-kms" {
for_each = var.enable_features.log_sink ? toset(local.kms_log_locations) : toset([])
for_each = var.enable_features.kms ? toset(local.kms_log_locations) : toset([])
source = "../../../modules/kms"
project_id = module.sec-project[0].project_id
keyring = {

View File

@ -23,11 +23,12 @@ locals {
: "REGIONAL"
)
log_types = toset([for k, v in var.log_sinks : v.type])
_log_keys = {
bq = var.enable_features.log_sink ? ["projects/${module.sec-project.project_id}/locations/${var.log_locations.bq}/keyRings/${var.log_locations.bq}/cryptoKeys/bq"] : null
pubsub = var.enable_features.log_sink ? ["projects/${module.sec-project.project_id}/locations/${var.log_locations.pubsub}/keyRings/${var.log_locations.pubsub}/cryptoKeys/pubsub"] : null
storage = var.enable_features.log_sink ? ["projects/${module.sec-project.project_id}/locations/${var.log_locations.storage}/keyRings/${var.log_locations.storage}/cryptoKeys/storage"] : null
}
_log_keys = var.enable_features.kms ? {
bq = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.bq}/keyRings/${var.log_locations.bq}/cryptoKeys/bq"] : null
pubsub = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.pubsub}/keyRings/${var.log_locations.pubsub}/cryptoKeys/pubsub"] : null
storage = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.storage}/keyRings/${var.log_locations.storage}/cryptoKeys/storage"] : null
} : {}
log_keys = {
for service, key in local._log_keys : service => key if key != null
@ -37,7 +38,7 @@ locals {
module "log-export-project" {
count = var.enable_features.log_sink ? 1 : 0
source = "../../../modules/project"
name = "audit-logs"
name = var.projects_create != null ? "audit-logs" : var.projects_id["audit-logs"]
parent = module.folder.id
billing_account = try(var.projects_create.billing_account_id, null)
project_create = var.projects_create != null
@ -51,7 +52,7 @@ module "log-export-project" {
"storage.googleapis.com",
"stackdriver.googleapis.com"
]
service_encryption_key_ids = var.enable_features.kms ? local.log_keys : null
service_encryption_key_ids = var.enable_features.kms ? local.log_keys : {}
depends_on = [
module.log-kms

View File

@ -22,6 +22,12 @@ locals {
file("${var.data_dir}/vpc-sc/restricted-services.yaml")
)
access_policy_create = var.access_policy == null ? {
parent = "organizations/${var.organization.id}"
title = "shielded-folder"
scopes = [module.folder.id]
} : null
groups = {
for k, v in var.groups : k => "${v}@${var.organization.domain}"
}
@ -51,15 +57,11 @@ locals {
}
module "folder" {
source = "../../../modules/folder"
folder_create = var.folder_create != null
parent = try(var.folder_create.parent, null)
name = try(var.folder_create.display_name, null)
id = var.folder_id
iam = {
"roles/owner" = ["serviceAccount:${var.bootstrap_service_account}"]
"roles/resourcemanager.projectCreator" = ["serviceAccount:${var.bootstrap_service_account}"]
}
source = "../../../modules/folder"
folder_create = var.folder_create != null
parent = try(var.folder_create.parent, null)
name = try(var.folder_create.display_name, null)
id = var.folder_create != null ? null : var.folder_id
group_iam = local.group_iam
org_policies_data_path = "${var.data_dir}/org-policies"
firewall_policy_factory = {
@ -77,6 +79,13 @@ module "folder" {
} : null
}
module "folder-workload" {
source = "../../../modules/folder"
parent = module.folder.id
name = "${var.prefix}-workload"
}
#TODO VPCSC: Access levels
data "google_projects" "folder-projects" {
filter = "parent.id:${split("/", module.folder.id)[1]}"
@ -85,15 +94,19 @@ data "google_projects" "folder-projects" {
module "vpc-sc" {
source = "../../../modules/vpc-sc"
access_policy = var.access_policy
access_policy_create = var.access_policy_create
access_policy_create = local.access_policy_create
access_levels = var.vpc_sc_access_levels
egress_policies = var.vpc_sc_egress_policies
ingress_policies = var.vpc_sc_ingress_policies
service_perimeters_regular = {
shielded = {
status = {
# Move `spec` definition to `status` and comment `use_explicit_dry_run_spec` variable to enforce VPC-SC configuration
# Before enforing configuration check logs and create Access Level, Ingress/Egress policy as needed
status = null
spec = {
access_levels = keys(var.vpc_sc_access_levels)
resources = null #TODO local.vpc_sc_resources
resources = local.vpc_sc_resources
restricted_services = local._vpc_sc_restricted_services
egress_policies = keys(var.vpc_sc_egress_policies)
ingress_policies = keys(var.vpc_sc_ingress_policies)
@ -102,6 +115,8 @@ module "vpc-sc" {
enable_restriction = true
}
}
use_explicit_dry_run_spec = true
}
}
}

View File

@ -0,0 +1,22 @@
# 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
#
# https://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.
output "folders" {
description = "Folders id."
value = {
shielded-folder = module.folder.id
workload-folder = module.folder-workload.id
}
}

View File

@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# tfdoc:file:description Folder resources.
# tfdoc:file:description Variables definition.
variable "access_policy" {
description = "Access Policy name, set to null if creating one."
type = string
default = null
}
variable "access_policy_create" {
@ -30,11 +30,6 @@ variable "access_policy_create" {
default = null
}
variable "bootstrap_service_account" {
description = "Folder bootstrap service account: owner of the folder."
type = string
}
variable "data_dir" {
description = "Relative path for the folder storing configuration data."
type = string
@ -134,6 +129,7 @@ variable "organization" {
description = "Organization details."
type = object({
domain = string
id = string
})
}
@ -156,9 +152,12 @@ variable "projects_create" {
}
variable "projects_id" {
description = "Project id, references existing project if `project_create` is null. Projects will be moved into the shielded folder."
type = map(string)
default = null
description = "Project id, references existing projects if `project_create` is null. Projects will be moved into the shielded folder."
type = object({
sec-core = string
audit-logs = string
})
default = null
}
variable "vpc_sc_access_levels" {