diff --git a/.gitignore b/.gitignore index 87d63021..f77ee85e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,31 @@ # Local tfvars terraform.tfvars **/terraform.tfvars *.tfvars.json +# OSX leaves these everywhere on SMB shares +._* + +# OSX trash +.DS_Store + +# Python +*.pyc + +# Emacs save files +*~ +\#*\# +.\#* + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +### https://raw.github.com/github/gitignore/90f149de451a5433aebd94d02d11b0e28843a1af/Terraform.gitignore + +# Local .terraform directories +**/.terraform/* # .tfstate files *.tfstate @@ -34,3 +59,19 @@ override.tf.json # example: *tfplan* *.vscode *.vsls.json +# Kitchen files +**/inspec.lock +**/.kitchen +**/kitchen.local.yml +**/Gemfile.lock + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +**/*.tfvars + +credentials.json + +.terraform* + +**/*.vscode diff --git a/examples/secure_cloud_run_standalone/providers.tf.example b/examples/secure_cloud_run_standalone/providers.tf.example index 2dfffc43..2e0fd0dd 100644 --- a/examples/secure_cloud_run_standalone/providers.tf.example +++ b/examples/secure_cloud_run_standalone/providers.tf.example @@ -23,4 +23,3 @@ provider "google-beta" { impersonate_service_account = "YOUR-TERRAFORM-SA" request_timeout = "60s" } - diff --git a/kitchen.yml b/kitchen.yml index 82d6fcdc..a6da53a6 100644 --- a/kitchen.yml +++ b/kitchen.yml @@ -1,5 +1,3 @@ - - # Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/modules/secure-cloud-run-core/README.md b/modules/secure-cloud-run-core/README.md index b5904579..d18b48b7 100644 --- a/modules/secure-cloud-run-core/README.md +++ b/modules/secure-cloud-run-core/README.md @@ -35,24 +35,41 @@ module "cloud_run_core" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| argument | Arguments passed to the ENTRYPOINT command, include these only if image entrypoint needs arguments. | `list(string)` | `[]` | no | +| certificate\_mode | The mode of the certificate (NONE or AUTOMATIC). | `string` | `"NONE"` | no | | cloud\_armor\_policies\_name | Cloud Armor policy name already created in the project. If `create_cloud_armor_policies` is `false`, this variable must be provided, If `create_cloud_armor_policies` is `true`, this variable will be ignored. | `string` | `null` | no | | cloud\_run\_sa | Service account to be used on Cloud Run. | `string` | n/a | yes | -| create\_cloud\_armor\_policies | When `true` the terraform will create the Cloud Armor policies. When `false`, the user must provide his own Cloud Armor name in `cloud_armor_policies_name`. | `bool` | `true` | no | +| container\_command | Leave blank to use the ENTRYPOINT command defined in the container image, include these only if image entrypoint should be overwritten. | `list(string)` | `[]` | no | +| container\_concurrency | Concurrent request limits to the service. | `number` | `null` | no | +| create\_cloud\_armor\_policies | When `true`, the terraform will create the Cloud Armor policies. When `false`, the user must provide their own Cloud Armor name in `cloud_armor_policies_name`. | `bool` | `true` | no | | default\_rules | Default rule for Cloud Armor. |
map(object({
action = string
priority = string
versioned_expr = string
src_ip_ranges = list(string)
description = string
}))
|
{
"default_rule": {
"action": "allow",
"description": "Default allow all rule",
"priority": "2147483647",
"src_ip_ranges": [
"*"
],
"versioned_expr": "SRC_IPS_V1"
}
}
| no | +| domain\_map\_annotations | Annotations to the domain map. | `map(string)` | `{}` | no | +| domain\_map\_labels | A set of key/value label pairs to assign to the Domain mapping. | `map(string)` | `{}` | no | | encryption\_key | CMEK encryption key self-link expected in the format projects/PROJECT/locations/LOCATION/keyRings/KEY-RING/cryptoKeys/CRYPTO-KEY. | `string` | n/a | yes | | env\_vars | Environment variables. |
list(object({
value = string
name = string
}))
| `[]` | no | +| force\_override | Option to force override existing mapping. | `bool` | `false` | no | +| generate\_revision\_name | Option to enable revision name generation. | `bool` | `true` | no | | image | GAR hosted image URL to deploy. | `string` | n/a | yes | | lb\_name | Name for load balancer and associated resources. | `string` | `"tf-cr-lb"` | no | +| limits | Resource limits to the container. | `map(string)` | `null` | no | | location | The location where resources are going to be deployed. | `string` | n/a | yes | | max\_scale\_instances | Sets the maximum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). | `number` | `2` | no | | members | Users/SAs to be given invoker access to the service with the prefix `serviceAccount:' for SAs and `user:` for users.` | `list(string)` | `[]` | no | | min\_scale\_instances | Sets the minimum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). | `number` | `1` | no | -| owasp\_rules | These are additional Cloud Armor rules for SQLi, XSS, LFI, RCE, RFI, Scannerdetection, Protocolattack and Sessionfixation (requires Cloud Armor default\_rule). |
map(object({
action = string
priority = string
expression = string
}))
|
{
"rule_lfi": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('lfi-v33-stable')",
"priority": "1002"
},
"rule_protocolattack": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('protocolattack-v33-stable')",
"priority": "1006"
},
"rule_rce": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('rce-v33-stable')",
"priority": "1003"
},
"rule_rfi": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('rfi-v33-stable')",
"priority": "1004"
},
"rule_scannerdetection": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('scannerdetection-v33-stable')",
"priority": "1005"
},
"rule_sessionfixation": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('sessionfixation-v33-stable')",
"priority": "1007"
},
"rule_sqli": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('sqli-v33-stable')",
"priority": "1000"
},
"rule_xss": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('xss-v33-stable')",
"priority": "1001"
}
}
| no | +| owasp\_rules | These are additional Cloud Armor rules for SQLi, XSS, LFI, RCE, RFI, Scannerdetection, Protocolattack and Sessionfixation (requires Cloud Armor default\_rule). |
map(object({
action = string
priority = string
expression = string
}))
|
{
"rule_canary": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('rce-v33-stable')",
"priority": "1003"
},
"rule_lfi": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('lfi-v33-stable')",
"priority": "1002"
},
"rule_protocolattack": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('protocolattack-v33-stable')",
"priority": "1006"
},
"rule_rfi": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('rfi-v33-stable')",
"priority": "1004"
},
"rule_scannerdetection": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('scannerdetection-v33-stable')",
"priority": "1005"
},
"rule_sessionfixation": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('sessionfixation-v33-stable')",
"priority": "1007"
},
"rule_sqli": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('sqli-v33-stable')",
"priority": "1000"
},
"rule_xss": {
"action": "deny(403)",
"expression": "evaluatePreconfiguredExpr('xss-v33-stable')",
"priority": "1001"
}
}
| no | +| ports | Port which the container listens to (http1 or h2c). |
object({
name = string
port = number
})
|
{
"name": "http1",
"port": 8080
}
| no | | project\_id | The project where cloud run is going to be deployed. | `string` | n/a | yes | | region | Location for load balancer and Cloud Run resources. | `string` | n/a | yes | +| requests | Resource requests to the container. | `map(string)` | `{}` | no | +| service\_labels | A set of key/value label pairs to assign to the service. | `map(string)` | `{}` | no | | service\_name | The name of the Cloud Run service to create. | `string` | n/a | yes | | ssl\_certificates | A object with a list of domains to auto-generate SSL certificates or a list of SSL Certificates self-links in the pattern `projects//global/sslCertificates/` to be used by Load Balancer. |
object({
ssl_certificates_self_links = list(string)
generate_certificates_for_domains = list(string)
})
| n/a | yes | -| verified\_domain\_name | Custom Domain Name | `list(string)` | n/a | yes | +| template\_labels | A set of key/value label pairs to assign to the container metadata. | `map(string)` | `{}` | no | +| timeout\_seconds | Timeout for each request. | `number` | `120` | no | +| traffic\_split | Managing traffic routing to the service. |
list(object({
latest_revision = bool
percent = number
revision_name = string
}))
|
[
{
"latest_revision": true,
"percent": 100,
"revision_name": "v1-0-0"
}
]
| no | +| verified\_domain\_name | List of custom Domain Name. | `list(string)` | n/a | yes | +| volume\_mounts | [Beta] Volume Mounts to be attached to the container (when using secret). |
list(object({
mount_path = string
name = string
}))
| `[]` | no | +| volumes | [Beta] Volumes needed for environment variables (when using secret). |
list(object({
name = string
secret = set(object({
secret_name = string
items = map(string)
}))
}))
| `[]` | no | | vpc\_connector\_id | VPC Connector id in the format projects/PROJECT/locations/LOCATION/connectors/NAME. | `string` | n/a | yes | | vpc\_egress\_value | Sets VPC Egress firewall rule. Supported values are all-traffic, all (deprecated), and private-ranges-only. all-traffic and all provide the same functionality. all is deprecated but will continue to be supported. Prefer all-traffic. | `string` | `"private-ranges-only"` | no | diff --git a/modules/secure-cloud-run-core/main.tf b/modules/secure-cloud-run-core/main.tf index 572652d1..c421698c 100644 --- a/modules/secure-cloud-run-core/main.tf +++ b/modules/secure-cloud-run-core/main.tf @@ -17,15 +17,32 @@ module "cloud_run" { source = "../.." - service_name = var.service_name - project_id = var.project_id - location = var.location - image = var.image - service_account_email = var.cloud_run_sa - encryption_key = var.encryption_key - members = var.members - env_vars = var.env_vars - verified_domain_name = var.verified_domain_name + service_name = var.service_name + project_id = var.project_id + location = var.location + image = var.image + service_account_email = var.cloud_run_sa + encryption_key = var.encryption_key + members = var.members + env_vars = var.env_vars + generate_revision_name = var.generate_revision_name + traffic_split = var.traffic_split + service_labels = var.service_labels + template_labels = var.template_labels + container_concurrency = var.container_concurrency + timeout_seconds = var.timeout_seconds + volumes = var.volumes + limits = var.limits + requests = var.requests + ports = var.ports + argument = var.argument + container_command = var.container_command + volume_mounts = var.volume_mounts + force_override = var.force_override + certificate_mode = var.certificate_mode + domain_map_labels = var.domain_map_labels + domain_map_annotations = var.domain_map_annotations + verified_domain_name = var.verified_domain_name service_annotations = { "run.googleapis.com/ingress" = "internal-and-cloud-load-balancing" diff --git a/modules/secure-cloud-run-core/variables.tf b/modules/secure-cloud-run-core/variables.tf index 965b0ed4..0eb55458 100644 --- a/modules/secure-cloud-run-core/variables.tf +++ b/modules/secure-cloud-run-core/variables.tf @@ -94,7 +94,7 @@ variable "owasp_rules" { priority = "1002" expression = "evaluatePreconfiguredExpr('lfi-v33-stable')" } - rule_rce = { + rule_canary = { action = "deny(403)" priority = "1003" expression = "evaluatePreconfiguredExpr('rce-v33-stable')" @@ -126,6 +126,7 @@ variable "owasp_rules" { expression = string })) } + variable "lb_name" { description = "Name for load balancer and associated resources." default = "tf-cr-lb" @@ -146,21 +147,152 @@ variable "members" { default = [] } -variable "create_cloud_armor_policies" { +variable "generate_revision_name" { + description = "Option to enable revision name generation." type = bool - description = "When `true` the terraform will create the Cloud Armor policies. When `false`, the user must provide his own Cloud Armor name in `cloud_armor_policies_name`." default = true } -variable "cloud_armor_policies_name" { - type = string - description = "Cloud Armor policy name already created in the project. If `create_cloud_armor_policies` is `false`, this variable must be provided, If `create_cloud_armor_policies` is `true`, this variable will be ignored." +variable "traffic_split" { + description = "Managing traffic routing to the service." + type = list(object({ + latest_revision = bool + percent = number + revision_name = string + })) + default = [{ + latest_revision = true + percent = 100 + revision_name = "v1-0-0" + }] +} + +variable "service_labels" { + description = "A set of key/value label pairs to assign to the service." + type = map(string) + default = {} +} + +// Metadata +variable "template_labels" { + description = "A set of key/value label pairs to assign to the container metadata." + type = map(string) + default = {} +} + +// template spec +variable "container_concurrency" { + description = "Concurrent request limits to the service." + type = number default = null } +variable "timeout_seconds" { + description = "Timeout for each request." + type = number + default = 120 +} + +variable "volumes" { + description = "[Beta] Volumes needed for environment variables (when using secret)." + type = list(object({ + name = string + secret = set(object({ + secret_name = string + items = map(string) + })) + })) + default = [] +} + +# template spec container +# resources +# cpu = (core count * 1000)m +# memory = (size) in Mi/Gi +variable "limits" { + description = "Resource limits to the container." + type = map(string) + default = null +} +variable "requests" { + description = "Resource requests to the container." + type = map(string) + default = {} +} + +variable "ports" { + description = "Port which the container listens to (http1 or h2c)." + type = object({ + name = string + port = number + }) + default = { + name = "http1" + port = 8080 + } +} + +variable "argument" { + description = "Arguments passed to the ENTRYPOINT command, include these only if image entrypoint needs arguments." + type = list(string) + default = [] +} + +variable "container_command" { + description = "Leave blank to use the ENTRYPOINT command defined in the container image, include these only if image entrypoint should be overwritten." + type = list(string) + default = [] +} + +variable "volume_mounts" { + type = list(object({ + mount_path = string + name = string + })) + description = "[Beta] Volume Mounts to be attached to the container (when using secret)." + default = [] +} + +// Domain Mapping variable "verified_domain_name" { + description = "List of custom Domain Name." type = list(string) - description = "Custom Domain Name" +} + +variable "force_override" { + description = "Option to force override existing mapping." + type = bool + default = false +} + +variable "certificate_mode" { + description = "The mode of the certificate (NONE or AUTOMATIC)." + type = string + default = "NONE" +} + +variable "domain_map_labels" { + description = "A set of key/value label pairs to assign to the Domain mapping." + type = map(string) + default = {} +} + +variable "domain_map_annotations" { + description = "Annotations to the domain map." + type = map(string) + default = {} +} + +variable "create_cloud_armor_policies" { + type = bool + description = "When `true`, the terraform will create the Cloud Armor policies. When `false`, the user must provide their own Cloud Armor name in `cloud_armor_policies_name`." + default = true +} + +variable "cloud_armor_policies_name" { + type = string + description = "Cloud Armor policy name already created in the project. If `create_cloud_armor_policies` is `false`, this variable must be provided, If `create_cloud_armor_policies` is `true`, this variable will be ignored." + default = null } variable "max_scale_instances" { @@ -180,7 +312,6 @@ variable "vpc_egress_value" { type = string default = "private-ranges-only" } - variable "ssl_certificates" { type = object({ ssl_certificates_self_links = list(string) diff --git a/modules/secure-cloud-run-net/variables.tf b/modules/secure-cloud-run-net/variables.tf index 13aa6502..e456bdd0 100644 --- a/modules/secure-cloud-run-net/variables.tf +++ b/modules/secure-cloud-run-net/variables.tf @@ -66,6 +66,7 @@ variable "flow_sampling" { type = number default = 1.0 } + variable "resource_names_suffix" { description = "A suffix to concat in the end of the resources names." type = string diff --git a/modules/secure-cloud-run/README.md b/modules/secure-cloud-run/README.md index 52f7a0aa..8a190595 100644 --- a/modules/secure-cloud-run/README.md +++ b/modules/secure-cloud-run/README.md @@ -129,7 +129,7 @@ The following dependencies must be available: ### APIs -The Secure-cloud-run project will enable the following APIs to the Serverlesss Project: +The Secure-cloud-run module will enable the following APIs to the Serverlesss Project: * Google VPC Access API: `vpcaccess.googleapis.com` * Compute API: `compute.googleapis.com` @@ -137,7 +137,7 @@ The Secure-cloud-run project will enable the following APIs to the Serverlesss P * Cloud Run API: `run.googleapis.com` * Cloud KMS API: `cloudkms.googleapis.com` -The Secure-cloud-run project will enable the following APIs to the VPC Project: +The Secure-cloud-run module will enable the following APIs to the VPC Project: * Google VPC Access API: `vpcaccess.googleapis.com` * Compute API: `compute.googleapis.com` diff --git a/modules/secure-cloud-run/main.tf b/modules/secure-cloud-run/main.tf index 553bab70..95319630 100644 --- a/modules/secure-cloud-run/main.tf +++ b/modules/secure-cloud-run/main.tf @@ -26,7 +26,8 @@ module "serverless_project_apis" { "compute.googleapis.com", "container.googleapis.com", "run.googleapis.com", - "cloudkms.googleapis.com" + "cloudkms.googleapis.com", + "run.googleapis.com" ] } @@ -43,6 +44,18 @@ module "vpc_project_apis" { ] } +module "kms_project_apis" { + source = "terraform-google-modules/project-factory/google//modules/project_services" + version = "~> 13.0" + + project_id = var.kms_project_id + disable_services_on_destroy = false + + activate_apis = [ + "cloudkms.googleapis.com" + ] +} + module "cloud_run_network" { source = "../secure-cloud-run-net" diff --git a/modules/secure-cloud-run/variables.tf b/modules/secure-cloud-run/variables.tf index c35205f3..a85b526a 100644 --- a/modules/secure-cloud-run/variables.tf +++ b/modules/secure-cloud-run/variables.tf @@ -150,6 +150,7 @@ variable "create_subnet" { type = bool default = true } + variable "policy_for" { description = "Policy Root: set one of the following values to determine where the policy is applied. Possible values: [\"project\", \"folder\", \"organization\"]." type = string diff --git a/test/integration/discover_test.go b/test/integration/discover_test.go index 712da9c2..349a396f 100644 --- a/test/integration/discover_test.go +++ b/test/integration/discover_test.go @@ -15,11 +15,11 @@ package test import ( - "testing" + "testing" - "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" ) func TestAll(t *testing.T) { - tft.AutoDiscoverAndTest(t) + tft.AutoDiscoverAndTest(t) }