Implementing IAM Roles with EC2 Instances

Security is one of the areas that I believe companies could easily improve with better practices - one example of this is granting the correct permissions to EC2 instances through IAM server roles.

Here’s an example of how to create an IAM role using CloudFormation and apply it using Auto Scaling groups and Launch Configuration. Specifically this example demonstrates how to grant access to an S3 bucket.

{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Description" : "An example template using IAM server roles.",
    "Parameters" : {
        ...
    },
    "Conditions" : {
        "IsUsWest2" : {"Fn::Equals" : \[{ "Ref" : "AWS::Region" }, "us-west-2"\]}
    },
    "Mappings" : {
        ...
    },
    "Resources" : {
        "InstanceRole":{
            "Type":"AWS::IAM::Role",
            "Properties":{
                "AssumeRolePolicyDocument":{
                    "Statement":\[
                        {
                            "Effect":"Allow",
                            "Principal":{
                                "Service":\[
                                    "ec2.amazonaws.com"
                                \]
                            },
                            "Action":\[
                                "sts:AssumeRole"
                            \]
                        }
                    \]
                },
                "Path":"/"
            }
        },
        "RolePolicy1":{
            "Type":"AWS::IAM::Policy",
            "Properties":{
                "PolicyName":"S3Download",
                "PolicyDocument":{
                    "Version": "2012-10-17",
                    "Statement":\[
                        {
                            "Action":\[
                                "s3:ListBucket"
                            \],
                            "Effect":"Allow",
                            "Resource":\[
                                {"Fn::Join" : \[ "", \[ "arn:aws:s3:::", "examplebucket"\] \]}
                            \]
                        },
                        {
                            "Action":\[
                                "s3:GetObject"
                            \],
                            "Effect":"Allow",
                            "Resource":\[
                                {"Fn::Join" : \[ "", \[ "arn:aws:s3:::", "examplebucket", "/", "path", "/\*"\] \]}
                            \]
                        }
                    \]
                },
                "Roles":\[
                    {
                        "Ref":"InstanceRole"
                    }
                \]
            }
        },
        "InstanceProfile":{
            "Type":"AWS::IAM::InstanceProfile",
            "Properties":{
                "Path":"/",
                "Roles":\[
                    {
                        "Ref":"InstanceRole"
                    }
                \]
            }
        },
        "AutoScalingGroup" : {
            "Type" : "AWS::AutoScaling::AutoScalingGroup",
            "Properties" : {
                ...
            },
            "CreationPolicy" : {
                ...
            },
            "UpdatePolicy": {
                ...
            }
        },
        "LaunchConfig" : {
            "Type" : "AWS::AutoScaling::LaunchConfiguration",
            "Metadata" : {
                ...
            },
            "Properties" : {
                "IamInstanceProfile":{
                    "Ref":"InstanceProfile"
                },
                ...
            }
        }
    },
    "Outputs" : {
        ...
    }
}

You can take this further by having the Launch Configuration copy files from S3 directly for you, assuming you’ve assigned the correct permissions to your server role.

"LaunchConfig" : {
    "Type" : "AWS::AutoScaling::LaunchConfiguration",
    "Metadata" : {
        "AWS::CloudFormation::Authentication": {
            "S3AccessCreds": {
                "type": "S3",
                "roleName": { "Ref" : "InstanceRole"},
                "buckets" : \["examplebucket"\]
            }
        },
        "AWS::CloudFormation::Init" : {
            "configSets" : {
                "ascending" : \[ "config" \]
            },
            "config" : {
                "commands" : {
                    ...
                },
                "packages" : {
                    "yum" : {
                        ...
                    }
                },
                "sources" : {
                    "/etc/someapp" : "https://s3.amazonaws.com/examplebucket/path/someapp.zip"
                },
                "files" : {
                    "/etc/cfn/cfn-hup.conf" : {
                        "content" : { "Fn::Join" : \["", \[
                            "\[main\]\\n",
                            "stack=", { "Ref" : "AWS::StackId" }, "\\n",
                            "region=", { "Ref" : "AWS::Region" }, "\\n"
                        \]\]},
                        "mode"    : "000400",
                        "owner"   : "root",
                        "group"   : "root"
                    },
                    "/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
                        "content": { "Fn::Join" : \["", \[
                            "\[cfn-auto-reloader-hook\]\\n",
                            "triggers=post.update\\n",
                            "path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\\n",
                            "action=/opt/aws/bin/cfn-init -v ",
                            "         --stack ", { "Ref" : "AWS::StackName" },
                            "         --resource LaunchConfig ",
                            "         --region ", { "Ref" : "AWS::Region" }, "\\n",
                            "runas=root\\n"
                        \]\]}
                    }
                },
                "services" : {
                    "sysvinit" : {
                        "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true", "files" : \["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"\]}
                    }
                }
            }
        }
    }
}

While I’m using IAM in conjuction with CloudFormation so that I can use versioning and source control on my IAM roles and policies, IAM is not restricted to just being used via CloudFormation. You can create IAM roles and policies via the AWS console and CLI.

The AWS IAM policy generator provided by AWS is really handy for fleshing out the desired IAM policy.

Official AWS IAM examples are available on docs.amazon.com - IAM.

Official documentation on deploying applications with CloudFormation stacks is available on docs.aws.amazon.com - deploying applications.