diff --git a/.gitignore b/.gitignore index c3c6dba1c..e4cc3111d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ .vscode/ credentials*.json -.run/ \ No newline at end of file +.run/ + + +__pycache__/ diff --git a/deployment-checklist.md b/deployment-checklist.md index 6e9e8b9b4..77410cfac 100644 --- a/deployment-checklist.md +++ b/deployment-checklist.md @@ -39,3 +39,4 @@ This is a checklist for deploying a new realm and setting up the first environme - [ ] Run the setup_env.py script. [docs](deployment-procedure.md#step-48-run-the-setuppy-script) - [ ] Prepare and Deploy the Environment. [docs](deployment-procedure.md#step-49-prepare-and-deploy-the-environment) - [ ] Configure the Auth Domains & Email Templates. [docs](deployment-procedure.md#step-410-configure-the-auth-domains-and-email-templates-for-the-identity-platform) + - [ ] (Optional) Add a backend NAT IP address into the MongoDB cluster access list. [docs](deployment-procedure.md#step-411-add-a-backend-nat-ip-address-into-the-mongodb-cluster-access-list) diff --git a/deployment-procedure.md b/deployment-procedure.md index 986882c2c..56288983f 100644 --- a/deployment-procedure.md +++ b/deployment-procedure.md @@ -555,7 +555,11 @@ flowchart The auth domains are configured automatically during the deployment process. -However, to show the `%APP_NAME%` in the email templates, for example, in the verification email you have to manually enable the consent-/branding-screen in the Google cloud console for the **environment project** and set the app name there. Even if the project uses a different OAuth project, the app name is taken from the environment project's consent screen (seems like a bug in the firebase). +However, to show the `%APP_NAME%` in the email templates, for example, in the verification email you have to manually enable the consent-/branding-screen in the Google Cloud console for the **environment project** and set the app name there. Even if the project uses a different OAuth project, the app name is taken from the environment project's consent screen (seems like a bug in the firebase). + +### Step 4.11: Add a backend NAT IP address into the MongoDB cluster access list. + +For security reasons, you can add a backend NAT IP address into the MongoDB cluster access list. It will be exported as an environment variable in the backend pulumi outputs. ## How to diff --git a/iac/backend/deploy_backend.py b/iac/backend/deploy_backend.py index 6e3eb9a2e..6ea0d08d4 100644 --- a/iac/backend/deploy_backend.py +++ b/iac/backend/deploy_backend.py @@ -199,7 +199,6 @@ def _get_fully_qualified_image_name( docker_repository: pulumi.Output[gcp.artifactregistry.Repository], tag: str ) -> pulumi.Output[str]: - def _get_self_link(repository_info): # Get the latest docker image with this tag. # Given the actual tag may be assigned to another image, we need to get the latest image with this tag. @@ -220,13 +219,60 @@ def _get_self_link(repository_info): project=repository_project_id ) - pulumi.info("Deploying image with the link: "+ image.self_link) + pulumi.info("Deploying image with the link: " + image.self_link) return image.self_link return docker_repository.apply(_get_self_link) +def _setup_nat_gateway(*, + basic_config: ProjectBaseConfig + ) -> tuple[gcp.compute.Network, gcp.compute.Subnetwork, list[pulumi.Resource]]: + """ + Sets up a NAT Gateway in Google Cloud Platform. + This is used so that all our cloud run instances route their requests through this NAT gateway with a static ip address. + ref: https://docs.cloud.google.com/run/docs/configuring/static-outbound-ip + """ + network = gcp.compute.Network( + get_resource_name(resource="nat-gateway", resource_type="network"), + auto_create_subnetworks=False, + opts=pulumi.ResourceOptions(provider=basic_config.provider)) + + sub_net = gcp.compute.Subnetwork( + get_resource_name(resource="nat-gateway", resource_type="sub-network"), + # Minimum /26 recommended for Cloud Run because ip addresses may change depending on the scaling of instances. + # ref: https://docs.cloud.google.com/run/docs/configuring/vpc-direct-vpc#scale_up_and_scale_down + ip_cidr_range="10.0.0.0/26", + region=basic_config.location, + network=network.id, + opts=pulumi.ResourceOptions(provider=basic_config.provider, depends_on=[network])) + + static_ip = gcp.compute.Address(get_resource_name(resource="nat-gateway", resource_type="static-ip"), + region=basic_config.location, + opts=pulumi.ResourceOptions(provider=basic_config.provider)) + + router = gcp.compute.Router( + get_resource_name(resource="nat-gateway", resource_type="router"), + network=network.id, + region=basic_config.location, + opts=pulumi.ResourceOptions(provider=basic_config.provider, depends_on=[network]) + ) + + router_nat = gcp.compute.RouterNat( + get_resource_name(resource="nat-gateway", resource_type="nat"), + router=router.name, + nat_ip_allocate_option="MANUAL_ONLY", + nat_ips=[static_ip.id], + region=basic_config.location, + source_subnetwork_ip_ranges_to_nat="ALL_SUBNETWORKS_ALL_IP_RANGES", + opts=pulumi.ResourceOptions(provider=basic_config.provider, depends_on=[router, sub_net, network])) + + # export the static IP since it might be used two whitelist the cloud run instances. + pulumi.export("cloudrun_nat_gateway_egress_static_ip", static_ip.address) + return network, sub_net, [router_nat] + + # Deploy cloud run service # See https://cloud.google.com/run/docs/overview/what-is-cloud-run for more information def _deploy_cloud_run_service( @@ -237,6 +283,8 @@ def _deploy_cloud_run_service( dependencies: list[pulumi.Resource], cv_bucket_name: Output[str], ): + nat_network, nat_sub_network, nat_dependencies = _setup_nat_gateway(basic_config=basic_config) + # See https://cloud.google.com/run/docs/securing/service-identity#per-service-identity for more information # Create a service account for the Cloud Run service service_account = gcp.serviceaccount.Account( @@ -377,8 +425,18 @@ def _deploy_cloud_run_service( ) ], service_account=service_account.email, + vpc_access=gcp.cloudrunv2.ServiceTemplateVpcAccessArgs( + network_interfaces=[ + gcp.cloudrunv2.ServiceTemplateVpcAccessNetworkInterfaceArgs( + network=nat_network.id, + subnetwork=nat_sub_network.id, + ) + ], + # All traffic in the system should pass through the network + egress="ALL_TRAFFIC", + ) ), - opts=pulumi.ResourceOptions(depends_on=dependencies + [iam_member], provider=basic_config.provider), + opts=pulumi.ResourceOptions(depends_on=dependencies + nat_dependencies + [iam_member], provider=basic_config.provider), ) pulumi.export("cloud_run_url", service.uri) return service, service_account