Fly, Penguin!

I blog so I don't forget.

AWS - auto-join Windows clients to a managed AD

Notes:

  • This post is part of a series: Part 1
  • I still consider this post a “snippet”, cause it is just a bit of - hopefully - copy-pastable Terraform code, with a bit of explanation.)

All right. in the previous post we created a VPC including an AD, and configured route53 to forward requests regarding “AD stuff” to it. But, an AD is quite useless without Windows clients. And Windows clients are annoying, cause you have to join them to the AD. Luckily, there’s an automation for that.

AWS introduced the AWS Systems Manager (formerly SSM), which is a small software component pre-installed on most (all?) AWS AMIs and connects to an AWS-internal management instance to perform actions. Like “domain auto-join”.

Approach

There are two ways to do this:

  1. Configure each host to join the AD, or
  2. Tell AWS to perform the auto-join action on every host with a certain tag

I’m following option (2) here, using the tag key/value combination { "adjoin": "true" }. Easy.

WARNING: AWS SSM was key in at least one massive data leak. SO BE VERY CAREFUL in how you use the Systems Manager and if in doubt refer to further documentation. I think this approach is safe, because we do not use any credentials in the following code.

Having said that, let’s go.

The code

resource "aws_ssm_document" "ad_join_domain" {
  name          = "ad-join-domain"
  document_type = "Command"
  content = jsonencode(
    {
      "schemaVersion" = "2.2"
      "description"   = "aws:domainJoin"
      "mainSteps" = [
        {
          "action" = "aws:domainJoin",
          "name"   = "domainJoin",
          "inputs" = {
            "directoryId"    = aws_directory_service_directory.ad.id,
            "directoryName"  = aws_directory_service_directory.ad.name,
            "dnsIpAddresses" = aws_directory_service_directory.ad.dns_ip_addresses
          }
        }
      ]
    }
  )
}

resource "aws_ssm_association" "windows_server" {
  name = aws_ssm_document.ad_join_domain.name
  targets {
    key    = "tag:adjoin"
    values = ["true"]
  }
}

resource "aws_iam_role" "ad_autojoin" {
  name = "ad-autojoin"
  assume_role_policy = jsonencode({
    "Version" = "2012-10-17",
    "Statement" = [
      {
        "Effect" = "Allow",
        "Principal" = {
          "Service" = "ec2.amazonaws.com"
        },
        "Action" = "sts:AssumeRole"
      }
    ]
  })
}

# required it seems
resource "aws_iam_role_policy_attachment" "ssm-instance" {
  role       = aws_iam_role.ad_autojoin.id
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# required it seems
resource "aws_iam_role_policy_attachment" "ssm-ad" {
  role       = aws_iam_role.ad_autojoin.id
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess"
}

resource "aws_iam_instance_profile" "ad_autojoin" {
  name = "ad-autojoin"
  role = aws_iam_role.ad_autojoin.name
}

The Windows instance

Some notes:

  • NOTE: I am using a terraform module here, but in my code I am using a modified version of it. So it might be that this code needs some adjustments.
  • AWS does not yet provide Windows 10 images as AMIs. So we use Windows Server.

Let’s go.

data "aws_ami" "win2019server" {
  owners      = ["amazon"]
  most_recent = true

  filter {
    name   = "name"
    values = ["Windows_Server-2019-German-Full-Base*"]
  }

  filter {
    name   = "platform"
    values = ["windows"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }
}

module "ec2-instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "3.2.0"

  ami           = data.aws_ami.win2019server.id
  instance_type = "t3.large"
  cpu_credits   = "unlimited"
  subnet_id     = element(module.vpc.private_subnets, 0)

  # THIS IS WHAT WE NEED TO AUTO_JOIN!! :))
  iam_instance_profile = aws_iam_instance_profile.ad_autojoin.name
  tags                 = merge({ "adjoin" = "true" }, local.tags)

  # yes, weird syntax
  root_block_device = [{
    volume_size = 50
    tags = {
      Name = "my-root-block"
    }
  }]
}

References