From b5de55d4a60315a7a23c9722ffa2c2b322c7c93a Mon Sep 17 00:00:00 2001 From: Conan Scott Date: Thu, 2 Jul 2026 11:04:19 +1000 Subject: [PATCH] feat(net): custom domain for the Fusion-facing API Fusion couldn't resolve the default *.execute-api.amazonaws.com endpoint, so front the same HTTP API with hncb-deid.apim-apac-demo.com (ACM DNS-validated cert, apigatewayv2 REGIONAL custom domain, root api mapping, Route53 A ALIAS). Callers resolve our hostname straight to IPs and never touch an execute-api name; also survives API-id churn. Point POLICY_SETUP + README demo at the custom URLs. Verified live: resolves to IPs, 401 unauthenticated, tokenizes with the key. Co-Authored-By: Claude Opus 4.8 --- README.md | 3 +- fusion/POLICY_SETUP.md | 12 ++++-- terraform/custom_domain.tf | 80 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 terraform/custom_domain.tf diff --git a/README.md b/README.md index 21ea78c..ff782bb 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ in `CLAUDE.md`, which record the exact sequencing and gotchas): ## Demo script (Fusion-less dry run — maps to the 8 steps) ```bash -cd terraform && DEMO=$(terraform output -raw demo_url) && UI=$(terraform output -raw ui_url); cd .. +cd terraform && DEMO=$(terraform output -raw demo_url_custom) && UI=$(terraform output -raw ui_url); cd .. +# (demo_url_custom uses the Route53 custom domain Fusion can resolve; demo_url is the raw execute-api one) # 1-2, 4-8: advisor query -> tokenize -> agent (on tokens) -> restore, in one call: curl -s -X POST "$DEMO" -H 'content-type: application/json' \ diff --git a/fusion/POLICY_SETUP.md b/fusion/POLICY_SETUP.md index 14e1763..5604d59 100644 --- a/fusion/POLICY_SETUP.md +++ b/fusion/POLICY_SETUP.md @@ -35,14 +35,20 @@ advisor ◀────┘ The transforms run **inside the policy boundary**; the raw name never crosses it. ## Backend the policy calls -- `TOKENIZE_URL` = `https://.execute-api..amazonaws.com/tokenize` - — `terraform -chdir=terraform output -raw tokenize_url` -- `RESTORE_URL` = `https://<...>/restore` — `terraform ... output -raw restore_url` +Use the **custom domain** — Fusion could not resolve the default +`*.execute-api.ap-southeast-1.amazonaws.com` endpoint, so the API is fronted by a +Route53-aliased name that resolves straight to IPs (see `terraform/custom_domain.tf`). +- `TOKENIZE_URL` = `https://hncb-deid.apim-apac-demo.com/tokenize` + — `terraform -chdir=terraform output -raw tokenize_url_custom` +- `RESTORE_URL` = `https://hncb-deid.apim-apac-demo.com/restore` + — `terraform -chdir=terraform output -raw restore_url_custom` - `AGENT_RUNTIME_ARN` (the route's upstream) = printed by `scripts/agentcore_setup.sh` - **Auth:** public API Gateway HTTP API; send the shared secret in the **`x-api-key`** header (`Authorization: Bearer ` also works). The secret is the Terraform `tokenize_api_key` var (gitignored `terraform/local.auto.tfvars`, never committed) — hand it to the console operator out of band. + - *Note:* the raw `…execute-api…` URLs (`output -raw tokenize_url` / `restore_url`) + still work for anything that can resolve them; Fusion should use the custom domain. - *Note:* a Lambda Function URL was the first choice, but this account's SCP blocks unauthenticated Function URLs, so the public front door is API Gateway. diff --git a/terraform/custom_domain.tf b/terraform/custom_domain.tf new file mode 100644 index 0000000..0b96716 --- /dev/null +++ b/terraform/custom_domain.tf @@ -0,0 +1,80 @@ +# --------------------------------------------------------------------------- +# Custom domain for the Fusion-facing API. Fusion couldn't resolve the default +# *.execute-api.amazonaws.com hostname, so we front the same HTTP API with a +# stable name in a zone we control and point it with a Route53 ALIAS (A record). +# Callers resolve hncb-deid.apim-apac-demo.com -> IPs directly; they never have to +# resolve an execute-api / amazonaws.com name. Also survives API-id churn. +# --------------------------------------------------------------------------- + +locals { + api_fqdn = "hncb-deid.apim-apac-demo.com" +} + +data "aws_route53_zone" "api" { + name = "apim-apac-demo.com" + private_zone = false +} + +# Regional API Gateway custom domains need the ACM cert in the SAME region. +resource "aws_acm_certificate" "api" { + domain_name = local.api_fqdn + validation_method = "DNS" + tags = local.onprem_tag + lifecycle { + create_before_destroy = true + } +} + +resource "aws_route53_record" "api_cert_validation" { + for_each = { + for dvo in aws_acm_certificate.api.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + type = dvo.resource_record_type + record = dvo.resource_record_value + } + } + zone_id = data.aws_route53_zone.api.zone_id + name = each.value.name + type = each.value.type + records = [each.value.record] + ttl = 60 + allow_overwrite = true +} + +resource "aws_acm_certificate_validation" "api" { + certificate_arn = aws_acm_certificate.api.arn + validation_record_fqdns = [for r in aws_route53_record.api_cert_validation : r.fqdn] +} + +resource "aws_apigatewayv2_domain_name" "api" { + domain_name = local.api_fqdn + domain_name_configuration { + certificate_arn = aws_acm_certificate_validation.api.certificate_arn + endpoint_type = "REGIONAL" + security_policy = "TLS_1_2" + } + tags = local.onprem_tag +} + +# Map the custom domain at the root to the $default stage (routes: /tokenize, etc.) +resource "aws_apigatewayv2_api_mapping" "api" { + api_id = aws_apigatewayv2_api.gateway.id + domain_name = aws_apigatewayv2_domain_name.api.id + stage = aws_apigatewayv2_stage.default.id +} + +# ALIAS (IPv4) -> returns IPs directly, so Fusion never resolves an AWS hostname. +resource "aws_route53_record" "api_alias" { + zone_id = data.aws_route53_zone.api.zone_id + name = local.api_fqdn + type = "A" + 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 + } +} + +output "tokenize_url_custom" { value = "https://${local.api_fqdn}/tokenize" } +output "restore_url_custom" { value = "https://${local.api_fqdn}/restore" } +output "demo_url_custom" { value = "https://${local.api_fqdn}/demo" }