Skip to content

Instantly share code, notes, and snippets.

@coordt
Last active April 12, 2018 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coordt/8c22595fdf9e2a909d9ad8b0aeca86e4 to your computer and use it in GitHub Desktop.
Save coordt/8c22595fdf9e2a909d9ad8b0aeca86e4 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
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: 'test'
Mappings:
RegionMap:
ap-south-1:
AMI: ami-531a4c3c
eu-west-3:
AMI: ami-8ee056f3
eu-west-2:
AMI: ami-403e2524
eu-west-1:
AMI: ami-d834aba1
ap-northeast-2:
AMI: ami-863090e8
ap-northeast-1:
AMI: ami-ceafcba8
sa-east-1:
AMI: ami-84175ae8
ca-central-1:
AMI: ami-a954d1cd
ap-southeast-1:
AMI: ami-68097514
ap-southeast-2:
AMI: ami-942dd1f6
eu-central-1:
AMI: ami-5652ce39
us-east-1:
AMI: ami-97785bed
us-east-2:
AMI: ami-f63b1193
us-west-1:
AMI: ami-824c4ee2
us-west-2:
AMI: ami-f2d3638a
Conditions:
HasKeyName: !Not [!Equals [!Ref KeyName, '']]
HasIAMUserSSHAccess: !Equals [!Ref IAMUserSSHAccess, 'true']
HasSSHBastionSecurityGroup: !Not [!Equals [!Ref ParentSSHBastionStack, '']]
HasNotSSHBastionSecurityGroup: !Equals [!Ref ParentSSHBastionStack, '']
Resources:
SecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: !Ref Name
VpcId:
'Fn::ImportValue': !Sub '${ParentVPCStack}-VPC'
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: /
Policies:
- PolicyName: testlogs
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'logs:DescribeLogStreams'
Resource:
- 'arn:aws:logs:*:*:*'
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, [awslogs, ssh-access, config], [awslogs, config]]
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 |
[general]
state_file = /var/lib/awslogs/agent-state
[/var/log/messages]
datetime_format = %b %d %H:%M:%S
file = /var/log/messages
log_stream_name = {instance_id}/var/log/messages
log_group_name = ${Logs}
[/var/log/secure]
datetime_format = %b %d %H:%M:%S
file = /var/log/secure
log_stream_name = {instance_id}/var/log/secure
log_group_name = ${Logs}
[/var/log/cron]
datetime_format = %b %d %H:%M:%S
file = /var/log/cron
log_stream_name = {instance_id}/var/log/cron
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/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-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/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/dmesg]
file = /var/log/dmesg
log_stream_name = {instance_id}/var/log/dmesg
log_group_name = ${Logs}
mode: '000644'
owner: root
group: root
services:
sysvinit:
awslogs:
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"
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:
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=VirtualMachine
runas=root
services:
sysvinit:
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
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
yum update -y
yum install -y docker
usermod -aG docker ec2-user
/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
Logs:
Type: 'AWS::Logs::LogGroup'
Properties:
RetentionInDays: 14
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