AWS Management Tools Blog

Join a Microsoft Active Directory Domain with Parameter Store and Amazon EC2 Systems Manager Documents

by Matteo Rinaudo | on |

The process of configuration management can be difficult, in particular when performed at scale. An example could be an application, running on your fleet, which uses configuration values like database connection strings or passwords.

For deployment best practices, isolate application configuration portions so that you can separately deploy configuration values specific to each environment, for example development and production environments. To ameliorate the security posture of your application, encrypt sensitive configuration values like passwords. From a management standpoint, store configuration values in a central and secure location, instead of storing and maintaining such information on your fleet. Central storage has the advantage of easily maintaining and rotating configuration values in one single place, and it also facilitates auditing changes and access to such configuration values.

Amazon EC2 Systems Manager is a management service that helps you configure and manage Amazon EC2 instances and on-premises servers. Parameter Store is a Systems Manager feature that makes it easier to reference your configuration data, securely stored in a central location. Parameter Store integrates with other AWS services like AWS Identity and Access Management (IAM) and AWS Key Management Service (AWS KMS). With IAM, you define access control to Systems Manager parameters. With KMS, you can encrypt sensitive information, such as SecureString parameters. API calls made to Systems Manager parameters can be recorded with AWS CloudTrail, so you can audit access or changes to your parameters, for example.

In this post, I show you a scenario for centralized configuration management, with an example of joining EC2 instances to a Microsoft Active Directory. You launch an EC2 instance that consumes and uses configuration values stored as Systems Manager parameters to join your Active Directory domain. For more information about creating an Active Directory with AWS Directory Service instead, see the Seamlessly Join EC2 Instances to a Domain blog post.

This is the diagram of the example infrastructure you create:

Walkthrough
In this walkthrough, you create an example Active Directory domain to run on a single EC2 instance. Next, you create Systems Manager parameters where you put the following information:

  • The Active Directory instance’s private IP address (used as a DNS server for domain members)
  • The domain name
  • The Active Directory administrator’s user name
  • The Active Directory administrator’s password

Then, you create a KMS customer master key (CMK) that you use to store the Active Directory admin password as an encrypted object in the relevant SecureString parameter.

Finally, you create an association between a Systems Manager document, where an example domain join script is defined, and an EC2 instance that joins the domain. Associations define the state to apply to a set of targets. In your example, the EC2 instance (a target) joins your domain (a state). State Manager, another Systems Manager feature, uses the association to keep your managed instance in the intended state.

For this walkthrough, you use the AWS Command Line Interface (AWS CLI) to create AWS resources. The examples show the us-east-1 region. I’ve also prepared example AWS CloudFormation templates that you can use to create AWS resources.

Step 1:  Select the AMI
Use an Amazon Machine Image (AMI) for Microsoft Windows 2012 R2 to launch both your example Active Directory instance and the instance you join to the Active Directory domain.

First, you need the ID of the AMI to use. Generate and choose from a list of AMIs, matching your search pattern. For readability, I’ve broken down the following command (and all the other commands in this post) in multiple lines instead of a single line:

aws ec2 describe-images
  --owners amazon
  --filters
    'Name=name,Values=Windows_Server-2012-R2_RTM-English-64Bit-Base-*'
    'Name=state,Values=available'
  --query 'Images[].[Name, ImageId]'
  --output text
  --region us-east-1

After running the command above, you should get a list with AMI names in the left column, and AMI IDs on the right column. Note the ID of the AMI to use, for example ami-1234abcd.

Step 2:  Create the EC2 key pair
Now, create an Amazon EC2 key pair to use to retrieve Windows passwords for the EC2 instances that you create. Call the key pair your-domain-join-demo and store the private key material on your computer in the your-domain-join-demo.pem file:

aws ec2 create-key-pair
  --key-name your-domain-join-demo
  --query "KeyMaterial"
  --output text
  --region us-east-1 > your-domain-join-demo.pem

