AutoScale Groups with Domain Join

When it comes to AWS, it often feels like Windows is a second class citizen.  I have been doing work in the Azure public cloud for a long time, where the situation is somewhat reversed.  In AWS, the commands, assumptions, and example use cases are almost always in Linux with Windows as a bit of an after thought.  I’ve been doing a lot of AWS work recently, and one of the things that came up was the ability to deploy AutoScale groups and Launch Configurations using CloudFormation.  By itself, that process is relatively straightforward, especially if you are working in a Linux context.  But what if you are deploying Windows boxes in a domain and want them to have a specific hostname structure?  That’s a bit more tricky, but I got it figured out.

The first thing to know is that I am deploying this configuration in an existing domain, and I am using CloudFormation to do it.  The AutoScale group is part of a larger environment that includes various SQL servers, web servers, and application servers.  The requirement to have domain-joined servers stems from a need to centrally apply policies and deploy software.  The members of the AutoScale group fall under that umbrella, so they need to be part of the domain.  There are two parts to the deployment, the first is the CloudFormation resources that deploy the Launch Configuration and AutoScale group, and then there is the PowerShell script that helps perform the renaming of the servers.  The full stack template and PowerShell script are included at the end of the post.

Here is the AutoScalingGroup resource:

    "MyServerAutoScalingGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "Properties": {
        "LaunchConfigurationName": {
          "Ref": "MyServerLaunchConfiguration"
        },
        "DesiredCapacity" : "2",
        "MaxSize" : "5",
        "MinSize" : "1",
        "VPCZoneIdentifier" : [{"Ref" : "AutoScaleSubnetId"}]
      }
    }

The VPCZoneIdentifier refers to a subnet Id that I grab from the parameters in the template.

The LaunchConfiguration resource has a few things worth noting.  In the Metadata section I have added an AWS::CloudFormation::Init section, which is how each instance gets configured.  Most of it is straightforward, like creating the configuration file for the cfn-auto-reloader.  There is a PowerShell script that gets loaded to handle the renaming of the server.  The source is an S3 bucket that is defined in the template parameters.

                            "c:\\cfn\\scripts\\Rename-LCComputer.ps1" : {
                                "source" : { "Fn::Join" : ["", [
                                    "https://", { "Ref" : "BucketName" }, 
                                    ".s3.amazonaws.com/ps-scripts/Rename-LCComputer.ps1"
                                  ]]}
                            }

Within the rename configset the script is called.

                            "a-execute-powershell-script-RenameComputer": {
                                "command": {
                                  "Fn::Join" : ["", [
                                    "powershell.exe -executionpolicy unrestricted -command ",
                                    "c:\\cfn\\scripts\\Rename-LCComputer.ps1 -ServerPrefix ",
                                    {"Ref" : "ServerPrefix"},
                                    " -DCIPAddress \"",
                                    { "Ref" : "ADServer1PrivateIP"
                                    },
                                    "\" -credentials ",
                                    "(New-Object System.Management.Automation.PSCredential('",
                                    {
                                        "Ref": "DomainNetBIOSName"
                                    },
                                    "\\",
                                    {
                                      "Ref" : "DomainAdminUserName"
                                    },
                                    "',",
                                    "(ConvertTo-SecureString '",
                                    {
                                        "Ref": "DomainAdminPassword"
                                    },
                                    "' -AsPlainText -Force)))"
                                  ]]
                                },
                                "waitAfterCompletion": "forever"
                            }

The script takes a ServerPrefix value that is used to figure out the server name. It queries Active Directory for all servers that begin with the prefix and sorts them in descending order.  If no servers are found, it tacks on an 01 to the prefix and renames the server.  If existing servers are found, it takes the last one and adds one to it, e.g. is myservername03 is found, server is renamed myservername04.  The script will work up to 99 servers.  The rest of the Init changes the DNS servers for the network interface and joins the instance to the domain.  In order to access the S3 bucket, I have included an AWS::CloudFormation::Authentication section.

                "AWS::CloudFormation::Authentication" : {
                    "S3AccessCreds" : {
                        "type" : "S3",
                        "accessKeyId" : { "Ref" : "CfnKeys" },
                        "secretKey" : {"Fn::GetAtt" : ["CfnKeys", "SecretAccessKey"]},
                        "buckets" : [ { "Ref" : "BucketName" } ]
                    }
                }

Here is the full stack template:

And here is the PowerShell script:

Share and enjoy!

2 thoughts on “AutoScale Groups with Domain Join

  1. Hi
    What is the difference /pro/cons/security etc about doing this, this way or this way;

    https://aws.amazon.com/blogs/security/how-to-configure-your-ec2-instances-to-automatically-join-a-microsoft-active-directory-domain/

    Thanks for posting this. After a year on AWS, Im still struggling with the automation part. I can create and do things through the AWS estate, but automating things im find hard.

    Do you have any resources that are really easy and simple and will help me learn?

    thanks
    Steve

    1. Hi Steve,
      I agree that AWS automation can be confusing at best, especially if you don’t come from a development background. I like the approach describe in the blog post. The downside is that it relies on another AWS service (SSM), rather than using the features available by default in EC2. That means you might find yourself constrained by the way in which SSM forces you to operate. Probably not a big deal, but worth thinking about.

      In terms of good sources for automation, if you are looking to automate provisioning of resources, then I would definitely look into Terraform. It does a great job of creating and destroying resources in a declarative way. For ongoing administration of instances, you could leverage something like Puppet or Desired State Configuration, both of which have good tutorials to get you started. If you are looking for a way to automate AWS components, then you could leverage Cloud Watch and Lamba. Cloud Watch monitors events in AWS, and can trigger actions based on those events. Lamba provides functions as a service, and can be triggered by Cloud Watch. Putting those two together lets you automate a lot of activity within AWS. There’s a ton of examples for that both on GitHub and in the AWS docs.

      Hopefully that helps!

Leave a Reply

Your email address will not be published. Required fields are marked *