From bea1e03bca47a5af03cd73a6ed379ff72a76f55d Mon Sep 17 00:00:00 2001 From: Dexter Lee Date: Thu, 28 Oct 2021 16:51:35 -0700 Subject: [PATCH] First commit --- README.md | 65 + license/license.yaml | 22 + s3upload.sh | 20 + scripts/bastion_bootstrap.sh | 725 ++++++++++ scripts/python-jwt/create_jwt.py | 14 + scripts/python-jwt/readme.md | 8 + scripts/python-jwt/requirements.txt | 1 + templates/datahub-infra-deployment.yaml | 827 ++++++++++++ templates/nested/admin.yaml | 602 +++++++++ templates/nested/eks.yaml | 1048 +++++++++++++++ templates/nested/elasticsearch.yaml | 200 +++ templates/nested/ingress-controller.yaml | 264 ++++ templates/nested/msk.yaml | 194 +++ templates/nested/mysql.yaml | 212 +++ templates/nested/privatelink.yaml | 35 + templates/nested/vpc.yaml | 1548 ++++++++++++++++++++++ 16 files changed, 5785 insertions(+) create mode 100644 README.md create mode 100644 license/license.yaml create mode 100755 s3upload.sh create mode 100644 scripts/bastion_bootstrap.sh create mode 100644 scripts/python-jwt/create_jwt.py create mode 100644 scripts/python-jwt/readme.md create mode 100644 scripts/python-jwt/requirements.txt create mode 100644 templates/datahub-infra-deployment.yaml create mode 100644 templates/nested/admin.yaml create mode 100644 templates/nested/eks.yaml create mode 100644 templates/nested/elasticsearch.yaml create mode 100644 templates/nested/ingress-controller.yaml create mode 100644 templates/nested/msk.yaml create mode 100644 templates/nested/mysql.yaml create mode 100644 templates/nested/privatelink.yaml create mode 100644 templates/nested/vpc.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd209df --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Cloudformation Demo +![AcryDatahubCFN](https://user-images.githubusercontent.com/1105928/138394072-c86ddffa-5b6d-433f-95c8-3764842445d4.png) + +## step 1 to 5 runs on customer AWS account + + +1. upload templates/scripts/license to S3 + + - upload needed files to S3 bucket 'cf-templates-blrxgroup-us-west-2', under folder 'development' +```console +cd cloudformation +export AWS_PROFILE=*** +./s3upload.sh cf-templates-blrxgroup-us-west-2 development +``` + + +2. create stack to deploy datahub platform in AWS + + - choose Oregon region -> Cloudformation -> Create stack + + - Template Amazon S3 URL: https://cf-templates-blrxgroup-us-west-2.s3.us-west-2.amazonaws.com/development/templates/datahub-infra-deployment.yaml + + - Stack name: datahub + + - The AZ's to deploy to: choose 'us-west-2a, us-west-2b, us-west-2c' + + - The key pair name to use to access the instances: choose 'developer' + + - The CIDR block to allow remote access: YOURIP/32, can find your IP from https://www.whatismyip.com/ + + - Stack failure options: choose 'Preserve successfully provisioned resources' (useful when working on development of cloudformation) + + - check: + - "I acknowledge that AWS CloudFormation might create IAM resources with custom names." + - "I acknowledge that AWS CloudFormation might require the following capability: CAPABILITY_AUTO_EXPAND" + + - click "Create stack" + + - you will see a stack 'datahub' (this is master stack), and it will invoke nested stacks in order + + + +3. find datahub platform info + - after Stack Info show Status 'CREATE_COMPLETE', you can find needed info from nested stack <>'s Outputs + + + +4. create vpc endpoint + - wait till datahub-kotsadm network load balancer's status is Active + - create stack + - Stack Name: datahub-privatelink + - Template Amazon S3 URL: https://cf-templates-blrxgroup-us-west-2.s3.us-west-2.amazonaws.com/development/templates/nested/privatelink.yaml + + +5. manually update DNS record + - find datahub.dev.blrxgroup.com in public hosted zone 'dev.blrxgroup.com', update it to point to new ALB (for example, dualstack.k8s-datahub-***.us-west-2.elb.amazonaws.com.) + + - access https://datahub.dev.blrxgroup.com for datahub app + + +## step 6 runs on Acryl AWS account +6. manually create VPC endpoint + - under Acryl AWS account, us-west-2 region, find service by service name, for example com.amazonaws.vpce.us-west-2.vpce-svc-*** (get service name from step 4.), select shared vpc, choose 3 private subnets, attach default security group + + - access https://{vpc_endpoint_dns} to for kotsadmin, default password: Passw0rd diff --git a/license/license.yaml b/license/license.yaml new file mode 100644 index 0000000..85ec500 --- /dev/null +++ b/license/license.yaml @@ -0,0 +1,22 @@ +apiVersion: kots.io/v1beta1 +kind: License +metadata: + name: cfn-customer +spec: + appSlug: datahub-poc + channelID: 1vuiQnAMMhXg50inWWm2bbTiqd7 + channelName: Unstable + customerName: cfn-customer + endpoint: https://replicated.app + entitlements: + expires_at: + description: License Expiration + title: Expiration + value: "" + valueType: String + isGitOpsSupported: true + isSnapshotSupported: true + licenseID: 1zgkcv8TjnYgEvsnV9iT7AcdT8W + licenseSequence: 1 + licenseType: dev + signature: eyJsaWNlbnNlRGF0YSI6ImV5SmhjR2xXWlhKemFXOXVJam9pYTI5MGN5NXBieTkyTVdKbGRHRXhJaXdpYTJsdVpDSTZJa3hwWTJWdWMyVWlMQ0p0WlhSaFpHRjBZU0k2ZXlKdVlXMWxJam9pWTJadUxXTjFjM1J2YldWeUluMHNJbk53WldNaU9uc2liR2xqWlc1elpVbEVJam9pTVhwbmEyTjJPRlJxYmxsblJYWnpibFk1YVZRM1FXTmtWRGhYSWl3aWJHbGpaVzV6WlZSNWNHVWlPaUprWlhZaUxDSmpkWE4wYjIxbGNrNWhiV1VpT2lKalptNHRZM1Z6ZEc5dFpYSWlMQ0poY0hCVGJIVm5Jam9pWkdGMFlXaDFZaTF3YjJNaUxDSmphR0Z1Ym1Wc1NVUWlPaUl4ZG5WcFVXNUJUVTFvV0djMU1HbHVWMWR0TW1KaVZHbHhaRGNpTENKamFHRnVibVZzVG1GdFpTSTZJbFZ1YzNSaFlteGxJaXdpYkdsalpXNXpaVk5sY1hWbGJtTmxJam94TENKbGJtUndiMmx1ZENJNkltaDBkSEJ6T2k4dmNtVndiR2xqWVhSbFpDNWhjSEFpTENKbGJuUnBkR3hsYldWdWRITWlPbnNpWlhod2FYSmxjMTloZENJNmV5SjBhWFJzWlNJNklrVjRjR2x5WVhScGIyNGlMQ0prWlhOamNtbHdkR2x2YmlJNklreHBZMlZ1YzJVZ1JYaHdhWEpoZEdsdmJpSXNJblpoYkhWbElqb2lJaXdpZG1Gc2RXVlVlWEJsSWpvaVUzUnlhVzVuSW4xOUxDSnBjMGRwZEU5d2MxTjFjSEJ2Y25SbFpDSTZkSEoxWlN3aWFYTlRibUZ3YzJodmRGTjFjSEJ2Y25SbFpDSTZkSEoxWlgxOSIsImlubmVyU2lnbmF0dXJlIjoiZXlKc2FXTmxibk5sVTJsbmJtRjBkWEpsSWpvaWFXcHVSRUp6VDJoQmIweFhZMjFVTkV0TWJURkJiRmN5Um1WWU0xcEViMFZRY3pnNVJYZERTRTR2VVcxb1lXNUVlbXRWY0dKYVoyUlhVblJZU2xnek1HSk1TeTh6TlhwRGRWTXhXbVJJYm01M015OVNhalZJZFVSeWQwcG1UWHAyT0VwTE5tUTJXRGRJTDBKR2QwbHBWRXBDTDJSUFptNURWMnBYUWtKaldsa3pVbkpMTjJGWGRXODRkR1pETmxvMlF6TmxSamx1YTJkYWFucHdjVWRXWlVsbFMySkxSbFJWUW5KVVpHeFFlREU0Ym5SNVlWTlZlRmRYSzNkRUwxSmtORkFyTldsV1YxVm9aMVZxWlhCcWVtZFNhV05sTTBKckx6QklhRTltZVRWemRYRnpORzlwVkUwMWFHSnBaRVJTYmpKVVJraENRVlI1ZEdGeWNISkdMMUkxWjJkbVVUUnRheXQ0Y1dSNU9XOHhjWGxrZUd0amRGRnRVbGsxV2tOa1NVdDBlV1puZDFoYVZpdFVVbGxyTDFsNlVIVXZMM0pYVVVFMFdVMXFhVlo1UkROclpXRm9jRlZWYjFkMGRIcFBablkxVHpKUlBUMGlMQ0p3ZFdKc2FXTkxaWGtpT2lJdExTMHRMVUpGUjBsT0lGQlZRa3hKUXlCTFJWa3RMUzB0TFZ4dVRVbEpRa2xxUVU1Q1oydHhhR3RwUnpsM01FSkJVVVZHUVVGUFEwRlJPRUZOU1VsQ1EyZExRMEZSUlVFNFMzcEtWQ3Q0UW1WU1VTOUVRMjF4V0Zoc2VGeHVkM1JZS3k5dE1UWjBkM1ZxV0hST016UTNWREZ5VW5VMFRucFBVMnhsYWs5U05WcHpXakZYYWxWMFowWnFkelpMVUV4Vk9XVk1SWGw2YmxWWmRrcDRjRnh1Vnk5eGN6aHBVbGMyYzNseFpuQlplREZNVTI4eGNuaHlTMjlyYkdwTlVUVlJWVmRVYlUxM1lsQlFRMVJzUzJjNVJsUTNlRTV3TDBaS2VIaDVaMkoyWWx4dVNVZ3ZjVU5sVVhweVVXWklZMDFKTW1GeFNHMVNXbEpPVVVKWlRUQnlNMHBEUWl0TlJuY3paVGRNYlZZMlNqUXJXRWgyVEc5eFQycDVTMlppYjJab1JWeHVkazlpTTBsakx5OVBUVTVvVkhsTU5UWTBhVXB2YlRkdGRYbEVlVzVHUkdWSWJrNTRVMjlCZUUweVNFVndha3BLYTFsWE0zWnRaM1JzUzBkT1drOHhiVnh1TmxVMVlVNDRXVlp0TXpodVowWm5ZamtyTVhvMVlrZDNWRzlDUlV3MmJWQmlUVVZwUW1Oc1dIbEhWVEF4ZW0weGNERlpXbWwxV2psU0wxSlRXSEF2ZVZ4dU4xRkpSRUZSUVVKY2JpMHRMUzB0UlU1RUlGQlZRa3hKUXlCTFJWa3RMUzB0TFZ4dUlpd2lhMlY1VTJsbmJtRjBkWEpsSWpvaVpYbEtlbUZYWkhWWldGSXhZMjFWYVU5cFNqSlVSMDVOVlZoS2NVMUVSa1JQV0ZZeVdUQkdkMU16U2xSU1ZrWlNUakZWTlUxdVZrcFVSRnBaVFdzNVIxcFhXbTFrVmtaRFVsUm9kV0ZzV2s1TGVrNXpXbTVDZDFac1duZGtTRkpNVlZoYVNGb3dOVFJTV0ZZeVdqSTFOVk14V2xSalNFNWhWRE5yZDFJelJraFNWa28xV2xWU2FsVnFXa0pTUkU1SlRWVm9hRlF3TlZaYVJWSXdUbFZHVkdNd1JUVlpNMXBKVjBaa1NFMHdPVmxSYTA1YVZqQldVMDR3YkRCWGJuQkRVMFJXYjFRd2FHOVdTRnBHVjBSQ2JtUkVSa0pqUldSM1dqTm9kV1JJUVRSVE1IQkVaR3BqTUZFeVNUUldSVWw0VFhwR2QxVlVVVEpVUldkM1lVTTVUbE5HU25OamExWkNVakIwTkZVd1ZUTlNWbXcwVjBkYVJVMVViREZVYWsxNVlqQjBlV0pFVGxGUlYyaFdVa1ZXYTFaSVVscFJiVko2V2pBeFVsWkZhekZUYXpVMVVUQldkRTFVUm5aWlZuQnpUakpzYTFaVk1UVlhiRUY2WVVWV2Ewc3dPV0ZqYWs1VVZYcEdVMlJ1U25wT2FrSjJXbXhrZDFZelZsbFplbHAzVWpOS1ZHRkdVbTFXUmxKWFZEQTVkMlJIYUU5Vk1IUkRZMFJzZDFGV2JGUlZSMXBUVTFob1ZFOUZkRWxUYmtFeFkwaENiVkZWT1ZsalZtTTBWMnN3TlZSWFJtRlVWMFY2WWtWSk1WVklZelZOYlRGYVZURmtRMkpHUlRsUVUwbHpTVzFrYzJJeVNtaGlSWFJzWlZWc2EwbHFiMmxaYlZKc1dsUlZNazVVV1hkWk1scHBUa1JPYWs5WFNYbFBSMHB0VDFSb2JGbFhUbWhhYlVVeVRrUlphV1pSUFQwaWZRPT0ifQ== diff --git a/s3upload.sh b/s3upload.sh new file mode 100755 index 0000000..371815b --- /dev/null +++ b/s3upload.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +if [ $# -ne 2 ]; then + echo "Usage: s3upload.sh " + exit 1 +else + S3_BUCKET=$1 + S3_KEY_PREFIX=$2 + + # Check if access to the bucket + if aws s3 ls "s3://$S3_BUCKET" 2>&1 | grep -q 'An error occurred' + then + echo "No access to S3 bucket: $S3_BUCKET !" + exit 1 + fi + + aws s3 cp ./templates s3://$S3_BUCKET/$S3_KEY_PREFIX/templates --recursive + aws s3 cp ./scripts s3://$S3_BUCKET/$S3_KEY_PREFIX/scripts --recursive + aws s3 cp ./license s3://$S3_BUCKET/$S3_KEY_PREFIX/license --recursive +fi diff --git a/scripts/bastion_bootstrap.sh b/scripts/bastion_bootstrap.sh new file mode 100644 index 0000000..51b70da --- /dev/null +++ b/scripts/bastion_bootstrap.sh @@ -0,0 +1,725 @@ +#!/bin/bash -e +# Bastion Bootstrapping +PROGRAM='Linux Hardening' +##################################### Functions Definitions +function checkos () { + platform='unknown' + unamestr=`uname` + if [[ "${unamestr}" == 'Linux' ]]; then + platform='linux' + else + echo "[WARNING] This script is not supported on MacOS or freebsd" + exit 1 + fi + echo "${FUNCNAME[0]} Ended" +} +function setup_environment_variables() { + REGION=$(curl -sq http://169.254.169.254/latest/meta-data/placement/availability-zone/) + #ex: us-east-1a => us-east-1 + REGION=${REGION: :-1} + ETH0_MAC=$(/sbin/ip link show dev eth0 | /bin/egrep -o -i 'link/ether\ ([0-9a-z]{2}:){5}[0-9a-z]{2}' | /bin/sed -e 's,link/ether\ ,,g') + userdata_file_path="/var/lib/cloud/instance/user-data.txt" + INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) + EIP_LIST=$(grep EIP_LIST ${userdata_file_path} | sed -e 's/EIP_LIST=//g' -e 's/\"//g') + LOCAL_IP_ADDRESS=$(curl -sq 169.254.169.254/latest/meta-data/network/interfaces/macs/${ETH0_MAC}/local-ipv4s/) + CWG=$(grep CLOUDWATCHGROUP ${userdata_file_path} | sed 's/CLOUDWATCHGROUP=//g') + # LOGGING CONFIGURATION + BASTION_MNT="/var/log/bastion" + BASTION_LOG="bastion.log" + echo "Setting up bastion session log in ${BASTION_MNT}/${BASTION_LOG}" + mkdir -p ${BASTION_MNT} + BASTION_LOGFILE="${BASTION_MNT}/${BASTION_LOG}" + BASTION_LOGFILE_SHADOW="${BASTION_MNT}/.${BASTION_LOG}" + touch ${BASTION_LOGFILE} + ln ${BASTION_LOGFILE} ${BASTION_LOGFILE_SHADOW} + mkdir -p /usr/bin/bastion + touch /tmp/messages + chmod 770 /tmp/messages + log_shadow_file_location="${bastion_mnt}/.${bastion_log}" + export REGION ETHO_MAC EIP_LIST CWG BASTION_MNT BASTION_LOG BASTION_LOGFILE BASTION_LOGFILE_SHADOW \ + LOCAL_IP_ADDRESS INSTANCE_ID +} +function verify_dependencies(){ + if [[ "a$(which aws)" == "a" ]]; then + pip install awscli + fi + echo "${FUNCNAME[0]} Ended" +} +function usage() { + echo "$0 " + echo " " + echo "options:" + echo -e "--help \t Show options for this script" + echo -e "--tcp-forwarding \t Enable or Disable TCP Forwarding" + echo -e "--x11-forwarding \t Enable or Disable X11 Forwarding" +} +function chkstatus () { + if [ $? -eq 0 ] + then + echo "Script [PASS]" + else + echo "Script [FAILED]" >&2 + exit 1 + fi +} + +function osrelease () { + OS=`cat /etc/os-release | grep '^NAME=' | tr -d \" | sed 's/\n//g' | sed 's/NAME=//g'` + if [ "${OS}" == "Ubuntu" ]; then + echo "Ubuntu" + elif [ "${OS}" == "Amazon Linux AMI" ] || [ "${OS}" == "Amazon Linux" ]; then + echo "AMZN" + elif [ "${OS}" == "CentOS Linux" ]; then + echo "CentOS" + else + echo "Operating System Not Found" + fi + echo "${FUNCNAME[0]} Ended" >> /var/log/cfn-init.log +} + +function harden_ssh_security () { + # Allow ec2-user only to access this folder and its content + #chmod -R 770 /var/log/bastion + #setfacl -Rdm other:0 /var/log/bastion + # Make OpenSSH execute a custom script on logins + echo -e "\nForceCommand /usr/bin/bastion/shell" >> /etc/ssh/sshd_config + + cat <<'EOF' >> /usr/bin/bastion/shell + bastion_mnt="/var/log/bastion" + bastion_log="bastion.log" + # Check that the SSH client did not supply a command. Only SSH to instance should be allowed. + export Allow_SSH="ssh" + export Allow_SCP="scp" + if [[ -z $SSH_ORIGINAL_COMMAND ]] || [[ $SSH_ORIGINAL_COMMAND =~ ^$Allow_SSH ]] || [[ $SSH_ORIGINAL_COMMAND =~ ^$Allow_SCP ]]; then + #Allow ssh to instance and log connection + if [ -z "$SSH_ORIGINAL_COMMAND" ]; then + /bin/bash + exit 0 + else + $SSH_ORIGINAL_COMMAND + fi + log_file=$(echo "$log_shadow_file_location") + DATE_TIME_WHOAMI="`whoami`:`date "+%Y-%m-%d %H:%M:%S"`" + LOG_ORIGINAL_COMMAND=`echo "$DATE_TIME_WHOAMI:$SSH_ORIGINAL_COMMAND"` + echo "$LOG_ORIGINAL_COMMAND" >> "${bastion_mnt}/${bastion_log}" + log_dir="/var/log/bastion/" + else + # The "script" program could be circumvented with some commands + # (e.g. bash, nc). Therefore, I intentionally prevent users + # from supplying commands. + echo "This bastion supports interactive sessions only. Do not supply a command" + exit 1 + fi +EOF + + # Make the custom script executable + chmod a+x /usr/bin/bastion/shell + release=$(osrelease) + if [ "${release}" == "CentOS" ]; then + semanage fcontext -a -t ssh_exec_t /usr/bin/bastion/shell + fi + echo "${FUNCNAME[0]} Ended" +} + +function amazon_os () { + echo "${FUNCNAME[0]} Started" + chown root:ec2-user /usr/bin/script + service sshd restart + echo -e "\nDefaults env_keep += \"SSH_CLIENT\"" >>/etc/sudoers + cat <<'EOF' >> /etc/bashrc + #Added by linux bastion bootstrap + declare -rx IP=$(echo $SSH_CLIENT | awk '{print $1}') +EOF + echo " declare -rx BASTION_LOG=${BASTION_MNT}/${BASTION_LOG}" >> /etc/bashrc + + cat <<'EOF' >> /etc/bashrc + declare -rx PROMPT_COMMAND='history -a >(logger -t "ON: $(date) [FROM]:${IP} [USER]:${USER} [PWD]:${PWD}" -s 2>>${BASTION_LOG})' +EOF + chown root:ec2-user ${BASTION_MNT} + chown root:ec2-user ${BASTION_LOGFILE} + chown root:ec2-user ${BASTION_LOGFILE_SHADOW} + chmod 662 ${BASTION_LOGFILE} + chmod 662 ${BASTION_LOGFILE_SHADOW} + chattr +a ${BASTION_LOGFILE} + chattr +a ${BASTION_LOGFILE_SHADOW} + touch /tmp/messages + chown root:ec2-user /tmp/messages + #Run security updates + cat <<'EOF' >> ~/mycron + 0 0 * * * yum -y update --security +EOF + crontab ~/mycron + rm ~/mycron + echo "${FUNCNAME[0]} Ended" +} + +function request_eip() { + # Is the already-assigned Public IP an elastic IP? + query_assigned_public_ip + set +e + determine_eip_assc_status ${PUBLIC_IP_ADDRESS} + set -e + if [[ ${eip_associated} -ne 1 ]]; then + echo "The Public IP address associated with eth0 (${PUBLIC_IP_ADDRESS}) is already an Elastic IP. Not proceeding further." + exit 1 + fi + EIP_ARRAY=(${EIP_LIST//,/ }) + eip_assigned_count=0 + for eip in "${EIP_ARRAY[@]}"; do + if [ "${eip}" == "Null" ]; then + echo "Detected a NULL Value, moving on." + continue + fi + # Determine if the EIP has already been assigned. + set +e + determine_eip_assc_status ${eip} + set -e + if [[ ${eip_associated} -eq 0 ]]; then + echo "Elastic IP [${eip}] already has an association. Moving on." + let eip_assigned_count+=1 + if [ "${eip_assigned_count}" -eq "${#EIP_ARRAY[@]}" ]; then + echo "All of the stack EIPs have been assigned (${eip_assigned_count}/${#EIP_ARRAY[@]}). I can't assign anything else. Exiting." + exit 1 + fi + continue + fi + determine_eip_allocation ${eip} + # Attempt to assign EIP to the ENI. + set +e + aws ec2 associate-address --instance-id ${INSTANCE_ID} --allocation-id ${eip_allocation} --region ${REGION} + rc=$? + set -e + if [ ${rc} -ne 0 ]; then + let eip_assigned_count+=1 + continue + else + echo "The newly-assigned EIP is ${eip}. It is mapped under EIP Allocation ${eip_allocation}" + break + fi + done + echo "${FUNCNAME[0]} Ended" +} +function query_assigned_public_ip() { + # Note: ETH0 Only. + # - Does not distinquish between EIP and Standard IP. Need to cross-ref later. + echo "Querying the assigned public IP" + PUBLIC_IP_ADDRESS=$(curl -sq 169.254.169.254/latest/meta-data/public-ipv4/${ETH0_MAC}/public-ipv4s/) +} +function determine_eip_assc_status(){ + # Is the provided EIP associated? + # Also determines if an IP is an EIP. + # 0 => true + # 1 => false + echo "Determining EIP Association Status for [${1}]" + set +e + aws ec2 describe-addresses --public-ips ${1} --output text --region ${REGION} 2>/dev/null | grep -o -i eipassoc -q + rc=$? + set -e + if [[ ${rc} -eq 1 ]]; then + eip_associated=1 + else + eip_associated=0 + fi +} +function determine_eip_allocation(){ + echo "Determining EIP Allocation for [${1}]" + resource_id_length=$(aws ec2 describe-addresses --public-ips ${1} --output text --region ${REGION} | awk {'print $2'} | sed 's/.*eipalloc-//') + if [ "${#resource_id_length}" -eq 17 ]; then + eip_allocation=$(aws ec2 describe-addresses --public-ips ${1} --output text --region ${REGION}| egrep 'eipalloc-([a-z0-9]{17})' -o) + else + eip_allocation=$(aws ec2 describe-addresses --public-ips ${1} --output text --region ${REGION}| egrep 'eipalloc-([a-z0-9]{8})' -o) + fi +} +function prevent_process_snooping() { + # Prevent bastion host users from viewing processes owned by other users. + mount -o remount,rw,hidepid=2 /proc + awk '!/proc/' /etc/fstab > temp && mv temp /etc/fstab + echo "proc /proc proc defaults,hidepid=2 0 0" >> /etc/fstab + echo "${FUNCNAME[0]} Ended" +} + +# function below address hardening for k8s and docker following their CIS benchmark +function harden_workernode_kubernetes() { + KUBELET_SERVICE_FILE="/etc/systemd/system/kubelet.service" + # K8s CIS benchmark 2.1.1 + #sed -i 's/allow-privileged=true/allow-privileged=false/g' $KUBELET_SERVICE_FILE + # K8s CIS benchmark 2.1.5 + sed -i '/--anonymous-auth=false \\/a \ \ --read-only-port=0 \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.1.6 + sed -i '/--anonymous-auth=false \\/a \ \ --streaming-connection-idle-timeout=5m \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.1.7 + sed -i '/--anonymous-auth=false \\/a \ \ --protect-kernel-defaults=true \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.1.9 + sed -i '/--anonymous-auth=false \\/a \ \ --keep-terminated-pod-volumes=false \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.1.11 + sed -i '/--anonymous-auth=false \\/a \ \ --event-qps=0 \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.1.13 + sed -i '/--anonymous-auth=false \\/a \ \ --cadvisor-port=0 \\' $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.2.1 + chmod 644 $KUBELET_SERVICE_FILE + + # K8s CIS benchmark 2.2.2 + chown root:root $KUBELET_SERVICE_FILE + # restart service + systemctl daemon-reload + systemctl restart kubelet.service + + # get auditd configuration and apply (it audits dockerd and kubelet processes) + # /etc/audit/rules.d/audit.rules + cat <<'EOF' >> /etc/audit/rules.d/audit.rules + # Remove any existing rules + -D + + # Buffer Size + ## Feel free to increase this if the machine panic's + -b 8192 + + # Failure Mode + ## Possible values: 0 (silent), 1 (printk, print a failure message), 2 (panic, halt the system) + -f 1 + + # Ignore errors + ## e.g. caused by users or files not found in the local environment + -i + + # Self Auditing --------------------------------------------------------------- + + ## Audit the audit logs + ### Successful and unsuccessful attempts to read information from the audit records + -w /var/log/audit/ -k auditlog + + ## Auditd configuration + ### Modifications to audit configuration that occur while the audit collection functions are operating + -w /etc/audit/ -p wa -k auditconfig + -w /etc/libaudit.conf -p wa -k auditconfig + -w /etc/audisp/ -p wa -k audispconfig + + ## Monitor for use of audit management tools + -w /sbin/auditctl -p x -k audittools + -w /sbin/auditd -p x -k audittools + + # Filters --------------------------------------------------------------------- + + ### We put these early because audit is a first match wins system. + + ## Ignore SELinux AVC records + -a always,exclude -F msgtype=AVC + + ## Ignore current working directory records + -a always,exclude -F msgtype=CWD + + ## Ignore EOE records (End Of Event, not needed) + -a always,exclude -F msgtype=EOE + + ## Cron jobs fill the logs with stuff we normally don't want (works with SELinux) + -a never,user -F subj_type=crond_t + -a exit,never -F subj_type=crond_t + + ## This prevents chrony from overwhelming the logs + -a never,exit -F arch=b64 -S adjtimex -F auid=unset -F uid=_chrony -F subj_type=chronyd_t + + ## This is not very interesting and wastes a lot of space if the server is public facing + -a always,exclude -F msgtype=CRYPTO_KEY_USER + + ## VMWare tools + -a exit,never -F arch=b32 -S fork -F success=0 -F path=/usr/lib/vmware-tools -F subj_type=initrc_t -F exit=-2 + -a exit,never -F arch=b64 -S fork -F success=0 -F path=/usr/lib/vmware-tools -F subj_type=initrc_t -F exit=-2 + + ### High Volume Event Filter (especially on Linux Workstations) + -a exit,never -F arch=b32 -F dir=/dev/shm -k sharedmemaccess + -a exit,never -F arch=b64 -F dir=/dev/shm -k sharedmemaccess + -a exit,never -F arch=b32 -F dir=/var/lock/lvm -k locklvm + -a exit,never -F arch=b64 -F dir=/var/lock/lvm -k locklvm + + ## More information on how to filter events + ### https://access.redhat.com/solutions/2482221 + + # Rules ----------------------------------------------------------------------- + + ## Kernel parameters + -w /etc/sysctl.conf -p wa -k sysctl + + ## Kernel module loading and unloading + -a always,exit -F perm=x -F auid!=-1 -F path=/sbin/insmod -k modules + -a always,exit -F perm=x -F auid!=-1 -F path=/sbin/modprobe -k modules + -a always,exit -F perm=x -F auid!=-1 -F path=/sbin/rmmod -k modules + -a always,exit -F arch=b64 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules + -a always,exit -F arch=b32 -S finit_module -S init_module -S delete_module -F auid!=-1 -k modules + ## Modprobe configuration + -w /etc/modprobe.conf -p wa -k modprobe + + ## KExec usage (all actions) + -a always,exit -F arch=b64 -S kexec_load -k KEXEC + -a always,exit -F arch=b32 -S sys_kexec_load -k KEXEC + + ## Special files + -a exit,always -F arch=b32 -S mknod -S mknodat -k specialfiles + -a exit,always -F arch=b64 -S mknod -S mknodat -k specialfiles + + ## Mount operations (only attributable) + -a always,exit -F arch=b64 -S mount -S umount2 -F auid!=-1 -k mount + -a always,exit -F arch=b32 -S mount -S umount -S umount2 -F auid!=-1 -k mount + + # Change swap (only attributable) + -a always,exit -F arch=b64 -S swapon -S swapoff -F auid!=-1 -k swap + -a always,exit -F arch=b32 -S swapon -S swapoff -F auid!=-1 -k swap + + ## Time + -a exit,always -F arch=b32 -S adjtimex -S settimeofday -S clock_settime -k time + -a exit,always -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time + ### Local time zone + -w /etc/localtime -p wa -k localtime + + ## Stunnel + -w /usr/sbin/stunnel -p x -k stunnel + + ## Cron configuration & scheduled jobs + -w /etc/cron.allow -p wa -k cron + -w /etc/cron.deny -p wa -k cron + -w /etc/cron.d/ -p wa -k cron + -w /etc/cron.daily/ -p wa -k cron + -w /etc/cron.hourly/ -p wa -k cron + -w /etc/cron.monthly/ -p wa -k cron + -w /etc/cron.weekly/ -p wa -k cron + -w /etc/crontab -p wa -k cron + -w /var/spool/cron/crontabs/ -k cron + + ## User, group, password databases + -w /etc/group -p wa -k etcgroup + -w /etc/passwd -p wa -k etcpasswd + -w /etc/gshadow -k etcgroup + -w /etc/shadow -k etcpasswd + -w /etc/security/opasswd -k opasswd + + ## Sudoers file changes + -w /etc/sudoers -p wa -k actions + + ## Passwd + -w /usr/bin/passwd -p x -k passwd_modification + + ## Tools to change group identifiers + -w /usr/sbin/groupadd -p x -k group_modification + -w /usr/sbin/groupmod -p x -k group_modification + -w /usr/sbin/addgroup -p x -k group_modification + -w /usr/sbin/useradd -p x -k user_modification + -w /usr/sbin/usermod -p x -k user_modification + -w /usr/sbin/adduser -p x -k user_modification + + ## Login configuration and information + -w /etc/login.defs -p wa -k login + -w /etc/securetty -p wa -k login + -w /var/log/faillog -p wa -k login + -w /var/log/lastlog -p wa -k login + -w /var/log/tallylog -p wa -k login + + ## Network Environment + ### Changes to hostname + -a always,exit -F arch=b32 -S sethostname -S setdomainname -k network_modifications + -a always,exit -F arch=b64 -S sethostname -S setdomainname -k network_modifications + ### Changes to other files + -w /etc/hosts -p wa -k network_modifications + -w /etc/sysconfig/network -p wa -k network_modifications + -w /etc/network/ -p wa -k network + -a always,exit -F dir=/etc/NetworkManager/ -F perm=wa -k network_modifications + ### Changes to issue + -w /etc/issue -p wa -k etcissue + -w /etc/issue.net -p wa -k etcissue + + ## System startup scripts + -w /etc/inittab -p wa -k init + -w /etc/init.d/ -p wa -k init + -w /etc/init/ -p wa -k init + + ## Library search paths + -w /etc/ld.so.conf -p wa -k libpath + + ## Pam configuration + -w /etc/pam.d/ -p wa -k pam + -w /etc/security/limits.conf -p wa -k pam + -w /etc/security/pam_env.conf -p wa -k pam + -w /etc/security/namespace.conf -p wa -k pam + -w /etc/security/namespace.init -p wa -k pam + + ## Postfix configuration + -w /etc/aliases -p wa -k mail + -w /etc/postfix/ -p wa -k mail + + ## SSH configuration + -w /etc/ssh/sshd_config -k sshd + + # Systemd + -w /bin/systemctl -p x -k systemd + -w /etc/systemd/ -p wa -k systemd + + ## SELinux events that modify the system's Mandatory Access Controls (MAC) + -w /etc/selinux/ -p wa -k mac_policy + + ## Critical elements access failures + -a exit,always -F arch=b64 -S open -F dir=/etc -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/bin -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/sbin -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/usr/bin -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/usr/sbin -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/var -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/home -F success=0 -k unauthedfileaccess + -a exit,always -F arch=b64 -S open -F dir=/srv -F success=0 -k unauthedfileaccess + + ## Process ID change (switching accounts) applications + -w /bin/su -p x -k priv_esc + -w /usr/bin/sudo -p x -k priv_esc + -w /etc/sudoers -p rw -k priv_esc + + ## Power state + -w /sbin/shutdown -p x -k power + -w /sbin/poweroff -p x -k power + -w /sbin/reboot -p x -k power + -w /sbin/halt -p x -k power + + ## Session initiation information + -w /var/run/utmp -p wa -k session + -w /var/log/btmp -p wa -k session + -w /var/log/wtmp -p wa -k session + + ## Discretionary Access Control (DAC) modifications + -a always,exit -F arch=b32 -S chmod -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S chown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fchmod -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fchown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fchownat -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S fsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S lremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S lsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S removexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b32 -S setxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S chmod -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S chown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fchmod -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fchmodat -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fchown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fchownat -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S fsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S lchown -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S lremovexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S lsetxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S removexattr -F auid>=500 -F auid!=4294967295 -k perm_mod + -a always,exit -F arch=b64 -S setxattr -F auid>=500 -F auid!=4294967295 -k perm_mod + + # Special Rules --------------------------------------------------------------- + + ## 32bit API Exploitation + ### If you are on a 64 bit platform, everything _should_ be running + ### in 64 bit mode. This rule will detect any use of the 32 bit syscalls + ### because this might be a sign of someone exploiting a hole in the 32 + ### bit API. + -a always,exit -F arch=b32 -S all -k 32bit_api + + ## Reconnaissance + -w /usr/bin/whoami -p x -k recon + -w /etc/issue -p r -k recon + -w /etc/hostname -p r -k recon + + ## Suspicious activity + -w /usr/bin/wget -p x -k susp_activity + -w /usr/bin/curl -p x -k susp_activity + -w /usr/bin/base64 -p x -k susp_activity + -w /bin/nc -p x -k susp_activity + -w /bin/netcat -p x -k susp_activity + -w /usr/bin/ncat -p x -k susp_activity + -w /usr/bin/ssh -p x -k susp_activity + -w /usr/bin/socat -p x -k susp_activity + -w /usr/bin/wireshark -p x -k susp_activity + -w /usr/bin/rawshark -p x -k susp_activity + -w /usr/bin/rdesktop -p x -k sbin_susp + + ## Sbin suspicious activity + -w /sbin/iptables -p x -k sbin_susp + -w /sbin/ifconfig -p x -k sbin_susp + -w /usr/sbin/tcpdump -p x -k sbin_susp + -w /usr/sbin/traceroute -p x -k sbin_susp + + ## Injection + ### These rules watch for code injection by the ptrace facility. + ### This could indicate someone trying to do something bad or just debugging + -a always,exit -F arch=b32 -S ptrace -k tracing + -a always,exit -F arch=b64 -S ptrace -k tracing + -a always,exit -F arch=b32 -S ptrace -F a0=0x4 -k code_injection + -a always,exit -F arch=b64 -S ptrace -F a0=0x4 -k code_injection + -a always,exit -F arch=b32 -S ptrace -F a0=0x5 -k data_injection + -a always,exit -F arch=b64 -S ptrace -F a0=0x5 -k data_injection + -a always,exit -F arch=b32 -S ptrace -F a0=0x6 -k register_injection + -a always,exit -F arch=b64 -S ptrace -F a0=0x6 -k register_injection + + ## Privilege Abuse + ### The purpose of this rule is to detect when an admin may be abusing power by looking in user's home dir. + -a always,exit -F dir=/home -F uid=0 -F auid>=1000 -F auid!=4294967295 -C auid!=obj_uid -k power_abuse + + # Software Management --------------------------------------------------------- + + # RPM (Redhat/CentOS) + -w /usr/bin/rpm -p x -k software_mgmt + -w /usr/bin/yum -p x -k software_mgmt + + # YAST/Zypper/RPM (SuSE) + -w /sbin/yast -p x -k yast + -w /sbin/yast2 -p x -k yast + -w /bin/rpm -p x -k software_mgmt + -w /usr/bin/zypper -k software_mgmt + + # DPKG / APT-GET (Debian/Ubuntu) + -w /usr/bin/dpkg -p x -k software_mgmt + -w /usr/bin/apt-add-repository -p x -k software_mgmt + -w /usr/bin/apt-get -p x -k software_mgmt + -w /usr/bin/aptitude -p x -k software_mgmt + + # Special Software ------------------------------------------------------------ + + ## GDS specific secrets + -w /etc/puppet/ssl -p wa -k puppet_ssl + + ## IBM Bigfix BESClient + -a exit,always -F arch=b64 -S open -F dir=/opt/BESClient -F success=0 -k soft_besclient + -w /var/opt/BESClient/ -p wa -k soft_besclient + + ## CHEF https://www.chef.io/chef/ + -w /etc/chef -p wa -k soft_chef + + # High volume events ---------------------------------------------------------- + + ## Remove them if the cause to much volumen in your einvironment + + ## Root command executions + -a exit,always -F arch=b64 -F euid=0 -S execve -k rootcmd + -a exit,always -F arch=b32 -F euid=0 -S execve -k rootcmd + + ## File Deletion Events by User + -a always,exit -F arch=b32 -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete + -a always,exit -F arch=b64 -S rmdir -S unlink -S unlinkat -S rename -S renameat -F auid>=500 -F auid!=4294967295 -k delete + + ## File Access + ### Unauthorized Access (unsuccessful) + -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k file_access + -a always,exit -F arch=b32 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k file_access + -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EACCES -F auid>=500 -F auid!=4294967295 -k file_access + -a always,exit -F arch=b64 -S creat -S open -S openat -S open_by_handle_at -S truncate -S ftruncate -F exit=-EPERM -F auid>=500 -F auid!=4294967295 -k file_access + + ### Unsuccessful Creation + -a always,exit -F arch=b32 -S creat,link,mknod,mkdir,symlink,mknodat,linkat,symlinkat -F exit=-EACCES -k file_creation + -a always,exit -F arch=b64 -S mkdir,creat,link,symlink,mknod,mknodat,linkat,symlinkat -F exit=-EACCES -k file_creation + -a always,exit -F arch=b32 -S link,mkdir,symlink,mkdirat -F exit=-EPERM -k file_creation + -a always,exit -F arch=b64 -S mkdir,link,symlink,mkdirat -F exit=-EPERM -k file_creation + + ### Unsuccessful Modification + -a always,exit -F arch=b32 -S rename -S renameat -S truncate -S chmod -S setxattr -S lsetxattr -S removexattr -S lremovexattr -F exit=-EACCES -k file_modification + -a always,exit -F arch=b64 -S rename -S renameat -S truncate -S chmod -S setxattr -S lsetxattr -S removexattr -S lremovexattr -F exit=-EACCES -k file_modification + -a always,exit -F arch=b32 -S rename -S renameat -S truncate -S chmod -S setxattr -S lsetxattr -S removexattr -S lremovexattr -F exit=-EPERM -k file_modification + -a always,exit -F arch=b64 -S rename -S renameat -S truncate -S chmod -S setxattr -S lsetxattr -S removexattr -S lremovexattr -F exit=-EPERM -k file_modification + + ### Record events for Docker + -w /usr/bin/dockerd -k docker + -w /usr/bin/docker -k docker + -w /usr/bin/docker-containerd -k docker + -w /usr/bin/docker-runc -k docker + -w /var/lib/docker -k docker + -w /etc/docker -k docker + -w /etc/sysconfig/docker -k docker + -w /etc/sysconfig/docker-storage -k docker + -w /usr/lib/systemd/system/docker.service -k docker + + ### Record events for Kubelet daemon + -w /usr/bin/kubelet -k kubelet + + # Make the configuration immutable -------------------------------------------- + ##-e 2 +EOF + service auditd restart + + DOCKERD_CIS_OPTIONS="--icc=false --log-level=info --iptables=true --userland-proxy=false" + sed -i s#ExecStart=/usr/bin/dockerd#ExecStart=/usr/bin/dockerd\ "$DOCKERD_CIS_OPTIONS"#g /usr/lib/systemd/system/docker.service + systemctl daemon-reload + systemctl restart docker.service + +} + +##################################### End Function Definitions +# Call checkos to ensure platform is Linux +checkos +# Verify dependencies are installed. +verify_dependencies +# Assuming it is, setup environment variables. +setup_environment_variables +# Read the options from cli input +TEMP=`getopt -o h: --long help,banner:,enable:,tcp-forwarding:,x11-forwarding: -n $0 -- "$@"` +eval set -- "${TEMP}" +if [ $# == 1 ] ; then echo "No input provided! type ($0 --help) to see usage help" >&2 ; exit 1 ; fi +# extract options and their arguments into variables. +while true; do + case "$1" in + -h | --help) + usage + exit 1 + ;; + --tcp-forwarding) + TCP_FORWARDING="$2"; + shift 2 + ;; + --x11-forwarding) + X11_FORWARDING="$2"; + shift 2 + ;; + --) + break + ;; + *) + break + ;; + esac +done + +# BANNER CONFIGURATION +BANNER_FILE="/etc/ssh_banner" +SSH_BANNER_BASTION="LINUX BASTION" +SSH_BANNER_WORKERNODE="LINUX WORKERNODE" + + +# Enable/Disable TCP forwarding +TCP_FORWARDING=`echo "${TCP_FORWARDING}" | sed 's/\\n//g'` +# Enable/Disable X11 forwarding +X11_FORWARDING=`echo "${X11_FORWARDING}" | sed 's/\\n//g'` +echo "Value of TCP_FORWARDING - ${TCP_FORWARDING}" +echo "Value of X11_FORWARDING - ${X11_FORWARDING}" +if [[ ${TCP_FORWARDING} == "false" ]];then + awk '!/AllowTcpForwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config + echo "AllowTcpForwarding no" >> /etc/ssh/sshd_config + harden_ssh_security +fi +if [[ ${X11_FORWARDING} == "false" ]];then + awk '!/X11Forwarding/' /etc/ssh/sshd_config > temp && mv temp /etc/ssh/sshd_config + echo "X11Forwarding no" >> /etc/ssh/sshd_config +fi +release=$(osrelease) +# AMZN Linux +amazon_os +prevent_process_snooping +# different banner and kubernetes hardening if workernode +if grep BastionLaunch /var/lib/cloud/instance/user-data.txt; then + request_eip + echo $SSH_BANNER_BASTION > $BANNER_FILE + echo -e "\nBanner ${BANNER_FILE}" >>/etc/ssh/sshd_config + service sshd restart +else + echo $SSH_BANNER_WORKERNODE > $BANNER_FILE + echo -e "\nBanner ${BANNER_FILE}" >>/etc/ssh/sshd_config + systemctl restart sshd.service + harden_workernode_kubernetes +fi +echo "Bootstrap complete." diff --git a/scripts/python-jwt/create_jwt.py b/scripts/python-jwt/create_jwt.py new file mode 100644 index 0000000..b1ff73f --- /dev/null +++ b/scripts/python-jwt/create_jwt.py @@ -0,0 +1,14 @@ +import jwt +import os +import datetime +import sys + +secret = os.environ['API_KEY_SECRET'] +if not secret or len(secret) == 0: + print("No secret found. Exiting..") + sys.exit() +six_months_ms = 15778800000 # token must be re-generated after 6 months +expiration_date = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(milliseconds=six_months_ms) +payload = { "v": 0, "permissions": [ "ALLOW_ALL" ], "type": "api", "exp": expiration_date, "iss": "admin.acryl.io" } +encoded = jwt.encode(payload, secret, algorithm="HS256") +print("Successfully generated authentication token for DataHub API Gateway: {}".format(encoded)) diff --git a/scripts/python-jwt/readme.md b/scripts/python-jwt/readme.md new file mode 100644 index 0000000..be1dbc1 --- /dev/null +++ b/scripts/python-jwt/readme.md @@ -0,0 +1,8 @@ +# Install Requirements +pip3 install -r requirements.txt + +# Set environment variable to contain secret +export API_KEY_SECRET= + +# Run +python3 create_jwt.py diff --git a/scripts/python-jwt/requirements.txt b/scripts/python-jwt/requirements.txt new file mode 100644 index 0000000..937f286 --- /dev/null +++ b/scripts/python-jwt/requirements.txt @@ -0,0 +1 @@ +pyjwt==2.3.0 diff --git a/templates/datahub-infra-deployment.yaml b/templates/datahub-infra-deployment.yaml new file mode 100644 index 0000000..4c791eb --- /dev/null +++ b/templates/datahub-infra-deployment.yaml @@ -0,0 +1,827 @@ +# Datahub Infrastructure Deployment AWS + +AWSTemplateFormatVersion: "2010-09-09" +Description: "Master template that set up Datahub platform in AWS" +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Nested CFN Templates Location + Parameters: + - TemplateBucketName + - TemplateBucketKeyPrefix + - Label: + default: VPC Stack Configuration + Parameters: + - CreateVPC + - VPCCIDR + - AvailabilityZones + - PrivateSubnet1CIDR + - PrivateSubnet2CIDR + - PrivateSubnet3CIDR + - PublicSubnet1CIDR + - PublicSubnet2CIDR + - PublicSubnet3CIDR + - Label: + default: ElasticSearch Stack Configuration + Parameters: + - CreateElasticSearch + - ESDataNodeCount + - ESInstanceType + - ESVolumeSize + - ESMasterNodeCount + - ESMasterInstanceType + - ESDomainName + - ElasticsearchVersion + - Label: + default: MySQL Stack Configuration + Parameters: + - CreateMySQL + - DBClusterIdentifier + - DBInstanceClass + - EngineVersion + - Label: + default: MSK Stack Configuration + Parameters: + - CreateMSK + - MSKClusterName + - KafkaVersion + - MSKInstanceType + - Label: + default: EKS Cluster Stack Configuration + Parameters: + - CreateEKS + - KeyPairName + - RemoteAccessCIDR + - BastionInstanceType + - MaxNumberOfBastionNodes + - MinNumberOfBastionNodes + - DesiredNumberOfBastionNodes + - NodeInstanceType + - MaxNumberOfNodes + - MinNumberOfNodes + - DesiredNumberOfNodes + - EksExternalUserArn + - EksMgmtIamRoleArn + - Label: + default: Ingress Controller Stack Configuration + Parameters: + - ControllerHostNetwork + - Label: + default: Admin Stack Configuration + Parameters: + - DomainName + - KeyPairName + - RemoteAccessCIDR + - BastionInstanceType + - MaxNumberOfBastionNodes + - MinNumberOfBastionNodes + - DesiredNumberOfBastionNodes + - K8sNamespace + - ElbCertArn + + ParameterLabels: + AvailabilityZones: + default: The AZ's to deploy to. + PrivateSubnet1CIDR: + default: The CIDR block for the first private subnet + PrivateSubnet2CIDR: + default: The CIDR block for the second private subnet + PrivateSubnet3CIDR: + default: The CIDR block for the third private subnet + PublicSubnet1CIDR: + default: The CIDR block for the first public subnet + PublicSubnet2CIDR: + default: The CIDR block for the second public subnet + PublicSubnet3CIDR: + default: The CIDR block for the third public subnet + KeyPairName: + default: The key pair name to use to access the instances + RemoteAccessCIDR: + default: The CIDR block to allow remote access + BastionInstanceType: + default: The instance type to deploy Bastion to + MaxNumberOfBastionNodes: + default: The maximum number of nodes to scale up to for Bastion + MinNumberOfBastionNodes: + default: The minimum number of nodes to scale down to for Bastion + DesiredNumberOfBastionNodes: + default: The desired number of nodes to keep running for Bastion + NodeInstanceType: + default: The instance type to deploy EKS Worker Node to + MaxNumberOfNodes: + default: The maximum number of nodes to scale up to for EKS Worker Node + MinNumberOfNodes: + default: The minimum number of nodes to scale down to for EKS Worker Node + DesiredNumberOfNodes: + default: The desired number of nodes to keep running for EKS Worker Node + TemplateBucketName: + default: The name of the S3 bucket that holds the templates + TemplateBucketKeyPrefix: + default: The Key prefix for the templates in the S3 template bucket + EksMgmtIamRoleArn: + default: The AWS IAM Role name that will be allowed to manage EKS. Note do not include path just name of role like "my-role" + EksExternalUserArn: + default: The AWS IAM user arn who will be authorised to connect the cluster externally + K8sNamespace: + default: The namespace in EKS to deploy kots and datahub app + CreateElasticSearch: + default: Enable Creation of ElasticSearch + CreateVPC: + default: Enable Creation of VPC + CreateMSK: + default: Enable Creation of MSK + CreateMySQL: + default: Enable Creation of Aurora RDS + CreateEKS: + default: Enable Creation of EKS + ESDataNodeCount: + default: ES Data Node Instance Count + ESMasterNodeCount: + default: ES Master Node Instance Count + ESInstanceType: + default: The ElasticSearch Data Node Instance Type + ESVolumeSize: + default: The ElasticSearch Data Node Volume Size + ESMasterInstanceType: + default: The ElasticSearch Master Node Instance Type + ESDomainName: + default: The ElasticSearch Domain Name + ElasticsearchVersion: + default: The ElasticSearch Version + DBClusterIdentifier: + default: Aurora Cluster Identifier + DBInstanceClass: + default: Aurora Instance Type + EngineVersion: + default: Aurora Engine Version + MSKClusterName: + default: MSK Cluster Name + KafkaVersion: + default: Kafka Version + MSKInstanceType: + default: MSK Instance Type + ControllerHostNetwork: + default: Deploy EKS load balancer controller + ElbCertArn: + default: ELB cert arn + DomainName: + default: The FQDN to access acryl + +Parameters: + AvailabilityZones: + Description: "List of Availability Zones to use for the subnets in the VPC. Please choose three zones." + Type: "List" + PrivateSubnet1CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.0.0/23" + Description: "CIDR block for private subnet 1 located in Availability Zone 1" + Type: "String" + PrivateSubnet2CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.2.0/23" + Description: "CIDR block for private subnet 2 located in Availability Zone 2" + Type: "String" + PrivateSubnet3CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.4.0/23" + Description: "CIDR block for private subnet 3 located in Availability Zone 3" + Type: "String" + PublicSubnet1CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.100.0/24" + Description: "CIDR block for the public (DMZ) subnet 1 located in Availability Zone 1" + Type: "String" + PublicSubnet2CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.102.0/24" + Description: "CIDR block for the public (DMZ) subnet 2 located in Availability Zone 2" + Type: "String" + PublicSubnet3CIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.104.0/24" + Description: "CIDR block for the public (DMZ) subnet 3 located in Availability Zone 3" + Type: "String" + VPCCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.0.0/16" + Description: "CIDR block for the VPC" + Type: "String" + KeyPairName: + Description: "The name of an existing public/private key pair, which allows you to securely connect to your instance after it launches" + Type: "AWS::EC2::KeyPair::KeyName" + RemoteAccessCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/x" + Description: "The CIDR IP range that is permitted to access the AWS resources. It is recommended that you set this value to a trusted IP range. For example /32" + Type: "String" + Default: "108.21.195.33/32" + BastionInstanceType: + Type: "String" + Default: "t2.micro" + Description: "The type of EC2 instance to be launched for Bastion Host" + AllowedValues: + # Add more instance types if needed + - t2.micro + - t2.medium + - t2.large + ConstraintDescription: "Must contain a valid instance type" + DesiredNumberOfBastionNodes: + Type: String + MinLength: 1 + Description: "The desired number of Bastion instance to run" + Default: "1" + MaxNumberOfBastionNodes: + Type: String + MinLength: 1 + Description: "The maximum number of Bastion instances to run" + Default: "1" + MinNumberOfBastionNodes: + Type: String + MinLength: 1 + Description: "The minimum number of Bastion instances to run" + Default: "1" + NodeInstanceType: + Type: "String" + Default: "m5.large" + Description: "The type of EC2 instance to be launched for EKS Worker Node" + AllowedValues: + # Add more instance types if needed + - t2.xlarge + - t2.2xlarge + - m3.xlarge + - m3.2xlarge + - m4.xlarge + - m4.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + ConstraintDescription: "Must contain a valid instance type" + DesiredNumberOfNodes: + Type: String + MinLength: 1 + Description: "The desired number of EKS Worker Nodes to run" + Default: "2" + MaxNumberOfNodes: + Type: String + MinLength: 1 + Description: "The maximum number of EKS Worker Nodes to run" + Default: "3" + MinNumberOfNodes: + Type: String + MinLength: 1 + Description: "The minimum number of EKS Worker Nodes to run" + Default: "2" + TemplateBucketName: + AllowedPattern: "^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$" + ConstraintDescription: "Bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Description: "S3 bucket name that contains the CFN templates (VPC, Bastion etc). This string can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Type: "String" + Default: "cf-templates-blrxgroup-us-west-2" + TemplateBucketKeyPrefix: + AllowedPattern: "^[0-9a-zA-Z-/]*$" + ConstraintDescription: "Template bucket key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/)." + Type: "String" + Default: "development" + EksMgmtIamRoleArn: + Type: String + Description: The AWS IAM role that will have manage access to EKS + Default: "arn:aws:iam::1234567:role/admin" + EksExternalUserArn: + Type: String + Description: The AWS IAM user arn who will be authorised to connect the cluster externally + Default: "arn:aws:iam::577660233792:user/blrxgroup-dev-admin" + K8sNamespace: + AllowedPattern: ".+" + ConstraintDescription: The K8s namespace can not be empty + Type: String + Description: The namespace in EKS to deploy kots and datahub app + Default: "datahub" + CreateVPC: + Description: "Set to true if you want to create VPC" + Type: String + Default: "true" + AllowedValues: + - "true" + - "false" + CreateElasticSearch: + Description: "Set to true if you want to create ElasticSearch" + Type: String + Default: "true" + AllowedValues: + - "true" + - "false" + CreateMySQL: + Description: "Set to true if you want to create MySQL" + Type: String + Default: "true" + AllowedValues: + - "true" + - "false" + CreateMSK: + Description: "Set to true if you want to create MSK" + Type: String + Default: "true" + AllowedValues: + - "true" + - "false" + CreateEKS: + Description: "Set to true if you want to create EKS" + Type: String + Default: "true" + AllowedValues: + - "true" + - "false" + ESVolumeSize: + Default: 50 + Description: Data Node Volume Size in GB + Type: String + ESDataNodeCount: + Default: 3 + Type: String + ESMasterNodeCount: + Default: 3 + Type: String + ESInstanceType: + Type: "String" + Default: "t3.small.elasticsearch" + Description: "The type of EC2 instance to be launched for ElasticSearch Data Node" + AllowedValues: + - t3.small.elasticsearch + - t3.large.elasticsearch + - m5.large.elasticsearch + ESMasterInstanceType: + Type: "String" + Default: "t3.small.elasticsearch" + Description: "The type of EC2 instance to be launched for ElasticSearch Master Node" + AllowedValues: + - t3.small.elasticsearch + - t3.large.elasticsearch + - c5.large.elasticsearch + ESDomainName: + Default: "datahub" + Description: Elasticsearch Domain Name + Type: String + ElasticsearchVersion: + Type: "String" + Default: "7.9" + Description: "The Elasticsearch Version" + AllowedValues: + - 7.9 + DBClusterIdentifier: + Description: "The Aurora Cluster Identifier" + Type: String + Default: datahub + DBInstanceClass: + AllowedValues: + - db.t3.small + - db.t3.large + ConstraintDescription: Must contain valid RDS instance type + Default: db.t3.small + Description: instance type for the Amazon Aurora + Type: String + EngineVersion: + AllowedValues: + - 5.7.mysql_aurora.2.09.1 + Default: 5.7.mysql_aurora.2.09.1 + Description: Aurora Engine Version + Type: String + MSKInstanceType: + AllowedValues: + - kafka.t3.small + - kafka.t3.large + - kafka.m5.large + ConstraintDescription: Must contain valid MSK instance type + Default: kafka.t3.small + Description: EC2 instance type for the Amazon MSK instances + Type: String + KafkaVersion: + AllowedValues: + - 2.4.1.1 + Default: 2.4.1.1 + Description: Kafka version + Type: String + MSKClusterName: + Default: "datahub" + Description: MSK Cluster Name + Type: String + ControllerHostNetwork: + Description: Enables Ingress Controller IAM + Type: String + Default: "Enabled" + AllowedValues: + - "Disabled" + - "Enabled" + ElbCertArn: + ConstraintDescription: The Elastic Load Balancer Cert Arn can not be empty + Type: String + Default: "arn:aws:acm:us-west-2:577660233792:certificate/66b92cc0-c78f-4a11-99f3-a6f8586d744f" + DomainName: + Default: "datahub.dev.blrxgroup.com" + Description: The FQDN to access Acryl + Type: String + + + +Conditions: + isCreateVPC: !Equals [!Ref CreateVPC, "true"] + isCreateElasticSearch: !Equals [!Ref CreateElasticSearch, "true"] + isCreateMySQL: !Equals [!Ref CreateMySQL, "true"] + isCreateMSK: !Equals [!Ref CreateMSK, "true"] + isCreateEKS: !Equals [!Ref CreateEKS, "true"] + +Resources: + + ESServiceRole: + Condition: isCreateElasticSearch + Type: 'AWS::IAM::ServiceLinkedRole' + Properties: + AWSServiceName: es.amazonaws.com + Description: 'Role for ES to access resources in my VPC' + + RDSSecret: + Type: AWS::SecretsManager::Secret + Properties: + Description: Secrets for RDS + GenerateSecretString: + SecretStringTemplate: '{"username": "admin"}' + GenerateStringKey: password + PasswordLength: 16 + ExcludeCharacters: "\"@/\\" + Tags: + - + Key: CloudFormation + Value: "true" + + ESSecret: + Type: AWS::SecretsManager::Secret + Properties: + Description: Secrets for RDS + GenerateSecretString: + SecretStringTemplate: '{"username": "admin"}' + GenerateStringKey: password + PasswordLength: 16 + ExcludeCharacters: "\"@/\\" + Tags: + - + Key: CloudFormation + Value: "true" + + VPCStack: + Condition: isCreateVPC + Type: AWS::CloudFormation::Stack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: !Sub "${AWS::StackName}-vpc" + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/vpc.yaml" + Parameters: + AvailabilityZones: !Join [",", !Ref AvailabilityZones] + PrivateSubnet1ACIDR: !Ref PrivateSubnet1CIDR + PrivateSubnet2ACIDR: !Ref PrivateSubnet2CIDR + PrivateSubnet3ACIDR: !Ref PrivateSubnet3CIDR + PrivateSubnetATag1: kubernetes.io/role/internal-elb=1 + PublicSubnet1CIDR: !Ref PublicSubnet1CIDR + PublicSubnet2CIDR: !Ref PublicSubnet2CIDR + PublicSubnet3CIDR: !Ref PublicSubnet3CIDR + PublicSubnetTag1: kubernetes.io/role/elb=1 + CreatePrivateSubnets: "true" + VPCCIDR: !Ref VPCCIDR + + EC2LogGroup: + Type: AWS::Logs::LogGroup + + NodeInstanceRole: + Type: AWS::IAM::Role + Properties: + Policies: + - PolicyName: cloudwatch-logs-policy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogStream + - logs:GetLogEvents + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + - logs:PutRetentionPolicy + - logs:PutMetricFilter + - logs:CreateLogGroup + Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${EC2LogGroup}:*" + Effect: Allow + - PolicyName: ebs-volume-policy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - ec2:AttachVolume + - ec2:DetachVolume + Resource: "arn:aws:ec2:*:*:volume/*" + Effect: Allow + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: / + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM + + NodeSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for all nodes in the cluster + VpcId: !GetAtt VPCStack.Outputs.VPCID + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-WorkerNodes-SG" + - Key: Component + Value: !Sub "${AWS::StackName}-NodeSG" + - Key: !Sub "kubernetes.io/cluster/${AWS::StackName}" + Value: 'owned' + - Key: KubernetesCluster + Value: !Sub "${AWS::StackName}" + + ElasticSearchStack: + Condition: isCreateElasticSearch + Type: AWS::CloudFormation::Stack + DependsOn: + - VPCStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: Datahub-ElasticSearch + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/elasticsearch.yaml" + Parameters: + # ES stack params + VPCID: !GetAtt VPCStack.Outputs.VPCID + VPCCIDR: !Ref VPCCIDR + PrivateSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1AID + PrivateSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2AID + PrivateSubnet3: !GetAtt VPCStack.Outputs.PrivateSubnet3AID + NodeSecurityGroup: !Ref NodeSecurityGroup + ESDataNodeCount: !Ref ESDataNodeCount + ESInstanceType: !Ref ESInstanceType + ESVolumeSize: !Ref ESVolumeSize + ESMasterNodeCount: !Ref ESMasterNodeCount + ESMasterInstanceType: !Ref ESMasterInstanceType + ESDomainName: !Ref ESDomainName + ElasticsearchVersion: !Ref ElasticsearchVersion + ESMasterUserPassword: + Fn::Sub: "{{resolve:secretsmanager:${ESSecret}::password}}" + + MySQLStack: + Condition: isCreateMySQL + Type: AWS::CloudFormation::Stack + DependsOn: + - VPCStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: Datahub-MySQL + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/mysql.yaml" + Parameters: + # ES stack params + AvailabilityZones: !Join [",", !Ref AvailabilityZones] + VPCID: !GetAtt VPCStack.Outputs.VPCID + VPCCIDR: !Ref VPCCIDR + DBSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1AID + DBSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2AID + DBSubnet3: !GetAtt VPCStack.Outputs.PrivateSubnet3AID + NodeSecurityGroup: !Ref NodeSecurityGroup + MasterUser: + Fn::Sub: "{{resolve:secretsmanager:${RDSSecret}::username}}" + MasterUserPassword: + Fn::Sub: "{{resolve:secretsmanager:${RDSSecret}::password}}" + DBClusterIdentifier: !Ref DBClusterIdentifier + DBInstanceClass: !Ref DBInstanceClass + EngineVersion: !Ref EngineVersion + + MSKStack: + Condition: isCreateMSK + Type: AWS::CloudFormation::Stack + DependsOn: + - VPCStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: Datahub-MSK + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/msk.yaml" + Parameters: + # MSK stack params + VPCID: !GetAtt VPCStack.Outputs.VPCID + VPCCIDR: !Ref VPCCIDR + DBSubnet1: !GetAtt VPCStack.Outputs.PrivateSubnet1AID + DBSubnet2: !GetAtt VPCStack.Outputs.PrivateSubnet2AID + DBSubnet3: !GetAtt VPCStack.Outputs.PrivateSubnet3AID + NodeSecurityGroup: !Ref NodeSecurityGroup + MSKClusterName: !Ref MSKClusterName + MSKInstanceType: !Ref MSKInstanceType + KafkaVersion: !Ref KafkaVersion + + EKSStack: + Condition: isCreateEKS + Type: AWS::CloudFormation::Stack + DependsOn: + - VPCStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: !Sub "${AWS::StackName}-Bastion" + - Key: KubernetesCluster + Value: !Sub "${AWS::StackName}" + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/eks.yaml" + Parameters: + # Bastion stack params + KeyPairName: !Ref KeyPairName + PublicSubnet1ID: !GetAtt VPCStack.Outputs.PublicSubnet1ID + PublicSubnet2ID: !GetAtt VPCStack.Outputs.PublicSubnet2ID + PublicSubnet3ID: !GetAtt VPCStack.Outputs.PublicSubnet3ID + PrivateSubnet1ID: !GetAtt VPCStack.Outputs.PrivateSubnet1AID + PrivateSubnet2ID: !GetAtt VPCStack.Outputs.PrivateSubnet2AID + PrivateSubnet3ID: !GetAtt VPCStack.Outputs.PrivateSubnet3AID + RemoteAccessCIDR: !Ref RemoteAccessCIDR + VPCID: !GetAtt VPCStack.Outputs.VPCID + BastionInstanceType: !Ref BastionInstanceType + MaxNumberOfBastionNodes: !Ref MaxNumberOfBastionNodes + MinNumberOfBastionNodes: !Ref MinNumberOfBastionNodes + DesiredNumberOfBastionNodes: !Ref DesiredNumberOfBastionNodes + # EKS Worker Node stack params + TemplateBucketName: !Ref TemplateBucketName + TemplateBucketKeyPrefix: !Ref TemplateBucketKeyPrefix + EC2LogGroup: !Ref EC2LogGroup + NodeSecurityGroup: !Ref NodeSecurityGroup + NodeInstanceRole: !Ref NodeInstanceRole + NodeInstanceRoleArn: !GetAtt NodeInstanceRole.Arn + NodeInstanceType: !Ref NodeInstanceType + MaxNumberOfNodes: !Ref MaxNumberOfNodes + MinNumberOfNodes: !Ref MinNumberOfNodes + DesiredNumberOfNodes: !Ref DesiredNumberOfNodes + EksMgmtIamRoleArn: !Ref EksMgmtIamRoleArn + EksExternalUserArn: !Ref EksExternalUserArn + EKSClusterName: !Sub "${AWS::StackName}" + + IngressControllerStack: + Type: AWS::CloudFormation::Stack + DependsOn: + - EKSStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: !Sub "${AWS::StackName}-Admin" + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/ingress-controller.yaml" + Parameters: + VpcId: !GetAtt VPCStack.Outputs.VPCID + EksClusterName: !Sub "${AWS::StackName}" + ControllerHostNetwork: !Ref ControllerHostNetwork + + AdminStack: + Type: AWS::CloudFormation::Stack + DependsOn: + - IngressControllerStack + - MSKStack + - ElasticSearchStack + - MySQLStack + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}" + - Key: Component + Value: !Sub "${AWS::StackName}-Admin" + TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/admin.yaml" + Parameters: + # Bastion stack params + KeyPairName: !Ref KeyPairName + PublicSubnet1ID: !GetAtt VPCStack.Outputs.PublicSubnet1ID + PublicSubnet2ID: !GetAtt VPCStack.Outputs.PublicSubnet2ID + PrivateSubnet1ID: !GetAtt VPCStack.Outputs.PrivateSubnet1AID + PrivateSubnet2ID: !GetAtt VPCStack.Outputs.PrivateSubnet2AID + RemoteAccessCIDR: !Ref RemoteAccessCIDR + VPCID: !GetAtt VPCStack.Outputs.VPCID + BastionInstanceRole: !GetAtt EKSStack.Outputs.BastionInstanceRole + BastionInstanceProfile: !GetAtt EKSStack.Outputs.BastionInstanceProfile + BastionInstanceType: !Ref BastionInstanceType + BastionSecurityGroup: !GetAtt EKSStack.Outputs.BastionSecurityGroup + NodeInstanceRoleArn: !GetAtt EKSStack.Outputs.NodeInstanceRoleArn + MaxNumberOfBastionNodes: !Ref MaxNumberOfBastionNodes + MinNumberOfBastionNodes: !Ref MinNumberOfBastionNodes + DesiredNumberOfBastionNodes: !Ref DesiredNumberOfBastionNodes + # EKS Worker Node stack params + TemplateBucketName: !Ref TemplateBucketName + TemplateBucketKeyPrefix: !Ref TemplateBucketKeyPrefix + EC2LogGroup: !Ref EC2LogGroup + EKSClusterName: !Sub "${AWS::StackName}" + K8sNamespace: !Ref K8sNamespace + # pass other stack info + DomainName: !Ref DomainName + MySQLEndpoint: !GetAtt MySQLStack.Outputs.ClusterEndpoint + ElasticSearchEndpoint: !GetAtt ElasticSearchStack.Outputs.DomainEndpoint + MSKClusterName: !Ref MSKClusterName + ElbCertArn: !Ref ElbCertArn + MasterUserPassword: + Fn::Sub: "{{resolve:secretsmanager:${RDSSecret}::password}}" + ESMasterUserPassword: + Fn::Sub: "{{resolve:secretsmanager:${ESSecret}::password}}" + +# PrivateLinkStack: +# Type: AWS::CloudFormation::Stack +# DependsOn: +# - AdminStack +# Properties: +# Tags: +# - Key: Name +# Value: !Sub "${AWS::StackName}" +# - Key: Component +# Value: !Sub "${AWS::StackName}-PrivateLink" +# TemplateURL: !Sub "https://${TemplateBucketName}.s3.amazonaws.com/${TemplateBucketKeyPrefix}/templates/nested/privatelink.yaml" + +Outputs: + # VPC stack + VPCID: + Value: !GetAtt VPCStack.Outputs.VPCID + Condition: isCreateVPC + RemoteAccessCIDR: + Value: !Ref RemoteAccessCIDR + Condition: isCreateEKS + + # Bastion stack + BastionSubstackName: + Value: !GetAtt EKSStack.Outputs.SubstackName + Condition: isCreateEKS + BastionSecurityGroup: + Value: !GetAtt EKSStack.Outputs.BastionSecurityGroup + Condition: isCreateEKS + BastionLaunchConfiguration: + Value: !GetAtt EKSStack.Outputs.BastionLaunchConfiguration + Condition: isCreateEKS + BastionAutoScalingGroup: + Value: !GetAtt EKSStack.Outputs.BastionAutoScalingGroup + Condition: isCreateEKS + BastionInstanceRole: + Value: !GetAtt EKSStack.Outputs.BastionInstanceRole + Condition: isCreateEKS + BastionInstanceProfile: + Value: !GetAtt EKSStack.Outputs.BastionInstanceProfile + Condition: isCreateEKS + BastionInstanceRole: + Value: !GetAtt EKSStack.Outputs.BastionInstanceRole + Condition: isCreateEKS + EC2LogGroup: + Value: !Ref EC2LogGroup + Condition: isCreateEKS + BastionSecurityGroup: + Value: !GetAtt EKSStack.Outputs.BastionSecurityGroup + Condition: isCreateEKS + + # ElasticSearch + DomainEndpoint: + Value: !GetAtt ElasticSearchStack.Outputs.DomainEndpoint + Condition: isCreateElasticSearch + + + # EKS Cluster + ControlPlaneSecurityGroup: + Value: !GetAtt EKSStack.Outputs.ControlPlaneSecurityGroup + Condition: isCreateEKS + EksClusterName: + Value: !GetAtt EKSStack.Outputs.EksClusterName + Condition: isCreateEKS + EksServiceRoleArn: + Value: !GetAtt EKSStack.Outputs.EksServiceRoleArn + Condition: isCreateEKS + NodeInstanceRoleArn: + Value: !GetAtt NodeInstanceRole.Arn + Condition: isCreateEKS + NodeSecurityGroupId: + Value: !Ref NodeSecurityGroup + Condition: isCreateEKS + AvailabilityZones: + Value: !Join [",", !Ref AvailabilityZones] + Condition: isCreateVPC diff --git a/templates/nested/admin.yaml b/templates/nested/admin.yaml new file mode 100644 index 0000000..23f2a4d --- /dev/null +++ b/templates/nested/admin.yaml @@ -0,0 +1,602 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "Bastion Stack that is used to deploy stuff to EKS." + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Cluster Configuration + Parameters: + - EKSClusterName + - K8sNamespace + - TemplateBucketName + - TemplateBucketKeyPrefix + - Label: + default: Bastion Stack Configuration + Parameters: + - VPCID + - PrivateSubnet1ID + - PrivateSubnet2ID + - PublicSubnet1ID + - PublicSubnet2ID + - EC2LogGroup + - KeyPairName + - RemoteAccessCIDR + - BastionInstanceType + - MaxNumberOfBastionNodes + - MinNumberOfBastionNodes + - DesiredNumberOfBastionNodes + + ParameterLabels: + TemplateBucketName: + default: The name of the S3 bucket that holds the templates + TemplateBucketKeyPrefix: + default: The Key prefix for the templates in the S3 template bucket + VPCID: + default: The ID of the VPC to deploy the Bastion and EKS Cluster into + PrivateSubnet1ID: + default: The ID of the first private subnet to deploy EKS Workers into + PrivateSubnet2ID: + default: The ID of the second private subnet to deploy EKS Workers into + PublicSubnet1ID: + default: The ID of the first public subet to deploy EKS into + PublicSubnet2ID: + default: The ID of the second public subnet to deploy EKS into + EC2LogGroup: + default: The bastion log group name + KeyPairName: + default: The key pair name to use to access the instances + RemoteAccessCIDR: + default: The CIDR block to allow remote access + BastionInstanceType: + default: The instance type to deploy Bastion to + MaxNumberOfBastionNodes: + default: The maximum number of nodes to scale up to for Bastion + MinNumberOfBastionNodes: + default: The minimum number of nodes to scale down to for Bastion + DesiredNumberOfBastionNodes: + default: The desired number of nodes to keep running for Bastion + EKSClusterName: + default: The EKS cluster name + K8sNamespace: + default: The namespace in EKS to deploy kots and datahub app + +Parameters: + TemplateBucketName: + AllowedPattern: "^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$" + ConstraintDescription: "Bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Description: "S3 bucket name that contains the CFN templates (VPC, Bastion etc). This string can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Type: "String" + TemplateBucketKeyPrefix: + AllowedPattern: "^[0-9a-zA-Z-/]*$" + ConstraintDescription: "Template bucket key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/)." + Type: "String" + VPCID: + Description: "ID for the VPC" + Type: "AWS::EC2::VPC::Id" + PublicSubnet1ID: + Description: "ID of Public Subnet 1" + Type: "AWS::EC2::Subnet::Id" + PublicSubnet2ID: + Description: "ID of Public Subnet 2" + Type: "AWS::EC2::Subnet::Id" + PrivateSubnet1ID: + Description: "ID of Private Subnet 1" + Type: "AWS::EC2::Subnet::Id" + PrivateSubnet2ID: + Description: "ID of Private Subnet 2" + Type: "AWS::EC2::Subnet::Id" + EC2LogGroup: + Description: The bastion log group name + Type: "String" + KeyPairName: + Description: "The name of an existing public/private key pair, which allows you to securely connect to your instance after it launches" + Type: "AWS::EC2::KeyPair::KeyName" + RemoteAccessCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/x" + Description: "The CIDR IP range that is permitted to access the AWS resources. It is recommended that you set this value to a trusted IP range." + Type: "String" + BastionInstanceType: + Type: "String" + Description: "The type of EC2 instance to be launched for Bastion Host" + AllowedValues: + # Add more instance types if needed + - t2.micro + - t2.medium + - t2.large + ConstraintDescription: "Must contain a valid instance type" + DesiredNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The desired number of Bastion instance to run" + MaxNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The maximum number of Bastion instances to run" + MinNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The minimum number of Bastion instances to run" + Default: "1" + EKSClusterName: + Type: String + Description: The name of the eks cluster + BastionSecurityGroup: + Type: String + BastionInstanceRole: + Type: String + BastionInstanceProfile: + Type: String + NodeInstanceRoleArn: + Type: String + K8sNamespace: + AllowedPattern: ".+" + ConstraintDescription: The K8s namespace can not be empty + Type: String + Description: The namespace in EKS to deploy kots and datahub app + Default: "datahub" + DomainName: + Type: String + MySQLEndpoint: + Type: String + ElasticSearchEndpoint: + Type: String + MSKClusterName: + Type: String + ElbCertArn: + Type: String + MasterUserPassword: + Type: String + ESMasterUserPassword: + Type: String + +Mappings: + # see https://github.com/aws-quickstart/quickstart-linux-bastion/blob/master/templates/linux-bastion.template for latest AMI IDs + # Use Amazon 2 Linux + BastionLatestAmiRegionMap: + us-west-2: + AmiId: ami-0873b46c45c11058d + us-west-1: + AmiId: ami-05655c267c89566dd + us-east-1: + AmiId: ami-02354e95b39ca8dec + us-east-2: + AmiId: ami-07c8bc5c1ce9598c3 + eu-central-1: + AmiId: ami-0c115dbd34c69a004 + eu-west-1: + AmiId: ami-07d9160fa81ccffb5 + # Use Amazon 2018 + BastionAmiRegionMap: + us-east-1: + AmiId: ami-0080e4c5bc078760e + us-west-2: + AmiId: ami-01e24be29428c15b2 + us-east-2: + AmiId: ami-0cd3dfa4e37921605 + eu-central-1: + AmiId: ami-0cfbf4f6db41068ac + eu-west-1: + AmiId: ami-08935252a36e25f85 + +Resources: + + # Bastion resources + #AdminBastionEIP: + # Type: AWS::EC2::EIP + # Properties: + # Domain: vpc + + AdminBastionAutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + LaunchConfigurationName: !Ref AdminBastionLaunchConfiguration + VPCZoneIdentifier: + - !Ref PublicSubnet1ID + - !Ref PublicSubnet2ID + MinSize: !Ref MinNumberOfBastionNodes + MaxSize: !Ref MaxNumberOfBastionNodes + Cooldown: "300" + DesiredCapacity: !Ref DesiredNumberOfBastionNodes + Tags: + - Key: Name + Value: !Sub "${EKSClusterName}-admin-bastion-node" + PropagateAtLaunch: true + - Key: Component + Value: datahub-Bastion-AutoScaling-Group + PropagateAtLaunch: true + CreationPolicy: + ResourceSignal: + Timeout: PT30M + + AdminBastionLaunchConfiguration: + Type: AWS::AutoScaling::LaunchConfiguration + Metadata: + AWS::CloudFormation::Authentication: + S3AccessCreds: + type: S3 + roleName: !Ref BastionInstanceRole + buckets: !Ref TemplateBucketName + AWS::CloudFormation::Init: + config: + packages: + yum: + awslogs: [] + files: + '/etc/awslogs/awscli.conf': + content: !Sub | + [default] + region = ${AWS::Region} + [plugins] + cwlogs = cwlogs + mode: '000644' + owner: root + group: root + '/etc/awslogs/awslogs.conf': + content: !Sub | + [general] + state_file = /var/lib/awslogs/agent-state + [/var/log/bastion/bastion.log] + file = /var/log/bastion/bastion.log + datetime_format = %b %d %H:%M:%S + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/bastion/bastion.log + log_group_name = ${EC2LogGroup} + [/var/log/dmesg] + file = /var/log/dmesg + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/dmesg + log_group_name = ${EC2LogGroup} + [/var/log/messages] + datetime_format = %b %d %H:%M:%S + file = /var/log/messages + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/messages + log_group_name = ${EC2LogGroup} + [/var/log/secure] + datetime_format = %b %d %H:%M:%S + file = /var/log/secure + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/secure + log_group_name = ${EC2LogGroup} + [/var/log/audit/audit.log] + datetime_format = + file = /var/log/audit/audit.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/audit/audit.log + log_group_name = ${EC2LogGroup} + [/var/log/cron] + datetime_format = %b %d %H:%M:%S + file = /var/log/cron + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cron + log_group_name = ${EC2LogGroup} + [/var/log/cfn-init.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-init.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-init.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-hup.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-hup.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-hup.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-init-cmd.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-init-cmd.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-init-cmd.log + log_group_name = ${EC2LogGroup} + [/var/log/cloud-init-output.log] + file = /var/log/cloud-init-output.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cloud-init-output.log + log_group_name = ${EC2LogGroup} + [/var/log/amazon/ssm/amazon-ssm-agent.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/amazon/ssm/amazon-ssm-agent.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/amazon/ssm/amazon-ssm-agent.log + log_group_name = ${EC2LogGroup} + [/var/log/amazon/ssm/errors.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/amazon/ssm/errors.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/amazon/ssm/errors.log + log_group_name = ${EC2LogGroup} + [/var/log/maillog] + datetime_format = %b %d %H:%M:%S + file = /var/log/maillog + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/maillog + log_group_name = ${EC2LogGroup} + [/var/log/yum.log] + datetime_format = %b %d %H:%M:%S + file = /var/log/yum.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/yum.log + log_group_name = ${EC2LogGroup} + [/var/log/awslogs.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/awslogs.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/awslogs.log + log_group_name = ${EC2LogGroup} + [/var/log/boot.log] + file = /var/log/boot.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/boot.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-wire.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-wire.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-wire.log + log_group_name = ${EC2LogGroup} + mode: '000644' + owner: root + group: root + /tmp/bastion_bootstrap.sh: + source: !Sub "https://s3.${AWS::Region}.amazonaws.com/${TemplateBucketName}/${TemplateBucketKeyPrefix}/scripts/bastion_bootstrap.sh" + mode: "000550" + owner: root + group: root + authentication: S3AccessCreds + /tmp/license.yaml: + source: !Sub "https://s3.${AWS::Region}.amazonaws.com/${TemplateBucketName}/${TemplateBucketKeyPrefix}/license/license.yaml" + mode: "000550" + owner: root + group: root + /tmp/eks_bootstrap.sh: + content: !Sub | + #!/bin/bash + set -x + echo "Checking whether cluster exists..." + aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} &> /dev/null + if [ $? -eq 0 ]; then + echo Updating kubeconfig file... + ENDPOINT=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.endpoint --output text) + CERT_DATA=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.certificateAuthority.data --output text) + sed -i s,ENDPOINT,$ENDPOINT,g /home/ec2-user/.kube/config + sed -i s,CERTIFICATE_DATA,$CERT_DATA,g /home/ec2-user/.kube/config + export KUBECONFIG=/home/ec2-user/.kube/config + chmod 600 /home/ec2-user/.kube/config + fi + echo Install Ingress Controller... + curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp + mv /tmp/eksctl /usr/local/bin + eksctl utils associate-iam-oidc-provider --cluster ${EKSClusterName} --approve + helm repo add eks https://aws.github.io/eks-charts + helm repo update + echo " serviceAccount:" > values.yaml + echo " annotations:" >> values.yaml + echo " eks.amazonaws.com/role-arn: arn:aws:iam::${AWS::AccountId}:role/datahub-aws-load-balancer-controller" >> values.yaml + helm install aws-load-balancer-controller eks/aws-load-balancer-controller --set region=${AWS::Region} -n kube-system --set clusterName=${EKSClusterName} --set serviceAccount.name=datahub-aws-load-balancer-controller --set image.tag=v2.2.0 --version 1.2.7 -f values.yaml + echo prepare configvalues.yaml... + ZOOKEEPER_CONNECT=$(aws kafka --region ${AWS::Region} list-clusters --cluster-name-filter ${MSKClusterName} --no-paginate --query 'ClusterInfoList[0].ZookeeperConnectString' --output text) + CLUSTER_ARN=$(aws kafka --region ${AWS::Region} list-clusters --cluster-name-filter ${MSKClusterName} --no-paginate --query 'ClusterInfoList[0].ClusterArn' --output text) + BOOTSTRAP_BROKERS=$(aws kafka --region ${AWS::Region} get-bootstrap-brokers --cluster-arn $CLUSTER_ARN --no-paginate --query 'BootstrapBrokerString' --output text) + sed -i "s|BOOTSTRAP_BROKERS|$BOOTSTRAP_BROKERS|g" /home/ec2-user/configvalues.yaml + sed -i "s|ZOOKEEPER_CONNECT|$ZOOKEEPER_CONNECT|g" /home/ec2-user/configvalues.yaml + parameter='/${EKSClusterName}/msk/bootstrap_brokers' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "$BOOTSTRAP_BROKERS" --overwrite --no-paginate + parameter='/${EKSClusterName}/msk/zookeeper_connect' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "$ZOOKEEPER_CONNECT" --overwrite --no-paginate + parameter='/${EKSClusterName}/mysql/endpoint' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "${MySQLEndpoint}" --overwrite --no-paginate + parameter='/${EKSClusterName}/elasticsearch/endpoint' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "${ElasticSearchEndpoint}" --overwrite --no-paginate + echo Creating namespace... + kubectl create ns ${K8sNamespace} + echo Generate UUID... + aws kms generate-random --region ${AWS::Region} --number-of-bytes 32 --output text --query Plaintext>uuid_secret + aws kms generate-random --region ${AWS::Region} --number-of-bytes 16 --output text --query Plaintext>datahub-password + DATAHUB_PASSWORD=$(cat datahub-password) + parameter='/${EKSClusterName}/admin/password' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "SecureString" --value "$DATAHUB_PASSWORD" --overwrite --no-paginate + echo prepare secrets... + kubectl get secret/play-secret -n ${K8sNamespace} &> /dev/null + if [ $? -gt 0 ]; then + echo "create secret uuid_secret in namespace: ${K8sNamespace}" + kubectl create secret generic play-secret --from-file=uuid_secret=uuid_secret --namespace ${K8sNamespace} + fi + kubectl create secret generic datahub-secrets --from-file=datahub-password=datahub-password --namespace ${K8sNamespace} + echo -n "${MasterUserPassword}" > mysql-password + echo -n "${ESMasterUserPassword}" > elasticsearch-password + + kubectl create secret generic mysql-secrets --from-file=mysql-password=mysql-password --namespace ${K8sNamespace} + kubectl create secret generic elasticsearch-secrets --from-file=elasticsearch-password=elasticsearch-password --namespace ${K8sNamespace} + echo Cleanup eks oidc... + parameter='/${EKSClusterName}/eks/oidc' + aws ssm delete-parameter --region ${AWS::Region} --name "$parameter" --no-paginate + echo Install kots... + kubectl kots install datahub-poc/unstable -n ${K8sNamespace} --shared-password Passw0rd --license-file /tmp/license.yaml --config-values /home/ec2-user/configvalues.yaml --wait-duration 120s --port-forward=false + echo Install kotsadmin svc... + kubectl apply -f /home/ec2-user/kotsadm-svc.yaml + echo Waiting datahub-kotsadm to be Active... + aws elbv2 describe-load-balancers --region ${AWS::Region} --names datahub-kotsadm + while [ $? -gt 0 ]; do + echo datahub-kotsadm is still creating, sleeping for 30 seconds... + sleep 30 + aws elbv2 describe-load-balancers --region ${AWS::Region} --names datahub-kotsadm + done + STATUS=$(aws elbv2 describe-load-balancers --region ${AWS::Region} --names datahub-kotsadm --query LoadBalancers[0].State.Code --output text --no-paginate) + while [ $STATUS != 'active' ]; do + echo datahub-kotsadm is still provisioning, sleeping for 10 seconds... + sleep 10 + STATUS=$(aws elbv2 describe-load-balancers --region ${AWS::Region} --names datahub-kotsadm --query LoadBalancers[0].State.Code --output text --no-paginate) + done + NLBARN=$(aws elbv2 describe-load-balancers --region ${AWS::Region} --names datahub-kotsadm --query LoadBalancers[0].LoadBalancerArn --output text --no-paginate) + parameter='/${EKSClusterName}/admin/nlbarn' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "$NLBARN" --overwrite --no-paginate + mode: "000750" + owner: root + group: root + /home/ec2-user/.kube/config: + content: !Sub | + apiVersion: v1 + clusters: + - cluster: + server: ENDPOINT + certificate-authority-data: CERTIFICATE_DATA + name: kubernetes + contexts: + - context: + cluster: kubernetes + user: aws + name: aws + current-context: aws + kind: Config + preferences: {} + users: + - name: aws + user: + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + command: aws-iam-authenticator + args: + - token + - -i + - ${EKSClusterName} + mode: "000666" + owner: ec2-user + group: ec2-user + /home/ec2-user/kotsadm-svc.yaml: + content: !Sub | + apiVersion: v1 + kind: Service + metadata: + annotations: + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp + service.beta.kubernetes.io/aws-load-balancer-name: datahub-kotsadm + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + service.beta.kubernetes.io/aws-load-balancer-scheme: internal + service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "${ElbCertArn}" + service.beta.kubernetes.io/aws-load-balancer-type: external + labels: + kots.io/kotsadm: "true" + name: kotsadm-nlb + namespace: datahub + spec: + ports: + - name: https + port: 443 + targetPort: 3000 + selector: + app: kotsadm + sessionAffinity: None + type: LoadBalancer + mode: "000666" + owner: ec2-user + group: ec2-user + /home/ec2-user/configvalues.yaml: + content: !Sub | + apiVersion: kots.io/v1beta1 + kind: ConfigValues + metadata: + name: datahub_app + spec: + values: + domain: + repeatableItem: service_port + value: "${DomainName}" + lr_order: + repeatableItem: service_port + value: "200" + mysql_endpoint: + repeatableItem: service_port + value: "${MySQLEndpoint}" + elasticsearch_endpoint: + repeatableItem: service_port + value: "${ElasticSearchEndpoint}" + bootstrap_brokers: + repeatableItem: service_port + value: BOOTSTRAP_BROKERS + zookeeper_connect: + repeatableItem: service_port + value: ZOOKEEPER_CONNECT + certificate_arn: + repeatableItem: service_port + value: "${ElbCertArn}" + mode: "000666" + owner: ec2-user + group: ec2-user + services: + sysvinit: + awslogs: + enabled: true + ensureRunning: true + packages: + yum: + - awslogs + files: + - '/etc/awslogs/awslogs.conf' + - '/etc/awslogs/awscli.conf' + commands: + 01_eks-bootstrap: + command: "./tmp/eks_bootstrap.sh |tee -a /tmp/eks_bootstrap.output 2>&1" + 02_bastion-bootstrap: + command: "./tmp/bastion_bootstrap.sh --tcp-forwarding false --x11-forwarding false|tee -a /tmp/bastion_bootstrap.output 2>&1" + Properties: + AssociatePublicIpAddress: true + PlacementTenancy: default + KeyName: !Ref KeyPairName + IamInstanceProfile: !Ref BastionInstanceProfile + ImageId: !FindInMap [BastionAmiRegionMap, !Ref "AWS::Region", AmiId] + SecurityGroups: + - !Ref BastionSecurityGroup + InstanceType: !Ref BastionInstanceType + UserData: + Fn::Base64: !Sub | + #!/bin/bash + export PATH=$PATH:/usr/local/bin + yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm + yum install -y git + pip install awscli --upgrade + easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz + CLOUDWATCHGROUP=${EC2LogGroup} + curl -LO https://dl.k8s.io/release/v1.20.2/bin/linux/amd64/kubectl + chmod +x ./kubectl + mv kubectl /usr/local/bin + kubectl version --short --client + curl -o aws-iam-authenticator https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/aws-iam-authenticator + chmod +x ./aws-iam-authenticator + mv aws-iam-authenticator /usr/local/bin + aws-iam-authenticator version + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + chmod +x ./get_helm.sh + ./get_helm.sh + helm version --client + wget https://github.com/replicatedhq/kots/releases/download/v1.55.0/kots_linux_amd64.tar.gz + tar xzvf kots_linux_amd64.tar.gz + mv -f kots /usr/local/bin/kubectl-kots + kubectl kots --help + cfn-init -v --stack ${AWS::StackName} --resource AdminBastionLaunchConfiguration --region ${AWS::Region} + cfn-signal -e $? --stack ${AWS::StackName} --resource AdminBastionAutoScalingGroup --region ${AWS::Region} + +Outputs: + SubstackName: + Description: The admin stack name + Value: !Sub "${AWS::StackName}" + AdminBastionName: + Description: The Admin Bastion ec2 Name tag + Value: "datahub-admin-bastion-node" + AdminBastionKeyPair: + Description: SSH Key Pair to access Admin Bastion + Value: !Ref KeyPairName + AdminBastionUser: + Description: SSH username to access Admin Bastion + Value: "ec2-user" + DatahubAppUrl: + Description: Datahub Application Url + Value: !Sub "https://${DomainName}" + DatahubUser: + Description: Datahub Username + Value: "admin" + DatahubPassword: + Description: Datahub password + Value: "Check SSM Parameter Store: /datahub/admin/password" + AuroraRDSEndpoint: + Description: Aurora RDS endpoint + Value: "Check SSM Parameter Store: /datahub/mysql/endpoint" + AuroraRDSUser: + Description: Aurora RDS master username + Value: "Check SSM Parameter Store: /datahub/mysql/username" + AuroraRDSPasswordj: + Description: Aurora RDS master password + Value: "Check SSM Parameter Store: /datahub/mysql/password" + ElasticSearchEndpoint: + Description: ElasticSearch endpoint + Value: "Check SSM Parameter Store: /datahub/elasticsearch/endpoint" + ElasticSearchUser: + Description: ElasticSearch master username + Value: "Check SSM Parameter Store: /datahub/elasticsearch/username" + ElasticSearchPassword: + Description: ElasticSearch master password + Value: "Check SSM Parameter Store: /datahub/elasticsearch/password" diff --git a/templates/nested/eks.yaml b/templates/nested/eks.yaml new file mode 100644 index 0000000..fe89915 --- /dev/null +++ b/templates/nested/eks.yaml @@ -0,0 +1,1048 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "Bastion Stack that is used to provision an EKS Cluster with Worker Nodes Group." + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Cluster Configuration + Parameters: + - EksMgmtIamRoleArn + - EksExternalUserArn + - EKSClusterName + - TemplateBucketName + - TemplateBucketKeyPrefix + - Label: + default: Bastion Stack Configuration + Parameters: + - VPCID + - PrivateSubnet1ID + - PrivateSubnet2ID + - PrivateSubnet3ID + - PublicSubnet1ID + - PublicSubnet2ID + - PublicSubnet3ID + - EC2LogGroup + - KeyPairName + - NodeSecurityGroup + - RemoteAccessCIDR + - BastionInstanceType + - MaxNumberOfBastionNodes + - MinNumberOfBastionNodes + - DesiredNumberOfBastionNodes + - Label: + default: EKS Worker Nodes Stack Configuration + Parameters: + - NodeInstanceRole + - NodeInstanceRoleArn + - NodeInstanceType + - NodeSecurityGroup + - MaxNumberOfNodes + - MinNumberOfNodes + - DesiredNumberOfNodes + + ParameterLabels: + TemplateBucketName: + default: The name of the S3 bucket that holds the templates + TemplateBucketKeyPrefix: + default: The Key prefix for the templates in the S3 template bucket + VPCID: + default: The ID of the VPC to deploy the Bastion and EKS Cluster into + PrivateSubnet1ID: + default: The ID of the first private subnet to deploy EKS Workers into + PrivateSubnet2ID: + default: The ID of the second private subnet to deploy EKS Workers into + PrivateSubnet3ID: + default: The ID of the third private subnet to deploy EKS Workers into + PublicSubnet1ID: + default: The ID of the first public subet to deploy EKS into + PublicSubnet2ID: + default: The ID of the second public subnet to deploy EKS into + PublicSubnet3ID: + default: The ID of the third public subnet to deploy EKS into + EC2LogGroup: + default: The bastion log group name + KeyPairName: + default: The key pair name to use to access the instances + RemoteAccessCIDR: + default: The CIDR block to allow remote access + BastionInstanceType: + default: The instance type to deploy Bastion to + MaxNumberOfBastionNodes: + default: The maximum number of nodes to scale up to for Bastion + MinNumberOfBastionNodes: + default: The minimum number of nodes to scale down to for Bastion + DesiredNumberOfBastionNodes: + default: The desired number of nodes to keep running for Bastion + NodeInstanceRole: + default: The AWS IAM Role to be applied to the EKS Worker Nodes + NodeInstanceRoleArn: + default: The AWS IAM Role ARN to be applied to the EKS Worker Nodes + NodeInstanceType: + default: The instance type to deploy EKS Worker Node to + NodeSecurityGroup: + default: The Security Group of EKS Worker nodes + MaxNumberOfNodes: + default: The maximum number of nodes to scale up to for EKS Worker Node + MinNumberOfNodes: + default: The minimum number of nodes to scale down to for EKS Worker Node + DesiredNumberOfNodes: + default: The desired number of nodes to keep running for EKS Worker Node + EksMgmtIamRoleArn: + default: The AWS IAM Role name that will be allowed to manage EKS. Note format is arn:aws:iam::123456789:role/admin-role + EksExternalUserArn: + default: The AWS IAM user arn who will be authorised to connect the cluster externally + EKSClusterName: + default: The EKS cluster name + +Parameters: + TemplateBucketName: + AllowedPattern: "^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$" + ConstraintDescription: "Bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Description: "S3 bucket name that contains the CFN templates (VPC, Bastion etc). This string can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-)." + Type: "String" + TemplateBucketKeyPrefix: + AllowedPattern: "^[0-9a-zA-Z-/]*$" + ConstraintDescription: "Template bucket key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/)." + Type: "String" + NodeSecurityGroup: + Description: "ID for the VPC, This will be used to get the node security group" + Type: "AWS::EC2::SecurityGroup::Id" + VPCID: + Description: "ID for the VPC" + Type: "AWS::EC2::VPC::Id" + PublicSubnet1ID: + Description: "ID of Public Subnet 1" + Type: "AWS::EC2::Subnet::Id" + PublicSubnet2ID: + Description: "ID of Public Subnet 2" + Type: "AWS::EC2::Subnet::Id" + PublicSubnet3ID: + Description: "ID of Public Subnet 3" + Type: "AWS::EC2::Subnet::Id" + PrivateSubnet1ID: + Description: "ID of Private Subnet 1" + Type: "AWS::EC2::Subnet::Id" + PrivateSubnet2ID: + Description: "ID of Private Subnet 2" + Type: "AWS::EC2::Subnet::Id" + PrivateSubnet3ID: + Description: "ID of Private Subnet 3" + Type: "AWS::EC2::Subnet::Id" + EC2LogGroup: + Description: The bastion log group name + Type: "String" + KeyPairName: + Description: "The name of an existing public/private key pair, which allows you to securely connect to your instance after it launches" + Type: "AWS::EC2::KeyPair::KeyName" + RemoteAccessCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/x" + Description: "The CIDR IP range that is permitted to access the AWS resources. It is recommended that you set this value to a trusted IP range." + Type: "String" + BastionInstanceType: + Type: "String" + Description: "The type of EC2 instance to be launched for Bastion Host" + AllowedValues: + # Add more instance types if needed + - t2.micro + - t2.medium + - t2.large + ConstraintDescription: "Must contain a valid instance type" + DesiredNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The desired number of Bastion instance to run" + MaxNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The maximum number of Bastion instances to run" + MinNumberOfBastionNodes: + Type: "String" + MinLength: 1 + Description: "The minimum number of Bastion instances to run" + Default: "1" + NodeInstanceRole: + Type: "String" + Description: "The AWS IAM Role to be applied to the EKS Worker Nodes" + NodeInstanceRoleArn: + Type: "String" + Description: "The AWS IAM Role ARN to be applied to the EKS Worker Nodes" + NodeInstanceType: + Type: "String" + Description: "The type of EC2 instance to be launched for EKS Worker Node" + AllowedValues: + # Add more instance types if needed + - t2.xlarge + - t2.2xlarge + - m3.xlarge + - m3.2xlarge + - m4.xlarge + - m4.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + ConstraintDescription: "Must contain a valid instance type" + DesiredNumberOfNodes: + Type: "String" + MinLength: 1 + Description: "The desired number of EKS Worker Nodes to run" + MaxNumberOfNodes: + Type: "String" + MinLength: 1 + Description: "The maximum number of EKS Worker Nodes to run" + MinNumberOfNodes: + Type: "String" + MinLength: 1 + Description: "The minimum number of EKS Worker Nodes to run" + EksMgmtIamRoleArn: + Type: String + Description: "The AWS IAM Role name that will be allowed to manage EKS. Note format is arn:aws:iam::123456789:role/admin-role" + EksExternalUserArn: + Type: String + Description: The AWS IAM user arn who will be authorised to connect the cluster externally + EKSClusterName: + Type: String + Description: The name of the eks cluster + +Mappings: + # see https://github.com/aws-quickstart/quickstart-linux-bastion/blob/master/templates/linux-bastion.template for latest AMI IDs + # Use Amazon 2 Linux + BastionLatestAmiRegionMap: + us-west-2: + AmiId: ami-0873b46c45c11058d + us-west-1: + AmiId: ami-05655c267c89566dd + us-east-1: + AmiId: ami-02354e95b39ca8dec + us-east-2: + AmiId: ami-07c8bc5c1ce9598c3 + eu-central-1: + AmiId: ami-0c115dbd34c69a004 + eu-west-1: + AmiId: ami-07d9160fa81ccffb5 + # Use Amazon 2018 + BastionAmiRegionMap: + us-east-1: + AmiId: ami-0080e4c5bc078760e + us-west-2: + AmiId: ami-01e24be29428c15b2 + us-east-2: + AmiId: ami-0cd3dfa4e37921605 + eu-central-1: + AmiId: ami-0cfbf4f6db41068ac + eu-west-1: + AmiId: ami-08935252a36e25f85 + NodeAmiRegionMap: + us-west-2: + AmiId: ami-0fd02f0f218f458ce + us-west-1: + AmiId: ami-071bd7a109135a490 + us-east-1: + AmiId: ami-0b8a17014e9e7f9b9 + us-east-2: + AmiId: ami-0bb8793bb0e4bdb1f + eu-central-1: + AmiId: ami-0c0083f53400a6a1b + eu-west-1: + AmiId: ami-0c085e378560aff3f + +#Conditions: +# isNodesMetricsEnabled: !Equals [!Ref NodesMetricsEnabled, "true"] + +Resources: +# SSHMetricFilter: +# Type: 'AWS::Logs::MetricFilter' +# Properties: +# LogGroupName: !Ref EC2LogGroup +# FilterPattern: ON FROM USER PWD +# MetricTransformations: +# - MetricName: SSHCommandCount +# MetricValue: "1" +# MetricNamespace: !Join +# - / +# - - AWSQuickStart +# - !Ref 'AWS::StackName' + + EKSServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: eks.amazonaws.com + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy + - arn:aws:iam::aws:policy/AmazonEKSServicePolicy + + BastionInstanceRole: + Type: AWS::IAM::Role + Properties: + Policies: + - PolicyName: !Sub "${TemplateBucketName}-s3-policy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - s3:GetObject + Resource: !Sub "arn:aws:s3:::${TemplateBucketName}/${TemplateBucketKeyPrefix}/scripts/*" + Effect: Allow + - PolicyName: cloudwatch-logs-policy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - logs:CreateLogStream + - logs:GetLogEvents + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + - logs:PutRetentionPolicy + - logs:PutMetricFilter + - logs:CreateLogGroup + Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${EC2LogGroup}:*" + Effect: Allow + - PolicyName: bastion-eip-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - ec2:AssociateAddress + - ec2:DescribeAddresses + - ec2:DescribeVolumes + Resource: "*" + Effect: Allow + - PolicyName: bastion-ssm-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - ssm:PutParameter + - ssm:GetParameters + - ssm:GetParameter + - ssm:DeleteParameter + Resource: "*" + Effect: Allow + - PolicyName: bastion-kms-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - kms:GenerateRandom + Resource: "*" + Effect: Allow + - PolicyName: bastion-OpenID-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - iam:GetOpenIDConnectProvider + - iam:CreateOpenIDConnectProvider + - iam:TagOpenIDConnectProvider + - cloudformation:ListStacks + - cloudformation:CreateStack + - iam:GetRole + - iam:TagRole + - iam:CreateRole + - iam:CreatePolicy + - iam:DetachRolePolicy + - iam:AttachRolePolicy + - kafka:Describe* + - kafka:Get* + - kafka:List* + - elasticloadbalancing:DescribeLoadBalancers + Resource: "*" + Effect: Allow + - PolicyName: eks-cluster-policy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + ### This a EKS limitation so far at deployment time + ### Bastion should only be able to delete or do anything to its own cluster + - eks:* + - sts:* + - iam:PassRole + - ec2:DescribeTags + Resource: "*" + Effect: Allow + Path: / + AssumeRolePolicyDocument: + Statement: + - Action: + - sts:AssumeRole + Principal: + Service: + - ec2.amazonaws.com + Effect: Allow + Version: "2012-10-17" + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM + + BastionInstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Roles: + - !Ref BastionInstanceRole + Path: / + + NodeInstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: + - !Ref NodeInstanceRole + + # Bastion resources + #BastionEIP: + # Type: AWS::EC2::EIP + # Properties: + # Domain: vpc + + BastionAutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + LaunchConfigurationName: !Ref BastionLaunchConfiguration + VPCZoneIdentifier: + - !Ref PublicSubnet1ID + - !Ref PublicSubnet2ID + - !Ref PublicSubnet3ID + MinSize: !Ref MinNumberOfBastionNodes + MaxSize: !Ref MaxNumberOfBastionNodes + Cooldown: "300" + DesiredCapacity: !Ref DesiredNumberOfBastionNodes + Tags: + - Key: Name + Value: !Sub "${EKSClusterName}-eks-bastion-node" + PropagateAtLaunch: true + - Key: Component + Value: datahub-Bastion-AutoScaling-Group + PropagateAtLaunch: true + CreationPolicy: + ResourceSignal: + Timeout: PT30M + +# BastionTerminationLifecycleHook: +# Type: AWS::AutoScaling::LifecycleHook +# Properties: +# AutoScalingGroupName: +# Ref: BastionAutoScalingGroup +# LifecycleTransition: "autoscaling:EC2_INSTANCE_TERMINATING" +# HeartbeatTimeout: 300 +# DefaultResult: "CONTINUE" +# NotificationMetadata: "optional metadata" + + BastionLaunchConfiguration: + Type: AWS::AutoScaling::LaunchConfiguration + Metadata: + AWS::CloudFormation::Authentication: + S3AccessCreds: + type: S3 + roleName: !Ref BastionInstanceRole + buckets: !Ref TemplateBucketName + AWS::CloudFormation::Init: + config: + packages: + yum: + awslogs: [] + files: + '/etc/awslogs/awscli.conf': + content: !Sub | + [default] + region = ${AWS::Region} + [plugins] + cwlogs = cwlogs + mode: '000644' + owner: root + group: root + '/etc/awslogs/awslogs.conf': + content: !Sub | + [general] + state_file = /var/lib/awslogs/agent-state + [/var/log/bastion/bastion.log] + file = /var/log/bastion/bastion.log + datetime_format = %b %d %H:%M:%S + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/bastion/bastion.log + log_group_name = ${EC2LogGroup} + [/var/log/dmesg] + file = /var/log/dmesg + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/dmesg + log_group_name = ${EC2LogGroup} + [/var/log/messages] + datetime_format = %b %d %H:%M:%S + file = /var/log/messages + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/messages + log_group_name = ${EC2LogGroup} + [/var/log/secure] + datetime_format = %b %d %H:%M:%S + file = /var/log/secure + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/secure + log_group_name = ${EC2LogGroup} + [/var/log/audit/audit.log] + datetime_format = + file = /var/log/audit/audit.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/audit/audit.log + log_group_name = ${EC2LogGroup} + [/var/log/cron] + datetime_format = %b %d %H:%M:%S + file = /var/log/cron + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cron + log_group_name = ${EC2LogGroup} + [/var/log/cfn-init.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-init.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-init.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-hup.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-hup.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-hup.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-init-cmd.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-init-cmd.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-init-cmd.log + log_group_name = ${EC2LogGroup} + [/var/log/cloud-init-output.log] + file = /var/log/cloud-init-output.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cloud-init-output.log + log_group_name = ${EC2LogGroup} + [/var/log/amazon/ssm/amazon-ssm-agent.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/amazon/ssm/amazon-ssm-agent.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/amazon/ssm/amazon-ssm-agent.log + log_group_name = ${EC2LogGroup} + [/var/log/amazon/ssm/errors.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/amazon/ssm/errors.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/amazon/ssm/errors.log + log_group_name = ${EC2LogGroup} + [/var/log/maillog] + datetime_format = %b %d %H:%M:%S + file = /var/log/maillog + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/maillog + log_group_name = ${EC2LogGroup} + [/var/log/yum.log] + datetime_format = %b %d %H:%M:%S + file = /var/log/yum.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/yum.log + log_group_name = ${EC2LogGroup} + [/var/log/awslogs.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/awslogs.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/awslogs.log + log_group_name = ${EC2LogGroup} + [/var/log/boot.log] + file = /var/log/boot.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/boot.log + log_group_name = ${EC2LogGroup} + [/var/log/cfn-wire.log] + datetime_format = %Y-%m-%d %H:%M:%S + file = /var/log/cfn-wire.log + log_stream_name = ${AWS::StackName}/bastion-{instance_id}/var/log/cfn-wire.log + log_group_name = ${EC2LogGroup} + mode: '000644' + owner: root + group: root + /tmp/bastion_bootstrap.sh: + source: !Sub "https://s3.${AWS::Region}.amazonaws.com/${TemplateBucketName}/${TemplateBucketKeyPrefix}/scripts/bastion_bootstrap.sh" + mode: "000550" + owner: root + group: root + authentication: S3AccessCreds + /tmp/eks_bootstrap.sh: + content: !Sub | + #!/bin/bash + echo "Checking whether cluster exists..." + aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} &> /dev/null + if [ $? -ne 0 ]; then + echo Cluster does not exist, creating... + aws eks create-cluster --region ${AWS::Region} \ + --name ${EKSClusterName} \ + --kubernetes-version 1.18 \ + --role-arn ${EKSServiceRole.Arn} \ + --resources-vpc-config subnetIds=${PrivateSubnet1ID},${PrivateSubnet2ID},${PrivateSubnet3ID},${PublicSubnet1ID},${PublicSubnet2ID},${PublicSubnet3ID},securityGroupIds=${ControlPlaneSecurityGroup} + if [ $? -ne 0 ]; then + exit 1 + fi + sleep 5 + STATUS=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.status --output text) + while [ \"$STATUS\" = \"CREATING\" ]; do + echo Cluster is still creating, sleeping for 30 seconds... + sleep 30 + STATUS=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.status --output text) + done + fi + echo "Saving oidc info..." + OIDC=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.identity.oidc --output text --no-paginate |cut -c 9-) + parameter='/${EKSClusterName}/eks/oidc' + aws ssm put-parameter --region ${AWS::Region} --name "$parameter" --type "String" --value "$OIDC" --overwrite --no-paginate + echo Updating kubeconfig file... + ENDPOINT=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.endpoint --output text) + CERT_DATA=$(aws eks describe-cluster --region ${AWS::Region} --name ${EKSClusterName} --query cluster.certificateAuthority.data --output text) + sed -i s,ENDPOINT,$ENDPOINT,g /home/ec2-user/.kube/config + sed -i s,CERTIFICATE_DATA,$CERT_DATA,g /home/ec2-user/.kube/config + export KUBECONFIG=/home/ec2-user/.kube/config + echo Checking whether aws-auth configmap exists... + kubectl get configmaps/aws-auth -n kube-system &> /dev/null + if [ $? -gt 0 ]; then + echo Configmap does not exist, applying... + kubectl apply -f /tmp/aws-auth-cm.yaml + fi + mode: "000750" + owner: root + group: root + /tmp/aws-auth-cm.yaml: + content: !Sub | + apiVersion: v1 + kind: ConfigMap + metadata: + name: aws-auth + namespace: kube-system + data: + mapRoles: | + - rolearn: ${NodeInstanceRoleArn} + username: system:node:{{EC2PrivateDNSName}} + groups: + - system:bootstrappers + - system:nodes + - rolearn: ${EksMgmtIamRoleArn} + groups: + -system:masters + username: federated-user + mapUsers: | + - userarn: ${EksExternalUserArn} + username: admin + groups: + - system:masters + mode: "000644" + owner: root + group: root + /home/ec2-user/.kube/config: + content: !Sub | + apiVersion: v1 + clusters: + - cluster: + server: ENDPOINT + certificate-authority-data: CERTIFICATE_DATA + name: kubernetes + contexts: + - context: + cluster: kubernetes + user: aws + name: aws + current-context: aws + kind: Config + preferences: {} + users: + - name: aws + user: + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + command: aws-iam-authenticator + args: + - token + - -i + - ${EKSClusterName} + mode: "000666" + owner: ec2-user + group: ec2-user + services: + sysvinit: + awslogs: + enabled: true + ensureRunning: true + packages: + yum: + - awslogs + files: + - '/etc/awslogs/awslogs.conf' + - '/etc/awslogs/awscli.conf' + commands: + 01_eks-bootstrap: + command: "./tmp/eks_bootstrap.sh |tee -a /tmp/eks_bootstrap.out 2>&1" + 02_bastion-bootstrap: + command: "./tmp/bastion_bootstrap.sh --tcp-forwarding false --x11-forwarding false|tee -a /tmp/bastion_bootstrap.out 2>&1" + Properties: + AssociatePublicIpAddress: true + PlacementTenancy: default + KeyName: !Ref KeyPairName + IamInstanceProfile: !Ref BastionInstanceProfile + ImageId: !FindInMap [BastionAmiRegionMap, !Ref "AWS::Region", AmiId] + SecurityGroups: + - !Ref BastionSecurityGroup + InstanceType: !Ref BastionInstanceType + UserData: + Fn::Base64: !Sub | + #!/bin/bash + set -x + export PATH=$PATH:/usr/local/bin + yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm + yum install -y jq + pip install awscli --upgrade + easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz + CLOUDWATCHGROUP=${EC2LogGroup} + curl -LO https://dl.k8s.io/release/v1.20.2/bin/linux/amd64/kubectl + chmod +x ./kubectl + mv kubectl /usr/local/bin + kubectl version --short --client + curl -o aws-iam-authenticator https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/aws-iam-authenticator + chmod +x ./aws-iam-authenticator + mv aws-iam-authenticator /usr/local/bin + aws-iam-authenticator version + cfn-init -v --stack ${AWS::StackName} --resource BastionLaunchConfiguration --region ${AWS::Region} + cfn-signal -e $? --stack ${AWS::StackName} --resource BastionAutoScalingGroup --region ${AWS::Region} + + BastionSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-BastionHost-SG" + GroupDescription: Enables SSH Access to Bastion Hosts + VpcId: !Ref VPCID + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: !Ref RemoteAccessCIDR + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: "76.126.154.78/32" + + # Cluster resources + ControlPlaneSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + Tags: + - Key: Name + Value: !Sub "${AWS::StackName}-ControlPlane-SG" + - Key: Component + Value: datahub-EKS-ControlPlane + GroupDescription: Cluster communication with worker nodes + VpcId: !Ref VPCID + + NodeSecurityGroupIngressSSH: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow SSH traffic from bastion nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref BastionSecurityGroup + IpProtocol: tcp + FromPort: 22 + ToPort: 22 + + NodeSecurityGroupIngressDNSTCP: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow DNS TCP traffic + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 53 + ToPort: 53 + + NodeSecurityIngressDNSUDP: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow DNS UDP traffic + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: udp + FromPort: 53 + ToPort: 53 + + NodeSecurityIngressHTTP: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow HTTP traffic + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 80 + ToPort: 80 + + NodeSecurityIngressHTTPS: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow HTTPS traffic + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + + NodeSecurityGroupIngressRepoCluster: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow the repo cluster to communicate across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 5701 + ToPort: 5701 + + NodeSecurityGroupIngressShareCluster: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow the Share cluster to communicate across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 5801 + ToPort: 5801 + + NodeSecurityGroupIngressRepoShare: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow Repo and Share access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8080 + ToPort: 8080 + + NodeSecurityGroupIngressTransformers: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow transformers access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8090 + ToPort: 8090 + + NodeSecurityGroupIngressTransformRouter: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow transform router access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8095 + ToPort: 8095 + + NodeSecurityGroupIngressSharedFileStore: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow shared file store access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8099 + ToPort: 8099 + + NodeSecurityGroupIngressSOLR: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow SOLR access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8983 + ToPort: 8983 + + NodeSecurityGroupIngressActiveMQ: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow ActiveMQ access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 61616 + ToPort: 61616 + + NodeSecurityGroupIngressKubernetesDashboard: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow Kubernetes Dashboard access across worker nodes + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 8443 + ToPort: 8443 + + # The following security groups are defined by the AWS EKS worker node template but with a restricted port range + + NodeSecurityGroupFromControlPlaneIngress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow worker Kubelets and pods to receive communication from the cluster control plane + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref ControlPlaneSecurityGroup + IpProtocol: tcp + FromPort: 10250 + ToPort: 10250 + + ControlPlaneEgressToNodeSecurityGroup: + Type: AWS::EC2::SecurityGroupEgress + Properties: + Description: Allow the cluster control plane to communicate with worker Kubelet and pods + GroupId: !Ref ControlPlaneSecurityGroup + DestinationSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 10250 + ToPort: 10250 + + # The following security groups are defined by the AWS EKS worker node template + + NodeSecurityGroupFromControlPlaneOn443Ingress: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow pods running extension API servers on port 443 to receive communication from cluster control plane + GroupId: !Ref NodeSecurityGroup + SourceSecurityGroupId: !Ref ControlPlaneSecurityGroup + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + + ControlPlaneEgressToNodeSecurityGroupOn443: + Type: AWS::EC2::SecurityGroupEgress + Properties: + Description: Allow the cluster control plane to communicate with pods running extension API servers on port 443 + GroupId: !Ref ControlPlaneSecurityGroup + DestinationSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + FromPort: 443 + ToPort: 443 + + ClusterControlPlaneSecurityGroupIngress01: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow pods to communicate with the cluster API Server + GroupId: !Ref ControlPlaneSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + IpProtocol: tcp + ToPort: 443 + FromPort: 443 + + ClusterControlPlaneSecurityGroupIngress02: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Allow bastion to communicate with the cluster API Server + GroupId: !Ref ControlPlaneSecurityGroup + SourceSecurityGroupId: !Ref BastionSecurityGroup + IpProtocol: tcp + ToPort: 443 + FromPort: 443 + + EKSNodegroup1: + Type: "AWS::EKS::Nodegroup" + DependsOn: BastionAutoScalingGroup + Properties: + NodegroupName: !Sub "${EKSClusterName}-nodegroup1" + ClusterName: !Ref EKSClusterName + Version: "1.18" + ScalingConfig: + MinSize: 1 + MaxSize: 3 + DesiredSize: 1 + InstanceTypes: + - !Ref NodeInstanceType + Subnets: + - !Ref PrivateSubnet1ID + AmiType: "AL2_x86_64" + NodeRole: !Ref NodeInstanceRoleArn + RemoteAccess: + Ec2SshKey: !Ref KeyPairName + SourceSecurityGroups: + - !Ref NodeSecurityGroup + Labels: {} + DiskSize: 50 + Tags: + Name: !Sub "${EKSClusterName}-nodegroup1" + CloudFormation: "true" + Environment: !Sub "${AWS::StackName}" + CapacityType: "ON_DEMAND" + + EKSNodegroup2: + Type: "AWS::EKS::Nodegroup" + DependsOn: BastionAutoScalingGroup + Properties: + NodegroupName: !Sub "${EKSClusterName}-nodegroup2" + ClusterName: !Ref EKSClusterName + Version: "1.18" + ScalingConfig: + MinSize: 1 + MaxSize: 3 + DesiredSize: 1 + InstanceTypes: + - !Ref NodeInstanceType + Subnets: + - !Ref PrivateSubnet2ID + AmiType: "AL2_x86_64" + NodeRole: !Ref NodeInstanceRoleArn + RemoteAccess: + Ec2SshKey: !Ref KeyPairName + SourceSecurityGroups: + - !Ref NodeSecurityGroup + Labels: {} + DiskSize: 50 + Tags: + Name: !Sub "${EKSClusterName}-nodegroup2" + CloudFormation: "true" + Environment: !Sub "${AWS::StackName}" + CapacityType: "ON_DEMAND" + + EKSNodegroup3: + Type: "AWS::EKS::Nodegroup" + DependsOn: BastionAutoScalingGroup + Properties: + NodegroupName: !Sub "${EKSClusterName}-nodegroup3" + ClusterName: !Ref EKSClusterName + Version: "1.18" + ScalingConfig: + MinSize: 1 + MaxSize: 3 + DesiredSize: 1 + InstanceTypes: + - !Ref NodeInstanceType + Subnets: + - !Ref PrivateSubnet3ID + AmiType: "AL2_x86_64" + NodeRole: !Ref NodeInstanceRoleArn + RemoteAccess: + Ec2SshKey: !Ref KeyPairName + SourceSecurityGroups: + - !Ref NodeSecurityGroup + Labels: {} + DiskSize: 50 + Tags: + Name: !Sub "${EKSClusterName}-nodegroup3" + CloudFormation: "true" + Environment: !Sub "${AWS::StackName}" + CapacityType: "ON_DEMAND" +Outputs: + SubstackName: + Description: The eks stack name + Value: !Sub "${AWS::StackName}" + EksClusterName: + Description: EKS Cluster name + Value: !Ref EKSClusterName + BastionSecurityGroup: + Description: The bastion security group id + Value: !Ref BastionSecurityGroup + BastionLaunchConfiguration: + Description: The bastion host launch config + Value: !Ref BastionLaunchConfiguration + BastionAutoScalingGroup: + Description: The Bastion host autoscaling group + Value: !Ref BastionAutoScalingGroup + BastionInstanceProfile: + Description: IAM Instance profile of Bastion host + Value: !Ref BastionInstanceProfile + BastionInstanceRole: + Description: IAM Role of Bastion host + Value: !Ref BastionInstanceRole + ControlPlaneSecurityGroup: + Description: The ControlPlane security group id + Value: !Ref ControlPlaneSecurityGroup + EksServiceRoleArn: + Value: !GetAtt EKSServiceRole.Arn + NodeInstanceRoleArn: + Value: !Ref NodeInstanceRoleArn diff --git a/templates/nested/elasticsearch.yaml b/templates/nested/elasticsearch.yaml new file mode 100644 index 0000000..e60b740 --- /dev/null +++ b/templates/nested/elasticsearch.yaml @@ -0,0 +1,200 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "ES stack that deploys ElasticSearch" + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: ES Stack Configuration + Parameters: + - VPCID + - VPCCIDR + - PrivateSubnet1 + - PrivateSubnet2 + - PrivateSubnet3 + - ESDomainName + - ElasticsearchVersion + + ParameterLabels: + NodeSecurityGroup: + default: The Node Security Group ID to use for ElasticSearch + PrivateSubnet1: + default: The ID of Private Subnet 1 + PrivateSubnet2: + default: The ID of Private Subnet 2 + PrivateSubnet3: + default: The ID of Private Subnet 3 + VPCID: + default: VPC ID + VPCCIDR: + default: VPC CIDR + ESDomainName: + default: Elasticsearch Domain + ElasticsearchVersion: + default: Elasticsearch Version + +Parameters: + NodeSecurityGroup: + Description: ID for the VPC, This will be used to get the node security group + Type: AWS::EC2::SecurityGroup::Id + PrivateSubnet1: + Description: ID of Private Subnet 1 + Type: AWS::EC2::Subnet::Id + PrivateSubnet2: + Description: ID of Private Subnet 2 + Type: AWS::EC2::Subnet::Id + PrivateSubnet3: + Description: ID of Private Subnet 3 + Type: AWS::EC2::Subnet::Id + VPCID: + Description: ID for the VPC + Type: AWS::EC2::VPC::Id + VPCCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.0.0/16" + Description: "CIDR block for the VPC" + Type: "String" + ESVolumeSize: + Default: 50 + Description: Data Node Volume Size in GB + Type: String + ESInstanceType: + AllowedValues: + - t3.small.elasticsearch + - t3.large.elasticsearch + - m5.large.elasticsearch + ConstraintDescription: Must contain valid ES instance type + Default: t3.small.elasticsearch + Description: EC2 instance type for the Amazon ES instances + Type: String + ESMasterInstanceType: + AllowedValues: + - t3.small.elasticsearch + - t3.large.elasticsearch + - c5.large.elasticsearch + ConstraintDescription: Must contain valid ES instance type + Default: t3.small.elasticsearch + Description: EC2 instance type for the Amazon ES instances + Type: String + + ElasticsearchVersion: + AllowedValues: + - 7.9 + Default: 7.9 + Description: Elasticsearch versions + Type: String + ESDomainName: + Default: "datahub" + Description: Elasticsearch Domain Name + Type: String + ESMasterUser: + Default: "admin" + Type: String + ESMasterUserPassword: + NoEcho: true + Type: String + ESDataNodeCount: + Type: String + ESMasterNodeCount: + Type: String + + +Resources: + ESSecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: "Allow inbound traffic to ElasticSearch from VPC CIDR" + GroupName: !Sub "${ESDomainName}-elasticsearch-sg" + VpcId: !Ref VPCID + SecurityGroupIngress: + - + CidrIp: !Ref VPCCIDR + FromPort: 443 + IpProtocol: "tcp" + ToPort: 443 + + ESSecurityGroupIngress01: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref ESSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + SourceSecurityGroupOwnerId: !Ref AWS::AccountId + FromPort: 443 + IpProtocol: "tcp" + ToPort: 443 + + ElasticsearchDomain: + Type: "AWS::Elasticsearch::Domain" + Properties: + DomainName: !Ref ESDomainName + ElasticsearchVersion: !Ref ElasticsearchVersion + ElasticsearchClusterConfig: + DedicatedMasterEnabled: true + InstanceCount: !Ref ESDataNodeCount + InstanceType: !Ref ESInstanceType + ZoneAwarenessConfig: + AvailabilityZoneCount: 3 + ZoneAwarenessEnabled: true + DedicatedMasterType: !Ref ESMasterInstanceType + DedicatedMasterCount: !Ref ESMasterNodeCount + WarmEnabled: false + AccessPolicies: !Sub "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"es:*\",\"Resource\":\"arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName}/*\"}]}" + SnapshotOptions: + AutomatedSnapshotStartHour: 0 + VPCOptions: + SecurityGroupIds: + - !Ref ESSecurityGroup + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 + - !Ref PrivateSubnet3 + EncryptionAtRestOptions: + Enabled: true + #KmsKeyId: + NodeToNodeEncryptionOptions: + Enabled: true + AdvancedOptions: + "rest.action.multi.allow_explicit_index": "true" + EBSOptions: + EBSEnabled: true + VolumeType: "gp2" + VolumeSize: !Ref ESVolumeSize + CognitoOptions: + Enabled: false + DomainEndpointOptions: + EnforceHTTPS: true + TLSSecurityPolicy: "Policy-Min-TLS-1-0-2019-07" + CustomEndpointEnabled: false + AdvancedSecurityOptions: + Enabled: true + InternalUserDatabaseEnabled: true + MasterUserOptions: + MasterUserName: !Ref ESMasterUser + MasterUserPassword: !Ref ESMasterUserPassword + Tags: + - + Key: "Environment" + Value: !Sub "${AWS::StackName}" + + ESMasterUserSSM: + Type: AWS::SSM::Parameter + Properties: + Description: ElasticSearch master uer + Name: '/datahub/elasticsearch/username' + Type: String + Value: !Ref ESMasterUser + ESMasterUserPasswordSSM: + Type: AWS::SSM::Parameter + Properties: + Description: ElasticSearch master password + Name: '/datahub/elasticsearch/password' + Type: String + Value: !Ref ESMasterUserPassword +Outputs: + SubstackName: + Description: The elasticsearch stack name + Value: !Sub "${AWS::StackName}" + DomainEndpoint: + Description: "The domain-specific endpoint that's used to submit index, search, and data upload requests to an Amazon ES domain." + Value: !GetAtt 'ElasticsearchDomain.DomainEndpoint' diff --git a/templates/nested/ingress-controller.yaml b/templates/nested/ingress-controller.yaml new file mode 100644 index 0000000..1be13db --- /dev/null +++ b/templates/nested/ingress-controller.yaml @@ -0,0 +1,264 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "Deploys the aws load balancer controller to an existing kubernetes cluster" +Metadata: + QSLint: + Exclusions: [W9002, W9003, W9004, W9006] +Parameters: + OIDCIssuerURLWithoutProtocol: + Type: String + Default: '{{resolve:ssm:/datahub/eks/oidc:1}}' + EksClusterName: + Type: String + VpcId: + Type: String + ControllerCPULimit: + Type: String + Default: "100m" + ControllerMemoryLimit: + Type: String + Default: "80Mi" + ControllerReplicaCount: + Type: Number + Default: 1 + ControllerHostNetwork: + Type: String + AllowedValues: [ Enabled, Disabled ] + Default: Disabled +Conditions: + HostNetworkEnabled: !Equals [ !Ref ControllerHostNetwork, Enabled ] +Mappings: + RegionMap: + me-south-1: + lbrepo: 558608220178.dkr.ecr.me-south-1.amazonaws.com/amazon/aws-load-balancer-controller + eu-south-1: + lbrepo: 590381155156.dkr.ecr.eu-south-1.amazonaws.com/amazon/aws-load-balancer-controller + ap-northeast-1: + lbrepo: 602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller + ap-northeast-2: + lbrepo: 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon/aws-load-balancer-controller + ap-northeast-3: + lbrepo: 602401143452.dkr.ecr.ap-northeast-3.amazonaws.com/amazon/aws-load-balancer-controller + ap-south-1: + lbrepo: 602401143452.dkr.ecr.ap-south-1.amazonaws.com/amazon/aws-load-balancer-controller + ap-southeast-1: + lbrepo: 602401143452.dkr.ecr.ap-southeast-1.amazonaws.com/amazon/aws-load-balancer-controller + ap-southeast-2: + lbrepo: 602401143452.dkr.ecr.ap-southeast-2.amazonaws.com/amazon/aws-load-balancer-controller + ca-central-1: + lbrepo: 602401143452.dkr.ecr.ca-central-1.amazonaws.com/amazon/aws-load-balancer-controller + eu-central-1: + lbrepo: 602401143452.dkr.ecr.eu-central-1.amazonaws.com/amazon/aws-load-balancer-controller + eu-north-1: + lbrepo: 602401143452.dkr.ecr.eu-north-1.amazonaws.com/amazon/aws-load-balancer-controller + eu-west-1: + lbrepo: 602401143452.dkr.ecr.eu-west-1.amazonaws.com/amazon/aws-load-balancer-controller + eu-west-2: + lbrepo: 602401143452.dkr.ecr.eu-west-2.amazonaws.com/amazon/aws-load-balancer-controller + eu-west-3: + lbrepo: 602401143452.dkr.ecr.eu-west-3.amazonaws.com/amazon/aws-load-balancer-controller + sa-east-1: + lbrepo: 602401143452.dkr.ecr.sa-east-1.amazonaws.com/amazon/aws-load-balancer-controller + us-east-1: + lbrepo: 602401143452.dkr.ecr.us-east-1.amazonaws.com/amazon/aws-load-balancer-controller + us-east-2: + lbrepo: 602401143452.dkr.ecr.us-east-2.amazonaws.com/amazon/aws-load-balancer-controller + us-west-1: + lbrepo: 602401143452.dkr.ecr.us-west-1.amazonaws.com/amazon/aws-load-balancer-controller + us-west-2: + lbrepo: 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon/aws-load-balancer-controller + ap-east-1: + lbrepo: 800184023465.dkr.ecr.ap-east-1.amazonaws.com/amazon/aws-load-balancer-controller + af-south-1: + lbrepo: 877085696533.dkr.ecr.af-south-1.amazonaws.com/amazon/aws-load-balancer-controller + cn-north-1: + lbrepo: 918309763551.dkr.ecr.cn-north-1.amazonaws.com.cn/amazon/aws-load-balancer-controller + cn-northwest-1: + lbrepo: 961992271922.dkr.ecr.cn-northwest-1.amazonaws.com.cn/amazon/aws-load-balancer-controller + us-gov-west-1: + lbrepo: 013241004608.dkr.ecr.us-gov-west-1.amazonaws.com/amazon/aws-load-balancer-controller + us-gov-east-1: + lbrepo: 151742754352.dkr.ecr.us-gov-east-1.amazonaws.com/amazon/aws-load-balancer-controller +Resources: + LoadBalancerControllerIAMRole: + Type: AWS::IAM::Role + Metadata: + cfn-lint: + config: + ignore_checks: [EIAMPolicyWildcardResource] + ignore_reasons: + EIAMPolicyWildcardResource: "resources are created dynamically by the k8s cloud controller, and aren't known at the time of policy creation" + Properties: + RoleName: !Sub "${EksClusterName}-aws-load-balancer-controller" + AssumeRolePolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/${OIDCIssuerURLWithoutProtocol}" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "${OIDCIssuerURLWithoutProtocol}:aud": "sts.amazonaws.com", + "${OIDCIssuerURLWithoutProtocol}:sub": "system:serviceaccount:kube-system:datahub-aws-load-balancer-controller" + } + } + } + ] + } + Policies: + - PolicyName: load-balancer-controller-policy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'iam:CreateServiceLinkedRole' + - 'ec2:DescribeAccountAttributes' + - 'ec2:DescribeAddresses' + - 'ec2:DescribeAvailabilityZones' + - 'ec2:DescribeInternetGateways' + - 'ec2:DescribeVpcs' + - 'ec2:DescribeSubnets' + - 'ec2:DescribeSecurityGroups' + - 'ec2:DescribeInstances' + - 'ec2:DescribeNetworkInterfaces' + - 'ec2:DescribeTags' + - 'ec2:GetCoipPoolUsage' + - 'ec2:DescribeCoipPools' + - 'elasticloadbalancing:DescribeLoadBalancers' + - 'elasticloadbalancing:DescribeLoadBalancerAttributes' + - 'elasticloadbalancing:DescribeListeners' + - 'elasticloadbalancing:DescribeListenerCertificates' + - 'elasticloadbalancing:DescribeSSLPolicies' + - 'elasticloadbalancing:DescribeRules' + - 'elasticloadbalancing:DescribeTargetGroups' + - 'elasticloadbalancing:DescribeTargetGroupAttributes' + - 'elasticloadbalancing:DescribeTargetHealth' + - 'elasticloadbalancing:DescribeTags' + Resource: '*' + - Effect: Allow + Action: + - 'cognito-idp:DescribeUserPoolClient' + - 'acm:ListCertificates' + - 'acm:DescribeCertificate' + - 'iam:ListServerCertificates' + - 'iam:GetServerCertificate' + - 'waf-regional:GetWebACL' + - 'waf-regional:GetWebACLForResource' + - 'waf-regional:AssociateWebACL' + - 'waf-regional:DisassociateWebACL' + - 'wafv2:GetWebACL' + - 'wafv2:GetWebACLForResource' + - 'wafv2:AssociateWebACL' + - 'wafv2:DisassociateWebACL' + - 'shield:GetSubscriptionState' + - 'shield:DescribeProtection' + - 'shield:CreateProtection' + - 'shield:DeleteProtection' + Resource: '*' + - Effect: Allow + Action: + - 'ec2:AuthorizeSecurityGroupIngress' + - 'ec2:RevokeSecurityGroupIngress' + Resource: '*' + - Effect: Allow + Action: + - 'ec2:CreateSecurityGroup' + Resource: '*' + - Effect: Allow + Action: + - 'ec2:CreateTags' + Resource: !Sub 'arn:${AWS::Partition}:ec2:*:*:security-group/*' + Condition: + StringEquals: + 'ec2:CreateAction': CreateSecurityGroup + 'Null': + 'aws:RequestTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'ec2:CreateTags' + - 'ec2:DeleteTags' + Resource: !Sub 'arn:${AWS::Partition}:ec2:*:*:security-group/*' + Condition: + 'Null': + 'aws:RequestTag/elbv2.k8s.aws/cluster': 'true' + 'aws:ResourceTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'ec2:AuthorizeSecurityGroupIngress' + - 'ec2:RevokeSecurityGroupIngress' + - 'ec2:DeleteSecurityGroup' + Resource: '*' + Condition: + 'Null': + 'aws:ResourceTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'elasticloadbalancing:CreateLoadBalancer' + - 'elasticloadbalancing:CreateTargetGroup' + Resource: '*' + Condition: + 'Null': + 'aws:RequestTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'elasticloadbalancing:CreateListener' + - 'elasticloadbalancing:DeleteListener' + - 'elasticloadbalancing:CreateRule' + - 'elasticloadbalancing:DeleteRule' + Resource: '*' + - Effect: Allow + Action: + - 'elasticloadbalancing:AddTags' + - 'elasticloadbalancing:RemoveTags' + Resource: + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:targetgroup/*/*' + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:loadbalancer/net/*/*' + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:loadbalancer/app/*/*' + Condition: + 'Null': + 'aws:RequestTag/elbv2.k8s.aws/cluster': 'true' + 'aws:ResourceTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'elasticloadbalancing:AddTags' + - 'elasticloadbalancing:RemoveTags' + Resource: + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:listener/net/*/*/*' + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:listener/app/*/*/*' + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:listener-rule/net/*/*/*' + - !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:listener-rule/app/*/*/*' + - Effect: Allow + Action: + - 'elasticloadbalancing:ModifyLoadBalancerAttributes' + - 'elasticloadbalancing:SetIpAddressType' + - 'elasticloadbalancing:SetSecurityGroups' + - 'elasticloadbalancing:SetSubnets' + - 'elasticloadbalancing:DeleteLoadBalancer' + - 'elasticloadbalancing:ModifyTargetGroup' + - 'elasticloadbalancing:ModifyTargetGroupAttributes' + - 'elasticloadbalancing:DeleteTargetGroup' + Resource: '*' + Condition: + 'Null': + 'aws:ResourceTag/elbv2.k8s.aws/cluster': 'false' + - Effect: Allow + Action: + - 'elasticloadbalancing:RegisterTargets' + - 'elasticloadbalancing:DeregisterTargets' + Resource: !Sub 'arn:${AWS::Partition}:elasticloadbalancing:*:*:targetgroup/*/*' + - Effect: Allow + Action: + - 'elasticloadbalancing:SetWebAcl' + - 'elasticloadbalancing:ModifyListener' + - 'elasticloadbalancing:AddListenerCertificates' + - 'elasticloadbalancing:RemoveListenerCertificates' + - 'elasticloadbalancing:ModifyRule' + Resource: '*' +Outputs: + SubstackName: + Description: The ingress controller stack name + Value: !Sub "${AWS::StackName}" diff --git a/templates/nested/msk.yaml b/templates/nested/msk.yaml new file mode 100644 index 0000000..9b47f38 --- /dev/null +++ b/templates/nested/msk.yaml @@ -0,0 +1,194 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "MSK stack that deploys managed Kafka" + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: MSK Stack Configuration + Parameters: + - VPCID + - VPCCIDR + - DBSubnet1 + - DBSubnet2 + - DBSubnet3 + - MSKClusterName + - KafkaVersion + - MSKInstanceType + + ParameterLabels: + NodeSecurityGroup: + default: The Node Security Group ID to use for MSK + DBSubnet1: + default: The ID of Private Subnet 1 + DBSubnet2: + default: The ID of Private Subnet 2 + DBSubnet3: + default: The ID of Private Subnet 3 + VPCID: + default: VPC ID + VPCCIDR: + default: VPC CIDR + MSKClusterName: + default: MSK Cluster Name + KafkaVersion: + default: Kafka Version + MSKInstanceType: + default: MSK Instance Type + +Parameters: + NodeSecurityGroup: + Description: ID for the VPC, This will be used to get the node security group + Type: AWS::EC2::SecurityGroup::Id + DBSubnet1: + Description: ID of Private Subnet 1 + Type: AWS::EC2::Subnet::Id + DBSubnet2: + Description: ID of Private Subnet 2 + Type: AWS::EC2::Subnet::Id + DBSubnet3: + Description: ID of Private Subnet 3 + Type: AWS::EC2::Subnet::Id + VPCID: + Description: ID for the VPC + Type: AWS::EC2::VPC::Id + VPCCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.0.0/16" + Description: "CIDR block for the VPC" + Type: "String" + MSKInstanceType: + AllowedValues: + - kafka.t3.small + - kafka.t3.large + - kafka.m5.large + ConstraintDescription: Must contain valid MSK instance type + Default: kafka.t3.small + Description: EC2 instance type for the Amazon MSK instances + Type: String + KafkaVersion: + AllowedValues: + - 2.4.1.1 + Default: 2.4.1.1 + Description: Kafka version + Type: String + MSKClusterName: + Default: "datahub" + Description: MSK Cluster Name + Type: String + + +Resources: + MSKSecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: "Managed by CloudFormation" + GroupName: !Sub "${MSKClusterName}-msk-sg" + VpcId: !Ref VPCID + + MSKSecurityGroupIngress01: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + SourceSecurityGroupOwnerId: !Ref AWS::AccountId + FromPort: 9094 + IpProtocol: "tcp" + ToPort: 9094 + + MSKSecurityGroupIngress01vpc: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + CidrIp: !Ref VPCCIDR + FromPort: 9094 + IpProtocol: "tcp" + ToPort: 9094 + + MSKSecurityGroupIngress02: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + SourceSecurityGroupOwnerId: !Ref AWS::AccountId + FromPort: 2181 + IpProtocol: "tcp" + ToPort: 2181 + + MSKSecurityGroupIngress02vpc: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + CidrIp: !Ref VPCCIDR + FromPort: 2181 + IpProtocol: "tcp" + ToPort: 2181 + + MSKSecurityGroupIngress03: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + SourceSecurityGroupOwnerId: !Ref AWS::AccountId + FromPort: 9092 + IpProtocol: "tcp" + ToPort: 9092 + + MSKSecurityGroupIngress03vpc: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref MSKSecurityGroup + CidrIp: !Ref VPCCIDR + FromPort: 9092 + IpProtocol: "tcp" + ToPort: 9092 + + MSKCluster: + Type: "AWS::MSK::Cluster" + DependsOn: MSKLogGroup + Properties: + ClusterName: !Sub "${MSKClusterName}" + NumberOfBrokerNodes: 3 + Tags: + CloudFormation: "true" + Environment: !Sub "${MSKClusterName}" + EnhancedMonitoring: "DEFAULT" + EncryptionInfo: + EncryptionInTransit: + ClientBroker: "TLS_PLAINTEXT" + InCluster: true + KafkaVersion: "2.4.1.1" + BrokerNodeGroupInfo: + BrokerAZDistribution: "DEFAULT" + ClientSubnets: + - !Ref DBSubnet1 + - !Ref DBSubnet2 + - !Ref DBSubnet3 + InstanceType: !Ref MSKInstanceType + SecurityGroups: + - !Ref MSKSecurityGroup + StorageInfo: + EBSStorageInfo: + VolumeSize: 10 + OpenMonitoring: + Prometheus: + JmxExporter: + EnabledInBroker: false + NodeExporter: + EnabledInBroker: false + LoggingInfo: + BrokerLogs: + CloudWatchLogs: + Enabled: true + LogGroup: !Sub "${MSKClusterName}-msk-broker-logs" + + MSKLogGroup: + Type: "AWS::Logs::LogGroup" + Properties: + LogGroupName: !Sub "${MSKClusterName}-msk-broker-logs" + +Outputs: + SubstackName: + Description: The msk stack name + Value: !Sub "${AWS::StackName}" diff --git a/templates/nested/mysql.yaml b/templates/nested/mysql.yaml new file mode 100644 index 0000000..8aee218 --- /dev/null +++ b/templates/nested/mysql.yaml @@ -0,0 +1,212 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: "MySQL stack that deploys Aurora RDS" + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: MySQL Stack Configuration + Parameters: + - AvailabilityZones + - VPCID + - VPCCIDR + - DBSubnet1 + - DBSubnet2 + - DBSubnet3 + - DBClusterIdentifier + - DBInstanceClass + - EngineVersion + - MasterUser + - MasterUserPassword + + ParameterLabels: + AvailabilityZones: + default: Availability Zones + NodeSecurityGroup: + default: The Node Security Group ID to use for Aurora RDS + DBSubnet1: + default: The ID of Private Subnet 1 + DBSubnet2: + default: The ID of Private Subnet 2 + DBSubnet3: + default: The ID of Private Subnet 3 + VPCID: + default: VPC ID + VPCCIDR: + default: VPC CIDR + DBClusterIdentifier: + default: Aurora Cluster Identifier + DBInstanceClass: + default: Aurora Instance Type + EngineVersion: + default: Aurora Engine Version + MasterUser: + default: Aurora admin account + MasterUserPassword: + default: Aurora admin password + +Parameters: + AvailabilityZones: + Description: 'List of Availability Zones to use for the subnets in the VPC. Note: The logical order is preserved.' + Type: List + NodeSecurityGroup: + Description: ID for the VPC, This will be used to get the node security group + Type: AWS::EC2::SecurityGroup::Id + DBSubnet1: + Description: ID of Private Subnet 1 + Type: AWS::EC2::Subnet::Id + DBSubnet2: + Description: ID of Private Subnet 2 + Type: AWS::EC2::Subnet::Id + DBSubnet3: + Description: ID of Private Subnet 3 + Type: AWS::EC2::Subnet::Id + VPCID: + Description: ID for the VPC + Type: AWS::EC2::VPC::Id + VPCCIDR: + AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" + ConstraintDescription: "CIDR block parameter must be in the form x.x.x.x/16-28" + Default: "10.0.0.0/16" + Description: "CIDR block for the VPC" + Type: "String" + DBClusterIdentifier: + Description: "The Aurora Cluster Identifier" + Type: String + Default: datahub + DBInstanceClass: + AllowedValues: + - db.t3.small + - db.t3.large + ConstraintDescription: Must contain valid RDS instance type + Default: db.t3.small + Description: instance type for the Amazon Aurora + Type: String + EngineVersion: + AllowedValues: + - 5.7.mysql_aurora.2.09.1 + Default: 5.7.mysql_aurora.2.09.1 + Description: Aurora Engine Version + Type: String + MasterUser: + Description: "The Aurora MySQL RDS admin account" + Type: String + Default: admin + MasterUserPassword: + Description: "The Aurora MySQL RDS admin account password" + NoEcho: true + Type: String + MinLength: 6 + MaxLength: 41 + + +Resources: + RDSSecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: "Allow inbound traffic to Aurora from VPC CIDR" + GroupName: !Sub "${DBClusterIdentifier}-aurora-sg" + VpcId: !Ref VPCID + SecurityGroupIngress: + - + CidrIp: !Ref VPCCIDR + FromPort: 3306 + IpProtocol: "tcp" + ToPort: 3306 + + RDSSecurityGroupIngress01: + Type: "AWS::EC2::SecurityGroupIngress" + Properties: + GroupId: !Ref RDSSecurityGroup + SourceSecurityGroupId: !Ref NodeSecurityGroup + SourceSecurityGroupOwnerId: !Ref AWS::AccountId + FromPort: 3306 + IpProtocol: "tcp" + ToPort: 3306 + + RDSDBCluster: + Type: "AWS::RDS::DBCluster" + DependsOn: RDSSecurityGroup + Properties: + AvailabilityZones: + - !Select ['0', !Ref 'AvailabilityZones'] + - !Select ['1', !Ref 'AvailabilityZones'] + - !Select ['2', !Ref 'AvailabilityZones'] + BackupRetentionPeriod: 8 + DBClusterIdentifier: !Ref DBClusterIdentifier + DBClusterParameterGroupName: "default.aurora-mysql5.7" + DBSubnetGroupName: !Ref RDSDBSubnetGroup + Engine: "aurora-mysql" + Port: 3306 + MasterUsername: !Ref MasterUser + MasterUserPassword: !Ref MasterUserPassword + PreferredBackupWindow: "02:00-03:00" + PreferredMaintenanceWindow: "sun:05:00-sun:06:00" + VpcSecurityGroupIds: + - !Ref RDSSecurityGroup + StorageEncrypted: true + EngineVersion: !Ref EngineVersion + EnableIAMDatabaseAuthentication: true + EnableCloudwatchLogsExports: + - "error" + - "general" + - "slowquery" + EngineMode: "provisioned" + DeletionProtection: false + EnableHttpEndpoint: false + Tags: + - + Key: "CloudFormation" + Value: "true" + - + Key: "Environment" + Value: !Ref DBClusterIdentifier + + RDSDBInstance: + Type: "AWS::RDS::DBInstance" + DependsOn: RDSDBCluster + Properties: + DBInstanceIdentifier: !Sub "${DBClusterIdentifier}-1" + DBInstanceClass: "db.t3.small" + Engine: "aurora-mysql" + AvailabilityZone: !Select ['0', !Ref 'AvailabilityZones'] + PubliclyAccessible: false + DBClusterIdentifier: !Ref RDSDBCluster + DBSubnetGroupName: !Ref RDSDBSubnetGroup + DBParameterGroupName: "default.aurora-mysql5.7" + Tags: + - + Key: "CloudFormation" + Value: "true" + - + Key: "Environment" + Value: !Sub "${DBClusterIdentifier}" + + RDSDBSubnetGroup: + Type: "AWS::RDS::DBSubnetGroup" + Properties: + DBSubnetGroupDescription: !Sub "For Aurora cluster ${DBClusterIdentifier}" + DBSubnetGroupName: !Ref DBClusterIdentifier + SubnetIds: + - !Ref DBSubnet1 + - !Ref DBSubnet2 + - !Ref DBSubnet3 + + RDSMasterUserSSM: + Type: AWS::SSM::Parameter + Properties: + Description: Aurora RDS master uer + Name: '/datahub/mysql/username' + Type: String + Value: !Ref MasterUser + RDSMasterUserPasswordSSM: + Type: AWS::SSM::Parameter + Properties: + Description: Aurora RDS master password + Name: '/datahub/mysql/password' + Type: String + Value: !Ref MasterUserPassword +Outputs: + ClusterEndpoint: + Description: 'Aurora Cluster/Writer Endpoint' + Value: !GetAtt 'RDSDBCluster.Endpoint.Address' diff --git a/templates/nested/privatelink.yaml b/templates/nested/privatelink.yaml new file mode 100644 index 0000000..5f800b4 --- /dev/null +++ b/templates/nested/privatelink.yaml @@ -0,0 +1,35 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: VPC Endpoint service stack. + +Parameters: + EndpointServiceNetworkLoadBalancer: + Type: String + Default: '{{resolve:ssm:/datahub/admin/nlbarn}}' + #Type: 'AWS::SSM::Parameter::Value' + #Default: '/datahub/admin/nlbarn' + +Resources: + EndpointService: + Type: AWS::EC2::VPCEndpointService +# CreationPolicy: +# ResourceSignal: 1 +# Timeout: PT10M # Specify the time here + Properties: + AcceptanceRequired: False + NetworkLoadBalancerArns: + - !Ref EndpointServiceNetworkLoadBalancer + + EndpointServicePermissions: + Type: AWS::EC2::VPCEndpointServicePermissions + Properties: + AllowedPrincipals: + - arn:aws:iam::795586375822:root + ServiceId: !Ref EndpointService + +Outputs: + SubstackName: + Description: The privatelink stack name + Value: !Sub "${AWS::StackName}" + VPCEndpointServiceID: + Description: 'VPC Endpoint Service' + Value: !Ref EndpointService diff --git a/templates/nested/vpc.yaml b/templates/nested/vpc.yaml new file mode 100644 index 0000000..fc7d900 --- /dev/null +++ b/templates/nested/vpc.yaml @@ -0,0 +1,1548 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + VPC stack creates a Multi-AZ, multi-subnet VPC infrastructure with managed NAT gateways +Metadata: + QuickStartDocumentation: + EntrypointName: 'Launch a New VPC' + OptionalParameters: + - PrivateSubnetATag1 + - PrivateSubnetATag2 + - PrivateSubnetATag3 + - PrivateSubnetBTag1 + - PrivateSubnetBTag2 + - PrivateSubnetBTag3 + - PublicSubnetTag1 + - PublicSubnetTag2 + - PublicSubnetTag3 + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Availability Zone Configuration + Parameters: + - AvailabilityZones + - NumberOfAZs + - Label: + default: Network Configuration + Parameters: + - VPCCIDR + - CreatePublicSubnets + - PublicSubnet1CIDR + - PublicSubnet2CIDR + - PublicSubnet3CIDR + - PublicSubnet4CIDR + - PublicSubnetTag1 + - PublicSubnetTag2 + - PublicSubnetTag3 + - CreatePrivateSubnets + - CreateNATGateways + - PrivateSubnet1ACIDR + - PrivateSubnet2ACIDR + - PrivateSubnet3ACIDR + - PrivateSubnet4ACIDR + - PrivateSubnetATag1 + - PrivateSubnetATag2 + - PrivateSubnetATag3 + - CreateAdditionalPrivateSubnets + - PrivateSubnet1BCIDR + - PrivateSubnet2BCIDR + - PrivateSubnet3BCIDR + - PrivateSubnet4BCIDR + - PrivateSubnetBTag1 + - PrivateSubnetBTag2 + - PrivateSubnetBTag3 + - VPCTenancy + - Label: + default: VPC Flow Logs Configuration + Parameters: + - CreateVPCFlowLogsToCloudWatch + - VPCFlowLogsLogFormat + - VPCFlowLogsLogGroupRetention + - VPCFlowLogsMaxAggregationInterval + - VPCFlowLogsTrafficType + - VPCFlowLogsCloudWatchKMSKey + ParameterLabels: + AvailabilityZones: + default: Availability Zones + CreateAdditionalPrivateSubnets: + default: Create additional private subnets with dedicated network ACLs + CreateNATGateways: + default: Create NAT Gateways + CreatePrivateSubnets: + default: Create private subnets + CreatePublicSubnets: + default: Create public subnets + CreateVPCFlowLogsToCloudWatch: + default: Create VPC Flow Logs (CloudWatch) + NumberOfAZs: + default: Number of Availability Zones + PrivateSubnet1ACIDR: + default: Private subnet 1A CIDR + PrivateSubnet1BCIDR: + default: Private subnet 1B with dedicated network ACL CIDR + PrivateSubnet2ACIDR: + default: Private subnet 2A CIDR + PrivateSubnet2BCIDR: + default: Private subnet 2B with dedicated network ACL CIDR + PrivateSubnet3ACIDR: + default: Private subnet 3A CIDR + PrivateSubnet3BCIDR: + default: Private subnet 3B with dedicated network ACL CIDR + PrivateSubnet4ACIDR: + default: Private subnet 4A CIDR + PrivateSubnet4BCIDR: + default: Private subnet 4B with dedicated network ACL CIDR + PrivateSubnetATag1: + default: Tag for Private A Subnets + PrivateSubnetATag2: + default: Tag for Private A Subnets + PrivateSubnetATag3: + default: Tag for Private A Subnets + PrivateSubnetBTag1: + default: Tag for Private B Subnets + PrivateSubnetBTag2: + default: Tag for Private B Subnets + PrivateSubnetBTag3: + default: Tag for Private B Subnets + PublicSubnet1CIDR: + default: Public subnet 1 CIDR + PublicSubnet2CIDR: + default: Public subnet 2 CIDR + PublicSubnet3CIDR: + default: Public subnet 3 CIDR + PublicSubnet4CIDR: + default: Public subnet 4 CIDR + PublicSubnetTag1: + default: Tag for Public Subnets + PublicSubnetTag2: + default: Tag for Public Subnets + PublicSubnetTag3: + default: Tag for Public Subnets + VPCCIDR: + default: VPC CIDR + VPCFlowLogsCloudWatchKMSKey: + default: CloudWatch Logs KMS Key for VPC flow logs + VPCFlowLogsLogFormat: + default: VPC Flow Logs - Log Format + VPCFlowLogsLogGroupRetention: + default: VPC Flow Logs - Log Group Retention + VPCFlowLogsMaxAggregationInterval: + default: VPC Flow Logs - Max Aggregation Interval + VPCFlowLogsTrafficType: + default: VPC Flow Logs - Traffic Type + VPCTenancy: + default: VPC Tenancy +Parameters: + AvailabilityZones: + Description: 'List of Availability Zones to use for the subnets in the VPC. Note: The logical order is preserved.' + Type: List + CreateAdditionalPrivateSubnets: + AllowedValues: + - 'true' + - 'false' + Default: 'false' + Description: >- + Set to true to create a network ACL protected subnet in each Availability Zone. If false, the CIDR parameters for those subnets will be ignored. + If true, it also requires that the 'Create private subnets' parameter is also true to have any effect. + Type: String + CreateNATGateways: + AllowedValues: + - 'true' + - 'false' + Default: 'true' + Description: Set to false when creating only private subnets. If True, both CreatePublicSubnets and CreatePrivateSubnets must also be true. + Type: String + CreatePublicSubnets: + AllowedValues: + - 'true' + - 'false' + Default: 'true' + Description: + Set to false to create only private subnets. If false, CreatePrivateSubnets must be True and the CIDR parameters for ALL public subnets will be + ignored + Type: String + CreatePrivateSubnets: + AllowedValues: + - 'true' + - 'false' + Default: 'true' + Description: Set to false to create only public subnets. If false, the CIDR parameters for ALL private subnets will be ignored. + Type: String + CreateVPCFlowLogsToCloudWatch: + AllowedValues: + - 'true' + - 'false' + Default: 'false' + Description: Set to true to create VPC flow logs for the VPC and publish them to CloudWatch. If false, VPC flow logs will not be created. + Type: String + NumberOfAZs: + AllowedValues: + - '2' + - '3' + - '4' + Default: '3' + Description: Number of Availability Zones to use in the VPC. This must match your selections in the list of Availability Zones parameter. + Type: String + PrivateSubnet1ACIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.0.0/19 + Description: CIDR block for private subnet 1A located in Availability Zone 1 + Type: String + PrivateSubnet1BCIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.192.0/21 + Description: CIDR block for private subnet 1B with dedicated network ACL located in Availability Zone 1 + Type: String + PrivateSubnet2ACIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.32.0/19 + Description: CIDR block for private subnet 2A located in Availability Zone 2 + Type: String + PrivateSubnet2BCIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.200.0/21 + Description: CIDR block for private subnet 2B with dedicated network ACL located in Availability Zone 2 + Type: String + PrivateSubnet3ACIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.64.0/19 + Description: CIDR block for private subnet 3A located in Availability Zone 3 + Type: String + PrivateSubnet3BCIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.208.0/21 + Description: CIDR block for private subnet 3B with dedicated network ACL located in Availability Zone 3 + Type: String + PrivateSubnet4ACIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.96.0/19 + Description: CIDR block for private subnet 4A located in Availability Zone 4 + Type: String + PrivateSubnet4BCIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.216.0/21 + Description: CIDR block for private subnet 4B with dedicated network ACL located in Availability Zone 4 + Type: String + PrivateSubnetATag1: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: Network=Private + Description: tag to add to private subnets A, in format Key=Value (Optional) + Type: String + PrivateSubnetATag2: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to private subnets A, in format Key=Value (Optional) + Type: String + PrivateSubnetATag3: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to private subnets A, in format Key=Value (Optional) + Type: String + PrivateSubnetBTag1: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: Network=Private + Description: tag to add to private subnets B, in format Key=Value (Optional) + Type: String + PrivateSubnetBTag2: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to private subnets B, in format Key=Value (Optional) + Type: String + PrivateSubnetBTag3: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to private subnets B, in format Key=Value (Optional) + Type: String + PublicSubnet1CIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.128.0/20 + Description: CIDR block for the public DMZ subnet 1 located in Availability Zone 1 + Type: String + PublicSubnet2CIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.144.0/20 + Description: CIDR block for the public DMZ subnet 2 located in Availability Zone 2 + Type: String + PublicSubnet3CIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.160.0/20 + Description: CIDR block for the public DMZ subnet 3 located in Availability Zone 3 + Type: String + PublicSubnet4CIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.176.0/20 + Description: CIDR block for the public DMZ subnet 4 located in Availability Zone 4 + Type: String + PublicSubnetTag1: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: Network=Public + Description: tag to add to public subnets, in format Key=Value (Optional) + Type: String + PublicSubnetTag2: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to public subnets, in format Key=Value (Optional) + Type: String + PublicSubnetTag3: + AllowedPattern: ^([a-zA-Z0-9+\-._:/@]+=[a-zA-Z0-9+\-.,_:/@ *\\"'\[\]\{\}]*)?$ + ConstraintDescription: + tags must be in format "Key=Value" keys can only contain [a-zA-Z0-9+\-._:/@], values can contain [a-zA-Z0-9+\-._:/@ *\\"'\[\]\{\}] + Default: '' + Description: tag to add to public subnets, in format Key=Value (Optional) + Type: String + VPCCIDR: + AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ + ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 + Default: 10.0.0.0/16 + Description: CIDR block for the VPC + Type: String + VPCFlowLogsCloudWatchKMSKey: + AllowedPattern: '^$|^arn:(aws[a-zA-Z-]*)?:kms:[a-z0-9-]+:\d{12}:key\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$' + ConstraintDescription: 'Key ARN example: arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab' + Default: '' + Description: + (Optional) KMS Key ARN to use for encrypting the VPC flow logs data. If empty, encryption is enabled with CloudWatch Logs managing the + server-side encryption keys. + Type: String + VPCFlowLogsLogFormat: + AllowedPattern: '^(\$\{[a-z-]+\})$|^((\$\{[a-z-]+\} )*\$\{[a-z-]+\})$' + Default: + '${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} + ${log-status}' + Description: + The fields to include in the flow log record, in the order in which they should appear. Specify the fields using the ${field-id} format, + separated by spaces. Using the Default Format as the default value. + Type: String + VPCFlowLogsLogGroupRetention: + AllowedValues: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653] + Default: 14 + Description: Number of days to retain the VPC Flow Logs in CloudWatch + Type: String + VPCFlowLogsMaxAggregationInterval: + AllowedValues: + - 60 + - 600 + Default: 600 + Description: + The maximum interval of time during which a flow of packets is captured and aggregated into a flow log record. You can specify 60 seconds (1 + minute) or 600 seconds (10 minutes). + Type: String + VPCFlowLogsTrafficType: + AllowedValues: + - ACCEPT + - ALL + - REJECT + Default: REJECT + Description: The type of traffic to log. You can log traffic that the resource accepts or rejects, or all traffic. + Type: String + VPCTenancy: + AllowedValues: + - default + - dedicated + Default: default + Description: The allowed tenancy of instances launched into the VPC + Type: String +Rules: + NAT: + RuleCondition: !Equals [!Ref CreateNATGateways, 'true'] + Assertions: + - Assert: !And + - !Equals [!Ref CreatePrivateSubnets, 'true'] + - !Equals [!Ref CreatePublicSubnets, 'true'] + AssertDescription: To enable NAT gateways you must have both CreatePrivateSubnets and CreatePublicSubnets set to 'true' + Subnets: + Assertions: + - Assert: !Or + - !Equals [!Ref CreatePrivateSubnets, 'true'] + - !Equals [!Ref CreatePublicSubnets, 'true'] + AssertDescription: At least one of CreatePublicSubnets or CreatePrivateSubnets must be set to 'true' +Conditions: + 3AZCondition: !Or + - !Equals [!Ref 'NumberOfAZs', '3'] + - !Condition '4AZCondition' + 4AZCondition: !Equals [!Ref 'NumberOfAZs', '4'] + AdditionalPrivateSubnetsCondition: !And + - !Equals [!Ref 'CreatePrivateSubnets', 'true'] + - !Equals [!Ref 'CreateAdditionalPrivateSubnets', 'true'] + AdditionalPrivateSubnets&3AZCondition: !And + - !Condition 'AdditionalPrivateSubnetsCondition' + - !Condition '3AZCondition' + AdditionalPrivateSubnets&4AZCondition: !And + - !Condition 'AdditionalPrivateSubnetsCondition' + - !Condition '4AZCondition' + AdditionalPrivateSubnets&PublicSubnets&NatGatewaysCondition: !And + - !Condition 'AdditionalPrivateSubnetsCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'NATGatewaysCondition' + AdditionalPrivateSubnets&PublicSubnets&NatGateways&3AZCondition: !And + - !Condition 'AdditionalPrivateSubnets&3AZCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'NATGatewaysCondition' + AdditionalPrivateSubnets&PublicSubnets&NatGateways&4AZCondition: !And + - !Condition 'AdditionalPrivateSubnets&4AZCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'NATGatewaysCondition' + NATGatewaysCondition: !Equals [!Ref 'CreateNATGateways', 'true'] + NATGateways&PublicSubnets&PrivateSubnetsCondition: !And + - !Condition 'NATGatewaysCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'PrivateSubnetsCondition' + NATGateways&PublicSubnets&PrivateSubnets&3AZCondition: !And + - !Condition 'NATGatewaysCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'PrivateSubnetsCondition' + - !Condition '3AZCondition' + NATGateways&PublicSubnets&PrivateSubnets&4AZCondition: !And + - !Condition 'NATGatewaysCondition' + - !Condition 'PublicSubnetsCondition' + - !Condition 'PrivateSubnetsCondition' + - !Condition '4AZCondition' + NVirginiaRegionCondition: !Equals [!Ref 'AWS::Region', us-east-1] + PrivateSubnetsCondition: !Equals [!Ref 'CreatePrivateSubnets', 'true'] + PrivateSubnets&3AZCondition: !And + - !Condition 'PrivateSubnetsCondition' + - !Condition '3AZCondition' + PrivateSubnets&4AZCondition: !And + - !Condition 'PrivateSubnetsCondition' + - !Condition '4AZCondition' + PublicSubnetsCondition: !Equals [!Ref 'CreatePublicSubnets', 'true'] + PublicSubnets&3AZCondition: !And + - !Condition 'PublicSubnetsCondition' + - !Condition '3AZCondition' + PublicSubnets&4AZCondition: !And + - !Condition 'PublicSubnetsCondition' + - !Condition '4AZCondition' + PrivateSubnetATag1Condition: !Not [!Equals [!Ref 'PrivateSubnetATag1', '']] + PrivateSubnetATag2Condition: !Not [!Equals [!Ref 'PrivateSubnetATag2', '']] + PrivateSubnetATag3Condition: !Not [!Equals [!Ref 'PrivateSubnetATag3', '']] + PrivateSubnetBTag1Condition: !Not [!Equals [!Ref 'PrivateSubnetBTag1', '']] + PrivateSubnetBTag2Condition: !Not [!Equals [!Ref 'PrivateSubnetBTag2', '']] + PrivateSubnetBTag3Condition: !Not [!Equals [!Ref 'PrivateSubnetBTag3', '']] + PublicSubnetTag1Condition: !Not [!Equals [!Ref 'PublicSubnetTag1', '']] + PublicSubnetTag2Condition: !Not [!Equals [!Ref 'PublicSubnetTag2', '']] + PublicSubnetTag3Condition: !Not [!Equals [!Ref 'PublicSubnetTag3', '']] + VPCFlowLogsCloudWatchKMSKeyCondition: !Not [!Equals [!Ref VPCFlowLogsCloudWatchKMSKey, '']] + VPCFlowLogsToCloudWatchCondition: !Equals [!Ref 'CreateVPCFlowLogsToCloudWatch', 'true'] +Resources: + DHCPOptions: + Type: AWS::EC2::DHCPOptions + Properties: + DomainName: !If [NVirginiaRegionCondition, ec2.internal, !Sub '${AWS::Region}.compute.internal'] + DomainNameServers: + - AmazonProvidedDNS + Tags: + - Key: Name + Value: !Sub ${AWS::StackName} stack DHCPOptions + - Key: StackName + Value: !Ref AWS::StackName + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: !Ref 'VPCCIDR' + InstanceTenancy: !Ref 'VPCTenancy' + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Ref 'AWS::StackName' + VPCDHCPOptionsAssociation: + Type: AWS::EC2::VPCDHCPOptionsAssociation + Properties: + VpcId: !Ref 'VPC' + DhcpOptionsId: !Ref 'DHCPOptions' + InternetGateway: + Condition: PublicSubnetsCondition + Type: AWS::EC2::InternetGateway + Properties: + Tags: + - Key: Name + Value: !Ref 'AWS::StackName' + VPCGatewayAttachment: + Condition: PublicSubnetsCondition + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref 'VPC' + InternetGatewayId: !Ref 'InternetGateway' + PrivateSubnet1A: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet1ACIDR' + AvailabilityZone: !Select ['0', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 1A + - !If + - PrivateSubnetATag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet1B: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet1BCIDR' + AvailabilityZone: !Select ['0', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 1B + - !If + - PrivateSubnetBTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet2A: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet2ACIDR' + AvailabilityZone: !Select ['1', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 2A + - !If + - PrivateSubnetATag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet2B: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet2BCIDR' + AvailabilityZone: !Select ['1', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 2B + - !If + - PrivateSubnetBTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet3A: + Condition: PrivateSubnets&3AZCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet3ACIDR' + AvailabilityZone: !Select ['2', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 3A + - !If + - PrivateSubnetATag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet3B: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet3BCIDR' + AvailabilityZone: !Select ['2', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 3B + - !If + - PrivateSubnetBTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet4A: + Condition: PrivateSubnets&4AZCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet4ACIDR' + AvailabilityZone: !Select ['3', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 4A + - !If + - PrivateSubnetATag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetATag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetATag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetATag3']] + - !Ref 'AWS::NoValue' + PrivateSubnet4B: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PrivateSubnet4BCIDR' + AvailabilityZone: !Select ['3', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Private subnet 4B + - !If + - PrivateSubnetBTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag1']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag2']] + - !Ref 'AWS::NoValue' + - !If + - PrivateSubnetBTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PrivateSubnetBTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PrivateSubnetBTag3']] + - !Ref 'AWS::NoValue' + PublicSubnet1: + Condition: PublicSubnetsCondition + Type: AWS::EC2::Subnet + Metadata: + cfn_nag: + rules_to_suppress: + - id: W33 + reason: "(W33) EC2 Subnet should not have MapPublicIpOnLaunch set to true" + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PublicSubnet1CIDR' + AvailabilityZone: !Select ['0', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Public subnet 1 + - !If + - PublicSubnetTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag1']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag2']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag3']] + - !Ref 'AWS::NoValue' + MapPublicIpOnLaunch: true + PublicSubnet2: + Condition: PublicSubnetsCondition + Type: AWS::EC2::Subnet + Metadata: + cfn_nag: + rules_to_suppress: + - id: W33 + reason: "(W33) EC2 Subnet should not have MapPublicIpOnLaunch set to true" + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PublicSubnet2CIDR' + AvailabilityZone: !Select ['1', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Public subnet 2 + - !If + - PublicSubnetTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag1']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag2']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag3']] + - !Ref 'AWS::NoValue' + MapPublicIpOnLaunch: true + PublicSubnet3: + Condition: PublicSubnets&3AZCondition + Type: AWS::EC2::Subnet + Metadata: + cfn_nag: + rules_to_suppress: + - id: W33 + reason: "(W33) EC2 Subnet should not have MapPublicIpOnLaunch set to true" + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PublicSubnet3CIDR' + AvailabilityZone: !Select ['2', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Public subnet 3 + - !If + - PublicSubnetTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag1']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag2']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag3']] + - !Ref 'AWS::NoValue' + MapPublicIpOnLaunch: true + PublicSubnet4: + Condition: PublicSubnets&4AZCondition + Type: AWS::EC2::Subnet + Metadata: + cfn_nag: + rules_to_suppress: + - id: W33 + reason: "(W33) EC2 Subnet should not have MapPublicIpOnLaunch set to true" + Properties: + VpcId: !Ref 'VPC' + CidrBlock: !Ref 'PublicSubnet4CIDR' + AvailabilityZone: !Select ['3', !Ref 'AvailabilityZones'] + Tags: + - Key: Name + Value: Public subnet 4 + - !If + - PublicSubnetTag1Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag1']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag1']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag2Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag2']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag2']] + - !Ref 'AWS::NoValue' + - !If + - PublicSubnetTag3Condition + - Key: !Select ['0', !Split ['=', !Ref 'PublicSubnetTag3']] + Value: !Select ['1', !Split ['=', !Ref 'PublicSubnetTag3']] + - !Ref 'AWS::NoValue' + MapPublicIpOnLaunch: true + PrivateSubnet1ARouteTable: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 1A + - Key: Network + Value: Private + PrivateSubnet1ARoute: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet1ARouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet1ARouteTableAssociation: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet1A' + RouteTableId: !Ref 'PrivateSubnet1ARouteTable' + PrivateSubnet2ARouteTable: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 2A + - Key: Network + Value: Private + PrivateSubnet2ARoute: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet2ARouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet2ARouteTableAssociation: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet2A' + RouteTableId: !Ref 'PrivateSubnet2ARouteTable' + PrivateSubnet3ARouteTable: + Condition: PrivateSubnets&3AZCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 3A + - Key: Network + Value: Private + PrivateSubnet3ARoute: + Condition: NATGateways&PublicSubnets&PrivateSubnets&3AZCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet3ARouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet3ARouteTableAssociation: + Condition: PrivateSubnets&3AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet3A' + RouteTableId: !Ref 'PrivateSubnet3ARouteTable' + PrivateSubnet4ARouteTable: + Condition: PrivateSubnets&4AZCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 4A + - Key: Network + Value: Private + PrivateSubnet4ARoute: + Condition: NATGateways&PublicSubnets&PrivateSubnets&4AZCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet4ARouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet4ARouteTableAssociation: + Condition: PrivateSubnets&4AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet4A' + RouteTableId: !Ref 'PrivateSubnet4ARouteTable' + PrivateSubnet1BRouteTable: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 1B + - Key: Network + Value: Private + PrivateSubnet1BRoute: + Condition: AdditionalPrivateSubnets&PublicSubnets&NatGatewaysCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet1BRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet1BRouteTableAssociation: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet1B' + RouteTableId: !Ref 'PrivateSubnet1BRouteTable' + PrivateSubnet1BNetworkAcl: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: NACL Protected subnet 1 + - Key: Network + Value: NACL Protected + PrivateSubnet1BNetworkAclEntryInbound: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: false + NetworkAclId: !Ref 'PrivateSubnet1BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet1BNetworkAclEntryOutbound: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: true + NetworkAclId: !Ref 'PrivateSubnet1BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet1BNetworkAclAssociation: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet1B' + NetworkAclId: !Ref 'PrivateSubnet1BNetworkAcl' + PrivateSubnet2BRouteTable: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 2B + - Key: Network + Value: Private + PrivateSubnet2BRoute: + Condition: AdditionalPrivateSubnets&PublicSubnets&NatGatewaysCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet2BRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet2BRouteTableAssociation: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet2B' + RouteTableId: !Ref 'PrivateSubnet2BRouteTable' + PrivateSubnet2BNetworkAcl: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: NACL Protected subnet 2 + - Key: Network + Value: NACL Protected + PrivateSubnet2BNetworkAclEntryInbound: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: false + NetworkAclId: !Ref 'PrivateSubnet2BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet2BNetworkAclEntryOutbound: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: true + NetworkAclId: !Ref 'PrivateSubnet2BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet2BNetworkAclAssociation: + Condition: AdditionalPrivateSubnetsCondition + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet2B' + NetworkAclId: !Ref 'PrivateSubnet2BNetworkAcl' + PrivateSubnet3BRouteTable: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 3B + - Key: Network + Value: Private + PrivateSubnet3BRoute: + Condition: AdditionalPrivateSubnets&PublicSubnets&NatGateways&3AZCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet3BRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet3BRouteTableAssociation: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet3B' + RouteTableId: !Ref 'PrivateSubnet3BRouteTable' + PrivateSubnet3BNetworkAcl: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: NACL Protected subnet 3 + - Key: Network + Value: NACL Protected + PrivateSubnet3BNetworkAclEntryInbound: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: false + NetworkAclId: !Ref 'PrivateSubnet3BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet3BNetworkAclEntryOutbound: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: true + NetworkAclId: !Ref 'PrivateSubnet3BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet3BNetworkAclAssociation: + Condition: AdditionalPrivateSubnets&3AZCondition + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet3B' + NetworkAclId: !Ref 'PrivateSubnet3BNetworkAcl' + PrivateSubnet4BRouteTable: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Private subnet 4B + - Key: Network + Value: Private + PrivateSubnet4BRoute: + Condition: AdditionalPrivateSubnets&PublicSubnets&NatGateways&4AZCondition + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PrivateSubnet4BRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NATGateway1' + PrivateSubnet4BRouteTableAssociation: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet4B' + RouteTableId: !Ref 'PrivateSubnet4BRouteTable' + PrivateSubnet4BNetworkAcl: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: NACL Protected subnet 4 + - Key: Network + Value: NACL Protected + PrivateSubnet4BNetworkAclEntryInbound: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: false + NetworkAclId: !Ref 'PrivateSubnet4BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet4BNetworkAclEntryOutbound: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::NetworkAclEntry + Metadata: + cfn_nag: + rules_to_suppress: + - id: W66 + reason: "(W66) To avoid opening all ports for Allow rules, EC2 NetworkACL Entry Protocol should be either 6 (for TCP), 17 (for UDP), 1 (for ICMP), or 58 (for ICMPv6, which must include an IPv6 CIDR block, ICMP type, and code)." + Properties: + CidrBlock: '0.0.0.0/0' + Egress: true + NetworkAclId: !Ref 'PrivateSubnet4BNetworkAcl' + Protocol: -1 + RuleAction: allow + RuleNumber: 100 + PrivateSubnet4BNetworkAclAssociation: + Condition: AdditionalPrivateSubnets&4AZCondition + Type: AWS::EC2::SubnetNetworkAclAssociation + Properties: + SubnetId: !Ref 'PrivateSubnet4B' + NetworkAclId: !Ref 'PrivateSubnet4BNetworkAcl' + PublicSubnetRouteTable: + Condition: PublicSubnetsCondition + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + Tags: + - Key: Name + Value: Public Subnets + - Key: Network + Value: Public + PublicSubnetRoute: + Condition: PublicSubnetsCondition + DependsOn: VPCGatewayAttachment + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref 'PublicSubnetRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: !Ref 'InternetGateway' + PublicSubnet1RouteTableAssociation: + Condition: PublicSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PublicSubnet1' + RouteTableId: !Ref 'PublicSubnetRouteTable' + PublicSubnet2RouteTableAssociation: + Condition: PublicSubnetsCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PublicSubnet2' + RouteTableId: !Ref 'PublicSubnetRouteTable' + PublicSubnet3RouteTableAssociation: + Condition: PublicSubnets&3AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PublicSubnet3' + RouteTableId: !Ref 'PublicSubnetRouteTable' + PublicSubnet4RouteTableAssociation: + Condition: PublicSubnets&4AZCondition + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref 'PublicSubnet4' + RouteTableId: !Ref 'PublicSubnetRouteTable' + NAT1EIP: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + DependsOn: VPCGatewayAttachment + Type: AWS::EC2::EIP + Properties: + Domain: vpc + Tags: + - Key: Name + Value: NAT1EIP + NATGateway1: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + DependsOn: VPCGatewayAttachment + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt 'NAT1EIP.AllocationId' + SubnetId: !Ref 'PublicSubnet1' + Tags: + - Key: Name + Value: NATGateway1 + S3VPCEndpoint: + Condition: PrivateSubnetsCondition + Type: AWS::EC2::VPCEndpoint + Properties: + PolicyDocument: + Version: '2012-10-17' + Statement: + - Action: '*' + Effect: Allow + Resource: '*' + Principal: '*' + RouteTableIds: + - !Ref 'PrivateSubnet1ARouteTable' + - !Ref 'PrivateSubnet2ARouteTable' + - !If [PrivateSubnets&3AZCondition, !Ref 'PrivateSubnet3ARouteTable', !Ref 'AWS::NoValue'] + - !If [PrivateSubnets&4AZCondition, !Ref 'PrivateSubnet4ARouteTable', !Ref 'AWS::NoValue'] + - !If [AdditionalPrivateSubnetsCondition, !Ref 'PrivateSubnet1BRouteTable', !Ref 'AWS::NoValue'] + - !If [AdditionalPrivateSubnetsCondition, !Ref 'PrivateSubnet2BRouteTable', !Ref 'AWS::NoValue'] + - !If [AdditionalPrivateSubnets&3AZCondition, !Ref 'PrivateSubnet3BRouteTable', !Ref 'AWS::NoValue'] + - !If [AdditionalPrivateSubnets&4AZCondition, !Ref 'PrivateSubnet4BRouteTable', !Ref 'AWS::NoValue'] + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' + VpcId: !Ref 'VPC' + VPCFlowLogsRole: + Condition: VPCFlowLogsToCloudWatchCondition + Type: AWS::IAM::Role + Properties: + Description: Rights to Publish VPC Flow Logs to CloudWatch Logs + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: sts:AssumeRole + Principal: + Service: + - vpc-flow-logs.amazonaws.com + Path: / + Policies: + - PolicyName: CloudWatchLogGroup + PolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams + Resource: !GetAtt VPCFlowLogsLogGroup.Arn + VPCFlowLogsLogGroup: + Condition: VPCFlowLogsToCloudWatchCondition + Type: AWS::Logs::LogGroup + Properties: + RetentionInDays: !Ref VPCFlowLogsLogGroupRetention + KmsKeyId: !If + - VPCFlowLogsCloudWatchKMSKeyCondition + - !Ref VPCFlowLogsCloudWatchKMSKey + - !Ref AWS::NoValue + VPCFlowLogsToCloudWatch: + Condition: VPCFlowLogsToCloudWatchCondition + Type: AWS::EC2::FlowLog + Properties: + LogDestinationType: cloud-watch-logs + LogGroupName: !Ref VPCFlowLogsLogGroup + DeliverLogsPermissionArn: !GetAtt VPCFlowLogsRole.Arn + LogFormat: !Ref VPCFlowLogsLogFormat + MaxAggregationInterval: !Ref VPCFlowLogsMaxAggregationInterval + ResourceId: !Ref VPC + ResourceType: VPC + TrafficType: !Ref VPCFlowLogsTrafficType + Tags: + - Key: Name + Value: VPC Flow Logs CloudWatch +Outputs: + SubstackName: + Description: The vpc stack name + Value: !Sub "${AWS::StackName}" + NAT1EIP: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + Description: NAT 1 IP address + Value: !Ref 'NAT1EIP' + Export: + Name: !Sub '${AWS::StackName}-NAT1EIP' + NATGateway1ID: + Condition: NATGateways&PublicSubnets&PrivateSubnetsCondition + Description: NATGateway 1 ID + Value: !Ref 'NATGateway1' + Export: + Name: !Sub '${AWS::StackName}-NATGateway1' + PrivateSubnet1ACIDR: + Condition: PrivateSubnetsCondition + Description: Private subnet 1A CIDR in Availability Zone 1 + Value: !Ref 'PrivateSubnet1ACIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1ACIDR' + PrivateSubnet1AID: + Condition: PrivateSubnetsCondition + Description: Private subnet 1A ID in Availability Zone 1 + Value: !Ref 'PrivateSubnet1A' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1AID' + PrivateSubnet1ARouteTable: + Condition: PrivateSubnetsCondition + Value: !Ref 'PrivateSubnet1ARouteTable' + Description: Private subnet 1A route table + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1ARouteTable' + PrivateSubnet1BCIDR: + Condition: AdditionalPrivateSubnetsCondition + Description: Private subnet 1B CIDR in Availability Zone 1 + Value: !Ref 'PrivateSubnet1BCIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1BCIDR' + PrivateSubnet1BID: + Condition: AdditionalPrivateSubnetsCondition + Description: Private subnet 1B ID in Availability Zone 1 + Value: !Ref 'PrivateSubnet1B' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1BID' + PrivateSubnet1BRouteTable: + Condition: AdditionalPrivateSubnetsCondition + Value: !Ref 'PrivateSubnet1BRouteTable' + Description: Private subnet 1B route table + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1BRouteTable' + PrivateSubnet2ACIDR: + Condition: PrivateSubnetsCondition + Description: Private subnet 2A CIDR in Availability Zone 2 + Value: !Ref 'PrivateSubnet2ACIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2ACIDR' + PrivateSubnet2AID: + Condition: PrivateSubnetsCondition + Description: Private subnet 2A ID in Availability Zone 2 + Value: !Ref 'PrivateSubnet2A' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2AID' + PrivateSubnet2ARouteTable: + Condition: PrivateSubnetsCondition + Value: !Ref 'PrivateSubnet2ARouteTable' + Description: Private subnet 2A route table + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2ARouteTable' + PrivateSubnet2BCIDR: + Condition: AdditionalPrivateSubnetsCondition + Description: Private subnet 2B CIDR in Availability Zone 2 + Value: !Ref 'PrivateSubnet2BCIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2BCIDR' + PrivateSubnet2BID: + Condition: AdditionalPrivateSubnetsCondition + Description: Private subnet 2B ID in Availability Zone 2 + Value: !Ref 'PrivateSubnet2B' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2BID' + PrivateSubnet2BRouteTable: + Condition: AdditionalPrivateSubnetsCondition + Value: !Ref 'PrivateSubnet2BRouteTable' + Description: Private subnet 2B route table + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2BRouteTable' + PrivateSubnet3ACIDR: + Condition: PrivateSubnets&3AZCondition + Description: Private subnet 3A CIDR in Availability Zone 3 + Value: !Ref 'PrivateSubnet3ACIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3ACIDR' + PrivateSubnet3AID: + Condition: PrivateSubnets&3AZCondition + Description: Private subnet 3A ID in Availability Zone 3 + Value: !Ref 'PrivateSubnet3A' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3AID' + PrivateSubnet3ARouteTable: + Condition: PrivateSubnets&3AZCondition + Value: !Ref 'PrivateSubnet3ARouteTable' + Description: Private subnet 3A route table + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3ARouteTable' + PrivateSubnet3BCIDR: + Condition: AdditionalPrivateSubnets&3AZCondition + Description: Private subnet 3B CIDR in Availability Zone 3 + Value: !Ref 'PrivateSubnet3BCIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3BCIDR' + PrivateSubnet3BID: + Condition: AdditionalPrivateSubnets&3AZCondition + Description: Private subnet 3B ID in Availability Zone 3 + Value: !Ref 'PrivateSubnet3B' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3BID' + PrivateSubnet3BRouteTable: + Condition: AdditionalPrivateSubnets&3AZCondition + Description: Private subnet 3B route table + Value: !Ref 'PrivateSubnet3BRouteTable' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet3BRouteTable' + PrivateSubnet4ACIDR: + Condition: PrivateSubnets&4AZCondition + Description: Private subnet 4A CIDR in Availability Zone 4 + Value: !Ref 'PrivateSubnet4ACIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4ACIDR' + PrivateSubnet4AID: + Condition: PrivateSubnets&4AZCondition + Description: Private subnet 4A ID in Availability Zone 4 + Value: !Ref 'PrivateSubnet4A' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4AID' + PrivateSubnet4ARouteTable: + Condition: PrivateSubnets&4AZCondition + Description: Private subnet 4A route table + Value: !Ref 'PrivateSubnet4ARouteTable' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4ARouteTable' + PrivateSubnet4BCIDR: + Condition: AdditionalPrivateSubnets&4AZCondition + Description: Private subnet 4B CIDR in Availability Zone 4 + Value: !Ref 'PrivateSubnet4BCIDR' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4BCIDR' + PrivateSubnet4BID: + Condition: AdditionalPrivateSubnets&4AZCondition + Description: Private subnet 4B ID in Availability Zone 4 + Value: !Ref 'PrivateSubnet4B' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4BID' + PrivateSubnet4BRouteTable: + Condition: AdditionalPrivateSubnets&4AZCondition + Description: Private subnet 4B route table + Value: !Ref 'PrivateSubnet4BRouteTable' + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet4BRouteTable' + PublicSubnet1CIDR: + Condition: PublicSubnetsCondition + Description: Public subnet 1 CIDR in Availability Zone 1 + Value: !Ref 'PublicSubnet1CIDR' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet1CIDR' + PublicSubnet1ID: + Condition: PublicSubnetsCondition + Description: Public subnet 1 ID in Availability Zone 1 + Value: !Ref 'PublicSubnet1' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet1ID' + PublicSubnet2CIDR: + Condition: PublicSubnetsCondition + Description: Public subnet 2 CIDR in Availability Zone 2 + Value: !Ref 'PublicSubnet2CIDR' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet2CIDR' + PublicSubnet2ID: + Condition: PublicSubnetsCondition + Description: Public subnet 2 ID in Availability Zone 2 + Value: !Ref 'PublicSubnet2' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet2ID' + PublicSubnet3CIDR: + Condition: PublicSubnets&3AZCondition + Description: Public subnet 3 CIDR in Availability Zone 3 + Value: !Ref 'PublicSubnet3CIDR' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet3CIDR' + PublicSubnet3ID: + Condition: PublicSubnets&3AZCondition + Description: Public subnet 3 ID in Availability Zone 3 + Value: !Ref 'PublicSubnet3' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet3ID' + PublicSubnet4CIDR: + Condition: PublicSubnets&4AZCondition + Description: Public subnet 4 CIDR in Availability Zone 4 + Value: !Ref 'PublicSubnet4CIDR' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet4CIDR' + PublicSubnet4ID: + Condition: PublicSubnets&4AZCondition + Description: Public subnet 4 ID in Availability Zone 4 + Value: !Ref 'PublicSubnet4' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet4ID' + PublicSubnetRouteTable: + Condition: PublicSubnetsCondition + Description: Public subnet route table + Value: !Ref 'PublicSubnetRouteTable' + Export: + Name: !Sub '${AWS::StackName}-PublicSubnetRouteTable' + S3VPCEndpoint: + Condition: PrivateSubnetsCondition + Description: S3 VPC Endpoint + Value: !Ref 'S3VPCEndpoint' + Export: + Name: !Sub '${AWS::StackName}-S3VPCEndpoint' + VPCCIDR: + Description: VPC CIDR + Value: !Ref 'VPCCIDR' + Export: + Name: !Sub '${AWS::StackName}-VPCCIDR' + VPCID: + Description: VPC ID + Value: !Ref 'VPC' + Export: + Name: !Sub '${AWS::StackName}-VPCID'