Step 3:  Create the Active Directory
For this post, you create your example Active Directory on a single EC2 instance, and launch it on a default VPC subnet. The directory’s admin user name is Administrator, and the directory’s admin password is the one retrieved from the instance.

As part of your example Active Directory creation, specify a value to set up for the restore mode password (SafeModeAdminPassword). The password can have a minimum of 8 characters and contain alphanumeric and special characters.

You are now ready to create your example Active Directory with CloudFormation. Sign in to the CloudFormation console. Choose Launch Stack below to create the stack called your-domain-join-demo-ad-single-instance with the ad-single-instance.yaml example template. You can also download the template and use it as a starting point for your own implementation. This template is a modified version of the Microsoft Windows Server Active Directory template found at Sample Solutions. I changed it to work with this walkthrough and converted it to YAML.

In the CloudFormation console, choose Next and specify the following values for parameters defined in the template:

  • The name of the domain to create (example: corp.example.com)
  • The NetBIOS name (example: CORP)
  • The AMI ID from step 1
  • The instance size (example: t2.micro)
  • The key pair name from step 2
  • The value for your restore mode password
  • The IPv4 CIDR block from which you are likely to connect, via RDP (if needed) into your instances

For more information, see Specifying Stack Name and Parameters.

In the CloudFormation console, look at the stack you are creating. In the example below, you have just launched your stack and it is in the CREATE_IN_PROGRESS status:

 

 

Wait for the stack creation to be in the CREATE_COMPLETE Status. This stack also creates a security group, called DomainMemberSecurityGroup, to be used later by your EC2 instance joining the domain.

Step 4:  Create the KMS CMK
Create the KMS customer master key to encrypt the SecureString parameter that you later create for your directory’s admin password.

Choose Launch Stack below to create the stack called your-domain-join-demo-kms-cmk with the kms-custom-cmk.yaml example template.

In the CloudFormation console, specify the following values for parameters defined in the template:

  • The KMS key alias (example: your-domain-join-demo)
  • The key description (example: “Example key“)

When I prepared the example template that describes the KMS key, I added an output, in the template’s Outputs section, called CMKID. Its value should show the ID of the KMS key created by the stack launched off of the previously mentioned template. When the stack creation is complete, you then describe the Outputs section of the stack to get the ID of the KMS key that you just created:

aws cloudformation describe-stacks
  --stack-name your-domain-join-demo-kms-cmk
  --query "Stacks[0].Outputs[?OutputKey == 'CMKID'].OutputValue"
  --output text
  --region us-east-1

You need the KMS key ID value later on this walkthrough. Refer to it as YOUR_KMS_KEY_ID.

Step 5:  Create Systems Manager parameters
Start by creating a parameter for the private IP address of the Active Directory instance. You set up this value later on as the DNS server in the DNS client configuration of your instance to join the domain.

When you created your Active Directory in step 3, in the CloudFormation template that created the instance, you set up output values relevant to the instance configuration, including instance ID and the private IP address of the instance. Retrieve this IP address by describing the Outputs section of the stack that you created on step 3, and by getting the value for the DNSIPAddress output key:

aws cloudformation describe-stacks
  --stack-name your-domain-join-demo-ad-single-instance
  --query "Stacks[0].Outputs[?OutputKey == 'DNSIPAddress'].OutputValue"
  --output text
  --region us-east-1

The command above should return the IP address that you need, for example 172.31.11.22. You could have set up a static private IP address for your Active Directory instance, but for this walkthrough, you just used an IP address that was automatically assigned.

Now store the value for the IP address in a parameter called your-domain-join-demo-dns-ip-address, as a String value:

aws ssm put-parameter
  --name your-domain-join-demo-dns-ip-address
  --description "Example parameter for your DNS server's IP address"
  --value '172.31.11.22'
  --type String
  --region us-east-1

Create another parameter where you put the name of the domain you are creating (corp.example.com). Call the parameter your-domain-join-demo-domain-name, as a String value:

