Skip to content

Commit

Permalink
feat: adds secure-cloud-function sub-module (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
amandakarina authored May 5, 2023
1 parent e006b0b commit 048efa0
Show file tree
Hide file tree
Showing 6 changed files with 725 additions and 0 deletions.
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

0 comments on commit 048efa0

Please sign in to comment.