Skip to content

Commit

Permalink
Merge pull request #17 from Coalfire-CF/issue-16
Browse files Browse the repository at this point in the history
Major: added user-data example and updated README.md
  • Loading branch information
douglas-f authored Jan 31, 2024
2 parents b98abdf + 504d0da commit 190888d
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 18 deletions.
30 changes: 13 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,16 @@ module "ec2_test" {
### User Data

```hcl-terraform
user_data = [
{
path = {
folder_name = "linux",
file_name = "test.sh"
}
vars = {
test-var1 = "test1",
test-var2 = "test2"
}
},
...
]
user_data = templatefile("${path.module}/../../shellscripts/linux/ud-os-join-ad.sh", {
aws_region = var.aws_region
domain_name = local.domain_name
dom_disname = local.dom_disname
ou_env = var.lin_prod_ou_env
linux_admins_ad_group = var.linux_admins_ad_group
domain_join_user_name = var.domain_join_user_name
sm_djuser_path = "${var.ad_secrets_path}${var.domain_join_user_name}"
is_asg = "false"
})
```

### Security Groups
Expand Down Expand Up @@ -175,7 +171,7 @@ module "ad2" {

| Name | Source | Version |
|------|--------|---------|
| <a name="module_security_group"></a> [security\_group](#module\_security\_group) | github.com/Coalfire-CF/terraform-aws-securitygroup | n/a |
| <a name="module_security_group"></a> [security\_group](#module\_security\_group) | github.com/Coalfire-CF/terraform-aws-securitygroup | v1.0.0 |

## Resources

Expand Down Expand Up @@ -216,12 +212,12 @@ module "ad2" {
| <a name="input_egress_rules"></a> [egress\_rules](#input\_egress\_rules) | The list of rules for egress traffic. Required fields for each rule are 'protocol', 'from\_port', 'to\_port', and at least one of 'cidr\_blocks', 'ipv6\_cidr\_blocks', 'security\_groups', 'self', or 'prefix\_list\_sg'. Optional fields are 'description' and those not used from the previous list | <pre>list(object({<br> protocol = string<br> from_port = string<br> to_port = string<br> cidr_blocks = optional(list(string), [])<br> ipv6_cidr_blocks = optional(list(string), [])<br> prefix_list_ids = optional(list(string), [])<br> security_groups = optional(list(string), [])<br> self = optional(bool)<br> description = optional(string, "Managed by Terraform")<br> }))</pre> | `[]` | no |
| <a name="input_get_password_data"></a> [get\_password\_data](#input\_get\_password\_data) | Whether or not to allow retrieval of the local admin password | `bool` | `false` | no |
| <a name="input_global_tags"></a> [global\_tags](#input\_global\_tags) | a map of strings that contains global level tags | `map(string)` | n/a | yes |
| <a name="input_http_tokens"></a> [http\_tokens](#input\_http\_tokens) | Whether or not the metadata service requires session tokens, required=IMDSv2, optional=IMDSv1 | `any` | `"required"` | no |
| <a name="input_iam_policies"></a> [iam\_policies](#input\_iam\_policies) | A list of the iam policy ARNs to attach to the IAM role | `list(string)` | `[]` | no |
| <a name="input_iam_profile"></a> [iam\_profile](#input\_iam\_profile) | A variable to attach an existing iam profile to the ec2 instance(s) created | `string` | `""` | no |
| <a name="input_ingress_rules"></a> [ingress\_rules](#input\_ingress\_rules) | The list of rules for ingress traffic. Required fields for each rule are 'protocol', 'from\_port', 'to\_port', and at least one of 'cidr\_blocks', 'ipv6\_cidr\_blocks', 'security\_groups', 'self', or 'prefix\_list\_sg'. Optional fields are 'description' and those not used from the previous list | <pre>list(object({<br> protocol = string<br> from_port = string<br> to_port = string<br> cidr_blocks = optional(list(string), [])<br> ipv6_cidr_blocks = optional(list(string), [])<br> prefix_list_ids = optional(list(string), [])<br> security_groups = optional(list(string), [])<br> self = optional(bool)<br> description = optional(string, "Managed by Terraform")<br> }))</pre> | `[]` | no |
| <a name="input_instance_count"></a> [instance\_count](#input\_instance\_count) | Number of instances to launch | `number` | `1` | no |
| <a name="input_keys_to_grant"></a> [keys\_to\_grant](#input\_keys\_to\_grant) | A list of kms keys to grant permissions to for the role created. | `list(string)` | `[]` | no |
| <a name="input_module_depends_on"></a> [module\_depends\_on](#input\_module\_depends\_on) | A variable to simulate the depends on feature that resources have | `any` | `null` | no |
| <a name="input_name"></a> [name](#input\_name) | The name of the ec2 instance | `string` | n/a | yes |
| <a name="input_private_ip"></a> [private\_ip](#input\_private\_ip) | The private ip for the instance | `string` | `null` | no |
| <a name="input_root_volume_size"></a> [root\_volume\_size](#input\_root\_volume\_size) | The size of the root ebs volume on the ec2 instances created | `string` | n/a | yes |
Expand All @@ -231,7 +227,7 @@ module "ad2" {
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | A list of the subnets to be used when provisioning ec2 instances. If instance count is 1, only the first subnet will be used | `list(string)` | n/a | yes |
| <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no |
| <a name="input_target_group_arns"></a> [target\_group\_arns](#input\_target\_group\_arns) | A list of aws\_alb\_target\_group ARNs, for use with Application Load Balancing | `list(string)` | `[]` | no |
| <a name="input_user_data"></a> [user\_data](#input\_user\_data) | The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user\_data\_base64 instead | `string` | `null` | no |
| <a name="input_user_data"></a> [user\_data](#input\_user\_data) | The User Data script to run | `string` | `null` | no |
| <a name="input_user_data_base64"></a> [user\_data\_base64](#input\_user\_data\_base64) | Can be used instead of user\_data to pass base64-encoded binary data directly. Use this instead of user\_data whenever the value is not a valid UTF-8 string. For example, gzip-encoded user data must be base64-encoded and passed via this argument to avoid corruption | `string` | `null` | no |
| <a name="input_user_data_replace_on_change"></a> [user\_data\_replace\_on\_change](#input\_user\_data\_replace\_on\_change) | When used in combination with user\_data or user\_data\_base64 will trigger a destroy and recreate when set to true. Defaults to false if not set | `bool` | `null` | no |
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | The id of the vpc where resources are being created | `string` | n/a | yes |
Expand Down
11 changes: 11 additions & 0 deletions examples/simple/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ module "ec2_test" {
ec2_key_pair = "ec2-module-test"
ebs_kms_key_arn = aws_kms_key.ebs_key.arn

user_data = templatefile("${path.module}/../../shellscripts/linux/ud-os-join-ad.sh", {
aws_region = var.aws_region
domain_name = local.domain_name
dom_disname = local.dom_disname
ou_env = var.lin_prod_ou_env
linux_admins_ad_group = var.linux_admins_ad_group
domain_join_user_name = var.domain_join_user_name
sm_djuser_path = "${var.ad_secrets_path}${var.domain_join_user_name}"
is_asg = "false"
})

# EBS
ebs_volumes = [
{
Expand Down
124 changes: 124 additions & 0 deletions examples/simple/userdata/ud-os-join-ad.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/bin/bash -xe
# Send the following variables from terraform:
#${aws_region}
#${is_asg} - True/False - Auto Scaling Group?
#${domain_name} - Domain Name: example.com
#${dom_disname} - dc=local,dc=fastramp,dc=internal
#${ou_env} - OU=Linux,OU=Production,OU=Management,OU=Servers
#${linux_admins_ad_group} - linuxadmins
#${domain_join_user_name} - svc_dj
#${sm_djuser_path} - /production/mgmt/ad/svc_dj

set -o nounset

linux_admins_ad_group=${linux_admins_ad_group}

# Get IMDSv2 token
TOKEN=$(curl -X PUT 'http://169.254.169.254/latest/api/token' -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')

# IF ASG is set - tag instanceID to compname and append, then check for 15 char length
instanceID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s "http://169.254.169.254/1.0/meta-data/instance-id")
hostname="$(aws ec2 describe-tags --region ${aws_region} --filters "Name=resource-id, Values=$instanceID" "Name=key, Values=Name" | jq -r '.Tags[].Value')"

# Truncate hostname to meet AD 15-char. requirement
if ${is_asg} ; then
instanceIDAppnd=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s "http://169.254.169.254/1.0/meta-data/instance-id" | cut -c 3-)
hostname=$(echo "$hostname" | cut -c1-6)
hostname="$hostname-$instanceIDAppnd"
hostname=$(echo "$hostname" | cut -c1-15)
else
hostname=$(echo "$hostname" | cut -c1-15)
fi

# Set hostname via hostnamectl set-hostname
echo "Setting hostname to $hostname.${domain_name}"
hostnamectl set-hostname "$hostname"."${domain_name}" --no-ask-password

# Set the new host name in /etc/hosts or rsyslog doesn't log new hostname.
sed -i "/127.0.0.1/i\127.0.0.1 $hostname $hostname.${domain_name}" /etc/hosts

# Bounce rsyslogd to grab the new hostname.
systemctl restart rsyslog

# Set default realm for Ubuntu
domain_upper=$(echo "${domain_name}" | tr '[:lower:]' '[:upper:]')
if [[ $(uname -a | tr '[:upper:]' '[:lower:]') =~ "ubuntu" ]]; then
cat > /etc/krb5.conf <<EOF
[libdefaults]
default_realm = $domain_upper
[realms]
[domain_realm]
EOF
chmod 0644 /etc/krb5.conf
fi

sm_fips_endpoint="https://secretsmanager-fips.${aws_region}.amazonaws.com"

# Use realm to join the AD domain.
for i in {1..10}
do
echo `aws secretsmanager get-secret-value --secret-id "${sm_djuser_path}" --region ${aws_region} --endpoint-url $sm_fips_endpoint | jq -r '.SecretString'`| realm join --membership-software=adcli --user=${domain_join_user_name} ${domain_name} --computer-ou="${ou_env},${dom_disname}"
if [[ ! -z "$(realm list)" ]];
then
echo "Realm joined successfully!"
break
else
echo "Realm join failed, retrying $i out of 10 times."
sleep 60s
cat /etc/resolv.conf
fi
if [[ $i == 10 ]];
then
if [[ ! -z "$(realm list)" ]];
then
echo "Realm join failed after 10 tries, exiting with code 1"
exit 1
fi
fi
done

# Modify sssd.conf to create <username> only directories.
sed -i -e 's/use_fully_qualified_names = True/use_fully_qualified_names = False/g' /etc/sssd/sssd.conf
sed -i -e 's+fallback_homedir = /home/%u@%d+fallback_homedir = /home/%u+g' /etc/sssd/sssd.conf

# Loosen up the permissions so sssd functions properly.
chmod 755 /var/lib/sss
chmod 755 /var/lib/sss/mc
chmod 755 /var/lib/sss/pipes/

# Ensure dynamic DNS updates are enabled.
echo "dyndns_update = True" >> /etc/sssd/sssd.conf
echo "dyndns_update_ptr = True" >> /etc/sssd/sssd.conf

# Allow domain users to get/create a home directory upon login
if [[ $(uname -a | tr '[:upper:]' '[:lower:]') =~ "ubuntu" ]]; then
pam-auth-update --enable mkhomedir --force
fi

# Restart the daemon and SSHD.
systemctl daemon-reload
systemctl restart sssd

# Update sshd_config to allow password authentication from windows domain.
sed -i -e 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sed -i -e 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sed -i -e 's/#PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config

# Shutoff GSSAPIAuthentication for SSH
sed -i -e 's/GSSAPIAuthentication yes/GSSAPIAuthentication no/g' /etc/ssh/sshd_config
sed -i -e 's/#GSSAPIAuthentication no/GSSAPIAuthentication no/g' /etc/ssh/sshd_config

# Allow the windows AD group to log in
realm permit -g ${linux_admins_ad_group}@${domain_name}

# Allow windows group permissions
echo "%$linux_admins_ad_group ALL=(ALL) ALL" | tee -a /etc/sudoers > /dev/null

# Reload sshd config.
systemctl reload sshd

#STIG Fix for and AD
#sed -i 's/.*optional/#&/' /etc/pam.d/sudo
#sed -i 's/.*required/#&/' /etc/pam.d/sudo

[[ -z $(grep root /etc/cron.allow) ]] && echo "root" >> /etc/cron.allow
2 changes: 1 addition & 1 deletion variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ variable "source_dest_check" {
}

variable "user_data" {
description = "The user data to provide when launching the instance. Do not pass gzip-compressed data via this argument; see user_data_base64 instead"
description = "The User Data script to run"
type = string
default = null
}
Expand Down

0 comments on commit 190888d

Please sign in to comment.