Hardened CIS AWS AMI

I had a need to create a hardened (CIS) base image for a project.The AMI created from this project was not used directly, but rather as a base image to other application-specific Stacks.

The following is a Packer file:

{
    "variables": {
        "aws_access_key": "",
        "aws_secret_key": "",
        "aws_region": "{{ env `AWS_DEFAULT_REGION` }}",
        "bitbucket_branch": "",
        "bitbucket_buildnumber": "",
        "bitbucket_commit": "",
        "bootstrap_commit": ""
    },
    "builders": [{
        "type": "amazon-ebs",
        "access_key": "{{user `aws_access_key`}}",
        "secret_key": "{{user `aws_secret_key`}}",
        "region": "ap-southeast-2",
        "vpc_id": "vpc-1cceEXAMPLE8178",
        "subnet_id": "subnet-b8fEXAMPLE1e0dc",
        "associate_public_ip_address": true,
        "ssh_private_ip": false,
        "ssh_pty": true,
        "source_ami": "ami-240EXAMPLEef546",
        "instance_type": "c4.large",
        "ssh_username": "ec2-user",
        "ami_name": "CIS-AWSLinux-Base {{isotime | clean_ami_name}}",
        "ami_users": ["<Account_Number>"],
        "launch_block_device_mappings":[{
          "device_name":"/dev/xvda",
          "volume_size":30,
          "volume_type":"gp2",
          "encrypted":false,
          "delete_on_termination":true
       }],
        "tags": {
            "OS_Version": "AWSLinux",
            "Name": "CIS-AWSLinux-Base",
            "Timestamp": "{{timestamp}}",
            "AMI_Branch": "{{user `bitbucket_branch`}}",
            "AMI_Build_Number": "{{ user `bitbucket_buildnumber`}}",
            "AMI_Commit": "{{ user `bitbucket_commit`}}",
            "Bootstrap_Commit": "{{ user `bootstrap_commit`}}"
        }
    }],
    "provisioners": [{
        "type": "shell",
        "inline": [
            "sleep 30",
            "sudo bash -c 'echo export PATH=\\$PATH:/opt/aws/bin/ >> /etc/profile'",
            "sudo yum clean all",
            "sudo yum -y update",
            "sudo yum -y install epel-release",
            "sudo yum install -y libselinux libselinux-utils libselinux-utils selinux-policy-minimum selinux-policy-mls selinux-policy-targeted policycoreutils awslogs",
            "sudo sh -c 'selinux=1 security=selinux enforcing=1'",
            "sudo sh -c 'touch /.autorelabel'",
            "sudo cp -prv /boot/grub/menu.lst /boot/grub/menu.lst.default",
            "sudo sed -i 's/selinux=0/selinux=1 security=selinux enforcing=1/g' /boot/grub/menu.lst",
            "sudo sed -i 's/selinux=0/selinux=1 security=selinux enforcing=1/g' /etc/grub.conf",
            "sudo yum -y install augeas git python36 python-pip36 bc unzip wget telnet jq nfs-utils",
            "sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm",
            "sudo pip install pystache argparse python-daemon requests",
            "sudo pip install --upgrade pbr",
            "sudo pip install --upgrade pip",
            "sudo mkdir /efs",
            "sudo chmod 755 /efs",
            "sudo sed -i 's/^server/#server/g' /etc/ntp.conf",
            "sudo sed -ie \"\\$aserver 169.254.169.123 prefer iburst\" /etc/ntp.conf",
            "sudo sed -i 's/daemon//g' /etc/init.d/ntpd",
            "sudo sed -i 's/$OPTIONS//g' /etc/init.d/ntpd",
            "sudo rm -f /root/.ssh/authorized_keys"
        ]
    }],
    "post-processors": [
        [{
            "type": "manifest",
            "output": "manifest.json",
            "strip_path": true
        }]
    ]
}

Build as follows:

packer build -var bitbucket_commit=${MAINCOMMIT} -var chef_commit=${CHEFCOMMIT} -var bootstrap_commit=${BOOTSTRAPCOMMIT} -var bitbucket_branch=${BITBUCKET_BRANCH} -var bitbucket_buildnumber=${BITBUCKET_BUILD_NUMBER} packer.json

Restore a Backup (SQL Server)

Prepare

ALTER DATABASE [DataBaseName] SET PARTNER OFF;

GO