aws ssm put-parameter
  --name your-domain-join-demo-domain-name
  --description "Example parameter for your domain name"
  --value 'corp.example.com'
  --type String
  --region us-east-1

The user name of your directory’s admin, in your example, is Administrator. Create another parameter called your-domain-join-demo-ad-admin-username, where you put this information as a String value:

aws ssm put-parameter
  --name your-domain-join-demo-ad-admin-username
  --description "Example parameter for the username of your AD admin account"
  --value 'Administrator'
  --type String
  --region us-east-1

Next, store your directory’s admin password as an encrypted object in another parameter. First, retrieve the Windows password from the Active Directory instance. For this task, you need the instance ID from the Outputs section of the stack created in step 3:

aws cloudformation describe-stacks
  --stack-name your-domain-join-demo-ad-single-instance
  --query "Stacks[0].Outputs[?OutputKey == 'InstanceID'].OutputValue"
  --output text
  --region us-east-1

The command above should return the ID of the Active Directory instance, for example i-1234567890abcdef0. Next, retrieve the Windows password for your instance, and decrypt the password with the private key material of the EC2 key pair that you created earlier:

aws ec2 get-password-data
  --instance-id i-1234567890abcdef0
  --priv-launch-key your-domain-join-demo.pem
  --query "PasswordData"
  --output text
  --region us-east-1

The command above should return the Windows password for your instance. Refer to this password as YOUR_WINDOWS_PASSWORD_FOR_AD on this step. Next, store YOUR_WINDOWS_PASSWORD_FOR_AD as a SecureString parameter. Create a parameter called your-domain-join-demo-ad-admin-password. Use the KMS CMK, created in step 4, for encryption:

aws ssm put-parameter
  --name your-domain-join-demo-ad-admin-password
  --description "Example parameter for the password of your AD admin account"
  --value 'YOUR_WINDOWS_PASSWORD_FOR_AD'
  --type SecureString
  --key-id YOUR_KMS_KEY_ID
  --region us-east-1

At the end of this step, you should have four parameters. In the EC2 console, view your parameters:

 

Step 6:  Create the IAM role for the EC2 instance
On this step, you create an IAM role to be used by your instance joining the domain, via an IAM instance profile, to get read-only access to Systems Manager parameters. The example template describes a role to which the AmazonEC2RoleforSSM managed policy is attached. An example policy is also attached to the role, and a snippet of this policy is shown below:

[…]
{
  "Action": [
    "kms:Decrypt"
  ],
  "Effect": "Allow",
  "Resource": "THE_ARN_OF_YOUR_KMS_KEY"
}
[…]

The example policy above allows kms:Decrypt API calls for the KMS key resource that you created earlier. In the policy snippet, you reference the Amazon Resource Name (ARN) of the KMS CMK, that you see in the relevant placeholder in capital letters above.

When editing templates for this walkthrough, I used the CloudFormation feature to export stack output values, to facilitate passing values between stacks. The stack that you are creating now needs the ARN of the KMS CMK created earlier, so that it can be referenced in the IAM policy for the role.

You have already exported the KMS CMK ARN from the stack that created the CMK. You can now import and reference the ARN by specifying the name of the CMK stack (in your example: your-domain-join-demo-kms-cmk) as a parameter to the stack you are creating for the role. Choose Launch Stack below to create the stack called your-domain-join-demo-ec2-instance-role with the ec2-instance-role.yaml example template.

In the CloudFormation console, specify the your-domain-join-demo-kms-cmk parameter value.

Then, wait for the stack creation to complete and continue with the next step.

Step 7:  Create the Systems Manager document
Create, with an example CloudFormation template, the Systems Manager document that contains commands to execute on the EC2 instance for it to join your domain. In the document, specify the parameter names that you created earlier. Pass these names to the CloudFormation stack that creates the document.

The example code below depicts a portion of an example Systems Manager document. You can see how this document uses the !Sub short form of the Fn::Sub intrinsic function in CloudFormation to reference stack parameters (for example, the DNSParameterStore parameter):

