helloV
helloV

Reputation: 52375

Mandatory tagging when launching EC2 instance

In AWS, is there a way to force an IAM user to tag the instance he/she is about to launch? It doesn't matter what the value is. I want to make sure it is correctly tagged so that long running instances can be properly identified and the owner notified. Currently tagging is optional.

What I do currently is to use CloudTrail and identify the instances with their IAM users. I do not like it because it is an extra work to run the script periodically and CloudTrail has only 7 days worth of data. It would be nice if AWS has an instance attribute for owner.

Using keypairs to identify the owners is not a viable solution in our case. Anyone faced this problem before and how did you tackle it?

Upvotes: 4

Views: 4190

Answers (7)

Harish KM
Harish KM

Reputation: 1353

Attach this policy to the user or group to prevent them from launching an instance without tagging it:

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "ec2:RunInstances",
        "Resource": "*",
        "Condition": {
            "Null": {
                "aws:RequestTag/Owner": "true"
            }
        }
    }
}

When the user tries to launch an instance, they'll get an error:

error message

(If anyone knows a way to display a cleaner error message, please let us know in the comments.)

Decode the error like so:

aws sts decode-authorization-message \
  --encoded-message <encoded-message> \
  --query DecodedMessage --output text | jq '.'

Part of the (giant) response is as follows:

{
  "allowed": false,
  "explicitDeny": true,
  "matchedStatements": {
    "items": [
      {
        "statementId": "",
        "effect": "DENY",
        "principals": {
          "items": [
            {
              "value": "AIDATDOMLI3YFAYEBFGSO"
            }
          ]
        },
        "principalGroups": {
          "items": []
        },
        "actions": {
          "items": [
            {
              "value": "ec2:RunInstances"
            }
          ]
        },
        "resources": {
          "items": [
            {
              "value": "*"
            }
          ]
        },
        "conditions": {
          "items": [
            {
              "key": "aws:RequestTag/Owner",
              "values": {
                "items": [
                  {
                    "value": "true"
                  }
                ]
              }
            }
          ]
        }
      }
    ]
  }
}

It shows that the launch failed because the Owner tag is missing.

Upvotes: 0

Subrata Fouzdar
Subrata Fouzdar

Reputation: 733

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GrantIAMPassRoleOnlyForEC2",
            "Action": [
                "iam:PassRole"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:iam::*:role/ec2tagrestricted",
                "arn:aws:iam::*:role/ec2tagrestricted"
            ],
            "Condition": {
                "StringEquals": {
                    "iam:PassedToService": "ec2.amazonaws.com"
                }
            }
        },
        {
            "Sid": "ReadOnlyEC2WithNonResource",
            "Action": [
                "ec2:Describe*",
                "iam:ListInstanceProfiles"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "ModifyingEC2WithNonResource",
            "Action": [
                "ec2:CreateKeyPair",
                "ec2:CreateSecurityGroup"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "RunInstancesWithTagRestrictions",
            "Effect": "Allow",
            "Action": "ec2:RunInstances",
            "Resource": [
                "arn:aws:ec2:us-east-1:*:instance/*",
                "arn:aws:ec2:us-east-1:*:volume/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:RequestTag/test": "${aws:userid}"
                }
            }
        },
        {
            "Sid": "RemainingRunInstancePermissionsNonResource",
            "Effect": "Allow",
            "Action": "ec2:RunInstances",
            "Resource": [
                "arn:aws:ec2:us-east-1::image/*",
                "arn:aws:ec2:us-east-1::snapshot/*",
                "arn:aws:ec2:us-east-1:*:network-interface/*",
                "arn:aws:ec2:us-east-1:*:key-pair/*",
                "arn:aws:ec2:us-east-1:*:security-group/*"
            ]
        },
        {
            "Sid": "EC2RunInstancesVpcSubnet",
            "Effect": "Allow",
            "Action": "ec2:RunInstances",
            "Resource": "arn:aws:ec2:us-east-1:*:subnet/*",
            "Condition": {
                "StringEquals": {
                    "ec2:Vpc": "arn:aws:ec2:us-east-1:*:vpc/vpc-8311b8f9"
                }
            }
        },
        {
            "Sid": "EC2VpcNonResourceSpecificActions",
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteNetworkAcl",
                "ec2:DeleteNetworkAclEntry",
                "ec2:DeleteRoute",
                "ec2:DeleteRouteTable",
                "ec2:AuthorizeSecurityGroupEgress",
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupEgress",
                "ec2:RevokeSecurityGroupIngress",
                "ec2:DeleteSecurityGroup",
                "ec2:CreateNetworkInterfacePermission",
                "ec2:CreateRoute",
                "ec2:UpdateSecurityGroupRuleDescriptionsEgress",
                "ec2:UpdateSecurityGroupRuleDescriptionsIngress"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "ec2:Vpc": "arn:aws:ec2:us-east-1:*:vpc/vpc-8311b8f9"
                }
            }
        },
        {
            "Sid": "AllowInstanceActionsTagBased",
            "Effect": "Allow",
            "Action": [
                "ec2:RebootInstances",
                "ec2:StopInstances",
                "ec2:TerminateInstances",
                "ec2:StartInstances",
                "ec2:AttachVolume",
                "ec2:DetachVolume",
                "ec2:AssociateIamInstanceProfile",
                "ec2:DisassociateIamInstanceProfile",
                "ec2:GetConsoleScreenshot",
                "ec2:ReplaceIamInstanceProfileAssociation"
            ],
            "Resource": [
                "arn:aws:ec2:us-east-1:347612567792:instance/*",
                "arn:aws:ec2:us-east-1:347612567792:volume/*"
            ],
            "Condition": {
                "StringEquals": {
                    "ec2:ResourceTag/test": "${aws:userid}"
                }
            }
        },
        {
            "Sid": "AllowCreateTagsOnlyLaunching",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": [
                "arn:aws:ec2:us-east-1:347612567792:instance/*",
                "arn:aws:ec2:us-east-1:347612567792:volume/*"
            ],
            "Condition": {
                "StringEquals": {
                    "ec2:CreateAction": "RunInstances"
                }
            }
        }
    ]
}