ALTER DATABASE [DataBaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE

GO

DROP DATABASE DataBaseName;

GO ​ 

Backup

exec msdb.dbo.rds_restore_database 

@restore_db_name='SitecoreMaster', 

@s3_arn_to_restore_from='arn:aws:s3:::myinstallfiles/SitecoreMaster.bak';

Check Status

exec msdb.dbo.rds_task_status

Convert LetsEncrypt pem file to pfx

#!/bin/sh

pemsdir='/etc/letsencrypt/archive'      # default search PEMs
pfxspath='/share/letsencrypt/archive'   # dest of the PFXs
passfile='/share/letsencrypt/pass.txt'  # password to be applied to the PFX file

for cnvifull in `find "${pemsdir}" -name 'cert*.pem' -o -name '*chain*.pem'`
do

  cnvifile=${cnvifull##*/}
  cnvinum=`echo ${cnvifile%.*} | sed -e "s#[cert|chain|fullchain]##g"`
  cnvipkey="${cnvifull%/*}/privkey${cnvinum}.pem"

  cnvopem=`echo ${cnvifull} | sed -e "s#${pemsdir}#${pfxspath}#g"`
  cnvofull="${cnvopem%.*}.pfx"

  echo "- :-) ->"
  echo "-in    ${cnvifull}"
  echo "-inkey ${cnvipkey}"
  echo "-out   ${cnvofull}"

  mkdir -p ${cnvofull%/*}

  openssl pkcs12 \
    -export \
    -in ${cnvifull} \
    -inkey ${cnvipkey} \
    -out ${cnvofull} \
    -passout file:${passfile}

done

How to use Chef to add Proxy server configuration on a Windows Host:

How to use Chef to add Proxy server configuration on a Windows Host?

Following is a Chef recipe:

registry_key 'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings' do
  values [{:name => 'MigrateProxy', :type => :dword, :data => '00000001'},
          {:name => 'ProxyEnable', :type => :dword, :data => '00000001'},
          {:name => 'ProxyHttp1.1', :type => :dword, :data => '00000000'},
          {:name => 'ProxyServer', :type => :dword, :data => 'http://proxy.mgt.example.au:3128'},
          {:name => 'ProxyOverride', :type => :dword, :data => '<local>'}
         ]
  action :create
end 

Version a CloudFormation template

Any suggestions to version the CloudFormation template? I suggested to use the description field:

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "4.5.180 - General Platform Resources",
  "Metadata" : {
  },
  "Parameters" : {
    "PlatformParameter" : {
        "Type" : "String",
        "Description" : "Platform Environment"
    },
    "VPCId" : {
      "Type" : "String",
      "Description" : "Select Platform to Deploy to."
    },
    "DBDataTierA" : {
      "Type" : "String",
      "Description" : "AZ A - Web Tier"
    },
    "DBDataTierB" : {
      "Type" : "String",
      "Description" : "AZ B - Web Tier"
    }
  },

Stop/Start RDS Instance

RDS Managed Instances are one of the top expends in AWS.

Stop/Start RDS Instance script in Python, provide a Tag "Shutdown" and "StartUp". You can program this via a Lambda handler.

from __future__ import print_function
import boto3
from datetime import datetime, timedelta

prof_name = ""   # Profile Name Identifier
acc_number = "" # AWS Account Number

#boto3.setup_default_session(profile_name=prof_name)

def rds_start(list_instances):

    for instance in list_instances:
        this_inst = instance.split(",")
        db_instance_id = this_inst[0]
        environment = this_inst[1]
        print ("{0} (UTC): Starting Instance '{1}' from Environment '{2}'".format(datetime.utcnow(), db_instance_id, environment))
        boto3.client('rds').start_db_instance(DBInstanceIdentifier=db_instance_id)

def rds_stop(list_instances):

    for instance in list_instances:
        this_inst = instance.split(",")
        db_instance_id = this_inst[0]
        environment = this_inst[1]
        print("{0} (UTC): Stopping Instance '{1}' from Environment '{2}'".format(datetime.utcnow(), this_inst[0], this_inst[1]))
        boto3.client('rds').stop_db_instance(DBInstanceIdentifier=db_instance_id)

def lambda_handler(event, context):

    rds = boto3.client('rds')

    instances_to_start = []
    instances_to_stop = []

    # Date calculation
    date = datetime.utcnow() + timedelta(hours=10)

    current_hour = date.hour
    current_day = date.weekday()

    print("Running State Change Script for hour {0} on day {1}".format(current_hour, current_day))

    try:
        # get all of the db instances
        dbs = rds.describe_db_instances()

        for db in dbs['DBInstances']:
            #print("--------------------------------------------")
            print("Checking RDS Instance: {0} {1} {2} {3} {4}".format(db['DBInstanceIdentifier'], db['MasterUsername'], db['Endpoint']['Address'], db['Endpoint']['Port'], db['DBInstanceStatus']) )

            arn = "arn:aws:rds:ap-southeast-2:" + acc_number + ":db:" + db['DBInstanceIdentifier']
            # print("{0}".format(arn))

            tags = rds.list_tags_for_resource(ResourceName=arn)
            # print (tags)

            instance_id = db['DBInstanceIdentifier']
            current_status = db['DBInstanceStatus']
            environment = ""
            startup = ""
            shutdown = ""

            for tg in tags['TagList']:

                if tg['Key'] == 'Environment':
                    environment = tg['Value']

                if tg['Key'] == 'StartUp':
                    startup = tg['Value']

                if tg['Key'] == 'Shutdown':
                    shutdown = tg['Value']

            if environment == "PROD": # Skip prod
                if startup != "":
                    print("Skipping Production RDS Instance. Do not assign StartUp/Shutdown Tags to PROD instances.")

                if shutdown != "":
                    print("Skipping Production RDS Instance. Do not assign StartUp/Shutdown Tags to PROD instances.")

                continue

            if startup != "":
                startup_schedule =  startup.split(" ")
                print("StartUp:  {0}".format(startup_schedule))
                if (int(startup_schedule[current_day]) == current_hour):
                    if current_status == "stopped" : instances_to_start.append(instance_id + "," + environment)

            if shutdown != "":
                shutdown_schedule =  shutdown.split(" ")
                print("Shutdown: {0}".format(shutdown_schedule))
                if (int(shutdown_schedule[current_day]) == current_hour):
                    if current_status == "available": instances_to_stop.append(instance_id + "," + environment)

            #print("--------------------------------------------")

        if (len(instances_to_start) == 0): print ("{0} (UTC): No instances to start at this time.".format(datetime.utcnow()))
        if (len(instances_to_stop) == 0): print ("{0} (UTC): No instances to stop at this time.".format(datetime.utcnow()))

        rds_start(instances_to_start)
        rds_stop(instances_to_stop)

    except Exception as error:
        print(error)

lambda_handler(0, 0)