top of page
Writer's pictureTenaka

Deploying Windows Domains as an EC2 Instance with PowerShell - Part 2

Updated: Oct 8

Welcome to Part 2! Let's take a deep dive into the specifics of what the DeployVPCwithDomain.ps1 script creates in AWS.


Here's a quick recap, a public-facing Remote Desktop Server (RDS) and a private Domain Controller (DC) will be deployed into AWS with all the required AWS infrastructure and services using PowerShell.


If you haven't read Part 1, I strongly suggest you do and ensure all the prerequisites are fulfilled, otherwise, it's likely to get messy.


To reiterate, deploying this will incur AWS costs, the instance type is t3.medium and the volume is set to $ebsVolType = "io1" and $ebsIops = 1000


Prerequisites

  • PowerShell version 7 or Visual Code Studio is required

  • An AWS Account and its corresponding Access ID and Secret Key.

  • The AWS account requires the AdministratorAccess' role or delegated permissions.

  • A basic understanding of both AWS and Windows Domains


This blog will focus on the execution of the script and the provisioning of the AWS services, including the configuration of the VPC, subnets, and security groups and the deployment of EC2 instances.


You’ll also see how the script sets up a fully functional Active Directory environment, complete with a domain controller, OU, Delegation and GPO configuration.


Let's Get Started!

Let's begin by loading DeployVPCwithDomain.ps1 in Visual Studio Code with elevated rights. I normally 'Ctrl + A' and then press F8 to execute the script, equally F5 works.


The script starts by installing the necessary AWS PowerShell modules from PowerShell Gallery.


Loading the modules can be problematic. If any of the modules fail, the script should catch the error. I suggest closing VSC, deleting the modules from "C:\Users\%username%\Documents\PowerShell\Modules\", and then restart the script from VCS.


Access Key and Secret Access Key

Enter both the Access Key and Secret Key created for the service account.


Regions

The script sets the default AWS region using `Set-defaultAWSRegion -Region $region1`, and this region is also hardcoded in the userdata script for both S3 and EC2 instances.

$region1 = "us-east-1"   #this is hardcoded in the ec2 userdata script
Set-defaultAWSRegion -Region $region1

VPC

The VPC is configured with the following CIDR block: `$cidr = "10.1.1"` and `$cidrFull = "$($cidr).0/24"`. This CIDR block specifies the VPC's address range, providing 254 usable IP addresses.

$cidr = "10.1.1"
$cidrFull = "$($cidr).0/24"
$newVPC = New-EC2vpc -CidrBlock "$cidrFull" 
$vpcID = $newVPC.VpcId

Subnets

Two subnets, each with 30 usable addresses will be created from the VPC: one for public access and one for private use.

$Ec2subnetPub = New-EC2Subnet -CidrBlock "$($cidr).0/27"  -VpcId $vpcID
$Ec2subnetPriv = new-EC2Subnet -CidrBlock "$($cidr).32/27"  -VpcId $vpcID

Internet Gateway

An Internet Gateway enables communication between your VPC and the Internet by acting as a bridge, allowing instances within your VPC to send and receive traffic from the Internet.

$Ec2InternetGateway = New-EC2InternetGateway
$InterGatewayID = $Ec2InternetGateway.InternetGatewayId

Add-EC2InternetGateway -InternetGatewayId $InterGatewayID -VpcId $vpcID   

Public and Private Route Tables

To enable internet access for your VPC's public subnet, you'll need to create a route table and configure it to direct traffic to the Internet Gateway.

$Ec2RouteTablePub = New-EC2RouteTable -VpcId $vpcID 

New-EC2Route -RouteTableId $Ec2RouteTablePub.RouteTableId -DestinationCidrBlock "0.0.0.0/0" -GatewayId $InterGatewayID

Register-EC2RouteTable -RouteTableId $Ec2RouteTablePubID -SubnetId $SubPubID         

Public IP

`Invoke-WebRequest`, fetches your public IP address by querying `ifconfig.me/ip`. If the request fails or returns an empty value, it defaults to "10.10.10.10".

$whatsMyIP = (Invoke-WebRequest ifconfig.me/ip).Content.Trim() 
  
if ([string]::IsNullOrWhiteSpace($whatsMyIP) -eq $true){$whatsMyIP = "10.10.10.10"}

If the Jump box becomes inaccessible and unless your public IP is static, it's likely to change, making it necessary to update the public security group.


