2017-11-29
For simple AWS SDK python and nodejs scripts you can easily pull in a lambda script into a cloudformation template with a jinja2 lookup statement. Below is part of our account setup mentioned previously.
In order to disable access keys that has not been used recently, we scan for it every morning and if we find one, disable it.
The lambda function is very simple, and pretty much just:
import datetime
import boto3
ZERO = datetime.timedelta(0)
class UTC(datetime.tzinfo):
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
utc = UTC()
def handler(event, context):
client = boto3.client('iam')
userList = client.list_users()
foo = []
for user in userList['Users']:
groupList = client.list_groups_for_user(UserName=user['UserName'])
isSUG = False
for group in groupList['Groups']:
if group['GroupName'] == 'ServiceUserGroup':
print ('sug user...',user['UserName'])
isSUG = True
if not isSUG:
keyList = client.list_access_keys(UserName=user['UserName'])
for key in keyList['AccessKeyMetadata']:
print (user['UserName'])
if key['Status'] == 'Active':
status = client.get_access_key_last_used( AccessKeyId=key['AccessKeyId'] )
print (status['UserName'],status['AccessKeyLastUsed'])
disableKey = True
if 'LastUsedDate' in status['AccessKeyLastUsed']:
now = datetime.datetime.now(utc)
diff = now - status['AccessKeyLastUsed']['LastUsedDate']
if diff.total_seconds() > 30*60:
print ('going to disable', status['UserName'],key['AccessKeyId'], diff.total_seconds())
else:
print ('key in use', status['UserName'],key['AccessKeyId'], diff.total_seconds())
disableKey = False
if disableKey:
client.update_access_key(UserName=user['UserName'], AccessKeyId=key['AccessKeyId'], Status='Inactive')
print('disabled....')
In our aws_account_config
variable for each account we specify a boolean that, if true, spits out the following yaml
###disable access tokens
{% if aws_account_config[acc].auto_disable_keys %}
disableKeysRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: 'lambda.amazonaws.com'
Action:
- 'sts:AssumeRole'
Path: '/'
Policies:
- PolicyName: logs
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
- PolicyName: iam
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'iam:ListUsers'
- 'iam:GetAccessKeyLastUsed'
- 'iam:ListAccessKeys'
- 'iam:ListGroupsForUser'
- 'iam:UpdateAccessKey'
Resource: '*'
disableKeysLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: |
{{lookup('file','roles/setup_account/files/disableKeys.py')|indent(width=12,indentfirst=False)}}
Handler: 'index.handler'
MemorySize: 128
Role: !GetAtt 'disableKeysRole.Arn'
Runtime: 'python3.6'
Timeout: 60
disableKeysEventsRule:
Type: 'AWS::Events::Rule'
DependsOn: disableKeysLambdaFunction
Properties:
Name: disableKeysEvents
ScheduleExpression: cron(0 2 * * ? *)
State: ENABLED
Targets:
- { Arn: !GetAtt 'disableKeysLambdaFunction.Arn', Id: disableKeys }
permissionForEventsToInvokeLambda:
Type: "AWS::Lambda::Permission"
Properties:
FunctionName: !Ref "disableKeysLambdaFunction"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn: !GetAtt 'disableKeysEventsRule.Arn'
{% endif %}
The template consists of a Lambda execution role, the Lambda itself, the Events Rule, and its Lambda Permission to trigger the Lambda.
In order to pull in the function into the template we use ansible’s lookup function and format it so it fits in properly with the template, with this line
{{lookup('file','roles/setup_account/files/disableKeys.py')|indent(width=12,indentfirst=False)}}
This way you can keep your code and your cloudformation seperate (so you can test it seperately from the deployment) and it allows you to skip the ‘zip and copy to s3’ steps during deployment.