Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Active Directory with seamless Windows EC2 join (from ASG) #253

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

### Examples

* `ad-ec2`: Demonstrate how an Windows EC2 instance seamlessly joins
an Active directory when it gets newly spawned.
* `ad-asg`: Demonstrate how an Windows EC2 instance from an ASG
seamlessly joins an Active directory when it gets newly spawned.


# v0.9.7

Expand Down Expand Up @@ -103,7 +108,6 @@
* Drop deprecated nexus-asg example
* Fixes for VPC Scenario 2 example


### Modules

* `iam-instance-profile`: Add new module, abstract the usage pattern of IAM instance profile.
Expand All @@ -114,7 +118,6 @@
* `persistent-ebs`: update to use new `iam-instance-profile` module and sync with recent updates to the `single-node-asg` module
* `asg`: parametize target group and health check type


### Examples

* `nexus-asg`: Drop deprecated example.
Expand All @@ -123,6 +126,7 @@
* Improve `Makefile`
* Set name tag on ELB


# v0.9.2

### Summary
Expand Down
74 changes: 74 additions & 0 deletions examples/ad-asg/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.PHONY: init ssh-key plan-vpc plan-subnets plan-gateway plan apply destroy clean

.DEFAULT_GOAL = help

# Hardcoding value of 3 minutes when we check if the plan file is stale
STALE_PLAN_FILE := `find "tf.out" -mmin -3 | grep -q tf.out`

## Check if tf.out is stale (Older than 2 minutes)
check-plan-file:
@if ! ${STALE_PLAN_FILE} ; then \
echo "ERROR: Stale tf.out plan file (older than 3 minutes)!"; \
exit 1; \
fi

## Runs terraform get and terraform init for env
init:
@terraform get
@terraform init

## Create ssh key
ssh-key:
@ssh-keygen -q -N "" -b 4096 -C "SSH key for vpc-scenario-1 example" -f ./id_rsa

## use 'terraform plan' to 'target' the vpc in the vpc module
plan-vpc:
@terraform plan \
-target="module.vpc.module.vpc" \
-out=tf.out

## use 'terraform plan' to 'target' the public subnets in the vpc module
plan-subnets:
@terraform plan \
-target="module.vpc.module.public-subnets" \
-out=tf.out

## use 'terraform plan' to 'target' the public gateway in the vpc module
plan-gateway:
@terraform plan \
-target="module.vpc.module.public-gateway" \
-out=tf.out

## use 'terraform plan' to map out updates to apply
plan:
@terraform plan -out=tf.out

## use 'terraform apply' to apply updates in a 'tf.out' plan file
apply: check-plan-file
@terraform apply tf.out

## use 'terraform destroy' to remove all resources from AWS
destroy:
@terraform destroy

## rm -rf all files and state
clean:
@rm -f tf.out
@rm -f id_rsa
@rm -f id_rsa.pub
@rm -f terraform.tfvars
@rm -f terraform.*.backup
@rm -f terraform.tfstate

## Show help screen.
help:
@echo "Please use \`make <target>' where <target> is one of\n\n"
@awk '/^[a-zA-Z\-\_0-9]+:/ { \
helpMessage = match(lastLine, /^## (.*)/); \
if (helpMessage) { \
helpCommand = substr($$1, 0, index($$1, ":")); \
helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
printf "%-30s %s\n", helpCommand, helpMessage; \
} \
} \
{ lastLine = $$0 }' $(MAKEFILE_LIST)
156 changes: 156 additions & 0 deletions examples/ad-asg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Active Directory with seamless Windows EC2 join (from ASG)

The terraform code is built on top of
[vpc-scenario1](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Scenario1.html)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would VPC Scenario 2 work here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ketzacoatl It will work, but it will require some slight modifications.

with two additional private subnets and a NAT gateway on a public
subnet. This example demonstrate how an Windows EC2 instance present
in
[ASG](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html)
seamlessly joins an Active directory when it gets newly spawned. The
only difference between this example and the [ad-ec2](../ad-ec2) is
that this example uses ASG.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include all of the EC2/ASG/windows/linux variants in one single example? and do the "across VPCs" in a second example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel having different example more clearly explains the differences. But I don't have strong opinion on that.


## Environment creation and deployment

To use this example set up AWS credentials and then run the commands in the
following order:

```
make ssh-key
make init
make plan-vpc
make apply
make plan-subnets
make apply
make plan-gateway
make apply
make plan
make apply
```

## Execution

Once you run the above commands, you will get an output like this:

``` shellsession
...
module.nat-gateway.aws_route_table_association.private-rta[0]: Refreshing state... [id=rtbassoc-0be4f2c71ef12e768]
module.nat-gateway.aws_route_table_association.private-rta[1]: Refreshing state... [id=rtbassoc-08a1f878abab73841]
aws_ssm_association.associate_ssm: Refreshing state... [id=996ff9a8-0931-4000-85aa-d01ef536f5a7]


Outputs:

asg-name = test-ad-project-asg-cluster20190919093341776000000005
microsoft-ad_dns_ip_addresses = [
"10.23.21.134",
"10.23.22.45",
]
microsoft-ad_dns_name = dev.fpcomplete.local
```

## Testing

You need to test that the Windows EC2 instance actually joined the
Active directory. There are two ways to test it:

* RDP to your instance and verify
* RDP using Active Directory authentication

### Method 1

On a Linux client machine, something like
[remmina](https://remmina.org) can be used to RDP into your Windows
EC2 instance. You need to fill three information in the Remmina client
to successfully RDP:

* Server: You can go and find the instance IP address using the
`asg-name` from the above output. This can be done either via AWS
Console or use the `aws` cli tool.
* User name: Administrator
* User password: The password you used with the variable named
`admin_password` in `variables.tf`.

![Remmina settings](./assets/remmina-settings1.png)

Note that if you try to take the password from the AWS Console using
your SSH private key, that won't work as it has been overridden using
[bootstrap.win.txt](./bootstrap.win.txt).

Once you connect into the instance, you need to check the properties
of your machine there:

![System Properties](./assets/system-properties.png)

If you have a `Domain:` entry there, then that means the instance has
successfully joined the Active directory. Instead, if you have an
entry that starts with `Workgroup:` then your device is not joined to an
Active Directory.

### Method 2

In this method, you again try to RDP via the Active directory
credentials. When you create a directory with AWS Managed Microsoft
AD, it will create a directory administrator account with the user
name `Admin` and the specified password (which you supplied through
terraform). Let's again use Remmina to fill the following four
information:

* Server: You can go and find the instance IP address using the
`asg-name` from the above output. This can be done either via AWS
Console or use the `aws` cli tool.
* User name: Admin
* User password: The password you used with the variable named
`active_directory_password` in `variables.tf`.
* Domain: The domain name which you passed in the `locals.tf`. For
this example, it is `dev.fpcomplete.local`.

![Remmina settings](./assets/remmina-settings2.png)

If it's able to successfully connect to the instance, you can confirm
that the EC2 instance has actually joined the AD. You can further verify that you have actually logged in via Active directory through the following steps:

* Start the "CMD" program.
* Type "set user".
* You will receive a output from the above command. Look at the line
start with `USERDOMAIN:` entry. If it contains your computer's name,
then you're logged in to the computer. If it contains the Active
Directory's name, you're logged in to the Active Directory. In our
case this is the output we receive which confirms that we are logged
in via AD:

``` shellsession
C:\Users\Admin>set user
USERDNSDOMAIN=DEV.FPCOMPLETE.LOCAL
USERDOMAIN=dev
USERDOMAIN_ROAMINGPROFILE=dev
USERNAME=Admin
USERPROFILE=C:\Users\Admin
```

## Destruction

To destroy the test environment run the following commands:

```
$ make destroy
$ make clean
```

## Debugging

The script execution using `user_data` is usually hard to debug. In
our [bootstrap script](./bootstrap.win.txt), we use
[Start-Transcript](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.host/start-transcript?view=powershell-6)
to create a record of the powershell session to a text file. For the
above launched instances, it is present in the following location:

```
C:\Users\Administrators\Documents
```

## Reference

* [AWS docs on AWS Managed Microsoft AD](https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_getting_started.html)
* [AWS docs on Joining an EC2 instance](https://docs.aws.amazon.com/directoryservice/latest/admin-guide/ms_ad_join_instance.html)
* [AWS docs on Systems manager and AD](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-systems-manager-dx-domain/)
Binary file added examples/ad-asg/assets/remmina-settings1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/ad-asg/assets/remmina-settings2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/ad-asg/assets/system-properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions examples/ad-asg/bootstrap.win.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<powershell>
Start-Transcript

# Set administrator password
net user Administrator "${admin_password}"
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE

# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block

# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null

# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'

# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force

# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM

# Associate SSM document for domain joining
$iid = (New-Object System.Net.WebClient).DownloadString("http://169.254.169.254/latest/meta-data/instance-id")
New-SSMAssociation -InstanceId $iid -Name "${ssm_document_name}"
</powershell>
5 changes: 5 additions & 0 deletions examples/ad-asg/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
locals {
stage = "dev"
base_domain = "fpcomplete.local"
domain = "${local.stage}.${local.base_domain}"
}
Loading