0

I'm trying to connect to an MSK cluster using a Route 53 DNS CNAME record that points to the DNS record that is provided by Amazon.

AWS MSK DNS: b-1.msksandbox.nrfnuy.c42.kafka.us-east-1.amazonaws.com DNS I need to use: b-1.msk.sandbox.internal.company.com

The error I get:

Error while executing topic command : SSL handshake failed

ERROR org.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed
Caused by: javax.net.ssl.SSLHandshakeException: No subject alternative DNS name matching b-1.msk.sandbox.internal.company.com found.

Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching b-1.msk.sandbox.internal.company.com found.

When I look at the server cert it says

Server certificate
subject=CN = *.msksandbox.nrfnuy.c42.kafka.us-east-1.amazonaws.com
issuer=C = US, O = Amazon, OU = Server CA 1B, CN = Amazon

I wonder if it's possible to make Route 53 and MSK work together (I'm using IAM authentication)

Dmitry
  • 2,626
  • 2
  • 10
  • 15

3 Answers3

1

I searched and searched for a way to be able to bootstrap Kafka clients using vanity DNS names instead of the AWS-generated DNS names for the MSK brokers.

My team and I finally figured out a solution after piecing together information from different sources. Here is our solution.

For each MSK broker:

  • Create a network load balancer (NLB) in the same VPC (for us, this was in the public subnet)
  • Create a TLS-type load balancer (LB) listener and attach a TLS certificate with the custom vanity domain name in the Subject Alternative Names list.
  • Create a TLS certificate for the vanity domain
  • Create a TLS-type load balancer (LB) target group (TG) that targets the MSK broker by IP address

The MSK broker IP addresses were looked up by their DNS A records

Here is the terraform configuration. I hope it is helpful:

locals {
  msk_scram_addrs = split(",", aws_msk_cluster.myclust.bootstrap_brokers_sasl_scram)
  msk_scram_hosts = toset([for x in local.msk_scram_addrs : split(":", x)[0]])
  # b-1, b-2, b-3, ...
  broker_short_names = toset([
    for a in data.dns_a_record_set.myclust_scram_brokers :
    split(".", a.host)[0]
  ])
}

data "dns_a_record_set" "myclust_scram_brokers" {
  for_each = local.msk_scram_hosts

  host = each.value

  depends_on = [
    aws_msk_cluster.myclust
  ]
}

resource "aws_lb" "msk_broker" {
  for_each = local.broker_short_names

  name               = each.value
  tags               = [...]
  internal           = false
  subnets            = local.aws_vpc_public_subnets
  load_balancer_type = "network"
  idle_timeout       = 3600

  timeouts {
    create = "20m"
  }
}

resource "aws_lb_target_group" "msk_broker_scram_public" {
  for_each = local.broker_short_names

  name = each.value
  tags = [...]

  # https://aws.amazon.com/blogs/aws/new-tls-termination-for-network-load-balancers/
  protocol    = "TLS"
  vpc_id      = local.aws_vpc_id
  target_type = "ip" // Targets Kafka broker by IP address.
  port        = 9196

  lifecycle {
    create_before_destroy = true
  }

  depends_on = [
    aws_lb.msk_broker
  ]
}

resource "aws_lb_target_group_attachment" "msk_broker_scram_public" {
  for_each = {
    for a in data.dns_a_record_set.myclust_scram_brokers : split(".", a.host)[0] => a.addrs[0]
  }

  target_group_arn = aws_lb_target_group.msk_broker_scram_public[each.key].arn
  target_id        = each.value // This is the Kafka broker IP address.
}

resource "aws_lb_listener" "msk_bootstrap_public" {
  for_each = local.broker_short_names

  # https://aws.amazon.com/blogs/aws/new-tls-termination-for-network-load-balancers/
  protocol          = "TLS"
  port              = 9196
  certificate_arn   = module.msk_lb_cert[each.value].acm_cert.arn
  load_balancer_arn = aws_lb.msk_broker[each.value].arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.msk_broker_scram_public[each.value].arn
  }

  lifecycle {
    create_before_destroy = true
  }
}

module "msk_lb_cert" {
  source = "../certificates"

  for_each = local.broker_short_names

  org_public_zone_id = local.org_zone_id
  io_public_zone_id   = data.aws_route53_zone.io_public.zone_id

  org_domain_name = "${each.value}.${local.params.msk_org_domain}"
  vanity_domain_names = [
    "${each.value}.${local.params.msk_vanity_domain}"
  ]

  resource_health_check = false
  resource_domain_name  = aws_lb.msk_broker[each.value].dns_name
  resource_zone_id      = aws_lb.msk_broker[each.value].zone_id

  providers = {
    aws.org_zone = aws.org
    aws.io_zone = aws.io
  }
}
0

You can implement this with NLB. Then you will attach you certificate on NLB. Your certificate will terminated on NLB. Connection between NLB and MSK will use MSK certificate.

Mehmet Güngören
  • 2,383
  • 1
  • 9
  • 16
  • `IAM authentication against cluster using a custom domain name through intermediate NLB is not supported as of now.` Found it here: https://stackoverflow.com/a/69660623/6133747 Do you know if that's no longer the case? – Dmitry Jan 17 '23 at 21:17
  • According documentation, it says you can update `advertised.listeners`. Check this pages [1](https://kafka.apache.org/documentation/#brokerconfigs_listeners) [2](https://kafka.apache.org/documentation/#brokerconfigs_advertised.listeners) – Mehmet Güngören Jan 17 '23 at 22:27
0

Custom domain names aren't supported currently.

In order to implement custom domain names using R53 and certificates, you will need to terminate certificates at NLB, and then target group will create a connection to the........ IP address of a broker, because in target group all you can specify is IP, not brokers' domain name. SSL connection will fail between NLB and a broker, because IP address of a broker is not added to a certificate deployed on a broker side, so NLB won't trust that connection.

The only way it may work is if you use PLAINTEXT connection (port 9092) between NLB and MSK. But this is not secure and not recommended approach.

EdbE
  • 204
  • 1
  • 4