I’ve recently been working a lot more with Terraform to stand up infrastructure in a multi AWS account environment. There are a number of assorted tips I’m collecting more as notes to myself than anything else. Perhaps it may be useful to someone else as well.

Introduction

Sharing a VPC endpoint service across AWS accounts may cause an issue with corresponding subnet AZs not translating to the same actual AZ. That is, one AWS account’s us-east-1a might not be the same physical underlying AZ as another’s us-east-1a (and might perhaps be that account’s us-east-1b for example).

Server account

We start with 2 AWS providers, one for the server account and one for the client. Details on setting up the providers to use different account elided:

provider "aws" {
  alias = "server"
}

data "aws_caller_identity" "server" {
  provider = aws.server
}

provider "aws" {
  alias = "client"
}

data "aws_caller_identity" "client" {
  provider = aws.client
}

In the server account we set a network load balancer that’s placed in its us-east-1a and us-east-1b subnets, create a VPC endpoint service and allow the client account access to it:

resource "aws_lb" "service" {
  name               = "Service-Privatelink"
  internal           = true
  load_balancer_type = "network"

  subnets = [
    data.aws_subnet.server["us_east_1a"].id,
    data.aws_subnet.server["us_east_1b"].id
  ]

  provider = aws.server
}

resource "aws_vpc_endpoint_service" "service" {
  acceptance_required        = true
  network_load_balancer_arns = [aws_lb.service.arn]

  provider = aws.server
}

resource "aws_vpc_endpoint_service_allowed_principal" "service_to_client" {
  vpc_endpoint_service_id = aws_vpc_endpoint_service.service.id
  principal_arn           = "arn:aws:iam::${data.aws_caller_identity.client.account_id}:root"

  provider = aws.server
}

Client account

Next in the client account we attempt to create the aws_vpc_endpoint in the client’s subnets that are in the same AZs as the service’s NLB:

resource "aws_vpc_endpoint" "service" {
  vpc_id            = aws_vpc.client.id
  service_name      = aws_vpc_endpoint_service.service.service_name
  vpc_endpoint_type = "Interface"

  subnet_ids = [
    data.aws_subnet.client["us_east_1a"].id,
    data.aws_subnet.client["us_east_1b"].id,
  ]

  provider = aws.client
}

However this may fail! AZ us-east-1a in the server account may not be the same as us-east-1a in the client account. For more details see this helpful AWS support page for more details on this.

Solution

Instead we must find the service in the client account and use the AZs from that data.aws_vpc_endpoint_service to attach the aws_vpc_endpoint to the same corresponding subnet:

data "aws_vpc_endpoint_service" "service" {
  service_name      = aws_vpc_endpoint_service.service.service_name

  provider = aws.client
}

resource "aws_vpc_endpoint" "service" {
  vpc_id            = aws_vpc.client.id
  service_name      = aws_vpc_endpoint_service.service.service_name
  vpc_endpoint_type = "Interface"

  # Loop through available subnets and find the ones that correspond to the service's AZs
  subnet_ids = [
    for _, subnet in data.aws_subnet.client : subnet.id
    if contains(data.aws_vpc_endpoint_service.service.availability_zones, subnet.availability_zone)
  ]

  provider = aws.client
}