Skip to content

Commit

Permalink
Fix listener certificate handling
Browse files Browse the repository at this point in the history
In the original design of this module, we assumed that each ECS
task had its own listener certificate and that these resources
could be applied and destroy along with the ECS task. This works
in most case, but was broken by the special case wherein >1 ECS
task shares a listener certificate because both tasks listen
on the same host_header but are differentiated by different
path_pattern values (and different priorities).

This assumption broke for services like authman which meet these
conditions by sharing a host_header. The original design allowed
for creating a listener certificate independently (outside the
apps directory), but naively assumed that separate listener
certificates could be managed in the individual apps subdirectories.

The result is that running the Terraform in *any* of the apps
subdirectories would apply or destroy the listener certificate
regardless of whether other ECS tasks were using it.

In order to fix this proble, we add a manage_listener_certificate
variable. This changes the behavior when applying or destroying
an ECS task.

This variable is only meaningful when a load_balancer object is
defined for the ECS task, and when a public load balancer is used.
Tasks using a private load balancer do not need SSL certificates
because intra-VPC traffic is deemed secure. Tasks not using a
load balancer (such as a daemon process) don't have a listener
at all.

The following assumes that the Terraform for an ECS task specifies a
load_balancer block:

*   If the manage_listener_certificate sub-object in the load_balancer
    block is true (which is the default when a load_balancer block is
    defined), the listener certificate is managed with the ECS task,
    and will have the same lifecycle.

*   If the manage_listener_certificate sub-object in the load_balancer
    block is false, the module assumes that a listener certificate is
    managed independently, in a separate configuration directory,
    using the terraform-aws-lb-listener-certificate module.

In the latter case, the listener certificate in this case is *not*
managed with the container, and persists beyond the lifetime of any
of the individual ECS tasks that use the listener_certificate.

The intent of setting manage_listener_certificate to false is for
use cases where multiple tasks share a host_header, and use
path_pattern and priority sub-objects in the load_balancer block
to distinguish the task to which traffic should be routed.
  • Loading branch information
JonRoma committed Feb 12, 2024
1 parent c35058b commit 174006d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
38 changes: 36 additions & 2 deletions certificate.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
# For local.manage_listener_certificate to be set to a non-null value,
# both of the following conditions must apply:
#
# (1) The var.load_balancer variable must be defined. An example of an
# ECS task that doesn't use a load balancer would be a daemon
# process that doesn't accept traffic from other ECS tasks.
#
# (2) The var.load_balancer.certificate_domain object must be non-null.
# Tasks using a private load balancer don't need SSL certificates
# because AWS traffic within a VPC is considered secure.
#
# Assuming both of the preceding conditions are satisfied, the
# var.load_balancer.manage_listener_certificate object determines the
# value of local.manage_listener_certificates.
#
# * If the var.load_balancer.manage_listener_certificate object is true
# (which is the default), the listener certificate is managed with
# exactly one ECS task, and will have the same lifecycle.
#
# * If the var.load_balancer.manage_listener_certificate object is
# false, the module assumes that a listener certificate is managed
# independently in a separate configuration directory using the
# terraform-aws-lb-listener-certificate module.
#
# In this case, the listener certificate will persist beyond the
# lifetime of any of the individual ECS tasks. This is useful where
# multiple tasks share a host_header, and use path_pattern and
# priority values to distinguish the task to which traffic should
# be routed.

locals {
manage_listener_certificate = try(var.load_balancer != null && var.load_balancer.certificate_domain != null && var.load_balancer.manage_listener_certificate, null)
}

data "aws_acm_certificate" "default" {
count = try(var.load_balancer.certificate_domain != null, false) ? 1 : 0
count = try(local.manage_listener_certificate == true, false) ? 1 : 0
domain = var.load_balancer.certificate_domain
statuses = ["ISSUED"]
}

resource "aws_lb_listener_certificate" "default" {
count = try(var.load_balancer.certificate_domain != null, false) ? 1 : 0
count = try(local.manage_listener_certificate == true, false) ? 1 : 0
listener_arn = data.aws_lb_listener.selected[0].arn
certificate_arn = data.aws_acm_certificate.default[0].arn
}
20 changes: 20 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,23 @@ output "_service_discovery" {
output "_task_definition" {
value = (var._debug) ? var.task_definition : null
}

#output "_aws_acm_certificate_arn" {
# value = try(data.aws_acm_certificate.default[0].arn, null)
#}
#
#output "_aws_acm_certificate_domain" {
# value = try(data.aws_acm_certificate.default[0].domain, null)
#}
#
#output "_aws_lb_listener_port" {
# value = try(data.aws_lb_listener.selected[0].port, null)
#}
#
#output "_aws_lb_listener_protocol" {
# value = try(data.aws_lb_listener.selected[0].protocol, null)
#}
#
#output "_manage_listener_certificate" {
# value = try(local.manage_listener_certificate, "null")
#}
21 changes: 11 additions & 10 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,17 @@ variable "launch_type" {
variable "load_balancer" {
description = "Load balancer block"
type = object({
certificate_domain = optional(string)
container_name = optional(string)
container_port = optional(number)
deregistration_delay = optional(number)
host_header = optional(string)
name = optional(string)
path_pattern = optional(string, "*")
port = optional(number, 443)
priority = optional(number)
security_group_id = optional(string)
certificate_domain = optional(string)
container_name = optional(string)
container_port = optional(number)
deregistration_delay = optional(number)
host_header = optional(string)
name = optional(string)
path_pattern = optional(string, "*")
port = optional(number, 443)
priority = optional(number)
security_group_id = optional(string)
manage_listener_certificate = optional(bool, true)
})
default = null

Expand Down

0 comments on commit 174006d

Please sign in to comment.