Skip to content

Instantly share code, notes, and snippets.

@coordt
Last active April 13, 2018 12:48
Show Gist options
  • Save coordt/5385e6098f41bfea84190730677cabaa to your computer and use it in GitHub Desktop.
Save coordt/5385e6098f41bfea84190730677cabaa to your computer and use it in GitHub Desktop.
AWSTemplateFormatVersion: 2010-09-09
Description: Test Server Infrastructure
Parameters:
ParentVPCStack:
Description: 'Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.'
Type: String
Default: 'TestVPC'
ParentSSHBastionStack:
Description: 'Optional but recommended stack name of parent SSH bastion host/instance stack based on vpc/vpc-ssh-bastion.yaml template.'
Type: String
Default: 'TestSSHBastion'
KeyName:
Description: 'Optional key pair of the ec2-user to establish a SSH connection to the EC2 instance.'
Type: String
Default: ''
IAMUserSSHAccess:
Description: 'Synchronize public keys of IAM users to enable personalized SSH access (Doc: https://cloudonaut.io/manage-aws-ec2-ssh-access-with-iam/).'
Type: String
Default: true
AllowedValues:
- true
- false
SystemsManagerAccess:
Description: 'Enable AWS Systems Manager agent and authorization.'
Type: String
Default: true
AllowedValues:
- true
- false
InstanceType:
Description: The instance type for the EC2 instance.
Type: String
Default: t2.micro
Name:
Description: 'The name for the EC2 instance.'
Type: String
Default: 'test2'
Mappings:
RegionMap:
ap-south-1:
AMI: ami-3b2f7954
eu-west-3:
AMI: ami-5ce55321
eu-west-2:
AMI: ami-6d263d09
eu-west-1:
AMI: ami-db1688a2
ap-northeast-2:
AMI: ami-3e04a450
ap-northeast-1:
AMI: ami-c2680fa4
sa-east-1:
AMI: ami-f1337e9d
ca-central-1:
AMI: ami-7549cc11
ap-southeast-1:
AMI: ami-4f89f533
ap-southeast-2:
AMI: ami-38708c5a
eu-central-1:
AMI: ami-1b2bb774
us-east-1:
AMI: ami-428aa838
us-east-2:
AMI: ami-710e2414
us-west-1:
AMI: ami-4a787a2a
us-west-2:
AMI: ami-7f43f307
Conditions:
HasKeyName: !Not [!Equals [Ref: KeyName, '']]
HasIAMUserSSHAccess: !Equals [!Ref IAMUserSSHAccess, 'true']
HasSystemsManagerAccess: !Equals [!Ref SystemsManagerAccess, 'true']
HasSSHBastionSecurityGroup: !Not [!Equals [!Ref ParentSSHBastionStack, '']]
HasNotSSHBastionSecurityGroup: !Equals [!Ref ParentSSHBastionStack, '']
Resources:
Logs:
Type: 'AWS::Logs::LogGroup'
Properties:
RetentionInDays: 14
SecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: !Ref Name
VpcId:
'Fn::ImportValue': !Sub '${ParentVPCStack}-VPC'
Metadata:
'AWS::CloudFormation::Designer':
id: a752d7c6-15af-41dd-bdd2-dba511f47448
SecurityGroupInSSHBastion:
Type: 'AWS::EC2::SecurityGroupIngress'
Condition: HasSSHBastionSecurityGroup
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId:
'Fn::ImportValue': !Sub '${ParentSSHBastionStack}-SecurityGroup'
SecurityGroupInSSHWorld:
Type: 'AWS::EC2::SecurityGroupIngress'
Condition: HasNotSSHBastionSecurityGroup
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
SecurityGroupIngressTcp:
Type: 'AWS::EC2::SecurityGroupIngress'
Properties:
GroupId: !Ref SecurityGroup
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
InstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Path: /
Roles:
- !Ref IAMRole
IAMRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns: !If [HasSystemsManagerAccess, ['arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM'], []]
Policies:
- PolicyName: testlogs
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'logs:DescribeLogStreams'
Resource:
- 'arn:aws:logs:*:*:*'
- PolicyName: ecraccess
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:DescribeImages
- ecr:GetAuthorizationToken
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:BatchCheckLayerAvailability
- ecr:GetRepositoryPolicy
Resource:
- '*'
IAMPolicySSHAccess:
Type: 'AWS::IAM::Policy'
Condition: HasIAMUserSSHAccess
Properties:
Roles:
- !Ref IAMRole
PolicyName: iam
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'iam:ListUsers'
Resource:
- '*'
- Effect: Allow
Action:
- 'iam:ListSSHPublicKeys'
- 'iam:GetSSHPublicKey'
Resource:
- !Sub 'arn:aws:iam::${AWS::AccountId}:user/*'
TestMachine:
Type: 'AWS::EC2::Instance'
Metadata:
'AWS::CloudFormation::Init':
configSets:
default: !If [HasIAMUserSSHAccess, ['journald-cloudwatch-logs', awslogs, ssh-access, config], ['journald-cloudwatch-logs', awslogs, config]]
'journald-cloudwatch-logs':
sources:
'/root': 'https://github.com/saymedia/journald-cloudwatch-logs/releases/download/v0.0.1/journald-cloudwatch-logs-linux.zip'
files:
'/usr/local/etc/journald-cloudwatch-logs.conf':
content: !Sub |
log_group = "${Logs}"
state_file = "/var/lib/journald-cloudwatch-logs/state"
'/usr/lib/systemd/system/journald-cloudwatch-logs.service':
content: |
[Unit]
Description=journald-cloudwatch-logs
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/journald-cloudwatch-logs /usr/local/etc/journald-cloudwatch-logs.conf
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
commands:
'a_create_state_dir':
command: 'mkdir /var/lib/journald-cloudwatch-logs'
test: '[ ! -d /var/lib/journald-cloudwatch-logs ]'
'b_create_state_file':
command: 'touch /var/lib/journald-cloudwatch-logs/state'
test: '[ ! -f /var/lib/journald-cloudwatch-logs/state ]'
'd_mv_bin':
command: 'mv /root/journald-cloudwatch-logs/journald-cloudwatch-logs /usr/local/bin/journald-cloudwatch-logs'
test: '[ ! -f /usr/local/bin/journald-cloudwatch-logs ]'
services:
sysvinit:
'journald-cloudwatch-logs':
enabled: true
ensureRunning: true
files:
- '/usr/local/etc/journald-cloudwatch-logs.conf'
awslogs:
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 |
content: !Sub |
[general]
state_file = /var/lib/awslogs/agent-state
[/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 = {instance_id}/var/log/amazon/ssm/amazon-ssm-agent.log
log_group_name = ${Logs}
[/var/log/awslogs.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/awslogs.log
log_stream_name = {instance_id}/var/log/awslogs.log
log_group_name = ${Logs}
[/var/log/boot.log]
file = /var/log/boot.log
log_stream_name = {instance_id}/var/log/boot.log
log_group_name = ${Logs}
[/var/log/cfn-hup.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/cfn-hup.log
log_stream_name = {instance_id}/var/log/cfn-hup.log
log_group_name = ${Logs}
[/var/log/cfn-init-cmd.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/cfn-init-cmd.log
log_stream_name = {instance_id}/var/log/cfn-init-cmd.log
log_group_name = ${Logs}
[/var/log/cfn-init.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/cfn-init.log
log_stream_name = {instance_id}/var/log/cfn-init.log
log_group_name = ${Logs}
[/var/log/cfn-wire.log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/cfn-wire.log
log_stream_name = {instance_id}/var/log/cfn-wire.log
log_group_name = ${Logs}
[/var/log/cloud-init-output.log]
file = /var/log/cloud-init-output.log
log_stream_name = {instance_id}/var/log/cloud-init-output.log
log_group_name = ${Logs}
[/var/log/cloud-init.log]
datetime_format = %b %d %H:%M:%S
file = /var/log/cloud-init.log
log_stream_name = {instance_id}/var/log/cloud-init.log
log_group_name = ${Logs}
[/var/log/dmesg]
file = /var/log/dmesg
log_stream_name = {instance_id}/var/log/dmesg
log_group_name = ${Logs}
[/var/log/grubby_prune_debug]
file = /var/log/grubby_prune_debug
log_stream_name = {instance_id}/var/log/grubby_prune_debug
log_group_name = ${Logs}
[/var/log/yum.log]
datetime_format = %b %d %H:%M:%S
file = /var/log/yum.log
log_stream_name = {instance_id}/var/log/yum.log
log_group_name = ${Logs}
mode: '000644'
owner: root
group: root
services:
sysvinit:
awslogsd:
enabled: true
ensureRunning: true
packages:
yum:
- awslogs
files:
- /etc/awslogs/awslogs.conf
- /etc/awslogs/awscli.conf
ssh-access:
files:
/opt/authorized_keys_command.sh:
content: |
#!/bin/bash -e
if [ -z "$1" ]; then
exit 1
fi
UnsaveUserName="$1"
UnsaveUserName=${UnsaveUserName//".plus."/"+"}
UnsaveUserName=${UnsaveUserName//".equal."/"="}
UnsaveUserName=${UnsaveUserName//".comma."/","}
UnsaveUserName=${UnsaveUserName//".at."/"@"}
aws iam list-ssh-public-keys --user-name "$UnsaveUserName" --query "SSHPublicKeys[?Status == 'Active'].[SSHPublicKeyId]" --output text | while read -r KeyId; do
aws iam get-ssh-public-key --user-name "$UnsaveUserName" --ssh-public-key-id "$KeyId" --encoding SSH --query "SSHPublicKey.SSHPublicKeyBody" --output text
done
mode: '000755'
owner: root
group: root
/opt/import_users.sh:
content: |
#!/bin/bash -e
aws iam list-users --query "Users[].[UserName]" --output text | while read User; do
SaveUserName="$User"
SaveUserName=${SaveUserName//"+"/".plus."}
SaveUserName=${SaveUserName//"="/".equal."}
SaveUserName=${SaveUserName//","/".comma."}
SaveUserName=${SaveUserName//"@"/".at."}
if [ "${#SaveUserName}" -le "32" ]; then
if ! id -u "$SaveUserName" >/dev/null 2>&1; then
# sudo will read each file in /etc/sudoers.d, skipping file names that end in ‘~’ or contain a ‘.’ character to avoid causing problems with package manager or editor temporary/backup files.
SaveUserFileName=$(echo "$SaveUserName" | tr "." " ")
/usr/sbin/useradd "$SaveUserName"
echo "$SaveUserName ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$SaveUserFileName"
# Add the user to the docker group
usermod -aG docker "$SaveUserName"
fi
else
echo "Can not import IAM user ${SaveUserName}. User name is longer than 32 characters."
fi
done
mode: '000755'
owner: root
group: root
/etc/cron.d/import_users:
content: |
*/10 * * * * root /opt/import_users.sh
mode: '000644'
owner: root
group: root
commands:
a_configure_sshd_command:
command: 'sed -i "s:#AuthorizedKeysCommand none:AuthorizedKeysCommand /opt/authorized_keys_command.sh:g" /etc/ssh/sshd_config'
b_configure_sshd_commanduser:
command: 'sed -i "s:#AuthorizedKeysCommandUser nobody:AuthorizedKeysCommandUser nobody:g" /etc/ssh/sshd_config'
c_import_users:
command: ./import_users.sh
cwd: /opt
services:
sysvinit:
sshd:
enabled: true
ensureRunning: true
commands:
- a_configure_sshd_command
- b_configure_sshd_commanduser
config:
packages:
yum:
docker: []
files:
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
interval=1
mode: '000400'
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.VirtualMachine.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init --verbose --stack=${AWS::StackName} --region=${AWS::Region} --resource=TestMachine
runas=root
/usr/lib/systemd/system/nginx_proxy.service:
content: |
[Unit]
Description=nginx_proxy
Requires=docker.service
After=docker.service
[Service]
ExecStart=/usr/bin/docker start -a nginx_proxy
ExecStop=/usr/bin/docker stop -t 2 nginx_proxy
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
/opt/install_nginx_proxy.sh:
content: |
#!/bin/bash -e
systemctl start docker
docker pull jwilder/nginx-proxy
docker create -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock --name nginx_proxy -t jwilder/nginx-proxy
mode: '000755'
owner: root
group: root
commands:
a_install_nginx_proxy:
command: ./install_nginx_proxy.sh
cwd: /opt
services:
sysvinit:
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
amazon-ssm-agent:
enabled: !If [HasSystemsManagerAccess, true, false]
ensureRunning: !If [HasSystemsManagerAccess, true, false]
docker:
enabled: true
ensureRunning: true
packages:
yum:
- docker
nginx_proxy:
enabled: true
ensureRunning: true
commands:
- a_install_nginx_proxy
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref SecurityGroup
KeyName: !If [HasKeyName, !Ref KeyName, !Ref 'AWS::NoValue']
SubnetId:
'Fn::ImportValue': !Sub '${ParentVPCStack}-SubnetAPublic'
UserData:
'Fn::Base64': !Sub |
#!/bin/bash -ex
trap '/opt/aws/bin/cfn-signal -e 1 --region ${AWS::Region} --stack ${AWS::StackName} --resource TestMachine' ERR
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource TestMachine --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e 0 --region ${AWS::Region} --stack ${AWS::StackName} --resource TestMachine
Tags:
- Key: Name
Value: !Ref Name
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT10M
Outputs:
StackName:
Description: Stack name.
Value: !Sub '${AWS::StackName}'
InstanceId:
Description: The EC2 instance id.
Value: !Ref TestMachine
Export:
Name: !Sub '${AWS::StackName}-InstanceId'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment