deployment.hcl (433 lines of code) (raw):

# Copyright 2020 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. # {{$recipes := "git://github.com/GoogleCloudPlatform/healthcare-data-protection-suite//templates/tfengine/recipes"}} # {{$ref := "ref=templates-v0.5.0"}} # {{$prefix := "sof"}} # {{$env := "test"}} # {{$domain := "example.com"}} # {{$default_location := "us-central1"}} # {{$default_zone := "a"}} # OIDC issuer to verify the token. # {{$oidc_issuer := "oidc_issue_url_placeholder"}} # Expected audience in token. # {{$audience := "audience_url_placeholder"}} ########################### # Optional feature flags ########################### # Enable if proxy want to accept opaque token # {{$use_userinfo_to_verify_accesstoken := false}} # Change to true if you want to enable caching opaque token. # {{$enable_cache := false}} # Change to true if you want to use secretmanager to store secret. # {{$use_secret_manager := false}} # Change to ture if you are setting up a e2e test project. # {{$setup_e2e_test_project := false}} data = { parent_type = "folder" parent_id = "000000000000" billing_account = "XXXXXX-XXXXXX-XXXXXX" state_bucket = "{{$prefix}}-{{$env}}-terraform-state" # Default locations for resources. Can be overridden in individual templates. bigquery_location = "us-east1" # BigQuery is not available in "us-central1" compute_region = "{{$default_location}}" gke_region = "{{$default_location}}" storage_location = "{{$default_location}}" } # Central devops project for Terraform state management and CI/CD. template "devops" { recipe_path = "{{$recipes}}/devops.hcl?{{$ref}}" output_path = "./devops" data = { # During Step 1, set to `true` and re-run the engine after generated devops module has been deployed. # Run `terraform init` in the devops module to backup its state to GCS. enable_gcs_backend = false admins_group = "{{$prefix}}-{{$env}}-folder-admins@{{$domain}}" project = { project_id = "{{$prefix}}-{{$env}}-devops" owners = [ "group:{{$prefix}}-{{$env}}-devops-owners@{{$domain}}", ] apis = [ "container.googleapis.com", "healthcare.googleapis.com", "cloudbuild.googleapis.com", "containerregistry.googleapis.com", "compute.googleapis.com", {{if $use_secret_manager}} "secretmanager.googleapis.com", {{end}} {{if $enable_cache}} "redis.googleapis.com", {{end}} ] } } } template "cicd" { recipe_path = "{{$recipes}}/cicd.hcl?{{$ref}}" output_path = "./cicd" data = { project_id = "{{$prefix}}-{{$env}}-devops" github = { owner = "GoogleCloudPlatform" name = "example" } branch_name = "main" terraform_root = "deploy_generated" # Prepare and enable default triggers. triggers = { validate = {} plan = {} apply = {} } # IAM members to give the roles/cloudbuild.builds.viewer permission so they can see build results. build_viewers = [ "group:{{$prefix}}-{{$env}}-cicd-viewers@{{$domain}}", ] managed_dirs = [ "devops", // NOTE: CICD service account can only update APIs on the devops project. "audit", "networks", "apps", ] } } template "audit" { recipe_path = "{{$recipes}}/audit.hcl?{{$ref}}" output_path = "./audit" data = { auditors_group = "{{$prefix}}-{{$env}}-auditors@{{$domain}}" project = { project_id = "{{$prefix}}-{{$env}}-audit" } logs_bigquery_dataset = { dataset_id = "{{$prefix}}_{{$env}}_1yr_audit_logs" } logs_storage_bucket = { name = "{{$prefix}}-{{$env}}-7yr-audit-logs" } } } # Central networks host project and resources. template "project_networks" { recipe_path = "{{$recipes}}/project.hcl?{{$ref}}" output_path = "./networks" data = { project = { project_id = "{{$prefix}}-{{$env}}-networks" apis = [ "container.googleapis.com", "compute.googleapis.com", "servicenetworking.googleapis.com", ] is_shared_vpc_host = true } resources = { compute_networks = [{ name = "{{$prefix}}-{{$env}}-network" subnets = [ { name = "{{$prefix}}-{{$env}}-gke-subnet" # 10.0.0.0 --> 10.0.127.255 ip_range = "10.0.0.0/17" secondary_ranges = [ { name = "{{$prefix}}-{{$env}}-pods-range" # /14 is the default size for the subnet's secondary IP range for Pods. # 172.16.0.0 --> 172.19.255.255 ip_range = "172.16.0.0/14" }, { name = "{{$prefix}}-{{$env}}-services-range" # /20 is the default size for the subnet's secondary IP address range for Services. # 172.20.0.0 --> 172.20.15.255 ip_range = "172.20.0.0/20" } ] }, ] }] compute_routers = [{ name = "{{$prefix}}-{{$env}}-router" network = "$${module.{{$prefix}}_{{$env}}_network.network.network.self_link}" nats = [{ name = "{{$prefix}}-{{$env}}-nat" source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" subnetworks = [ { name = "$${module.{{$prefix}}_{{$env}}_network.subnets[\"{{$default_location}}/{{$prefix}}-{{$env}}-gke-subnet\"].self_link}" source_ip_ranges_to_nat = ["ALL_IP_RANGES"] secondary_ip_range_names = [] }, ] }] }] } terraform_addons = { raw_config = <<EOF resource "google_compute_firewall" "fw_allow_k8s_ingress_lb_health_checks" { name = "fw-allow-k8s-ingress-lb-health-checks" description = "GCE L7 firewall rule" network = module.{{$prefix}}_{{$env}}_network.network.network.self_link project = module.project.project_id allow { protocol = "tcp" ports = ["30000-32767"] } # Load Balancer Health Check IP ranges. source_ranges = [ "130.211.0.0/22", "209.85.152.0/22", "209.85.204.0/22", "35.191.0.0/16", ] # This Service Account will later be created in the apps project and associated # with the GKE cluster. Firewall rule creation accepts a non-existing target # service account. target_service_accounts = [ "sofuser@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com" ] } {{if $enable_cache -}} # Create IPs for redis. resource "google_compute_global_address" "redis_private_ip_alloc" { name = "redis-ip" purpose = "VPC_PEERING" address_type = "INTERNAL" prefix_length = 16 network = module.{{$prefix}}_{{$env}}_network.network.network.id project = module.project.project_id } resource "google_service_networking_connection" "redis_service_connect" { network = module.{{$prefix}}_{{$env}}_network.network.network.id service = "servicenetworking.googleapis.com" reserved_peering_ranges = [google_compute_global_address.redis_private_ip_alloc.name] } {{end -}} EOF } } } # Apps project and resources. template "project_apps" { recipe_path = "{{$recipes}}/project.hcl?{{$ref}}" output_path = "./apps" data = { project = { project_id = "{{$prefix}}-{{$env}}-apps" apis = [ "iam.googleapis.com", "healthcare.googleapis.com", "cloudbuild.googleapis.com", "containerregistry.googleapis.com", "container.googleapis.com", "compute.googleapis.com", {{if $use_secret_manager}} "secretmanager.googleapis.com", {{end}} {{if $enable_cache}} "redis.googleapis.com", {{end}} ] shared_vpc_attachment = { host_project_id = "{{$prefix}}-{{$env}}-networks" subnets = [{ name = "{{$prefix}}-{{$env}}-gke-subnet" }] } } resources = { # Terraform-generated service account for use by the GKE apps. service_accounts = [ # used to run smart on fhir proxy { account_id = "sofuser" display_name = "sofuser" description = "Used to run smart on FHIR proxy" }, # used to access fhir API { account_id = "fhiruser" display_name = "fhiruser" description = "Used to access FHIR API" }, {{if $setup_e2e_test_project}} # used to run test. Uncomment for project used to run E2E test. { account_id = "testuser" display_name = "testuser" description = "Used to run E2E test" }, {{end}} ] iam_members = { "roles/monitoring.metricWriter" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] "roles/monitoring.viewer" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] "roles/logging.logWriter" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] "roles/storage.objectViewer" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] "roles/iam.serviceAccountTokenCreator" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] {{if $use_secret_manager}} "roles/secretmanager.viewer" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] {{end}} {{if $enable_cache}} "roles/redis.editor" = [ "serviceAccount:$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", ] {{end}} "roles/healthcare.datasetViewer" = [ {{if $setup_e2e_test_project}} "serviceAccount:$${google_service_account.testuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", {{end}} ] "roles/healthcare.datasetAdmin" = [ {{if $setup_e2e_test_project}} "serviceAccount:$${google_service_account.testuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", {{end}} ] "roles/healthcare.fhirStoreAdmin" = [ {{if $setup_e2e_test_project}} "serviceAccount:$${google_service_account.testuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", {{end}} ] "roles/healthcare.fhirResourceEditor" = [ "serviceAccount:$${google_service_account.fhiruser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", {{if $setup_e2e_test_project}} "serviceAccount:$${google_service_account.testuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com", {{end}} ] } } terraform_addons = { outputs = [ { name = "external_ip" value = "External ip of GKE ingress: $${google_compute_global_address.ingress_static_ip.address}" } ] raw_config = <<EOF # Reserve a static external IP for the Ingress. resource "google_compute_global_address" "ingress_static_ip" { name = "sof-ingress-ip" description = "Reserved static external IP for the GKE cluster Ingress and DNS configurations." address_type = "EXTERNAL" # This is the default, but be explicit because it's important. project = module.project.project_id } # Build Smart on FHIR proxy container. resource "null_resource" "cloudbuild_image_builder" { provisioner "local-exec" { working_dir = "$${path.module}/../.." command = <<EOT gcloud builds submit . \ --project $${module.project.project_id} \ --config=proxy/deploy/cloudbuild/cloudbuild.yaml EOT } } {{if $enable_cache -}} # Use cache to improve opaque token validation performance. module "memorystore" { source = "terraform-google-modules/memorystore/google" version = "1.3.0" name = "{{$prefix}}-{{$env}}-redis" project = module.project.project_id region = "{{$default_location}}" authorized_network = "projects/{{$prefix}}-{{$env}}-networks/global/networks/{{$prefix}}-{{$env}}-network" memory_size_gb = 1 connect_mode = "PRIVATE_SERVICE_ACCESS" } # Update REDIS IP in k8s yaml. resource "null_resource" "update_ip_in_k8s_yaml" { depends_on = [module.memorystore] provisioner "local-exec" { working_dir = "$${path.module}/../kubernetes" command = <<EOT sed -i 's/{REDIS_IP}/$${module.memorystore.host}/g' ./k8s.yaml EOT } } {{end -}} module "{{$prefix}}_{{$env}}_gke_cluster" { source = "terraform-google-modules/kubernetes-engine/google//modules/beta-private-cluster-update-variant" version = "~> 12.0.0" # Required. name = "{{$prefix}}-{{$env}}-gke-cluster" project_id = module.project.project_id region = "{{$default_location}}" regional = true network_project_id = "{{$prefix}}-{{$env}}-networks" network = "{{$prefix}}-{{$env}}-network" subnetwork = "{{$prefix}}-{{$env}}-gke-subnet" ip_range_pods = "{{$prefix}}-{{$env}}-pods-range" ip_range_services = "{{$prefix}}-{{$env}}-services-range" service_account = "$${google_service_account.sofuser.account_id}@{{$prefix}}-{{$env}}-apps.iam.gserviceaccount.com" master_ipv4_cidr_block = "192.168.0.0/28" istio = true skip_provisioners = true enable_private_endpoint = false release_channel = "STABLE" network_policy = true # Removing the default node pull, as it cannot be modified without destroying the cluster. remove_default_node_pool = true # Basic Auth disabled basic_auth_username = "" basic_auth_password = "" issue_client_certificate = false deploy_using_private_endpoint = true # Private nodes better control public exposure, and reduce the # ability of nodes to reach to the Internet without additional configurations. enable_private_nodes = true # Allow the cluster master to be accessible globally (from any region). master_global_access_enabled = true # master_authorized_networks can be specified to restrict access to the public endpoint. # Also see https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters. # TODO: add policies. enable_binary_authorization = true # Disable workload identity as using a single Compute Engine SA is sufficient. identity_namespace = null # Expose GCE metadata to pods. node_metadata = "EXPOSE" } EOF } } } template "k8s_configs" { component_path = "./proxy/deploy/gke/" output_path = "./kubernetes" data = { project_id = "{{$prefix}}-{{$env}}-apps" global_ip_name = "sof-ingress-ip" oidc_issuer = "{{$oidc_issuer}}" audience = "{{$audience}}" use_userinfo_to_verify_accesstoken = "{{$use_userinfo_to_verify_accesstoken}}" {{if $enable_cache}} cache_addr = "{REDIS_IP}:6379" {{else}} cache_addr = "\"\"" {{end}} use_secret_manager = "{{$use_secret_manager}}" } }