diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml
index 166afb83..f1ac7932 100644
--- a/build/int.cloudbuild.yaml
+++ b/build/int.cloudbuild.yaml
@@ -27,7 +27,6 @@ steps:
args: ['/bin/bash', '-c', 'cft test run all --stage init --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- prepare
@@ -47,17 +46,15 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2GCSSource --stage teardown --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- cloud-func-gcs-source-verify
- id: secure-cloud-func-sql-apply
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
- args: ['/bin/bash', '-c', 'cft test run TestGCF2CloudSQL --stage apply --verbose']
+ args: ['/bin/bash', '-c', './test/install_build_dependencies.sh && cft test run TestGCF2CloudSQL --stage apply --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- cloud-func-init
@@ -66,7 +63,6 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2CloudSQL --stage verify --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- secure-cloud-func-sql-apply
@@ -75,11 +71,35 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2CloudSQL --stage teardown --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- secure-cloud-func-sql-verify
+- id: secure-cloud-func-internal-server-apply
+ name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
+ args: ['/bin/bash', '-c', './test/install_build_dependencies.sh && cft test run TestCFInternalServer --stage apply --verbose']
+ env:
+ - 'TF_VAR_org_id=$_ORG_ID'
+ - 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
+ waitFor:
+ - cloud-func-init
+- id: secure-cloud-func-internal-server-verify
+ name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
+ args: ['/bin/bash', '-c', 'cft test run TestCFInternalServer --stage verify --verbose']
+ env:
+ - 'TF_VAR_org_id=$_ORG_ID'
+ - 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
+ waitFor:
+ - secure-cloud-func-internal-server-apply
+- id: secure-cloud-func-internal-server-teardown
+ name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
+ args: ['/bin/bash', '-c', 'cft test run TestCFInternalServer --stage teardown --verbose']
+ env:
+ - 'TF_VAR_org_id=$_ORG_ID'
+ - 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
+ waitFor:
+ - secure-cloud-func-internal-server-verify
+
- id: cloud-func-pubsub-trigger-apply
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
args: ['/bin/bash', '-c', 'cft test run TestGCF2PubSubTrigger --stage apply --verbose']
@@ -95,17 +115,15 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2PubSubTrigger --stage teardown --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- cloud-func-pubsub-trigger-verify
- id: secure-cloud-func-bigquery-apply
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
- args: ['/bin/bash', '-c', 'cft test run TestGCF2BigqueryTrigger --stage apply --verbose']
+ args: ['/bin/bash', '-c', './test/install_build_dependencies.sh && cft test run TestGCF2BigqueryTrigger --stage apply --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- cloud-func-init
@@ -114,7 +132,6 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2BigqueryTrigger --stage verify --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- secure-cloud-func-bigquery-apply
@@ -123,7 +140,6 @@ steps:
args: ['/bin/bash', '-c', 'cft test run TestGCF2BigqueryTrigger --stage teardown --verbose']
env:
- 'TF_VAR_org_id=$_ORG_ID'
- - 'TF_VAR_folder_id=$_FOLDER_ID'
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
waitFor:
- secure-cloud-func-bigquery-verify
diff --git a/examples/secure_cloud_function_bigquery_trigger/README.md b/examples/secure_cloud_function_bigquery_trigger/README.md
new file mode 100644
index 00000000..143d6bbf
--- /dev/null
+++ b/examples/secure_cloud_function_bigquery_trigger/README.md
@@ -0,0 +1,158 @@
+# Secure Cloud Function Triggered by BigQuery
+
+This examples shows how trigger Secure Cloud Function (2nd Gen) by BigQuery.
+
+The resources and services that this example will create or enable are:
+
+* The **secure-serverless-harness** module will:
+ * Create a Security Project with the following APIS enabled:
+ * Cloud KMS API: `cloudkms.googleapis.com`
+ * Create a Cloud Function project with the following APIS enabled:
+ * Google VPC Access API: `vpcaccess.googleapis.com`
+ * Compute API: `compute.googleapis.com`
+ * Container Registry API: `container.googleapis.com`
+ * Cloud Function API: `run.googleapis.com`
+ * Create a Shared VPC Project with:
+ * A Shared Network
+ * A firewall rule to deny all egress traffic
+ * A firewall rule to allow Internal APIs traffic
+ * A configured Private Connect
+ * And the following APIs enabled:
+ * Google VPC Access API: `vpcaccess.googleapis.com`
+ * Compute API: `compute.googleapis.com`
+
+* The **secure-serverless-network** module will:
+ * Create the following Firewall rules on the **Shared VPC Project**.
+ * Serverless to VPC Connector
+ * VPC Connector to Serverless
+ * VPC Connector Health Checks
+ * Create a a sub network to VPC Connector usage purpose.
+ * Create a Serverless Connector on the **Shared VPC Project** or **Serverless Project**. Refer to the following comparison to choose where to create Serverless Connector:
+ * Advantages of creating connectors in the [Shared 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 the Cloud Function to be able to use the VPC Connector on the Shared VPC if creating the VPC Connector in the host project:
+ * Grant Network User role to the [Google API Service Agent](https://cloud.google.com/compute/docs/access/service-accounts#google_apis_service_agent) service account.
+ * Grant VPC Access User to the [Google Cloud Functions Service Agent](https://cloud.google.com/functions/docs/concepts/iam#access_control_for_service_accounts) when deploying VPC Access.
+
+* The **secure-web-proxy** module will:
+ * Create a sub network for Regional Managed Proxy purpose
+ * Create the following Firewall rule on the **Shared VPC Project**:
+ * Cloud Build to Secure Web Proxy
+ * Create a VPC peering for the Shared VPC Network with:
+ * A Compute Global Address
+ * A Service Networking Connection
+ * Upload a example generated self-signed certificate to Certificate Manager
+ * Create a Gateway Security Policy with:
+ * A Gateway Security Policy Rule
+ * A Security URL Lists resource
+ * Create the Secure Web Proxy/Gateway (SWP/SWG) instance
+
+* The **secure-cloud-serverless-security** module will:
+ * Create 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)
+ * Enable the following Organization Policies related to Cloud Function (2nd Gen) in the **Serverless Project**:
+ * Allowed ingress settings - Allow HTTP traffic from private VPC sources and through GCLB.
+ * Allowed VPC Connector egress settings - Force the use of VPC Access Connector for all egress traffic from the function.
+ * Grant the following roles if groups emails are provided:
+ * **Serverless Administrator** group on the Service Project:
+ * Cloud Run Admin: `roles/run.admin`
+ * Cloud Functions Admin: `roles/cloudfunctions.admin`
+ * Network Viewer: `roles/compute.networkViewer`
+ * Network User: `roles/compute.networkUser`
+ * **Servervless Security Administrator** group on the Security project:
+ * Cloud Functions Viewer: `roles/cloudfunctions.viewer`
+ * Cloud Frun Viewer: `roles/run.viewer`
+ * Cloud KMS Viewer: `roles/cloudkms.viewer`
+ * Artifact Registry Reader: `roles/artifactregistry.reader`
+ * **Cloud Function (2nd Gen) developer** group on the Security project:
+ * Cloud Functions Developer: `roles/cloudfunctions.developer`
+ * Artifact Registry Writer: `roles/artifactregistry.writer`
+ * Cloud KMS CryptoKey Encrypter: `roles/cloudkms.cryptoKeyEncrypter`
+ * **Cloud Function (2nd Gen) user** group on the Service project:
+ * Cloud Functions Invoker: `roles/cloudfunctions.invoker`
+
+* The **secure-cloud-function-core** module will:
+ * Create a Cloud Function (2nd Gen)
+ * Create 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
+ * Create a private worker pool for Cloud Build configured to not use External IP
+ * Grant Cloud Functions Invoker to the [EventArc Trigger Service Account](https://cloud.google.com/functions/docs/calling/eventarc#trigger-identity)
+ * Enable [Container Registry Automatic Scanning](https://cloud.google.com/artifact-registry/docs/analysis)
+
+* In addition to all the secure-cloud-function resources created, this example will also create::
+ * BigQuery Dataset
+ * BigQuery Table
+ * Storage Bucket to store Cloud Function source Code
+ * KMS Keys to be used by:
+ * Pub/Sub Topic
+ * BigQuery Dataset and Table
+
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| access\_context\_manager\_policy\_id | The id of the default Access Context Manager policy. Can be obtained by running `gcloud access-context-manager policies list --organization YOUR_ORGANIZATION_ID --format="value(name)"`. This variable must be provided if `create_access_context_manager_access_policy` is set to `false` | `number` | `null` | no |
+| access\_level\_members | The list of members who will be in the access level. | `list(string)` | n/a | yes |
+| billing\_account | The ID of the billing account to associate this project with. | `string` | n/a | yes |
+| create\_access\_context\_manager\_access\_policy | Defines if Access Context Manager will be created by Terraform. If set to `false`, you must provide `access_context_manager_policy_id`. More information about Access Context Manager creation in [this documentation](https://cloud.google.com/access-context-manager/docs/create-access-level). | `bool` | n/a | yes |
+| egress\_policies | A list of all [egress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#egress-rules-reference), each list object has a `from` and `to` value that describes egress\_from and egress\_to.
Example: `[{ from={ identities=[], identity_type="ID_TYPE" }, to={ resources=[], operations={ "SRV_NAME"={ OP_TYPE=[] }}}}]`
Valid Values:
`ID_TYPE` = `null` or `IDENTITY_TYPE_UNSPECIFIED` (only allow indentities from list); `ANY_IDENTITY`; `ANY_USER_ACCOUNT`; `ANY_SERVICE_ACCOUNT`
`SRV_NAME` = "`*`" (allow all services) or [Specific Services](https://cloud.google.com/vpc-service-controls/docs/supported-products#supported_products)
`OP_TYPE` = [methods](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions) or [permissions](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions). |
list(object({| `[]` | no | +| folder\_id | The ID of a folder to host the infrastructure created in this example. | `string` | `""` | no | +| ingress\_policies | A list of all [ingress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#ingress-rules-reference), each list object has a `from` and `to` value that describes ingress\_from and ingress\_to.
from = any
to = any
}))
list(object({| `[]` | no | +| org\_id | The organization ID. | `string` | n/a | yes | +| terraform\_service\_account | The e-mail of the service account who will impersionate when creating infrastructure. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| bigquery\_kms\_key | KMS Key used in the Bigquery dataset. | +| cloud\_function\_name | The service account email created to be used by Cloud Function. | +| cloudfunction\_bucket | The Cloud Function source bucket. | +| cloudfunction\_bucket\_name | Name of the Cloud Function source bucket. | +| cloudfunction\_url | The URL on which the deployed service is available. | +| connector\_id | VPC serverless connector ID. | +| network\_project\_id | The network project id. | +| restricted\_access\_level\_name | Access level name. | +| restricted\_service\_perimeter\_name | Service Perimeter name. | +| security\_project\_id | The security project id. | +| security\_project\_number | The security project number. | +| serverless\_project\_id | The serverless project id. | +| serverless\_project\_number | The serverless project number. | +| service\_account\_email | The service account email created to be used by Cloud Function. | +| service\_vpc\_name | The Network self-link created in harness. | +| service\_vpc\_self\_link | The Network self-link created in harness. | +| service\_vpc\_subnet\_name | The sub-network name created in harness. | +| table\_id | Bigquery table name. | + + + +## Requirements + +### Software + +The following dependencies must be available: + +* [Terraform](https://www.terraform.io/downloads.html) >= 1.3 +* [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) < 5.0 +* [Google Cloud SDK CLI](https://cloud.google.com/sdk/docs/install) > 428.0.0 + +### APIs +#TODO: Fill with APIs needed on SA project + +### Service Account + +A service account with the following roles must be used to provision +the resources of this module: + +* Shared VPC Project +* Organization Level + * Access Context Manager Admin: `roles/accesscontextmanager.policyAdmin` + * Organization Policy Admin: `roles/orgpolicy.policyAdmin` +* Folder Level: + * Folder Admin: `roles/resourcemanager.folderAdmin` + * Project Creator: `roles/resourcemanager.projectCreator` + * Project Deleter: `roles/resourcemanager.projectDeleter` + * Compute Shared VPC Admin: `roles/compute.xpnAdmin` +* Billing: + * Billing User: `roles/billing.user` diff --git a/examples/secure_cloud_function_bigquery_trigger/functions/bq-to-cf/main.go b/examples/secure_cloud_function_bigquery_trigger/functions/bq-to-cf/main.go index 9fb2d805..33875f99 100644 --- a/examples/secure_cloud_function_bigquery_trigger/functions/bq-to-cf/main.go +++ b/examples/secure_cloud_function_bigquery_trigger/functions/bq-to-cf/main.go @@ -61,14 +61,14 @@ func helloPubSub(ctx context.Context, e event.Event) error { log.Printf("Error listing compute regions: %s.", err.Error()) fmt.Errorf(err.Error()) } - log.Println("Regions: %v!\n", regions) + log.Printf("Regions: %v!\n", regions) buckets, err := listBuckets() if err != nil { log.Printf("Error listing project buckets: %s.", err.Error()) fmt.Errorf(err.Error()) } - log.Println("Buckets: %v!\n", buckets) + log.Printf("Buckets: %v!\n", buckets) return nil } @@ -79,7 +79,7 @@ func helloPubSub(ctx context.Context, e event.Event) error { func listBuckets() ([]string, error) { projectID := os.Getenv("PROJECT_ID") ctx := context.Background() - log.Println("Creating Client for Storage.") + log.Printf("Creating Client for Storage.") client, err := storage.NewClient(ctx) if err != nil { return nil, fmt.Errorf("storage.NewClient: %v", err) @@ -90,7 +90,7 @@ func listBuckets() ([]string, error) { defer cancel() var buckets []string - log.Println("Getting buckets in project.") + log.Printf("Getting buckets in project.") it := client.Buckets(ctx, projectID) for { battrs, err := it.Next() @@ -108,13 +108,13 @@ func listBuckets() ([]string, error) { func listComputeRegions() ([]string, error) { ctx := context.Background() - log.Println("Creating Default Client for Compute client.") + log.Printf("Creating Default Client for Compute client.") c, err := google.DefaultClient(ctx) if err != nil { log.Fatal(err) } - log.Println("Creating service for Compute client.") + log.Printf("Creating service for Compute client.") computeService, err := compute.New(c) if err != nil { log.Fatal(err) @@ -123,7 +123,7 @@ func listComputeRegions() ([]string, error) { // Project ID for this request. project := os.Getenv("PROJECT_ID") var regions []string - log.Println("Getting compute regions.") + log.Printf("Getting compute regions.") req := computeService.Regions.List(project) if err := req.Pages(ctx, func(page *compute.RegionList) error { for _, region := range page.Items { diff --git a/examples/secure_cloud_function_bigquery_trigger/main.tf b/examples/secure_cloud_function_bigquery_trigger/main.tf index 0ae9de28..78a75359 100644 --- a/examples/secure_cloud_function_bigquery_trigger/main.tf +++ b/examples/secure_cloud_function_bigquery_trigger/main.tf @@ -21,7 +21,9 @@ locals { repository_name = "rep-secure-cloud-function" table_name = "tbl_test" kms_bigquery = "key-secure-bigquery" + subnet_ip = "10.0.0.0/28" } + resource "random_id" "random_folder_suffix" { byte_length = 2 } @@ -40,11 +42,11 @@ module "secure_harness" { region = local.region location = local.location vpc_name = "vpc-secure-cloud-function" - subnet_ip = "10.0.0.0/28" + subnet_ip = local.subnet_ip private_service_connect_ip = "10.3.0.5" create_access_context_manager_access_policy = var.create_access_context_manager_access_policy access_context_manager_policy_id = var.access_context_manager_policy_id - access_level_members = var.access_level_members + access_level_members = distinct(concat(var.access_level_members, ["serviceAccount:${var.terraform_service_account}"])) key_name = "key-secure-artifact-registry" keyring_name = "krg-secure-artifact-registry" prevent_destroy = false @@ -59,6 +61,20 @@ module "secure_harness" { "prj-secure-cloud-function" = ["roles/eventarc.eventReceiver", "roles/viewer", "roles/compute.networkViewer", "roles/run.invoker"] } + network_project_extra_apis = ["networksecurity.googleapis.com"] + + serverless_project_extra_apis = { + "prj-secure-cloud-function" = ["networksecurity.googleapis.com"] + } +} + +resource "google_project_service" "network_project_apis" { + for_each = toset(["networkservices.googleapis.com", "certificatemanager.googleapis.com"]) + project = module.secure_harness.network_project_id[0] + service = each.value + disable_on_destroy = false + + depends_on = [module.secure_harness] } data "archive_file" "cf_bigquery_source" { @@ -144,6 +160,85 @@ module "bigquery" { ] } +resource "null_resource" "generate_certificate" { + triggers = { + project_id = module.secure_harness.network_project_id[0] + region = local.region + } + + provisioner "local-exec" { + when = create + command = <
from = any
to = any
}))
list(object({| `[]` | no | +| folder\_id | The ID of a folder to host the infrastructure created in this example. | `string` | `""` | no | +| ingress\_policies | A list of all [ingress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#ingress-rules-reference), each list object has a `from` and `to` value that describes ingress\_from and ingress\_to.
from = any
to = any
}))
list(object({| `[]` | no | +| org\_id | The organization ID. | `string` | n/a | yes | +| terraform\_service\_account | The e-mail of the service account who will impersionate when creating infrastructure. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| cloud\_function\_name | The service account email created to be used by Cloud Function. | +| cloudfunction\_bucket | The Cloud Function source bucket. | +| cloudfunction\_bucket\_name | Name of the Cloud Function source bucket. | +| cloudfunction\_url | The URL on which the deployed service is available. | +| connector\_id | VPC serverless connector ID. | +| network\_project\_id | The network project id. | +| restricted\_access\_level\_name | Access level name. | +| restricted\_service\_perimeter\_name | Service Perimeter name. | +| security\_project\_id | The security project id. | +| security\_project\_number | The security project number. | +| serverless\_project\_id | The serverless project id. | +| serverless\_project\_number | The serverless project number. | +| service\_account\_email | The service account email created to be used by Cloud Function. | +| service\_vpc\_name | The Network self-link created in harness. | +| service\_vpc\_self\_link | The Network self-link created in harness. | +| service\_vpc\_subnet\_name | The sub-network name created in harness. | + + +## Requirements + +### Software + +The following dependencies must be available: + +* [Terraform](https://www.terraform.io/downloads.html) >= 1.3 +* [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) < 5.0 + +### APIs + +The Secure Cloud Function Internal Server Example will enable the following APIs to the Serverless Project: + +* Google VPC Access API: `vpcaccess.googleapis.com` +* Compute API: `compute.googleapis.com` +* Container Registry API: `container.googleapis.com` +* Artifact Registry API: `artifactregistry.googleapis.com` +* Cloud Function API: `cloudfunctions.googleapis.com` +* Cloud Run API: `run.googleapis.com` +* Service Networking API: `servicenetworking.googleapis.com` +* Cloud KMS API: `cloudkms.googleapis.com` +* Container Scanning API: `containerscanning.googleapis.com` +* Eventarc API: `eventarc.googleapis.com` +* Eventarc Publishing API: `eventarcpublishing.googleapis.com` +* Cloud Build API: `cloudbuild.googleapis.com` + +The Secure Cloud Function with Internal Server Example will enable the following APIs to the VPC Project: + +* Google VPC Access API: `vpcaccess.googleapis.com` +* Compute API: `compute.googleapis.com` +* Service Networking API: `servicenetworking.googleapis.com` +* DNS API: `dns.googleapis.com` + +The Secure Cloud Function with Internal Server Example will enable the following APIs to the Security Project: + +* Cloud KMS API: `cloudkms.googleapis.com` +* Artifact Registry API: `artifactregistry.googleapis.com` + +### Service Account + +A service account with the following roles must be used to provision the resources of this module: + +* Organization Level + * Access Context Manager Admin: `roles/accesscontextmanager.policyAdmin` + * Organization Policy Admin: `roles/orgpolicy.policyAdmin` +* Folder Level: + * Folder Admin: `roles/resourcemanager.folderAdmin` + * Project Creator: `roles/resourcemanager.projectCreator` + * Project Deleter: `roles/resourcemanager.projectDeleter` + * Compute Shared VPC Admin: `roles/compute.xpnAdmin` +* Billing: + * Billing User: `roles/billing.user` diff --git a/examples/secure_cloud_function_internal_server/function/go.mod b/examples/secure_cloud_function_internal_server/function/go.mod new file mode 100644 index 00000000..2d275316 --- /dev/null +++ b/examples/secure_cloud_function_internal_server/function/go.mod @@ -0,0 +1,5 @@ +module example.com/module/helloworld + +require ( + github.com/GoogleCloudPlatform/functions-framework-go v1.6.1 +) diff --git a/examples/secure_cloud_function_internal_server/function/main.go b/examples/secure_cloud_function_internal_server/function/main.go new file mode 100644 index 00000000..5284e73a --- /dev/null +++ b/examples/secure_cloud_function_internal_server/function/main.go @@ -0,0 +1,65 @@ +// 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 +// +// 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. + +package helloworld + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + + "github.com/GoogleCloudPlatform/functions-framework-go/functions" +) + +func init() { + functions.HTTP("helloHTTP", helloHTTP) +} + +func helloHTTP(w http.ResponseWriter, r *http.Request) { + ipAddress := os.Getenv("TARGET_IP") + if ipAddress == "" { + log.Println("TARGET_IP environment variable not set") + http.Error(w, "TARGET_IP not set", http.StatusInternalServerError) + return + } + + url := fmt.Sprintf("http://%s:8000/index.html", ipAddress) + + // Send GET request to the server + response, err := http.Get(url) + if err != nil { + log.Printf("Failed to send GET request: %s\n", err) + http.Error(w, "Failed to send GET request", http.StatusInternalServerError) + return + } + defer response.Body.Close() + + // Read the response body + content, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Printf("Failed to read response body: %s\n", err) + http.Error(w, "Failed to read response body", http.StatusInternalServerError) + return + } + + // Log the content + log.Printf("Message returned from internal server: %s\n", string(content)) + + // Write the content to the response + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(content)) +} diff --git a/examples/secure_cloud_function_internal_server/internal_server.tf b/examples/secure_cloud_function_internal_server/internal_server.tf new file mode 100644 index 00000000..e9429c2b --- /dev/null +++ b/examples/secure_cloud_function_internal_server/internal_server.tf @@ -0,0 +1,107 @@ +/** + * 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. + */ + +resource "google_project_service_identity" "compute_identity_sa" { + provider = google-beta + + project = module.secure_harness.serverless_project_ids[0] + service = "compute.googleapis.com" +} + +module "compute_service_account" { + source = "terraform-google-modules/service-accounts/google" + version = "~> 3.0" + project_id = module.secure_harness.serverless_project_ids[0] + names = ["sa-compute-instance"] +} + +resource "google_project_iam_member" "service_account_roles" { + project = module.secure_harness.serverless_project_ids[0] + member = "serviceAccount:${module.compute_service_account.email}" + role = "roles/compute.instanceAdmin.v1" + + depends_on = [module.compute_service_account] +} + +data "google_project" "serverless_project_id" { + project_id = module.secure_harness.serverless_project_ids[0] +} + +resource "google_service_account_iam_member" "service_account_user" { + service_account_id = module.compute_service_account.service_account.id + role = "roles/iam.serviceAccountUser" + member = "serviceAccount:service-${data.google_project.serverless_project_id.number}@compute-system.iam.gserviceaccount.com" + + depends_on = [google_project_iam_member.service_account_roles] +} + +resource "google_compute_instance" "internal_server" { + name = local.webserver_instance + project = module.secure_harness.serverless_project_ids[0] + zone = local.zone + machine_type = "e2-small" + can_ip_forward = true + + boot_disk { + initialize_params { + image = "debian-cloud/debian-11" + } + } + tags = ["https-server"] + metadata_startup_script = file("${abspath(path.module)}/web_server/internal_server_setup.sh") + + network_interface { + subnetwork = module.secure_harness.service_subnet[0] + network_ip = local.network_ip + subnetwork_project = module.secure_harness.network_project_id[0] + } + + service_account { + email = module.compute_service_account.email + scopes = ["cloud-platform"] + } + + depends_on = [ + google_service_account_iam_member.service_account_user, + module.secure_harness + ] +} + +module "internal_server_firewall_rule" { + source = "terraform-google-modules/network/google//modules/firewall-rules" + version = "~> 7.0" + project_id = module.secure_harness.network_project_id[0] + network_name = module.secure_harness.service_vpc[0].network.name + + rules = [{ + name = "fw-e-shared-restricted-internal-server" + description = "Allow Cloud Function to connect in Internal Server using the private IP" + direction = "EGRESS" + priority = 100 + + log_config = { + metadata = "INCLUDE_ALL_METADATA" + } + deny = [] + allow = [{ + protocol = "tcp" + ports = ["8000"] + }] + + ranges = ["10.0.0.0/28"] + target_tags = ["allow-google-apis", "vpc-connector"] + }] +} diff --git a/examples/secure_cloud_function_internal_server/main.tf b/examples/secure_cloud_function_internal_server/main.tf new file mode 100644 index 00000000..95c415e1 --- /dev/null +++ b/examples/secure_cloud_function_internal_server/main.tf @@ -0,0 +1,236 @@ +# /** +# * 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. +# */ + +locals { + location = "us-west1" + region = "us-west1" + zone = "us-west1-b" + repository_name = "rep-secure-cloud-function" + network_ip = "10.0.0.3" + webserver_instance = "webserver" + subnet_ip = "10.0.0.0/28" +} +resource "random_id" "random_folder_suffix" { + byte_length = 2 +} + +module "secure_harness" { + source = "GoogleCloudPlatform/cloud-run/google//modules/secure-serverless-harness" + version = "~> 0.8" + + billing_account = var.billing_account + security_project_name = "prj-security" + network_project_name = "prj-restricted-shared" + serverless_project_names = ["prj-secure-cloud-function"] + org_id = var.org_id + parent_folder_id = var.folder_id + serverless_folder_suffix = random_id.random_folder_suffix.hex + region = local.region + location = local.location + vpc_name = "vpc-secure-cloud-function" + subnet_ip = local.subnet_ip + private_service_connect_ip = "10.3.0.5" + create_access_context_manager_access_policy = var.create_access_context_manager_access_policy + access_context_manager_policy_id = var.access_context_manager_policy_id + access_level_members = distinct(concat(var.access_level_members, ["serviceAccount:${var.terraform_service_account}"])) + key_name = "key-secure-artifact-registry" + keyring_name = "krg-secure-artifact-registry" + prevent_destroy = false + artifact_registry_repository_name = local.repository_name + egress_policies = var.egress_policies + ingress_policies = var.ingress_policies + serverless_type = "CLOUD_FUNCTION" + use_shared_vpc = true + time_to_wait_vpc_sc_propagation = "660s" + + service_account_project_roles = { + "prj-secure-cloud-function" = [ + "roles/eventarc.eventReceiver", + "roles/viewer", + "roles/compute.networkViewer", + "roles/run.invoker" + ] + } + + network_project_extra_apis = [ + "networksecurity.googleapis.com" + ] + + serverless_project_extra_apis = { + "prj-secure-cloud-function" = [ + "networksecurity.googleapis.com" + ] + } +} + +resource "google_project_service" "network_project_apis" { + for_each = toset(["networkservices.googleapis.com", "certificatemanager.googleapis.com"]) + project = module.secure_harness.network_project_id[0] + service = each.value + disable_on_destroy = false + + depends_on = [module.secure_harness] +} + +data "archive_file" "cf-internal-server-source" { + type = "zip" + source_dir = "${path.module}/function" + output_path = "function/cloudfunction-${random_id.random_folder_suffix.hex}.zip" +} + +resource "google_storage_bucket_object" "function-source" { + source = data.archive_file.cf-internal-server-source.output_path + content_type = "application/zip" + + # Append to the MD5 checksum of the files's content + # to force the zip to be updated as soon as a change occurs + name = "src-${data.archive_file.cf-internal-server-source.output_md5}.zip" + bucket = module.secure_harness.cloudfunction_source_bucket[module.secure_harness.serverless_project_ids[0]].name + + depends_on = [ + data.archive_file.cf-internal-server-source + ] +} + +resource "null_resource" "generate_certificate" { + triggers = { + project_id = module.secure_harness.network_project_id[0] + region = local.region + } + + provisioner "local-exec" { + when = create + command = <
from = any
to = any
}))