-
Notifications
You must be signed in to change notification settings - Fork 0
Managed domains #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
siddhsuresh
wants to merge
26
commits into
main
Choose a base branch
from
managed-domains
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
30a2f22
feat(ecs_cluster): Ravion-managed wildcard domain (opt-in)
siddhsuresh fb4da3b
feat(ecs_service): Ravion-managed domains (auto-FQDN + custom certs)
siddhsuresh 67660a5
feat(static_site): Ravion-managed domains (CloudFront, us-east-1)
siddhsuresh cddbf96
feat(ecs): Ravion-managed domains support private ALB, not just public
siddhsuresh 10b1227
use my proxy
siddhsuresh e1f338a
fix(ecs_cluster): name the Ravion wildcard from the module instance g…
siddhsuresh 74d0889
fix(ecs_service): auto-FQDN leaf from module instance given id
siddhsuresh 0fa3e66
feat(ecs_cluster): point wildcard cert at the cluster ALB
siddhsuresh a695922
fix(ecs_service): use name_prefix for TG so attribute changes don't c…
siddhsuresh 291cac2
feat(static_site): add routing="raw" for object sites
siddhsuresh 2f78c63
feat: pull ravion provider from CloudFront-hosted registry
siddhsuresh 638ab14
revert: keep ravion provider source on providers.siddharthsuresh.dev
siddhsuresh 3a4f1f7
Revert "revert: keep ravion provider source on providers.siddharthsur…
siddhsuresh 21cf2d5
Revert "feat(static_site): add routing="raw" for object sites"
siddhsuresh c315c8f
feat(domains): unify cluster ALB HTTPS listener + surface managed-dom…
siddhsuresh 64eb79e
feat(domains): collapse ecs_service Mode A/B into per-entry apex clas…
siddhsuresh ce3eac1
fix(ecs_service): normalize domain entries before apex classification
siddhsuresh 1a4577f
feat(ecs_service): reject in-apex non-single-label domains at plan time
siddhsuresh 9c97fcd
fix(managed-domains): review findings B3/M13/M14/#39/#40
siddhsuresh 6591dd8
fix(ecs_cluster): create_before_destroy on cluster wildcard cert for …
siddhsuresh bbe5d8c
feat(ecs_cluster): reject duplicate wildcard apex via ravion_dns_coll…
siddhsuresh 8e85aee
feat(ecs_service): plan-time parent-apex authorization + require prov…
siddhsuresh ac14c22
feat(ecs_cluster): surface wildcard-apex dependents via ravion_apex_d…
siddhsuresh 3a3cdc5
docs(ecs_service): parent-apex guard wording reflects reference-based…
siddhsuresh b1a5779
feat(ecs): pass module_instance_id to Ravion certs/domains
siddhsuresh 7fffc23
naming fixes
siddhsuresh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| ################################################################################ | ||
| # Cluster ALB HTTPS listeners | ||
| ################################################################################ | ||
| # ecs_cluster ALWAYS owns the cluster ALB HTTPS listener(s) (the alb submodule | ||
| # never creates them — see load_balancers.tf) so that toggling | ||
| # var.use_ravion_managed_domains is an IN-PLACE certificate swap on a stable TF | ||
| # address rather than a destroy+create across two addresses. Only the default | ||
| # certificate SOURCE changes by mode: | ||
| # | ||
| # - use_ravion_managed_domains = true -> the Ravion wildcard cert | ||
| # (ravion_aws_acm_certificate.cluster, see ravion_domains.tf) is the default cert on | ||
| # BOTH listeners; public/private services nest their auto-FQDNs under it. | ||
| # - use_ravion_managed_domains = false -> the listener uses the customer's | ||
| # first public/private_alb_certificate_arns entry as default and attaches | ||
| # the rest for SNI. | ||
| # | ||
| # The listeners live here (not in the alb submodule) to avoid a DAG cycle: | ||
| # aws_lb.this -> ravion_aws_acm_certificate.cluster -> aws_lb_listener.public_https | ||
| # (uses the cert). ravion_aws_acm_certificate with role=shared_wildcard blocks until | ||
| # ISSUED, so cert_arn is valid at listener create time. | ||
|
|
||
| # Public ALB HTTPS listener. Mode-independent address: created whenever the | ||
| # public ALB has HTTPS enabled. | ||
| resource "aws_lb_listener" "public_https" { | ||
| count = var.enable_public_alb && var.public_alb_enable_https ? 1 : 0 | ||
|
|
||
| load_balancer_arn = module.public_alb[0].alb_arn | ||
| port = 443 | ||
| protocol = "HTTPS" | ||
| ssl_policy = var.public_alb_ssl_policy | ||
| # try(...) defers to the precondition below for the clean error when BYO mode | ||
| # has no cert ARN, instead of a cryptic index-out-of-range. | ||
| certificate_arn = local.enable_ravion_domain ? ravion_aws_acm_certificate.cluster[0].arn : try(var.public_alb_certificate_arns[0], null) | ||
|
|
||
| default_action { | ||
| type = "fixed-response" | ||
| fixed_response { | ||
| content_type = "text/plain" | ||
| message_body = "Not found" | ||
| status_code = "404" | ||
| } | ||
| } | ||
|
|
||
| lifecycle { | ||
| precondition { | ||
| condition = local.enable_ravion_domain || length(var.public_alb_certificate_arns) >= 1 | ||
| error_message = "public_alb_certificate_arns must include at least one ACM certificate ARN when public_alb_enable_https = true and use_ravion_managed_domains = false." | ||
| } | ||
| } | ||
|
|
||
| tags = merge(local.tags, { Name = "${var.name}-pub-https" }) | ||
| } | ||
|
|
||
| # Customer SNI certs for the public listener (BYO mode only; the Ravion wildcard | ||
| # needs no extra SNI certs). Gated on the listener existing so the slice is | ||
| # never evaluated when HTTPS / the ALB is off (mirrors the alb submodule's | ||
| # `additional` idiom and avoids slice([], 1, 0) on the default config). | ||
| resource "aws_lb_listener_certificate" "public_sni" { | ||
| # length > 1 keeps slice() self-safe (never slice([], 1, 0)) independent of the | ||
| # listener precondition: only the 2nd+ ARNs become SNI certs. | ||
| for_each = (var.enable_public_alb && var.public_alb_enable_https && !local.enable_ravion_domain && length(var.public_alb_certificate_arns) > 1) ? toset(slice(var.public_alb_certificate_arns, 1, length(var.public_alb_certificate_arns))) : toset([]) | ||
|
|
||
| listener_arn = aws_lb_listener.public_https[0].arn | ||
| certificate_arn = each.value | ||
| } | ||
|
|
||
| # Private ALB HTTPS listener (same Ravion wildcard cert as the public one in | ||
| # managed mode; the customer's first private cert ARN otherwise). | ||
| resource "aws_lb_listener" "private_https" { | ||
| count = var.enable_private_alb && var.private_alb_enable_https ? 1 : 0 | ||
|
|
||
| load_balancer_arn = module.private_alb[0].alb_arn | ||
| port = 443 | ||
| protocol = "HTTPS" | ||
| ssl_policy = var.private_alb_ssl_policy | ||
| certificate_arn = local.enable_ravion_domain ? ravion_aws_acm_certificate.cluster[0].arn : try(var.private_alb_certificate_arns[0], null) | ||
|
|
||
| default_action { | ||
| type = "fixed-response" | ||
| fixed_response { | ||
| content_type = "text/plain" | ||
| message_body = "Not found" | ||
| status_code = "404" | ||
| } | ||
| } | ||
|
|
||
| lifecycle { | ||
| precondition { | ||
| condition = local.enable_ravion_domain || length(var.private_alb_certificate_arns) >= 1 | ||
| error_message = "private_alb_certificate_arns must include at least one ACM certificate ARN when private_alb_enable_https = true and use_ravion_managed_domains = false." | ||
| } | ||
| } | ||
|
|
||
| tags = merge(local.tags, { Name = "${var.name}-priv-https" }) | ||
| } | ||
|
|
||
| # Customer SNI certs for the private listener (BYO mode only). | ||
| resource "aws_lb_listener_certificate" "private_sni" { | ||
| for_each = (var.enable_private_alb && var.private_alb_enable_https && !local.enable_ravion_domain && length(var.private_alb_certificate_arns) > 1) ? toset(slice(var.private_alb_certificate_arns, 1, length(var.private_alb_certificate_arns))) : toset([]) | ||
|
|
||
| listener_arn = aws_lb_listener.private_https[0].arn | ||
| certificate_arn = each.value | ||
| } | ||
|
|
||
| ################################################################################ | ||
| # 443 ingress | ||
| ################################################################################ | ||
| # The alb submodule only opens 443 when it owns the HTTPS listener; it no longer | ||
| # does, so ecs_cluster opens 443 here in BOTH modes (mirrors the submodule's | ||
| # rules). Mode-independent so toggling use_ravion_managed_domains never churns | ||
| # the SG rules. | ||
| resource "aws_vpc_security_group_ingress_rule" "public_https_ipv4" { | ||
| for_each = var.enable_public_alb && var.public_alb_enable_https ? toset(var.public_alb_ingress_cidr_blocks) : toset([]) | ||
|
|
||
| security_group_id = module.public_alb[0].security_group_id | ||
| description = "Allow HTTPS from ${each.value}" | ||
| cidr_ipv4 = each.value | ||
| from_port = 443 | ||
| to_port = 443 | ||
| ip_protocol = "tcp" | ||
| tags = local.tags | ||
| } | ||
|
|
||
| resource "aws_vpc_security_group_ingress_rule" "public_https_ipv6" { | ||
| for_each = var.enable_public_alb && var.public_alb_enable_https ? toset(["::/0"]) : toset([]) | ||
|
|
||
| security_group_id = module.public_alb[0].security_group_id | ||
| description = "Allow HTTPS from ${each.value}" | ||
| cidr_ipv6 = each.value | ||
| from_port = 443 | ||
| to_port = 443 | ||
| ip_protocol = "tcp" | ||
| tags = local.tags | ||
| } | ||
|
|
||
| # Private ALB 443 ingress (mirrors the public rules for the private listener). | ||
| resource "aws_vpc_security_group_ingress_rule" "private_https_ipv4" { | ||
| for_each = var.enable_private_alb && var.private_alb_enable_https ? toset(var.private_alb_ingress_cidr_blocks) : toset([]) | ||
|
|
||
| security_group_id = module.private_alb[0].security_group_id | ||
| description = "Allow HTTPS from ${each.value}" | ||
| cidr_ipv4 = each.value | ||
| from_port = 443 | ||
| to_port = 443 | ||
| ip_protocol = "tcp" | ||
| tags = local.tags | ||
| } | ||
|
|
||
| resource "aws_vpc_security_group_ingress_rule" "private_https_ipv6" { | ||
| for_each = var.enable_private_alb && var.private_alb_enable_https ? toset(["::/0"]) : toset([]) | ||
|
|
||
| security_group_id = module.private_alb[0].security_group_id | ||
| description = "Allow HTTPS from ${each.value}" | ||
| cidr_ipv6 = each.value | ||
| from_port = 443 | ||
| to_port = 443 | ||
| ip_protocol = "tcp" | ||
| tags = local.tags | ||
| } | ||
|
|
||
| ################################################################################ | ||
| # State moves | ||
| ################################################################################ | ||
| # Renames within ecs_cluster (clusters already in Ravion mode keep their state): | ||
| moved { | ||
| from = aws_lb_listener.ravion_https | ||
| to = aws_lb_listener.public_https | ||
| } | ||
|
|
||
| moved { | ||
| from = aws_lb_listener.ravion_https_private | ||
| to = aws_lb_listener.private_https | ||
| } | ||
|
|
||
| moved { | ||
| from = aws_vpc_security_group_ingress_rule.ravion_https_ipv4 | ||
| to = aws_vpc_security_group_ingress_rule.public_https_ipv4 | ||
| } | ||
|
|
||
| moved { | ||
| from = aws_vpc_security_group_ingress_rule.ravion_https_ipv6 | ||
| to = aws_vpc_security_group_ingress_rule.public_https_ipv6 | ||
| } | ||
|
|
||
| moved { | ||
| from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv4 | ||
| to = aws_vpc_security_group_ingress_rule.private_https_ipv4 | ||
| } | ||
|
|
||
| # BYO clusters with existing state had their HTTPS listener inside the alb | ||
| # submodule; refactoring it out to the root is expressed with a cross-module | ||
| # moved block (supported "refactor out of a module" pattern). The submodule's | ||
| # 443 SG ingress rules came from a for_each in the security-groups module and | ||
| # cannot be moved this way — those are a one-time destroy+create on the BYO | ||
| # migration (acceptable: nothing is in prod yet). | ||
| moved { | ||
| from = module.public_alb[0].aws_lb_listener.https[0] | ||
| to = aws_lb_listener.public_https[0] | ||
| } | ||
|
|
||
| moved { | ||
| from = module.private_alb[0].aws_lb_listener.https[0] | ||
| to = aws_lb_listener.private_https[0] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| ################################################################################ | ||
| # Ravion-managed cluster domain (opt-in) | ||
| ################################################################################ | ||
| # When var.use_ravion_managed_domains = true, Ravion issues ONE wildcard cert | ||
| # `*.<name>-<hash>.<ravion-apex>` (+ apex). That cert becomes the default cert | ||
| # on the cluster ALB HTTPS listener(s) (see listeners.tf) — a single ACM cert | ||
| # can default both the public and the private listener, so public AND private | ||
| # services nest their domains under the one wildcard. The cert also publishes a | ||
| # `*.<apex>` ALIAS to the cluster ALB so service auto-FQDNs (<svc>.<apex>) | ||
| # resolve. When the flag is off, this resource is absent and the listeners fall | ||
| # back to the customer-supplied certificate ARNs. | ||
|
|
||
| locals { | ||
| enable_ravion_domain = var.use_ravion_managed_domains && (var.enable_public_alb || var.enable_private_alb) | ||
| } | ||
|
|
||
| # Plan-time guard against two clusters claiming the same wildcard apex. The | ||
| # backend resolves the bare name leaf to the managed wildcard (*.<name>.<apex>) | ||
| # and reports collides=true ONLY when a DIFFERENT module instance already owns | ||
| # that domain — a re-apply of THIS cluster does not collide with itself. The | ||
| # allocator enforces the same rule server-side as an apply-time backstop. | ||
| data "ravion_dns_collision_check" "cluster" { | ||
| count = local.enable_ravion_domain ? 1 : 0 | ||
| domain_name = coalesce(var.ravion_cluster_name, var.module_instance_given_id, var.name) | ||
| } | ||
|
|
||
| resource "ravion_aws_acm_certificate" "cluster" { | ||
| count = local.enable_ravion_domain ? 1 : 0 | ||
|
|
||
| role = "shared_wildcard" | ||
| wildcard = true | ||
| name = coalesce(var.ravion_cluster_name, var.module_instance_given_id, var.name) | ||
| module_instance_id = var.module_instance_id | ||
| aws_account_id = var.ravion_aws_account_id | ||
| aws_region = coalesce(var.ravion_aws_region, local.region) | ||
|
|
||
| # Ravion publishes a *.<apex> ALIAS to this ALB so service auto-FQDNs | ||
| # (<svc>.<apex>) resolve under the cluster wildcard. Public ALB if present, | ||
| # else private. (A single wildcard record serves one ALB; mixed public+private | ||
| # clusters route to the public one.) | ||
| target_dns_name = var.enable_public_alb ? module.public_alb[0].alb_dns_name : (var.enable_private_alb ? module.private_alb[0].alb_dns_name : null) | ||
| target_zone_id = var.enable_public_alb ? module.public_alb[0].alb_zone_id : (var.enable_private_alb ? module.private_alb[0].alb_zone_id : null) | ||
|
|
||
| lifecycle { | ||
| # Rotating the cluster wildcard cert (any RequiresReplace change, e.g. a | ||
| # renamed apex) must issue the new cert and swap it onto the HTTPS | ||
| # listener(s) BEFORE the old one is torn down. Without this, terraform | ||
| # destroys the old cert first while it is still the listener's default — | ||
| # ACM returns ResourceInUse and the rotation deadlocks. create_before_destroy | ||
| # makes it new -> listener in-place swap -> delete old (now detached). | ||
| create_before_destroy = true | ||
|
|
||
| precondition { | ||
| condition = !var.use_ravion_managed_domains || var.enable_public_alb || var.enable_private_alb | ||
| error_message = "use_ravion_managed_domains requires at least one ALB (enable_public_alb or enable_private_alb)." | ||
| } | ||
| precondition { | ||
| condition = !var.use_ravion_managed_domains || (var.ravion_aws_account_id != null && var.ravion_aws_account_id != "") | ||
| error_message = "ravion_aws_account_id (aws_*) is required when use_ravion_managed_domains = true." | ||
| } | ||
| precondition { | ||
| condition = !coalesce(one(data.ravion_dns_collision_check.cluster[*].collides), false) | ||
| error_message = "Cluster wildcard apex is already claimed by another cluster: a managed *.<ravion_cluster_name>.<apex> domain owned by a different module instance already exists. Pick a unique ravion_cluster_name." | ||
| } | ||
| } | ||
| } | ||
|
|
||
| # Live service domains nested under this cluster's wildcard apex (they ride its | ||
| # cert + `*.<apex>` ALIAS). Surfaced via the ravion_cluster_dependent_domains | ||
| # output for the UI / safe-teardown orchestration. NOT used as a precondition: | ||
| # a cluster legitimately has dependents during normal operation and Terraform | ||
| # can't scope a precondition to destroy-time, so it would block every apply. | ||
| # The control plane already refuses a teardown while dependents exist | ||
| # (Dns:CERT_APEX_IN_USE), which is the real backstop. | ||
| data "ravion_apex_dependents" "cluster" { | ||
| count = local.enable_ravion_domain ? 1 : 0 | ||
| apex = ravion_aws_acm_certificate.cluster[0].domain_name | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
movedblock. Three of the four symmetrical SG rule renames havemovedblocks (ravion_https_ipv4,ravion_https_ipv6,ravion_https_private_ipv4), butravion_https_private_ipv6→private_https_ipv6is absent. For any cluster already in Ravion mode with a private ALB, Terraform will destroy the old rule and create a new one, creating a brief window where IPv6 traffic on port 443 to the private ALB is not covered.Prompt To Fix With AI