AWS - create VPC and a managed AD with terraform

2 minute read

Note:

  • This post is part of a series: Part 2

AWS offers a managed AD service, in two flavors (Samba-based, and “original” Microsoft). This shows how you create …

  • a VPC
  • a managed Microsoft AD
  • an internal DNS zone
  • an internal DNS forwarder for the “AD domain” to the AD

… with terraform.

Let’s go.

Part 1 - the VPC and the AD

locals {
  name                  = "testproject"
  internal_dns_zone     = "testproject.local"
  ad_domain             = "testad.aws"
  cidr                  = "10.0.0.0/24"
  azs                   = ["eu-central-1b", "eu-central-1c"]
  database_subnets      = [cidrsubnet(local.cidr, 3, 0), cidrsubnet(local.cidr, 3, 1)]
  private_subnets       = [cidrsubnet(local.cidr, 3, 2)]
  public_subnets        = [cidrsubnet(local.cidr, 3, 3)]
  secret_winad_password = "uiuiuiui_super_secret"
  tags                  = { broughttoyouby = "flypenguin ;)" }
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.7.0"

  azs                   = local.azs
  cidr                  = local.cidr
  create_igw            = true # for jump hosts, see also: https://is.gd/2Ry3H7
  database_subnets      = local.database_subnets
  enable_dns_hostnames  = true
  enable_nat_gateway    = true
  enable_vpn_gateway    = false # not yet
  name                  = local.name
  private_subnets       = local.private_subnets
  public_subnets        = local.public_subnets
  single_nat_gateway    = true
}

resource "aws_directory_service_directory" "ad" {
  name      = local.ad_domain
  password  = local.secret_winad_password
  edition   = "Standard"
  type      = "MicrosoftAD"
  tags      = local.tags

  vpc_settings {
    vpc_id     = module.vpc.vpc_id
    subnet_ids = module.vpc.database_subnets
  }
}

resource "aws_route53_zone" "vpc" {
  name = local.internal_dns_zone

  vpc {
    vpc_id = module.vpc.vpc_id
  }
}

Part 2 - the resolver

Now this was pretty easy. Problem: Windows thinks it is responsible for performing autoritative (I think) DNS lookups for the AD “domain”. And the internal DNS resolver has absolutely no clue about any AD related stuff. Amazon itself offers two possibilities:

  1. using the managed AD as the primary DNS “servers”, forwarding requests to route53, or
  2. configure route53 to forward to the managed AD for requests regarding “testad.aws” (our AD “domain”)

This describes option (1), because in my eyes the VPC should not stop functioning without the AD (which it does if you give the AD primary DNS responsibility).

resource "aws_route53_resolver_endpoint" "ad_hcm_resolver" {
  name      = "ad-hcm-resolver"
  direction = "OUTBOUND"
  tags      = local.tags

  ip_address { subnet_id = module.vpc.database_subnets[0] }
  ip_address { subnet_id = module.vpc.database_subnets[1] }
}

resource "aws_route53_resolver_rule" "ad_fwd" {
  domain_name          = local.ad_domain
  name                 = "ad_hcm_forward"
  rule_type            = "FORWARD"
  resolver_endpoint_id = aws_route53_resolver_endpoint.ad_hcm_resolver.id
  tags                 = local.tags

  target_ip { ip = sort(aws_directory_service_directory.ad.dns_ip_addresses)[0] }
  target_ip { ip = sort(aws_directory_service_directory.ad.dns_ip_addresses)[1] }
}

# WE NEED THIS. without it does not work.
resource "aws_route53_resolver_rule_association" "ad_fwd" {
  resolver_rule_id = aws_route53_resolver_rule.ad_fwd.id
  vpc_id           = module.vpc.vpc_id
}

WARNING: That stupid forwarder costs about 180$ / month (about 90¢ per IP address, and you must have two). It is REALLY expensive for what it does.