Make VPCSC optional; ingress policy
This commit is contained in:
parent
fb04e78829
commit
f36b004664
|
@ -47,6 +47,39 @@ The blueprint support the configuration of an instance of Cloud KMS to handle en
|
|||
|
||||
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.
|
||||
|
||||
## Customizations
|
||||
|
||||
### VPC Service Control
|
||||
VPC Service Control is configured to have a Perimeter containing all projects within the folder. Additional projects you may add to the folder won't be automatically added to perimeter, a new Terraform apply is needed to add the project to the perimeter.
|
||||
|
||||
The VPC SC configuration is set to dry-run mode, but switching to enforced mode is a simple operation involving modifying a few lines of code highlighted by ad-hoc comments.
|
||||
|
||||
Access level rules are not defined, before moving the configuration to enforced mode, be sure to configure an access policy to be able to continue to access resources from outside of the perimeter.
|
||||
|
||||
An access level based on the network range you are using to reach the console (eg. Proxy IP, Internet connection, ...) is suggested. Example:
|
||||
|
||||
```
|
||||
vpc_sc_access_levels = {
|
||||
users = {
|
||||
conditions = [
|
||||
{ members = ["user:user1@example.com"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatevelly you can configure an access level based on the identity that need to reach resources from outside the perimeter.
|
||||
|
||||
```
|
||||
vpc_sc_access_levels = {
|
||||
users = {
|
||||
conditions = [
|
||||
{ ip_subnetworks = ["101.101.101.0/24"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
|
@ -84,23 +117,22 @@ terraform apply
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [organization](variables.tf#L128) | Organization details. | <code title="object({ domain = string id = string })">object({…})</code> | ✓ | |
|
||||
| [prefix](variables.tf#L136) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | |
|
||||
| [organization](variables.tf#L130) | Organization details. | <code title="object({ domain = string id = string })">object({…})</code> | ✓ | |
|
||||
| [prefix](variables.tf#L138) | 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({ parent = string title = string scopes = optional(list(string)) })">object({…})</code> | | <code>null</code> |
|
||||
| [data_dir](variables.tf#L33) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>"data"</code> |
|
||||
| [enable_features](variables.tf#L39) | Flag to enable features on the solution. | <code title="object({ kms = bool log_sink = bool })">object({…})</code> | | <code title="{ kms = false log_sink = true }">{…}</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({ display_name = string parent = string })">object({…})</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(string)</code> | | <code title="{ data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map(object({ iam = optional(map(list(string)), {}) labels = optional(map(string), {}) locations = optional(list(string), ["global", "europe", "europe-west1"]) rotation_period = optional(string, "7776000s") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "europe") storage = optional(string, "europe") logging = optional(string, "global") pubsub = optional(string, "global") })">object({…})</code> | | <code title="{ bq = "europe" storage = "europe" logging = "global" pubsub = null }">{…}</code> |
|
||||
| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "bigquery" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "bigquery" } }">{…}</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({ billing_account_id = string })">object({…})</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({ sec-core = string audit-logs = string })">object({…})</code> | | <code>null</code> |
|
||||
| [vpc_sc_access_levels](variables.tf#L163) | VPC SC access level definitions. | <code title="map(object({ combining_function = optional(string) conditions = optional(list(object({ device_policy = optional(object({ allowed_device_management_levels = optional(list(string)) allowed_encryption_statuses = optional(list(string)) require_admin_approval = bool require_corp_owned = bool require_screen_lock = optional(bool) os_constraints = optional(list(object({ os_type = string minimum_version = optional(string) require_verified_chrome_os = optional(bool) }))) })) ip_subnetworks = optional(list(string), []) members = optional(list(string), []) negate = optional(bool) regions = optional(list(string), []) required_access_levels = optional(list(string), []) })), []) description = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_egress_policies](variables.tf#L192) | VPC SC egress policy defnitions. | <code title="map(object({ from = object({ identity_type = optional(string, "ANY_IDENTITY") identities = optional(list(string)) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) resource_type_external = optional(bool, false) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_ingress_policies](variables.tf#L212) | VPC SC ingress policy defnitions. | <code title="map(object({ from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</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({ access_levels = optional(list(string), []) egress_policies = optional(list(string), []) ingress_policies = optional(list(string), []) })">object({…})</code> | | <code>{}</code> |
|
||||
| [enable_features](variables.tf#L39) | Flag to enable features on the solution. | <code title="object({ kms = bool log_sink = bool vpc_sc = bool })">object({…})</code> | | <code title="{ kms = false log_sink = true vpc_sc = true }">{…}</code> |
|
||||
| [folder_create](variables.tf#L52) | Provide values if folder creation is needed, uses existing folder if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object({ display_name = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [folder_id](variables.tf#L61) | Folder ID in case you use folder_create=null. | <code>string</code> | | <code>null</code> |
|
||||
| [groups](variables.tf#L67) | User groups. | <code>map(string)</code> | | <code title="{ data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [kms_keys](variables.tf#L77) | KMS keys to create, keyed by name. | <code title="map(object({ iam = optional(map(list(string)), {}) labels = optional(map(string), {}) locations = optional(list(string), ["global", "europe", "europe-west1"]) rotation_period = optional(string, "7776000s") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [log_locations](variables.tf#L88) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "europe") storage = optional(string, "europe") logging = optional(string, "global") pubsub = optional(string, "global") })">object({…})</code> | | <code title="{ bq = "europe" storage = "europe" logging = "global" pubsub = null }">{…}</code> |
|
||||
| [log_sinks](variables.tf#L105) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "bigquery" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "bigquery" } }">{…}</code> |
|
||||
| [projects_create](variables.tf#L148) | Provide values if projects creation is needed, uses existing project if null. Projects will be created in the shielded folder. | <code title="object({ billing_account_id = string })">object({…})</code> | | <code>null</code> |
|
||||
| [projects_id](variables.tf#L156) | Project id, references existing projects if `project_create` is null. Projects will be moved into the shielded folder. | <code title="object({ sec-core = string audit-logs = string })">object({…})</code> | | <code>null</code> |
|
||||
| [vpc_sc_access_levels](variables.tf#L165) | VPC SC access level definitions. | <code title="map(object({ combining_function = optional(string) conditions = optional(list(object({ device_policy = optional(object({ allowed_device_management_levels = optional(list(string)) allowed_encryption_statuses = optional(list(string)) require_admin_approval = bool require_corp_owned = bool require_screen_lock = optional(bool) os_constraints = optional(list(object({ os_type = string minimum_version = optional(string) require_verified_chrome_os = optional(bool) }))) })) ip_subnetworks = optional(list(string), []) members = optional(list(string), []) negate = optional(bool) regions = optional(list(string), []) required_access_levels = optional(list(string), []) })), []) description = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_egress_policies](variables.tf#L194) | VPC SC egress policy defnitions. | <code title="map(object({ from = object({ identity_type = optional(string, "ANY_IDENTITY") identities = optional(list(string)) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) resource_type_external = optional(bool, false) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_ingress_policies](variables.tf#L214) | VPC SC ingress policy defnitions. | <code title="map(object({ from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -15,6 +15,19 @@
|
|||
# tfdoc:file:description Folder resources.
|
||||
|
||||
locals {
|
||||
# Create Log sink ingress policies
|
||||
_sink_ingress_policies = var.enable_features.log_sink ? {
|
||||
log_sink = {
|
||||
from = {
|
||||
access_levels = ["*"]
|
||||
identities = values(module.folder.sink_writer_identities)
|
||||
}
|
||||
to = {
|
||||
resources = ["projects/${module.log-export-project.0.number}"]
|
||||
operations = [{ service_name = "*" }]
|
||||
} }
|
||||
} : null
|
||||
|
||||
_vpc_sc_vpc_accessible_services = var.data_dir != null ? yamldecode(
|
||||
file("${var.data_dir}/vpc-sc/restricted-services.yaml")
|
||||
) : null
|
||||
|
@ -92,12 +105,13 @@ data "google_projects" "folder-projects" {
|
|||
}
|
||||
|
||||
module "vpc-sc" {
|
||||
count = var.enable_features.vpc_sc ? 1 : 0
|
||||
source = "../../../modules/vpc-sc"
|
||||
access_policy = var.access_policy
|
||||
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
|
||||
ingress_policies = merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies)
|
||||
service_perimeters_regular = {
|
||||
shielded = {
|
||||
# Move `spec` definition to `status` and comment `use_explicit_dry_run_spec` variable to enforce VPC-SC configuration
|
||||
|
@ -109,7 +123,7 @@ module "vpc-sc" {
|
|||
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)
|
||||
ingress_policies = keys(merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies))
|
||||
vpc_accessible_services = {
|
||||
allowed_services = local._vpc_sc_vpc_accessible_services
|
||||
enable_restriction = true
|
||||
|
|
|
@ -20,3 +20,11 @@ output "folders" {
|
|||
}
|
||||
}
|
||||
|
||||
output "folders_sink_writer_identities" {
|
||||
description = "Folders id."
|
||||
value = {
|
||||
shielded-folder = module.folder.sink_writer_identities
|
||||
workload-folder = module.folder-workload.sink_writer_identities
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,10 +41,12 @@ variable "enable_features" {
|
|||
type = object({
|
||||
kms = bool
|
||||
log_sink = bool
|
||||
vpc_sc = bool
|
||||
})
|
||||
default = {
|
||||
kms = false
|
||||
log_sink = true
|
||||
vpc_sc = true
|
||||
}
|
||||
}
|
||||
variable "folder_create" {
|
||||
|
@ -229,14 +231,3 @@ variable "vpc_sc_ingress_policies" {
|
|||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "vpc_sc_perimeters" {
|
||||
description = "VPC SC regular perimeter definitions for shielded folder. All projects in the perimeter will be added."
|
||||
type = object({
|
||||
access_levels = optional(list(string), [])
|
||||
egress_policies = optional(list(string), [])
|
||||
ingress_policies = optional(list(string), [])
|
||||
})
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ import pytest
|
|||
|
||||
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||
|
||||
|
||||
def test_resources(e2e_plan_runner):
|
||||
"Test that plan works and the numbers of resources is as expected."
|
||||
modules, resources = e2e_plan_runner(FIXTURES_DIR)
|
||||
assert len(modules) == 5
|
||||
assert len(resources) == 18
|
||||
"Test that plan works and the numbers of resources is as expected."
|
||||
modules, resources = e2e_plan_runner(FIXTURES_DIR)
|
||||
assert len(modules) == 5
|
||||
assert len(resources) == 18
|
||||
|
|
Loading…
Reference in New Issue