[…]
  Document:
    Type: AWS::SSM::Document
    Properties:
      Content:
        description: Run a PowerShell script to domain join a Windows instance securely
        mainSteps:
        - action: aws:runPowerShellScript
          inputs:
            runCommand:
            - '# Example PowerShell script to domain join a Windows instance securely'
            - ''
            - $ErrorActionPreference = 'Stop'
            - ''
            - try{
            - '    # Parameter names'
            - !Sub '    $dnsParameterStore = ''${DNSParameterStore}'''
            - !Sub '    $domainNameParameterStore = ''${DomainNameParameterStore}'''
            - !Sub '    $domainJoinUserNameParameterStore = ''${DomainJoinUserNameParameterStore}'''
            - !Sub '    $domainJoinPasswordParameterStore = ''${DomainJoinPasswordParameterStore}'''
            - ''
            - '    # Retrieve configuration values from parameters'
            - '    $ipdns = (Get-SSMParameterValue -Name $dnsParameterStore).Parameters[0].Value'
            - '    $domain = (Get-SSMParameterValue -Name $domainNameParameterStore).Parameters[0].Value'
            - '    $username = $domain + ''\'' + (Get-SSMParameterValue -Name $domainJoinUserNameParameterStore).Parameters[0].Value'
            - '    $password = (Get-SSMParameterValue -Name $domainJoinPasswordParameterStore
              -WithDecryption $True).Parameters[0].Value | ConvertTo-SecureString
              -asPlainText -Force'
[…]

Create the document with a stack. Choose Launch Stack below to create the stack called your-domain-join-demo-ssm-document-domain-join with the ssm-document-domain-join.yaml example template.

In the CloudFormation console, specify the following values for parameters defined in the template:

  • your-domain-join-demo-dns-ip-address
  • your-domain-join-demo-ad-admin-password
  • your-domain-join-demo-ad-admin-username
  • your-domain-join-demo-domain-name

After the stack creation is complete, you should find your new document listed in the Documents page in the console:

 

Step 8:  Launch the instance and join the domain
On this last step, use CloudFormation to create an EC2 instance based on the Microsoft Windows 2012 R2 AMI you chose in step 1.

After the instance starts up, it is associated to the document you created in the previous step, and should then join the domain after running the script in the document. When you create the stack for the instance, you import values that you have exported in other stacks, so you can easily reference the values you need. Specify the names of the stacks that created the Active Directory domain and the Systems Manager document, so you can reference the following:

  • The ID of the domain member security group for your EC2 instance
  • The name of the document for the association between the document and instance

Now, create the instance. Choose Launch Stack below to create the stack called your-domain-join-demo-ec2-instance with the ec2-instance.yaml example template.

In the CloudFormation console, specify the following values for parameters defined in the template:

  • your-domain-join-demo-ad-single-instance
  • your-domain-join-demo-ssm-document-domain-join
  • The AMI ID from step 1
  • your-domain-join-demo-ec2-instance-role
  • The instance size (in your example: t2.micro)
  • The key pair name from step 2

Wait for the stack creation to complete. At the end of the process, your instance should have joined your corp.example.com domain. In the EC2 console, State Manager should show that the association between the Systems Manager document and the instance is successful:

 

Verify that the machine joined the domain by connecting via RDP to the instance with your Active Directory admin credentials.

Conclusion
In this post, I showed you how to use Parameter Store to set up configuration values in a central and secure location. Using Parameter Store, you securely stored the configuration values needed for your EC2 instance to join the domain. You also stored the admin password for the example Active Directory as a SecureString parameter, and used a KMS key for encryption. Then, you launched an EC2 instance associated with a Systems Manager document, ran a custom script on the instance, and joined your example domain.

About the Author
Matteo Rinaudo is a Sr. Consultant at AWS Professional Services. Matteo enjoys working with our customers to deliver solutions and best practices around DevOps automation, infrastructure-as-code and configuration management. In his spare time, Matteo enjoys spending time with his wife, playing the trumpet, the piano, and listening to classical music.