Google Cloud forwarding rule http -> https using terraform - google-cloud-platform

I have setup forwarding rules, to map an URL onto my GCS Bucket using Terraform. Now, I am searching for a way to automatically forward all traffic from HTTP to HTTPS, so everybody reaching my page via HTTP automatically enters the secured page.
Any idea how I can do this using terraform? Below you can find all the code I used to set this up so far which is working perfectly fine. I just need this additional forwarding rule but don't know how to set this up. Any help would be highly appreciated.
locals {
static_bucket_name = "${var.environment}-${var.project_name}-static-pages"
domain_name = var.environment == "prd" ? "products.${project_name}.org" : "${var.environment}.products.${project_name}.org"
}
module "static-assets_cloud-storage-static-website" {
source = "gruntwork-io/static-assets/google//modules/cloud-storage-static-website"
version = "0.2.0"
website_domain_name = local.static_bucket_name
project = var.project_id
website_location = "EU"
force_destroy_access_logs_bucket = true
force_destroy_website = true
custom_labels = {
environment = var.environment
purpose = "static-site"
}
}
resource "google_compute_backend_bucket" "static_pages" {
name = local.static_bucket_name
description = "Contains static app assets"
bucket_name = module.static-assets_cloud-storage-static-website.website_bucket_name
enable_cdn = true
}
resource "google_compute_url_map" "static_pages" {
name = "${var.environment}-products"
default_service = google_compute_backend_bucket.static_pages.self_link
}
resource "google_compute_global_address" "static_pages" {
name = "${var.environment}-products-ip"
}
resource "google_compute_global_forwarding_rule" "http_to_static_pages" {
name = "${var.environment}-products-forward-rule"
target = google_compute_target_http_proxy.http_static_pages.self_link
ip_address = google_compute_global_address.static_pages.address
port_range = "80"
}
resource "google_compute_target_http_proxy" "http_static_pages" {
name = "${var.environment}-products-target-proxy"
url_map = google_compute_url_map.static_pages.self_link
}
resource "google_compute_target_https_proxy" "https_static_pages" {
project = var.project_id
name = "${var.environment}-products-target-proxy"
url_map = google_compute_url_map.static_pages.self_link
ssl_certificates = [google_compute_managed_ssl_certificate.static_pages.self_link]
}
resource "google_compute_global_forwarding_rule" "https_to_static_pages" {
name = "${var.environment}-products-https-forward-rule"
target = google_compute_target_https_proxy.https_static_pages.self_link
ip_address = google_compute_global_address.static_pages.address
port_range = "443"
}
resource "google_compute_managed_ssl_certificate" "static_pages" {
provider = google-beta
project = var.project_id
name = "${var.environment}-products-certificate"
managed {
domains = [local.domain_name]
}
}
```

Google supports this nicely with (only) three extra Terraform resources that create a second load balancer without backend but with a forwarding rule that just redirects to https.
The following is the (working) translation of their documentation:
resource "google_compute_url_map" "http-redirect" {
name = "http-redirect"
default_url_redirect {
redirect_response_code = "MOVED_PERMANENTLY_DEFAULT" // 301 redirect
strip_query = false
https_redirect = true // this is the magic
}
}
resource "google_compute_target_http_proxy" "http-redirect" {
name = "http-redirect"
url_map = google_compute_url_map.http-redirect.self_link
}
resource "google_compute_global_forwarding_rule" "http-redirect" {
name = "http-redirect"
target = google_compute_target_http_proxy.http-redirect.self_link
ip_address = google_compute_global_address.static_pages.address
port_range = "80"
}

I a little bit changed the Terraform code of Rutger de Knijf's answer above.
I removed "redirect_response_code" then the result is the same:
resource "google_compute_url_map" "http-redirect" {
name = "http-redirect"
default_url_redirect {
// "redirect_response_code" is removed
// redirect_response_code = "MOVED_PERMANENTLY_DEFAULT" // 301 redirect
strip_query = false
https_redirect = true // this is the magic
}
}
resource "google_compute_target_http_proxy" "http-redirect" {
name = "http-redirect"
url_map = google_compute_url_map.http-redirect.self_link
}
resource "google_compute_global_forwarding_rule" "http-redirect" {
name = "http-redirect"
target = google_compute_target_http_proxy.http-redirect.self_link
ip_address = google_compute_global_address.static_pages.address
port_range = "80"
}

Related

wafv2 firewall rules to restrict domain names

I created WAFV2 firewall rules to restrict few IP address to hit API gateways. it is working as expected.
# ipsets
resource "aws_wafv2_ip_set" "example-ip-sets" {
name = "ipsetsexample"
description = "Example IP set"
scope = "REGIONAL"
ip_address_version = "IPV4"
addresses = ["137.137.43.154/32", "40.121.120.150/32"]
tags = {
Tag1 = "Value1"
Tag2 = "Value2"
}
}
resource "aws_wafv2_web_acl" "firewall" {
name = "firewall"
scope = "REGIONAL"
default_action {
block {}
}
rule {
name = "ip-blacklist"
priority = 1
action {
allow {}
}
statement {
ip_set_reference_statement {
arn = aws_wafv2_ip_set.example-ip-sets.arn
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlacklistedIP"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "Allowed"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_association" "this" {
resource_arn = aws_api_gateway_stage.api_stage.arn
web_acl_arn = aws_wafv2_web_acl.firewall.arn
}
but now new scenario came in to picture like, block some domain names instead of IP's (abcde.com, us1a.bcde.com) to invoke the API gateways. I have searched many terraform docs but didn't find any related.
is there any docs or source to restrict domain names to hit the API gateway ?

Why does my user_pool_domain always need to be replaced?

I get the following whenever I run terraform plan/apply and I don't know why this is saying it always needs to be replaced. The ACM is managed at the root of my project, and then the ARN is passed to my Cognito module.
# module.cognito["users"].aws_cognito_user_pool_domain.main must be replaced
+/- resource "aws_cognito_user_pool_domain" "main" {
~ certificate_arn = "arn:aws:acm:us-east-1:123456789:certificate/bc955b8a-45c6-4003-1b2a-5z66333fef275" -> (known after apply) # forces replacement
}
Update - add module call and DNS file for clarity
cognito.tf (root of the project)
module "cognito" {
source = "../modules/cognito"
for_each = var.cognito_userpools
cognito_name_prefix = "${try(each.value.name_prefix, local.name_prefix)}-${each.key}"
cognito_domain_name = try("${each.value.domain_prefix}.${local.dns_address}", null)
cognito_https_acm_arn = try(aws_acm_certificate.cognito_https_cert[each.key].arn, null)
hosted_zone_id = try(aws_route53_zone.public_hosted_zone.id, null)
cognito_callback_urls = each.value.callback_urls
cognito_logout_urls = each.value.logout_urls
cognito_sms_external_id = each.value.sms_external_id
cognito_userpool_schemas = each.value.userpool_schemas
cognito_mfa_configuration = try(each.value.mfa_configuration, "ON")
cognito_enable_software_token_mfa_configuration = try(each.value.enable_software_token_mfa_configuration, true)
cognito_userpool_groups = try(each.value.groups, [])
tags = local.default_tags
}
acm.tf (root of the project)
resource "aws_acm_certificate" "cognito_https_cert" {
for_each = var.cognito_userpools
provider = aws.us-east-1
domain_name = "${each.value.domain_prefix}.${local.dns_address}"
subject_alternative_names = ["*.${each.value.domain_prefix}.${local.dns_address}"]
validation_method = "DNS"
tags = local.default_tags
lifecycle {
create_before_destroy = true
}
}
modules/cognito/dns.tf
resource "aws_cognito_user_pool_domain" "main" {
domain = var.cognito_domain_name
certificate_arn = var.cognito_https_acm_arn
user_pool_id = aws_cognito_user_pool.pool.id
}
resource "aws_route53_record" "cognito_record" {
name = aws_cognito_user_pool_domain.main.domain
type = "A"
zone_id = var.hosted_zone_id
allow_overwrite = true
alias {
evaluate_target_health = false
name = aws_cognito_user_pool_domain.main.cloudfront_distribution_arn
# NOTE: This zone_id is fixed
zone_id = "Z2FDTNDATAQYW2"
}
}
Update 2
Upon further investigation tag updates were causing the arn not to be known until after apply, this isn’t ideal and only a problem with cognito custom domains - I don’t have the same issue with API Gateway custom domains for example. Does anyone have any idea on a workaround rather than relying on ignoring tag updates as a lifecycle meta argument?

What are all the things that I need to connect an existing known-good API gateway endpoint to a Route53 subdomain with Terraform?

Here's the code I have so far, hopefully I got everything relevant. The API gateway is deployed and working and has been for a while now. Our app is currently pointing at the xxxyyyzz12.execute-api.us-west-2.amazonaws.com endpoint and working fine. But I need to route it to the subdomain ui-backend.app-name-here-dev.company.services.
data "aws_acm_certificate" "app_name_dev_wildcard_cert" {
domain = "*.app-name-here-dev.company.services"
statuses = ["ISSUED"]
}
// pull in the existing zone (defined by devops) via a data block
data "aws_route53_zone" "myapp_zone" {
name = local.domain
}
resource "aws_route53_record" "ui_backend" {
name = aws_apigatewayv2_domain_name.ui_backend_api_gateway.domain_name
type = "A"
zone_id = data.aws_route53_zone.myapp_zone.zone_id
alias {
name = aws_apigatewayv2_domain_name.ui_backend_api_gateway.domain_name_configuration[0].target_domain_name
zone_id = aws_apigatewayv2_domain_name.ui_backend_api_gateway.domain_name_configuration[0].hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_apigatewayv2_domain_name" "ui_backend_api_gateway" {
domain_name = "${local.subdomain}.${local.domain}"
domain_name_configuration {
certificate_arn = data.aws_acm_certificate.app_name_dev_wildcard_cert.arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
}
locals {
// trimmed
domain = "app-name-here${var.envToZoneName[var.environment]}.company.services"
subdomain = var.deploymentNameModifier == "" ? "ui-backend" : "ui-backend-${var.deploymentNameModifier}"
}
But when I try to use the curl (the one that works for xxxyyyzz12.execute-api.us-west-2.amazonaws.com) I'm getting a 403. I added a x-apigw-api-id: 153utdsv9h header but it didn't help. I must be missing a resource.
Well, 16 hrs have gone by with no answers/comments. Here's the thing that was missing:
resource "aws_apigatewayv2_api_mapping" "ui_backend_to_subdomain" {
api_id = aws_apigatewayv2_api.ui_backend_gateway.id
domain_name = aws_apigatewayv2_domain_name.ui_backend_api_gateway.domain_name
stage = aws_apigatewayv2_stage.ui_backend.id
}

Terraform GCP groups: I am using FQDNs as in the DNS, but still get "The URL is malformed". What kind of URL is it looking for?

I'm using Terraform with GCP ... I have the groups variable that I have not been able to get to work. Here's the definitions:
resource "google_compute_instance_group" "vm_group" {
name = "vm-group"
zone = "us-central1-c"
project = "myproject-dev"
instances = [google_compute_instance.east_vm.id, google_compute_instance.west_vm.id]
named_port {
name = "http"
port = "8080"
}
named_port {
name = "https"
port = "8443"
}
lifecycle {
create_before_destroy = true
}
}
data "google_compute_image" "debian_image" {
family = "debian-9"
project = "debian-cloud"
}
resource "google_compute_instance" "west_vm" {
name = "west-vm"
project = "myproject-dev"
machine_type = "e2-micro"
zone = "us-central1-c"
boot_disk {
initialize_params {
image = data.google_compute_image.debian_image.self_link
}
}
network_interface {
network = "default"
}
}
resource "google_compute_instance" "east_vm" {
name = "east-vm"
project = "myproject-dev"
machine_type = "e2-micro"
zone = "us-central1-c"
boot_disk {
initialize_params {
image = data.google_compute_image.debian_image.self_link
}
}
network_interface {
network = "default"
}
}
And here are the variables:
http_forward = true
https_redirect = true
create_address = true
project = "myproject-dev"
backends = {
"yobaby" = {
description = "my app"
enable_cdn = false
security_policy = ""
custom_request_headers = null
custom_response_headers = null
iap_config = {
enable = false
oauth2_client_id = ""
oauth2_client_secret = ""
}
log_config = {
enable = false
sample_rate = 0
}
groups = [{group = "google_compute_instance_group.vm_group.id"}]
}
}
... this is my latest attempt to get a group value that works, but this one won't work for me either; I still get
Error 400: Invalid value for field 'resource.backends[0].group': 'google_compute_instance_group.vm_group.id'. The URL is malformed., invalid
I've tried this with DNS FQDNs and variations on the syntax above; still no go.
Thanks much for any advice whatsoever!
There are couple clues that can lead in this direction based from the error message reported by Terraform Error 400: Invalid value for field 'resource.backends[0].group': 'google_compute_instance_group.vm_group.id'. The URL is malformed., invalid:
Error code 400 means the request was actually sent to the server, who rejected it as malformed (HTTP error code 400 is for client-side errors); this implies that Terraform itself has no problem with the syntax, i.e., the configuration file is correct and actionable from TF's PoV
Value of field resource.backends[0].group is reported as being literally 'google_compute_instance_group.vm_group.id' which strongly suggests that a variable substitution did not take place.
The quotes around the code block makes it into a literal value instead of a variable reference. The solution is to change this:
groups = [{group = "google_compute_instance_group.vm_group.id"}]
To this:
groups = [{group = google_compute_instance_group.vm_group.id}]
I gave up on Terraform and used gcloud scripts to do what I needed to do, based on this posting.

Terraformed AWS API Gateway Custom Domain Names throws 403 Forbidden

I am trying to expose all the stages of my Regional API Gateway through a regional Custom Domain.
Problem
If I curl directly my API Gateway (ie. https://xx.execute-api.eu-west-3.amazonaws.com/default/users), it works, but I get a 403 if I curl de domain name (ie. https://api.acme.com/default/users).
Configuration
My Terraform files looks like that:
data "aws_route53_zone" "acme" {
name = "acme.com."
}
resource "aws_api_gateway_rest_api" "backend" {
name = "acme-backend-api"
description = "Backend API"
body = "SOMETHING"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_deployment" "backend" {
rest_api_id = aws_api_gateway_rest_api.backend.id
stage_name = "default"
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_domain_name" "backend" {
domain_name = "api.acme.com"
regional_certificate_arn = "arn:aws:acm:xx:certificate/xx"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_route53_record" "backend" {
name = aws_api_gateway_domain_name.backend.domain_name
type = "A"
zone_id = data.aws_route53_zone.acme.id
alias {
evaluate_target_health = true
name = aws_api_gateway_domain_name.backend.regional_domain_name
zone_id = aws_api_gateway_domain_name.backend.regional_zone_id
}
}
resource "aws_api_gateway_base_path_mapping" "backend" {
api_id = aws_api_gateway_rest_api.backend.id
domain_name = aws_api_gateway_domain_name.backend.domain_name
# No stage_name: expose all stages
}
According to the Terraform api_gateway_domain_name and api_gateway_base_path_mapping examples, it should be ok.
I have also followed many howtos, and I have these elements:
The certificate
The A record to the API custom domain
The mapping to the deployed stage (which works if you call it directly)
What do I miss/do wrong?
This is v2 example working for me as off today, this "aws_apigatewayv2_api_mapping" is key to avoid port 80: Connection refused
or {"message":"Forbidden"} errors which I see you have but I did struggle with.
// ACM
resource "aws_acm_certificate" "cert_api" {
domain_name = var.api_domain
validation_method = "DNS"
tags = {
Name = var.api_domain
}
}
resource "aws_acm_certificate_validation" "cert_api" {
certificate_arn = aws_acm_certificate.cert_api.arn
}
// API Gateway V2
resource "aws_apigatewayv2_api" "lambda" {
name = "serverless_lambda_gw"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_stage" "lambda" {
api_id = aws_apigatewayv2_api.lambda.id
name = "serverless_lambda_stage"
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gw.arn
format = jsonencode({
requestId = "$context.requestId"
sourceIp = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
protocol = "$context.protocol"
httpMethod = "$context.httpMethod"
resourcePath = "$context.resourcePath"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
}
)
}
}
resource "aws_apigatewayv2_integration" "testimonials" {
api_id = aws_apigatewayv2_api.lambda.id
integration_uri = aws_lambda_function.testimonials.invoke_arn
integration_type = "AWS_PROXY"
integration_method = "POST"
}
resource "aws_apigatewayv2_route" "testimonials" {
api_id = aws_apigatewayv2_api.lambda.id
route_key = "GET /testimonials"
target = "integrations/${aws_apigatewayv2_integration.testimonials.id}"
}
resource "aws_cloudwatch_log_group" "api_gw" {
name = "/aws/api_gw/${aws_apigatewayv2_api.lambda.name}"
retention_in_days = 30
}
resource "aws_lambda_permission" "api_gw" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.testimonials.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.lambda.execution_arn}/*/*"
}
resource "aws_apigatewayv2_domain_name" "api" {
domain_name = var.api_domain
domain_name_configuration {
certificate_arn = aws_acm_certificate.cert_api.arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
}
resource "aws_apigatewayv2_api_mapping" "api" {
api_id = aws_apigatewayv2_api.lambda.id
domain_name = aws_apigatewayv2_domain_name.api.id
stage = aws_apigatewayv2_stage.lambda.id
}
// Route53
resource "aws_route53_zone" "api" {
name = var.api_domain
}
resource "aws_route53_record" "cert_api_validations" {
allow_overwrite = true
count = length(aws_acm_certificate.cert_api.domain_validation_options)
zone_id = aws_route53_zone.api.zone_id
name = element(aws_acm_certificate.cert_api.domain_validation_options.*.resource_record_name, count.index)
type = element(aws_acm_certificate.cert_api.domain_validation_options.*.resource_record_type, count.index)
records = [element(aws_acm_certificate.cert_api.domain_validation_options.*.resource_record_value, count.index)]
ttl = 60
}
resource "aws_route53_record" "api-a" {
name = aws_apigatewayv2_domain_name.api.domain_name
type = "A"
zone_id = aws_route53_zone.api.zone_id
alias {
name = aws_apigatewayv2_domain_name.api.domain_name_configuration[0].target_domain_name
zone_id = aws_apigatewayv2_domain_name.api.domain_name_configuration[0].hosted_zone_id
evaluate_target_health = false
}
}