diff --git a/CHANGELOG b/CHANGELOG index eb7a005822ec73c1a84097cd1eb648000bceb431..0b8bada35b8cf4a38490c7e4db6423cb9b12a781 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.1.0] - 2021-07-28 +### Added + - Support load balancer ingress style alongside Cloud Run domain mapping. + ## [4.0.1] - 2021-07-15 ### Changed - Surface Cloud NAT variable for minimum number of SNAT tuples, supporting a larger diff --git a/README.md b/README.md index 30cfb1c4b0e6ae2a019511ce7eaf0586e661931f..ce61e550e01fb8e5f70976a333d71ff31d7a43c3 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,24 @@ The `master` branch contains the tip of development and corresponds to the `v2` branch. The `v1` branch will maintain source compatibility with the initial release. -## Custom domain mapping +## Ingress style +There are two supported ingress styles depending on `var.ingress_style` variable. -Setting the `dns_name` will create a domain mapping for the webapp. Before -setting this value you *must* have verified ownership of the domain with Google. -[Instructions on how to do +`var.ingress_style` can be: + +- `domain-mapping` (default): passing DNS domains as `var.dns_names` or `var.dns_name`, +which takes precedence over `var.dns_names`, will create domain mappings to the Cloud +Run service. Before setting this, you *must* have verified ownership of the provided +domains with Google. [Instructions on how to do this](https://guidebook.devops.uis.cam.ac.uk/en/latest/notes/google-domain-verification/) can be found in the DevOps division guidebook. +- `load-balancer`: a load balancer will be configured instead of a domain mapping. The +DNS domains in `var.dns_names` or `var.dns_name`, which takes precedence over `var.dns_names`, +will get Google-managed or custom TLS certificates depending on `var.use_ssl_certificates` +and `var.ssl_certificates`. An IPv6 address can also be allocated to the load balancer if +`var.enable_ipv6` is `true`. + ## Monitoring and Alerting If the variable [alerting_email_address](variables.tf) is set, the module adds diff --git a/load_balancer.tf b/load_balancer.tf new file mode 100644 index 0000000000000000000000000000000000000000..ac0fc3506688e8b48a22e7af39c42560f05048b6 --- /dev/null +++ b/load_balancer.tf @@ -0,0 +1,71 @@ +# load_balancer.tf configures Cloud Load Balancer resources for the Cloud Run +# service if var.ingress_style == "load-balancer". + +# A network endpoint group for the "webapp" application. +resource "google_compute_region_network_endpoint_group" "webapp" { + count = var.ingress_style == "load-balancer" ? 1 : 0 + + name = var.name + network_endpoint_type = "SERVERLESS" + region = var.cloud_run_region + cloud_run { + service = google_cloud_run_service.webapp.name + } + + provider = google-beta +} + +# A load balancer for the "webapp" application. This is just a set of sane +# defaults. See the full documentation at [1] for customisation. +# +# [1] https://registry.terraform.io/modules/GoogleCloudPlatform/lb-http/google/latest/submodules/serverless_negs +module "webapp_http_load_balancer" { + for_each = toset([for neg in google_compute_region_network_endpoint_group.webapp : neg.id]) + + # The double slash is important(!) + source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs" + version = "~> 5.0" + + project = var.project + name = var.name + + ssl = true + https_redirect = true + + # Use custom TLS certs if var.use_ssl_certificates is true, otherwise, use the Google-managed certs. + use_ssl_certificates = var.use_ssl_certificates + ssl_certificates = var.ssl_certificates + managed_ssl_certificate_domains = local.dns_names + + # Whether to create an IPv6 address to the load balancer. + enable_ipv6 = var.enable_ipv6 + create_ipv6_address = var.create_ipv6_address + + backends = { + default = { + description = null + enable_cdn = false + custom_request_headers = null + security_policy = null + + log_config = { + enable = true + sample_rate = 1.0 + } + + groups = [ + { + group = each.key + } + ] + + # Currently Cloud IAP is not supported for Cloud Run endpoints. We still + # need to specify that we don't want to use it though :). + iap_config = { + enable = false + oauth2_client_id = null + oauth2_client_secret = null + } + } + } +} diff --git a/locals.tf b/locals.tf index 8b5966323b050e90dddcc52d0fabed6f7da34e02..ce0b188ba911aa7a69fb843aec59e06d4b73be2b 100644 --- a/locals.tf +++ b/locals.tf @@ -5,7 +5,39 @@ locals { sql_instance_project = coalesce(var.sql_instance_project, var.project) # Should a DNS domain mapping be created? - domain_mapping_present = var.dns_name != "" + domain_mapping_present = anytrue([for dm in google_cloud_run_domain_mapping.webapp : true]) + + # DNS names for web app + dns_names = var.dns_name != "" ? [var.dns_name] : var.dns_names + + # DNS records for webapp. Merge records from any domain mappings or load balancers. + dns_records = flatten(concat( + [ + for domain_mapping in google_cloud_run_domain_mapping.webapp : [ + { + type = domain_mapping.status[0].resource_records[0].type + rrdata = domain_mapping.status[0].resource_records[0].rrdata + } + ] + ], + [ + for load_balancer in module.webapp_http_load_balancer : [ + { + type = "A" + rrdata = load_balancer.external_ip + }, + { + type = "AAAA" + rrdata = load_balancer.external_ipv6_address + } + ] + ] + )) + + # Certain ingress styles imply that we disallow external access to the base Cloud Run service. + webapp_allowed_ingress = lookup({ + load-balancer = "internal-and-cloud-load-balancing" + }, var.ingress_style, var.allowed_ingress) # Do we need to enable the 'beta' launch stage - only required if certain beta # functionality is being used, or if `enable_beta_launch_stage` is set downstream. @@ -13,14 +45,10 @@ locals { var.enable_beta_launch_stage || length(var.secrets_volume) != 0 || length(var.secrets_envars) != 0 ) - # Whether we should monitor the custom domain - only possible if there is a dns_name - # set and unauthenticated invocation is enabled - can_monitor_custom_dns = var.dns_name != "" && var.allow_unauthenticated_invocations - # Hosts to monitor. We use the automatic host from Cloud Run and any custom - # domain mapped host, if can_monitor_custom_dns is true + # domain mapped host. monitor_hosts = var.disable_monitoring ? [] : concat( [trimsuffix(trimprefix(google_cloud_run_service.webapp.status[0].url, "https://"), "/")], - local.can_monitor_custom_dns ? [var.dns_name] : [] + var.allow_unauthenticated_invocations ? local.dns_names : [], ) } diff --git a/main.tf b/main.tf index b8a3be531b2759d8a437d4d385962a4c2ca6ad6a..6c9246429d3e76f2e813932521bf5e4e375e74ed 100644 --- a/main.tf +++ b/main.tf @@ -51,7 +51,7 @@ resource "google_cloud_run_service" "webapp" { # Specify the allowable ingress types. { - "run.googleapis.com/ingress" : var.allowed_ingress, + "run.googleapis.com/ingress" : local.webapp_allowed_ingress, }, var.service_annotations, @@ -216,11 +216,11 @@ resource "google_cloud_run_service_iam_member" "webapp_all_users_invoker" { # the domain mapping if the domain is *not* verified because Google won't let # us. resource "google_cloud_run_domain_mapping" "webapp" { - count = local.domain_mapping_present ? 1 : 0 + for_each = toset(var.ingress_style == "domain-mapping" ? local.dns_names : []) location = var.cloud_run_region - name = var.dns_name + name = each.key metadata { # For managed Cloud Run, the namespace *must* be the project name. diff --git a/outputs.tf b/outputs.tf index 29ba066cca000da7d3830825c5d29bcd915baaa7..3564fca04b345c3bcabc3b533c363611b473451f 100644 --- a/outputs.tf +++ b/outputs.tf @@ -16,15 +16,22 @@ output "domain_mapping_present" { } output "domain_mapping_resource_record" { + value = try(local.dns_records[0], {}) description = <<EOI -Resource record for domain mapping. If a domain mapping is configured the -following keys will be set: type and rrdata. If no mapping is configured, the -map will be empty. -EOI - value = local.domain_mapping_present ? { - type = google_cloud_run_domain_mapping.webapp[0].status[0].resource_records[0].type - rrdata = google_cloud_run_domain_mapping.webapp[0].status[0].resource_records[0].rrdata - } : {} + Deprecated. Use dns_resource_records output instead. + + Resource record for DNS hostnames. If a domain mapping or load balancing is configured + the following keys will be set: type and rrdata. If no mapping is configured, the + map will be empty. + EOI +} + +output "dns_resource_records" { + value = local.dns_records + description = <<EOI + List of DNS records for web application. Each element is an object with "type" and "rrdata" + keys. + EOI } output "domain_mapping_dns_name" { diff --git a/variables.tf b/variables.tf index 88935bbafb912b3ee7578e0b892ece0540447814..239f6293e77446ac6f42780f8c2ac28088da643f 100644 --- a/variables.tf +++ b/variables.tf @@ -73,17 +73,80 @@ EOI default = true } +variable "ingress_style" { + type = string + default = "domain-mapping" + description = "Whether to configure a load balancer or create a domain mapping" + validation { + condition = contains(["domain-mapping", "load-balancer"], var.ingress_style) + error_message = "Ingress style must be one of 'domain-mapping' or 'load-balancer'." + } +} + variable "dns_name" { + default = "" description = <<EOI -If non-empty, a domain mapping will be created for the webapp from this domain -to point to the webapp. The domain must first have been verified by Google and -the account being used by the google provider must have been added as an owner. + Deprecated: use the dns_names variable instead. -If and only if a domain mapping has been created, the -"domain_mapping_resource_record" output will be a non-empty map and the -"domain_mapping_present" output will be true. -EOI - default = "" + If non-empty, var.dns_names will be ignored. + + If non-empty, a domain mapping will be created for the webapp from this host + to point to the webapp or a load balancer will be created for this host depending + on the value of the ingress_style variable. + + The domain must first have been verified by Google and the account being used by + the google provider must have been added as an owner. + + If and only if a domain mapping has been created, the + "domain_mapping_present" output will be true. + + If a domain mapping or load balancer has been created, the "dns_resource_records" + output contains the appropriate DNS records. + EOI +} + +variable "dns_names" { + type = list(any) + default = [] + description = <<EOI + List of DNS names for web application. Note that no records are created, + the records to be created can be found in the dns_resource_records output. + + Ignored if var.dns_name is non-empty. + EOI +} + +variable "use_ssl_certificates" { + type = bool + default = false + + description = <<EOI + Whether to use the custom TLS certs in var.ssl_certificates for the load balancer + or the Google-managed certs for the specified var.dns_names. + EOI +} + +variable "ssl_certificates" { + type = list(any) + default = [] + + description = <<EOI + A list of self-links to any custom TLS certificates to add to the load balancer. + Requires that var.ingress_style be "load-balancer". The self-link is available as + the "self_link" attribute of "google_compute_ssl_certificate" resources. + EOI +} + +variable "enable_ipv6" { + type = bool + default = false + description = "Whether to enable IPv6 address on the CDN load-balancer." +} + +variable "create_ipv6_address" { + type = bool + default = false + description = "Allocate an IPv6 address to the load balancer if var.enable_ipv6 is true." } variable "service_account_id" { @@ -153,6 +216,9 @@ variable "allowed_ingress" { Specify the allowed ingress to the service. Should be one of: "all", "internal" or "internal-and-cloud-load-balancing". + If var.ingress_style == "load-balancer", the provided var.allowed_ingress will be ignored + and the allowed ingress will be set automatically to "internal-and-cloud-load-balancing". + Setting this to a value other than "all" implies that the service will be moved to the "beta" launch stage. See https://cloud.google.com/run/docs/troubleshooting#launch-stage-validation. @@ -177,7 +243,6 @@ variable "template_annotations" { EOL } - variable "enable_beta_launch_stage" { default = false description = "Force use of the 'BETA' launch stage for the service."