Skip to content

Instantly share code, notes, and snippets.

@noda-sin
Created February 11, 2016 07:20
Show Gist options
  • Save noda-sin/025060a4f9a071444477 to your computer and use it in GitHub Desktop.
Save noda-sin/025060a4f9a071444477 to your computer and use it in GitHub Desktop.
bastion with managing iam
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description" : "Bastion Instance",
"Parameters" : {
"KeyName" : {
"Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type" : "AWS::EC2::KeyPair::KeyName",
"ConstraintDescription" : "can contain only alphanumeric characters, spaces, dashes and underscores."
},
"InstanceType" : {
"Description" : "Instance type for node.",
"Type" : "String",
"Default" : "t2.nano",
"AllowedValues" : [ "t2.nano", "t2.micro","t2.small","t2.medium","m3.medium","m3.large","m3.xlarge","m3.2xlarge","c3.large","c3.xlarge","c3.2xlarge","c3.4xlarge","c3.8xlarge" ],
"ConstraintDescription" : "must be a valid T2, M3 or C3 instance type."
},
"Subnets" : {
"Description" : "ID of your existing subnet for launching Bastion",
"Type" : "List<AWS::EC2::Subnet::Id>"
},
"SSHSecurityGroup" : {
"Description" : "SecurityGroup for accessing with SSH",
"Type" : "AWS::EC2::SecurityGroup::Id"
},
"ElasticIpId": {
"Description" : "ID of ElasticIp for launching Bastion",
"Type" : "String",
"AllowedPattern" : "eipalloc-[a-z0-9]{8}",
"ConstraintDescription" : "Input format is eipalloc-xxxxxxxx"
},
"Recurrence" : {
"Type" : "String",
"Description" : "schedule for updating keys. crontab style syntax. ex) 0 0 * * *",
"AllowedPattern" : "(((([*])|(((([0-5])?[0-9])((-(([0-5])?[0-9])))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))(,(((([*])|(((([0-5])?[0-9])((-(([0-5])?[0-9])))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?)))* (((([*])|(((((([0-1])?[0-9]))|(([2][0-3])))((-(((([0-1])?[0-9]))|(([2][0-3])))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))(,(((([*])|(((((([0-1])?[0-9]))|(([2][0-3])))((-(((([0-1])?[0-9]))|(([2][0-3])))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?)))* (((((((([*])|(((((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))((-(((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))|(L)|(((((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))W))))(,(((((([*])|(((((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))((-(((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))|(L)|(((((([1-2])?[0-9]))|(([3][0-1]))|(([1-9])))W)))))*)|([?])) (((([*])|((((([1-9]))|(([1][0-2])))((-((([1-9]))|(([1][0-2])))))?))|((((JAN)|(FEB)|(MAR)|(APR)|(MAY)|(JUN)|(JUL)|(AUG)|(SEP)|(OKT)|(NOV)|(DEC))((-((JAN)|(FEB)|(MAR)|(APR)|(MAY)|(JUN)|(JUL)|(AUG)|(SEP)|(OKT)|(NOV)|(DEC))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))(,(((([*])|((((([1-9]))|(([1][0-2])))((-((([1-9]))|(([1][0-2])))))?))|((((JAN)|(FEB)|(MAR)|(APR)|(MAY)|(JUN)|(JUL)|(AUG)|(SEP)|(OKT)|(NOV)|(DEC))((-((JAN)|(FEB)|(MAR)|(APR)|(MAY)|(JUN)|(JUL)|(AUG)|(SEP)|(OKT)|(NOV)|(DEC))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?)))* (((((((([*])|((([0-6])((-([0-6])))?))|((((SUN)|(MON)|(TUE)|(WED)|(THU)|(FRI)|(SAT))((-((SUN)|(MON)|(TUE)|(WED)|(THU)|(FRI)|(SAT))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))|((([0-6])L))|(W)|(([#][1-5]))))(,(((((([*])|((([0-6])((-([0-6])))?))|((((SUN)|(MON)|(TUE)|(WED)|(THU)|(FRI)|(SAT))((-((SUN)|(MON)|(TUE)|(WED)|(THU)|(FRI)|(SAT))))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))|((([0-6])L))|(W)|(([#][1-5])))))*)|([?]))((( (((([*])|((([1-2][0-9][0-9][0-9])((-([1-2][0-9][0-9][0-9])))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?))(,(((([*])|((([1-2][0-9][0-9][0-9])((-([1-2][0-9][0-9][0-9])))?)))((/(([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?([0-9])?[0-9])))?)))*))?)",
"Default" : "*/10 * * * *"
}
},
"Mappings": {
"AWSBastionAMI" : {
"ap-northeast-1" : { "AMI" : "ami-383c1956" }
}
},
"Resources" : {
"BastionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [ {
"Effect": "Allow",
"Principal": {
"Service": [ "ec2.amazonaws.com" ]
},
"Action": [ "sts:AssumeRole" ]
} ]
},
"Path": "/",
"Policies": [ {
"PolicyName": "Bastion_Takeover",
"PolicyDocument": {
"Statement": [ {
"Effect": "Allow",
"Action": [
"iam:ListUsers",
"iam:ListSshPublicKeys",
"iam:GetSshPublicKey",
"ec2:AssociateAddress"
],
"Resource": "*"
} ]
}
} ]
}
},
"BastionRoleProfile": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": [ {
"Ref": "BastionRole"
} ]
}
},
"BastionLaunchConfig" : {
"Type" : "AWS::AutoScaling::LaunchConfiguration",
"Metadata" : {
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"jq" : []
}
},
"files" : {
"/etc/sshkey-update/cron.sh" : {
"content" : { "Fn::Join" : [ "\n", [
"#!/bin/bash",
"PATH=$PATH:/sbin:/bin:/usr/sbin:/usr/bin:/opt/aws/bin",
"function contains() {",
" for row in $1; do",
" if [ \"$row\" == \"$2\" ]; then",
" echo \"y\"",
" return 0",
" fi",
" done",
" echo \"n\"",
" return 1",
"}",
"# current users in host.",
"host_users=$(find /home -maxdepth 1 -type d | grep '^/home/' | sed -e 's/^\\/home\\/\\(.\\)/\\1/')",
"# create iam users",
"iam_users=$(aws iam list-users | jq -r '.Users[].UserName')",
"for iam_user in $iam_users; do",
" user_home=\"/home/$iam_user\"",
" user_ssh_dir=\"$user_home/.ssh\"",
" useradd \"$iam_user\"",
" # setup ssh directory",
" mkdir -p \"$user_ssh_dir\"",
" chown -R \"$iam_user:$iam_user\" \"$user_home\"",
" chmod -R 500 \"$user_home\"",
" # setup ssh key",
" touch \"$user_ssh_dir/authorized_keys\"",
" touch \"$user_ssh_dir/new_authorized_keys\"",
" key_ids=$(aws iam list-ssh-public-keys --user-name $iam_user | jq -r 'select(.SSHPublicKeys[].Status == \"Active\") | .SSHPublicKeys[].SSHPublicKeyId')",
" for key_id in $key_ids; do",
" aws iam get-ssh-public-key --user-name $iam_user --ssh-public-key-id $key_id --encoding SSH | jq -r '.SSHPublicKey.SSHPublicKeyBody' >> \"$user_ssh_dir/new_authorized_keys\"",
" done",
" keys_diff=$(diff \"$user_ssh_dir/authorized_keys\" \"$user_ssh_dir/new_authorized_keys\")",
" if [ \"$keys_diff\" == \"\" ]; then",
" rm -f \"$user_ssh_dir/new_authorized_keys\"",
" continue",
" fi",
" rm -f \"$user_ssh_dir/authorized_keys\"",
" mv \"$user_ssh_dir/new_authorized_keys\" \"$user_ssh_dir/authorized_keys\"",
" chown \"$iam_user:$iam_user\" \"$user_ssh_dir/authorized_keys\"",
" chmod 500 \"$user_ssh_dir/authorized_keys\"",
" ps aux | grep \"sshd: $iam_user@pts/\" | grep -v grep | awk '{ print \"kill -9\", $2 }' | sh",
"done",
"# delete iam user deleted by aws management console from host",
"for host_user in $host_users; do",
" # if ec2-user, not action",
" if [ \"$host_user\" = \"ec2-user\" ]; then",
" continue",
" fi",
" if [[ \"$(contains \"${iam_users[@]}\" \"$host_user\")\" == \"y\" ]]; then",
" continue",
" fi",
" ps aux | grep \"sshd: $host_user@pts/\" | grep -v grep | awk '{ print \"kill -9\", $2 }' | sh",
" userdel -r \"$host_user\"",
" rm -rf \"/home/$host_user\"",
" echo \"delete $host_user from host.\"",
"done"
]]},
"mode" : "0755",
"owner" : "root",
"group" : "root"
}
}
}
}
},
"Properties" : {
"InstanceType" : { "Ref" : "InstanceType" },
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Fn::FindInMap" : [ "AWSBastionAMI", { "Ref" : "AWS::Region" }, "AMI"] },
"IamInstanceProfile" : { "Ref" : "BastionRoleProfile" },
"AssociatePublicIpAddress" : "true",
"SecurityGroups" : [ { "Ref" : "SSHSecurityGroup" } ],
"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [
"#!/bin/bash\n",
"yum update -y\n",
"export AWS_DEFAULT_REGION=", { "Ref" : "AWS::Region" }, "\n",
"/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r BastionLaunchConfig --region ", { "Ref" : "AWS::Region" }, "\n",
"echo \"", { "Ref" : "Recurrence" }, " /bin/bash /etc/sshkey-update/cron.sh\" >> /tmp/$$.tmp\n",
"crontab /tmp/$$.tmp && rm -rf /tmp/$$.tmp\n",
"/bin/bash /etc/sshkey-update/cron.sh\n",
"instanceId=$(curl http://169.254.169.254/latest/meta-data/instance-id)\n",
"aws ec2 associate-address --instance-id $instanceId --allocation-id ", { "Ref" : "ElasticIpId" }, "\n"
]]}
}
}
},
"BastionAutoScalingGroup" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"LaunchConfigurationName" : { "Ref" : "BastionLaunchConfig" },
"MaxSize" : "1",
"MinSize" : "1",
"VPCZoneIdentifier" : { "Ref" : "Subnets" },
"Tags" : [
{ "Key" : "Name", "Value" : "bastion", "PropagateAtLaunch" : "true" }
]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment