Skip to content

Managed Active Directory

AWS Managed Microsoft AD directories are defined in the managed_ads variable. Each entry creates a directory via the directory_service_directory module, with optional conditional forwarders (directory_service_conditional_forwarder module) and custom security group rules (directory_service_security_group_rule module).

For joining EC2 instances to a directory, see Domain Join.

Basic Example

The key in the map object (epic below) is the name used to reference the directory in other parts of the tfvars configuration, such as domain join documents and DHCP options.

1
2
3
4
5
6
7
8
managed_ads = {
    epic = {
        name = "sapphire.dev"
        subnets = ["SharedInfraPrivateAZ1", "SharedInfraPrivateAZ2"]
        vpc = "SharedInfra"
        edition = "Enterprise"
    }
}

Options

Option Default Description
name key-based naming convention Fully qualified domain name (e.g. sapphire.dev)
vpc (required) Key of the VPC in vpcs to deploy into
subnets (required) Two subnet keys in different AZs
edition Standard Standard or Enterprise
type MicrosoftAD Directory type
size null Only used for SimpleAD and ADConnector types
desired_number_of_domain_controllers 2 Minimum of 2
admin_password_key ad_password Key of the secret holding the directory Admin password
conditional_forwarders [] DNS forwarding to other domains (see below)
security_group_rules {} Custom rules on the directory's security group (see below)
tags {} Tags for the directory

Conditional Forwarders

Forward DNS queries for other domains to their name servers:

managed_ads = {
    epic = {
        name = "sapphire.dev"
        subnets = ["SharedInfraPrivateAZ1", "SharedInfraPrivateAZ2"]
        vpc = "SharedInfra"
        conditional_forwarders = [
            {
                remote_domain_name = "corp.example.com"
                dns_ips = ["10.248.12.2"]
            }
        ]
    }
}

Security Group Rules

AWS creates and manages the directory's security group itself, and its default inbound rules are scoped to the VPC where the directory is deployed. Anything else that needs to reach the domain controllers — spoke accounts, peered VPCs, Transit Gateway-connected VPCs — needs additional rules, which can be defined in code under security_group_rules.

Additive only

Custom rules are created alongside the rules AWS pre-populates for AD to function. Terraform never modifies or removes the AWS-managed rules.

Overlapping rules fail the apply

A rule that already exists in the security group is a hard failure (InvalidPermission.Duplicate), not a no-op — the apply stops partway with some rules created and some not. Three ways to hit this:

  • pointing allow_ad_ports_from at the directory VPC's own CIDR (AWS already created those rules)
  • enabling the template for a network that already has manually added rules (as the domain join docs used to instruct)
  • an individual ingress rule that duplicates one of the template's ports for the same source

Migrating a directory with manually added rules: delete the manual rules for that network in the console first, then apply with allow_ad_ports_from. Do it during a maintenance window if the network is in active use — access is briefly gone between the manual deletion and the apply.

Allow the standard AD ports from another network

allow_ad_ports_from is the shortcut for the common case: each listed source gets the full set of ports AWS Managed AD requires (see table below), so a subnet or VPC can be granted domain-client access with one entry instead of ~20 hand-written rules.

managed_ads = {
    epic = {
        name = "sapphire.dev"
        subnets = ["SharedInfraPrivateAZ1", "SharedInfraPrivateAZ2"]
        vpc = "SharedInfra"
        security_group_rules = {
            allow_ad_ports_from = {
                coder = {
                    cidr_ipv4 = "10.197.0.0/25"
                }
            }
        }
    }
}

Sources can be a cidr_ipv4, cidr_ipv6, prefix_list (key from prefix_list), or referenced_security_group (key of a security group in the directory's VPC).

The port set is the managed_ad_port_template variable (default in src/variables.ad.tf), which an environment can override in tfvars. The default covers:

Port(s) Protocol Purpose
ICMP Ping / PMTU discovery
53 TCP + UDP DNS
88 TCP + UDP Kerberos
123 UDP Windows Time
135 TCP RPC endpoint mapper
138 UDP DFSN and NetLogon
389 TCP + UDP LDAP
445 TCP + UDP SMB
464 TCP + UDP Kerberos password change
636 TCP LDAPS
3268-3269 TCP Global Catalog
49152-65535 TCP RPC ephemeral

RPC ephemeral range

The template's RPC ephemeral rule (TCP 49152-65535) is narrower than the TCP 1024-65535 rule AWS grants the directory's own VPC. Modern Windows RPC services only listen on 49152+, but a workload that needs downlevel RPC endpoints below 49152 (some FSx/RDS SQL Server domain join paths) will work from inside the directory VPC yet fail from a template-granted network. If that happens, add an individual ingress rule for the wider range for that source.

AD management tooling

The template intentionally excludes TCP 9389 (AD Web Services), which is not present in the security group AWS creates. It is only needed by AD management tooling such as the Active Directory PowerShell module and AD Administrative Center. If a network needs to run those tools, add an individual ingress rule for TCP 9389 alongside allow_ad_ports_from.

Individual rules

One-off ingress and egress rules use the same format as security group rules in vpcs:

managed_ads = {
    epic = {
        name = "sapphire.dev"
        subnets = ["SharedInfraPrivateAZ1", "SharedInfraPrivateAZ2"]
        vpc = "SharedInfra"
        security_group_rules = {
            ingress = {
                ldaps = {
                    description = "LDAPS from Coder"
                    cidr_ipv4 = "10.197.0.0/25"
                    from_port = 636
                    to_port = 636
                }
            }
        }
    }
}

Admin Password

The directory Admin password is read from the secret named by admin_password_key (default ad_password), typically supplied via the TF_VAR_ad_password environment variable. See Referencing Secrets.