This policy restricts a user to Launch an ec2 instance only if the Tag key is test and value is the variable ${aws.userid} different values can be found here Notable things

  • This does not restrict the number of ec2 instances a user can launch
  • User can change the tag of existing instances tag and gain control

We can use TagKeys https://docs.aws.amazon.com/IAM/latest/UserGuide/access_tags.html#access_tags_control-tag-keys to tackle the above two situations but I did not do it

Upvotes: 0

tleyden
tleyden

Reputation: 1980

Check out the capitalone.io/cloud-custodian open source project -- it has the ability to enforce policies like this

Upvotes: 0

JHowIX
JHowIX

Reputation: 1803

As @helloV mentions, this is possible by using AWS CloudTrail logs (once properly enabled) and AWS Lambda. I was able to accomplish this with the following code running in a python Lambda function:

s3 = boto3.client('s3')
ec2 = boto3.client(service_name='ec2', aws_access_key_id=aws_key, aws_secret_access_key=aws_secret_key)

def lambda_handler(event, context):

    # Get the object from the event and show its content type
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf8')

    try:
        response = s3.get_object(Bucket=bucket, Key=key)
        compressed_file = StringIO.StringIO()
        compressed_file.write(response['Body'].read())
        compressed_file.seek(0)
        decompressed_file = gzip.GzipFile(fileobj=compressed_file, mode='rb')

        successful_tags = 0;
        json_data = json.load(decompressed_file)
        for record in json_data['Records']:
            if record['eventName'] == 'RunInstances':
                instance_user = record['userIdentity']['userName']
                instances_set = record['responseElements']['instancesSet']
                for instance in instances_set['items']:
                    instance_id = instance['instanceId']
                    ec2.create_tags(Resources=[instance_id], Tags=[{'Key':'Owner', 'Value':instance_user}])
                    successful_tags += 1
        return 'Tagged ' + str(successful_tags) + ' instances successfully'
    except Exception as e:
        print(e)
        print('Error tagging object {} from bucket {}'.format(key, bucket))
        raise e

Upvotes: 0

Rodrigo Murillo
Rodrigo Murillo

Reputation: 13632

Do you use/require userdata scripts at launch time? We use that script process to properly tag each instance as it is launched.

We burn a support script into the AMI that is launched by the userdata, and parses the command line for parameters. These parameters are then used to create tags for the newly launched instances.

For manual launches, the user must load the correct userdata script for this to work. But from automated launching script, or from a properly configured Launch Configuration in an Auto-scaling Group, it works perfectly.

<script>
PowerShell -ExecutionPolicy Bypass -NoProfile -File c:\tools\server_userdata.ps1  -function Admin -environment production
</script>

Using this method, an instance launched with that userdata will be automatically tagged with the Function and Environment tags.

Upvotes: -1

helloV
helloV

Reputation: 52375

I resolved this by using AWS Lambda. When CloudTrail creates an object in S3, it triggers an event that cause a Lambda function to execute. The Lambda function then parses the S3 object and creates the tag. There is a lag of ~2 mins but the solution works perfectly.

Upvotes: 2

BraveNewCurrency
BraveNewCurrency

Reputation: 13065

One way: Don't give them IAM permissions to launch boxes. Instead, have a web service that allows them to do it. (Production should be fully automated anyway). When they use your service, you can enforce all the rules you want. Yes, it's quite a bit of work, so not for everybody.

Currently tagging is optional.

It's worse than that. Tagging requires a 2nd API call, so even when using the API, things can launch without tags because of a hiccup.

Upvotes: 4

Related Questions