Security Groups

This script creates 2 security groups within a specified VPC. The PublicSubnet security group to manages traffic rules for public subnet instances.

$SecurityGroupPub = New-EC2SecurityGroup -Description "Public Security Group" -GroupName "PublicSubnet" -VpcId $vpcID -Force -errorAction Stop

The script defines inbound and outbound rules for a security group.

#Inbound Rules
$InTCPWhatmyIP3389 = @{IpProtocol="tcp"; FromPort="3389"; ToPort="3389"; IpRanges="$($whatsMyIP)/32"}
#Outbound Rules
$EgAllCidr = @{IpProtocol="-1"; FromPort="-1"; ToPort="-1"; IpRanges=$cidrFull}

`Grant-EC2SecurityGroupIngress applies inbound rules to the defined security group.

Grant-EC2SecurityGroupIngress -GroupId $SecurityGroupPub -IpPermission @($InTCPWhatmyIP3389)

S3 Bucket

An S3 bucket is created to host the AD script.

$news3Bucket = New-S3Bucket -BucketName "auto-domain-create-$($dateTodayMinutes)" 

$s3BucketName = $news3Bucket.BucketName
$S3BucketARN = "arn:aws:s3:::$($s3BucketName)"

$s3Url = "https://$($s3BucketName).s3.amazonaws.com/Domain/"

S3 Bucket Access

To grant EC2 instance access to the S3 bucket for running the AD script, a new IAM user is created.

$s3User = "DomainCtrl-S3-READ"
$newIAMS3Read = New-IAMUser -UserName $s3User 

A new access key for the specified IAM user is generated and written into the UserData allowing the EC2 instance access to securely authenticate and access the S3 bucket.

$newIAMAccKey = New-IAMAccessKey -UserName $newIAMS3Read.UserName
 
$iamS3AccessID = $newIAMAccKey.AccessKeyId
$iamS3AccessKey = $newIAMAccKey.SecretAccessKey

The following IAM Group is created and the IAM user added to the group.

$s3Group = 'S3-AWS-DC'
New-IAMGroup -GroupName 'S3-AWS-DC' 

Add-IAMUserToGroup -GroupName $s3Group -UserName $s3User

The policy for read access to the S3 bucket is defined.

$s3Policy = @'
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*",
                "s3:Describe*"
            ],
            "Resource": "*"
        }
    ]
}
'@

The IAM policy is created and added to the above group.

$iamNewS3ReadPolicy = New-IAMPolicy -PolicyName 'S3-DC-Read' -Description 'Read S3 from DC' -PolicyDocument $s3Policy

Register-IAMGroupPolicy -GroupName $s3Group -PolicyArn $iamNewS3ReadPolicy.Arn
     

VPC Endpoint

A VPC endpoint, which allows resources within your VPC to privately connect to AWS services without needing an internet gateway is created to allow the Private EC2 instance to access the S3 Bucket.

$newEnpointS3 = New-EC2VpcEndpoint -ServiceName "com.amazonaws.us-east-1.s3" -VpcEndpointType Gateway -VpcId $vpcID -RouteTableId $Ec2RouteTablePubID, $Ec2RouteTablePrivID

UserData Scripts

EC2 Userdata provides commands automatically to the instance at its initial launch and at first boot. In this case, the PowerShell script changes the default AWS assigned password to 'ChangeMe1234' and renames the EC2 instance to JUMPBOX1 for the Public instance.

$RDPScript = 
'<powershell>
Set-LocalUser -Name "administrator" -Password (ConvertTo-SecureString -AsPlainText ChangeMe1234 -Force)
Rename-Computer -NewName "JUMPBOX1"    
shutdown /r /t 10
</powershell>'

The PowerShell script for EC2 instance Userdata is encoded in Base64 because AWS requires userdata to be in this format.

$RDPUserData = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($RDPScript))

EC2 Encrypted Volumes

EC2 encrypted volumes use AWS Key Management Service (KMS) to automatically encrypt data at rest, in transit between the instance and the volume, and during snapshots. This ensures that all data on the volume is securely protected, with encryption keys managed by AWS.


To enable EC2 encrypted volumes, KMS permissions must be granted in IAM, and the following values will be specified.

    $ebsVolType = "io1"
    $ebsIops = 2000
    $ebsTrue = $true
    $ebsFalse = $false
    $ebskmsKeyArn = $newKMSKey.Arn
    $ebsVolSize = 50

    $blockDeviceMapping = New-Object Amazon.EC2.Model.BlockDeviceMapping
    $blockDeviceMapping.DeviceName = "/dev/sda1"
    $blockDeviceMapping.Ebs = New-Object Amazon.EC2.Model.EbsBlockDevice
    $blockDeviceMapping.Ebs.DeleteOnTermination = $enc
    $blockDeviceMapping.Ebs.Iops = $ebsIops
    $blockDeviceMapping.Ebs.KmsKeyId = $ebsKmsKeyArn
    $blockDeviceMapping.Ebs.Encrypted = $ebsTrue
    $blockDeviceMapping.Ebs.VolumeSize = $ebsVolSize
    $blockDeviceMapping.Ebs.VolumeType = $ebsVolType

EC2 Instance Attributes

The New-EC2Instance command and the following configuration parameters are declared to deploy and manage the EC2 instances in AWS.

$new2022InstancePub = New-EC2Instance `
    -ImageId $gtSrv2022AMI.value `
    -MinCount 1 -MaxCount 1 `
    -KeyName $newKeyPair.KeyName `
    -SecurityGroupId $SecurityGroupPub `
    -InstanceType t3.medium `
    -SubnetId $SubPubID `
    -UserData $RDPUserData `
    -BlockDeviceMapping $blockDeviceMapping

Accessing the Jump Box

The public RDP jump box, accessible only from your public IP, will launch quickly. Retrieve the instance's public IP from the AWS EC2 page, type 'mstsc' at the Run command, and enter the IP. Be sure to wait for the instance to fully initialize before connecting.


Enter 'Administrator' and the password 'ChangeMe1234', once logged on, change the password to something more secure.


Accessing the Domain Controller

The Domain Controller will take some time to deploy, even after it shows as Running on the EC2 page. It undergoes a few reboots and runs scripts to install AD roles, create an OU structure, delegate access, and set up the GPOs. It's a good time to grab a coffee and take a 10-minute break.


Once you've finished your coffee, retrieve the Domain Controller's private IP, based on the VPC Private Subnet, from within the AWS EC2 page. Then, from within the Jump box, launch 'mstsc' and enter the Domain Controller's IP.


The FQDN for the domain is 'testdom.loc'.


Enter 'Administrator' and the password 'ChangeMe1234'. To update the password, open 'Active Directory Users and Computers', find the 'Administrator' account, and reset the password.


OU Structure

A comprehensive OU structure with GPOs, URA, and Restricted and Nested Groups is deployed in a tiered model. It's too involved to cover here, but a full description can be found @ https://www.tenaka.net/post/deploy-domain-with-powershell-and-json-part-2-ou-delegation


JSON

The script deployed for AWS is a slightly modified version of the original. Similarly, it is tied to the hostname of the Domain Controller, which is hardcoded as 'AWSDC01' in both the UserData and the JSON file.


The other modification involves the IP address. The IP section in the JSON file is ignored, with the Domain Controller being statically assigned the IP provided by AWS's DHCP server.

{
    "FirstDC":
        {
        "PDCName":"AWSDC01",
        "PDCRole":"true",
        "IPAddress":"10.0.2.69",
        "Subnet":"255.255.255.0",
        "DefaultGateway":"10.0.2.1",
        "CreateDnsDelegation":"false", 
        "DatabasePath":"c:\\Windows\\NTDS", 
        "DomainMode":"WinThreshold", 
        "DomainName":"testdom.loc", 
        "DomainNetbiosName":"TESTDOM", 
        "ForestMode":"WinThreshold", 
        "InstallDns":"true", 
        "LogPath":"c:\\Windows\\NTDS", 
        "NoRebootOnCompletion":"false", 
        "SysvolPath":"c:\\Windows\\SYSVOL", 
        "Force":"true",
        "DRSM":"Recovery1234",
        "DomAcct":"Administrator",
        "DomPwd":"ChangeMe1234",
        "PromptPw":"false"
        },

Finally.....

These two posts only scratch the surface of deploying Active Directory on AWS with PowerShell. Additional AD Sites, VPN's, AWS Transit Gateways and AD integration into AWS are some of the topics I hope to cover in the future.


For now, thank you for taking the time to read my blog; I truly appreciate it. I hope you found it useful.











6 views0 comments

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page