Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds secure-cloud-function sub-module #22

4 changes: 4 additions & 0 deletions modules/secure-cloud-function-core/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ resource "google_cloudfunctions2_function_iam_member" "invokers" {
cloud_function = module.cloud_function.function_name
role = "roles/cloudfunctions.invoker"
member = "serviceAccount:${var.event_trigger.service_account_email}"

depends_on = [
module.cloud_function
]
}
196 changes: 196 additions & 0 deletions modules/secure-cloud-function/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Secure Cloud Function

This module handles the deployment required for Cloud Function (2nd Gen) usage. Secure-cloud-function module will call the secure-cloud-function-core, secure-cloud-serverless-net and secure-cloud-serverless-security modules.

When using a Shared VPC, you can chose where to create the VPC Connector.

_Note:_ When using a single VPC you should provides VPC and Serverless project id with the same value and the value for `connector_on_host_project` variable must be `false`.

The resources/services/activations/deletions that this module will create/trigger are:

* secure-cloud-serverless-network module will apply:
* Creates Firewall rules on your **VPC Project**.
* Serverless to VPC Connector
* VPC Connector to Serverless
* VPC Connector to LB
* VPC Connector Health Checks
* Creates a sub network to VPC Connector usage purpose.
* Creates Serverless Connector on your **VPC Project** or **Serverless Project**. Refer the comparison below:
* Advantages of creating connectors in the [VPC Project](https://cloud.google.com/run/docs/configuring/connecting-shared-vpc#host-project)
* Advantages of creating connectors in the [Serverless Project](https://cloud.google.com/run/docs/configuring/connecting-shared-vpc#service-projects)
* Grant the necessary roles for Cloud Function are able to use VPC Connector on your Shared VPC when creating VPC Connector in host project.
* Grant Network User role to Cloud Services service account.
* Grant VPC Access User to Cloud Function Service Identity when deploying VPC Access.

* secure-cloud-serverless-security module will apply:
* Creates KMS Keyring and Key for [customer managed encryption keys](https://cloud.google.com/run/docs/securing/using-cmek) in the **KMS Project** to be used by Cloud Function (2nd Gen).
* Enables Organization Policies related to Cloud Function (2nd Gen) in the **Serverless Project**.
* Allow Ingress only from internal and Cloud Load Balancing.
* Allow VPC Egress to Private Ranges Only.
* When groups emails are provided, this module will grant the roles for each persona.
* Serverless administrator - Service Project
* roles/run.admin
* roles/cloudfunctions.admin
* roles/compute.networkViewer
* compute.networkUser
* Servervless Security Administrator - Security project
* roles/cloudfunctions.viewer
* roles/run.viewer
* roles/cloudkms.viewer
* roles/artifactregistry.reader
* Cloud Function (2nd Gen) developer - Security project
* roles/cloudfunctions.developer
* roles/artifactregistry.writer
* roles/cloudkms.cryptoKeyEncrypter
* Cloud Function (2nd Gen) user - Service project
* roles/cloudfunctions.invoker

* secure-cloud-function-core module will apply:
* Creates a Cloud Function (2nd Gen).
* Creates the Cloud Function source bucket in the same location as the Cloud Function.
* Configure the EventArc Google Channel to use Customer Encryption Key in the Cloud Function location.
* **Warning:** If there is another CMEK configured for the same region, it will be overwritten.
* Creates a private worker pool for Cloud Build configured to not use External IP.
* Grants Cloud Functions Invoker to EventArc Trigger Service Account.

## Usage

Basic usage of this module is as follows:

```hcl
module "secure_cloud_run" {
source = "../../modules/secure-cloud-function"

function_name = <FUNCTION-NAME>
function_description = <FUNCTION-DESCRIPTION>
location = <FUNCTION-LOCATION>
region = <FUNCTION-REGION>
serverless_project_id = <FUNCTION-PROJECT-ID>
vpc_project_id = <VPC-PROJECT-ID>
kms_project_id = <KMS-PROJECT-IF>
key_name = <KMS-KEY-NAME>
keyring_name = <KMS-KEYRING-NAME>
service_account_email = <FUNCTION-SERVICE-ACCOUNT>
connector_name = <VPC-CONNECTOR-NAME>
subnet_name = <SUBNET-NAME>
create_subnet = false
shared_vpc_name = <SHARE-VPC-NAME>
ip_cidr_range = "10.0.0.0/28"

storage_source = {
bucket = <SOURCE-BUCKET-NAME>
object = <SOURCE-FILE-NAME>
}
runtime = <FUNCTION-RUNTIME>
entry_point = <FUNCTION-ENTRY-POINT>
}
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| all\_traffic\_on\_latest\_revision | Timeout for each request. | `bool` | `true` | no |
| available\_memory\_mb | The amount of memory in megabytes allotted for the function to use. | `string` | `"256Mi"` | no |
| build\_environment\_variables | A set of key/value environment variable pairs to be used when building the Function. | `map(string)` | `{}` | no |
| connector\_name | The name for the connector to be created. | `string` | `"serverless-vpc-connector"` | no |
| create\_subnet | The subnet will be created with the subnet\_name variable if true. When false, it will use the subnet\_name for the subnet. | `bool` | `true` | no |
| entry\_point | The name of a method in the function source which will be invoked when the function is executed. | `string` | n/a | yes |
| environment\_variables | A set of key/value environment variable pairs to assign to the function. | `map(string)` | `{}` | no |
| event\_trigger | A source that fires events in response to a condition in another service. | <pre>object({<br> trigger_region = optional(string)<br> event_type = string<br> service_account_email = string<br> pubsub_topic = optional(string)<br> retry_policy = string<br> event_filters = optional(set(object({<br> attribute = string<br> attribute_value = string<br> operator = optional(string)<br> })))<br> })</pre> | n/a | yes |
| folder\_id | The folder ID to apply the policy to. | `string` | `""` | no |
| function\_description | Cloud Function description. | `string` | n/a | yes |
| function\_name | Cloud Function name. | `string` | n/a | yes |
| groups | Groups which will have roles assigned.<br> The Serverless Administrators email group which the following roles will be added: Cloud Run Admin, Compute Network Viewer and Compute Network User.<br> The Serverless Security Administrators email group which the following roles will be added: Cloud Run Viewer, Cloud KMS Viewer and Artifact Registry Reader.<br> The Cloud Run Developer email group which the following roles will be added: Cloud Run Developer, Artifact Registry Writer and Cloud KMS CryptoKey Encrypter.<br> The Cloud Run User email group which the following roles will be added: Cloud Run Invoker. | <pre>object({<br> group_serverless_administrator = optional(string, null)<br> group_serverless_security_administrator = optional(string, null)<br> group_cloud_run_developer = optional(string, null)<br> group_cloud_run_developer = optional(string, null)<br> group_cloud_run_user = optional(string, null)<br> })</pre> | `{}` | no |
| ingress\_settings | The ingress settings for the function. Allowed values are ALLOW\_ALL, ALLOW\_INTERNAL\_AND\_GCLB and ALLOW\_INTERNAL\_ONLY. Changes to this field will recreate the cloud function. | `string` | `"ALLOW_INTERNAL_AND_GCLB"` | no |
| ip\_cidr\_range | The range of internal addresses that are owned by the subnetwork and which is going to be used by VPC Connector. For example, 10.0.0.0/28 or 192.168.0.0/28. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. | `string` | n/a | yes |
| key\_name | The name of KMS Key to be created and used in Cloud Run. | `string` | `"cloud-run-kms-key"` | no |
| key\_protection\_level | The protection level to use when creating a version based on this template. Possible values: ["SOFTWARE", "HSM"] | `string` | `"HSM"` | no |
| key\_rotation\_period | Period of key rotation in seconds. | `string` | `"2592000s"` | no |
| keyring\_name | Keyring name. | `string` | `"cloud-run-kms-keyring"` | no |
| kms\_project\_id | The project where KMS will be created. | `string` | n/a | yes |
| labels | Labels to be assigned to resources. | `map(any)` | `{}` | 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 |
| 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 |
| organization\_id | The organization ID to apply the policy to. | `string` | `""` | no |
| policy\_for | Policy Root: set one of the following values to determine where the policy is applied. Possible values: ["project", "folder", "organization"]. | `string` | `"project"` | no |
| prevent\_destroy | Set the `prevent_destroy` lifecycle attribute on the Cloud KMS key. | `bool` | `true` | no |
| region | Location for load balancer and Cloud Run resources. | `string` | n/a | yes |
| repo\_source | The source repository where the Cloud Function Source is stored. Do not use combined with source\_path. | <pre>object({<br> project_id = optional(string)<br> repo_name = string<br> branch_name = string<br> dir = optional(string)<br> tag_name = optional(string)<br> commit_sha = optional(string)<br> invert_regex = optional(bool, false)<br> })</pre> | `null` | no |
| resource\_names\_suffix | A suffix to concat in the end of the network resources names being created. | `string` | `null` | no |
| runtime | The runtime in which the function will be executed. | `string` | n/a | yes |
| secret\_environment\_variables | A list of maps which contains key, project\_id, secret\_name (not the full secret id) and version to assign to the function as a set of secret environment variables. | <pre>set(object({<br> key_name = string<br> project_id = optional(string)<br> secret = string<br> version = string<br> }))</pre> | `null` | no |
| secret\_volumes | [Beta] Environment variables (Secret Manager). | <pre>set(object({<br> mount_path = string<br> project_id = optional(string)<br> secret = string<br> versions = set(object({<br> version = string<br> path = string<br> }))<br> }))</pre> | `null` | no |
| serverless\_project\_id | The project to deploy the cloud run service. | `string` | n/a | yes |
| service\_account\_email | Service account to be used on Cloud Function. | `string` | n/a | yes |
| shared\_vpc\_name | Shared VPC name which is going to be re-used to create Serverless Connector. | `string` | n/a | yes |
| storage\_source | Get the source from this location in Google Cloud Storage. | <pre>object({<br> bucket = string<br> object = string<br> generation = optional(string, null)<br> })</pre> | `null` | no |
| subnet\_name | Subnet name to be re-used to create Serverless Connector. | `string` | `null` | no |
| timeout\_seconds | Timeout for each request. | `number` | `120` | no |
| 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 |
| vpc\_project\_id | The host project for the shared vpc. | `string` | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| cloud\_services\_sa | Service Account for Cloud Function. |
| connector\_id | VPC serverless connector ID. |
| gca\_vpcaccess\_sa | Service Account for VPC Access. |
| key\_self\_link | Name of the Cloud KMS crypto key. |
| keyring\_self\_link | Name of the Cloud KMS keyring. |
| serverless\_identity\_services\_sa | Service Identity to serverless services. |
| service\_name | ID of the created service. |
| service\_url | Url of the created service. |

<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

## Requirements

### Software

The following dependencies must be available:

* [Terraform](https://www.terraform.io/downloads.html) >= 0.13.0
* [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) < 5.0

### APIs

The Secure-cloud-function module will enable the following APIs to the Serverlesss Project:

* Google VPC Access API: `vpcaccess.googleapis.com`
* Compute API: `compute.googleapis.com`
* Container Registry API: `container.googleapis.com`
* Cloud Function API: `run.googleapis.com`

The Secure-cloud-function module will enable the following APIs to the VPC Project:

* Google VPC Access API: `vpcaccess.googleapis.com`
* Compute API: `compute.googleapis.com`

The Secure-cloud-function module will enable the following APIs to the KMS Project:

* Cloud KMS API: `cloudkms.googleapis.com`

### Service Account

A service account with the following roles must be used to provision
the resources of this module:

* VPC Project
* Compute Shared VPC Admin: `roles/compute.xpnAdmin`
* Network Admin: `roles/compute.networkAdmin`
* Security Admin: `roles/compute.securityAdmin`
* Serverless VPC Access Admin: `roles/vpcaccess.admin`
* KMS Project
* Cloud KMS Admin: `roles/cloudkms.admin`
* Serverless Project
* Security Admin: `roles/compute.securityAdmin`
* Serverless VPC Access Admin: `roles/vpcaccess.admin`
* Cloud Function Developer: `roles/run.developer`
* Compute Network User: `roles/compute.networkUser`
* Artifact Registry Reader: `roles/artifactregistry.reader`

**Note:** [Secret Manager Secret Accessor](https://cloud.google.com/run/docs/configuring/secrets#access-secret) role must be granted to the Cloud Function service account to allow read access on the secret.
140 changes: 140 additions & 0 deletions modules/secure-cloud-function/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Copyright 2023 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.
*/


module "cloud_serverless_network" {
source = "GoogleCloudPlatform/cloud-run/google//modules/secure-cloud-serverless-net"
version = "~> 0.6"

connector_name = var.connector_name
subnet_name = var.subnet_name
location = var.location
vpc_project_id = var.vpc_project_id
serverless_project_id = var.serverless_project_id
shared_vpc_name = var.shared_vpc_name
connector_on_host_project = false
ip_cidr_range = var.ip_cidr_range
create_subnet = var.create_subnet
resource_names_suffix = var.resource_names_suffix

serverless_service_identity_email = google_project_service_identity.cloudfunction_sa.email
}

data "google_service_account" "cloud_serverless_sa" {
account_id = var.service_account_email
}

resource "google_service_account_iam_member" "identity_service_account_user" {
service_account_id = data.google_service_account.cloud_serverless_sa.name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_project_service_identity.cloudfunction_sa.email}"
}

resource "google_project_service_identity" "eventarc_sa" {
provider = google-beta

project = var.serverless_project_id
service = "eventarc.googleapis.com"
}

resource "google_project_service_identity" "cloudfunction_sa" {
provider = google-beta

project = var.serverless_project_id
service = "cloudfunctions.googleapis.com"
}

resource "google_project_service_identity" "artifact_sa" {
provider = google-beta

project = var.serverless_project_id
service = "artifactregistry.googleapis.com"
}

data "google_storage_project_service_account" "gcs_account" {
project = var.serverless_project_id
}

module "cloud_serverless_security" {
source = "../secure-cloud-serverless-security"

kms_project_id = var.kms_project_id
location = var.location
serverless_project_id = var.serverless_project_id
prevent_destroy = var.prevent_destroy
key_name = var.key_name
keyring_name = var.keyring_name
key_rotation_period = var.key_rotation_period
key_protection_level = var.key_protection_level
policy_for = var.policy_for
folder_id = var.folder_id
organization_id = var.organization_id
groups = var.groups

encrypters = [
"serviceAccount:${google_project_service_identity.cloudfunction_sa.email}",
"serviceAccount:${var.service_account_email}",
"serviceAccount:${google_project_service_identity.artifact_sa.email}",
"serviceAccount:${google_project_service_identity.eventarc_sa.email}",
"serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"
]

decrypters = [
"serviceAccount:${google_project_service_identity.cloudfunction_sa.email}",
"serviceAccount:${var.service_account_email}",
"serviceAccount:${google_project_service_identity.artifact_sa.email}",
"serviceAccount:${google_project_service_identity.eventarc_sa.email}",
"serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"
]
}

module "cloud_function_core" {
source = "../secure-cloud-function-core"

function_name = var.function_name
function_description = var.function_description
project_id = var.serverless_project_id
labels = var.labels
location = var.region
runtime = var.runtime
entry_point = var.entry_point
repo_source = var.repo_source
storage_source = var.storage_source
build_environment_variables = var.build_environment_variables
event_trigger = var.event_trigger
force_destroy = !var.prevent_destroy
encryption_key = module.cloud_serverless_security.key_self_link

service_config = {
max_instance_count = var.max_scale_instances
min_instance_count = var.min_scale_instances
available_memory = var.available_memory_mb
timeout_seconds = var.timeout_seconds
vpc_connector = module.cloud_serverless_network.connector_id
service_account_email = var.service_account_email
ingress_settings = var.ingress_settings
all_traffic_on_latest_revision = var.all_traffic_on_latest_revision
vpc_connector_egress_settings = var.vpc_egress_value
runtime_env_variables = var.environment_variables

runtime_secret_env_variables = var.secret_environment_variables
secret_volumes = var.secret_volumes
}

depends_on = [
google_service_account_iam_member.identity_service_account_user
]
}
Loading