Using Lambda to Disable IAM Users


Travers Annan

6 minute read

Travers Annan

6 minute read

Problem

Picture this. You work as a cloud IT professional at a medium size company and you recently hired some AWS contractors. They do a good job, and you get permission to hire more in the future. So you do, and this goes on for some time. However, you get tired of manually removing old contractor users from your IAM groups. What’s an IT pro to do?

Solution

A good way to solve this problem would be to use a scheduled Lambda function and good tagging practices to identify old users and remove their permissions. If you tag any new contractors as role: contractor, you can use a script to identify and remove them from groups automatically using this boto3 method. We will be using the serverless framework to handle the infrastructure deployment for this lambda function. Let’s get into it.

You will need three files for this solution to work:

  1. A serverless.yml file to configure the serverless deployment.
  2. A YAML file to define your Lambda function.
  3. A Python file that contains your actual script.

Your file structure should look something like this:

[IMAGE]

Let’s start by walking through the serverless.yml file, here’s the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# The service name.
service: contractorCheck
# This is where you choose the runtime and memory size.
provider:
  name: aws
  runtime: python3.7
  # Replace the text in < > with your own information.
  region: ${opt:region, '<default region>'}
  stage: ${opt:stage,'<default ci script stage>'}
  owner: ${opt:owner,'<default owner>'}
  ownerEmail: ${opt:owner,'<default owner email>'}
  app: ${opt:app,'contractorCheck'}
# This script won't need very much memory to run, so set it to the minimum.
  memorySize: 128
# Iam role statements here.
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - iam:ListUsers
        - iam:ListUserTags
        - iam:ListGroupsForUser
        - iam:RemoveUserFromGroup
      Resource:
        - '*'
# Point to the path where the YAML for your lambda function lives.
functions:
  - ${file(functions/contractorCheck.yml)}
 

This file allows you to name and configure your serverless application. The provider section specifies the runtime environment, metadata, memory size, and IAM permissions that are passed to your Lambda function. The function section points to the path where your Lambda function lives, i.e. the contractorCheck.yml file in the functions folder.

contractorCheck.yml code:

1
2
3
4
5
contractorCheck:
  description: Schedule ContractorCheck API calls in CloudWatch
  handler: src/contractorCheck.lambda_handler
  events:
    - schedule: rate(1 day)

This file points to the lambda_handler method in our python file and sets up a scheduled cloudwatch event that triggers our function once per day (via the handler).

And now for the actual script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import boto3
import datetime

def main():
    # Start client.
    client = boto3.client('iam')
    # Run user paginator.
    userPages = getUsers(client)
    # Get list of users.
    users = listUsers(userPages)
    # Get dict of user creation dates format --> username: createdate.
    createDates = getDates(userPages)
    # Make list of contractors.
    contractors = getContractors(users, client)
    # Remove contractors older than 45 days from any group they're part of.
    checkContractors(contractors, createDates, client)


def getUsers(client):
    # Get a dict of user objects through a paginator.
    paginator = client.get_paginator('list_users')
    pages = paginator.paginate()
    return pages


def listUsers(pages):
    # For each user in each page returned by the paginator, append their username to a list and return it.
    users = []
    for page in pages:
        for user in page['Users']:
            users.append(user['UserName'])
    return users


def getDates(pages):
    # For each user in returned pages, add their creation dates to a dict: {username: createDate} and return it.
    dates = {}
    for page in pages:
        for user in page['Users']:
            dates.update({user['UserName']: user['CreateDate']})
    return dates


def getContractors(users, client):
    # For each user, check if they are tagged as role:contractor and populate a list of contractors.
    contractors = []
    userTags = {}
    for u in users:
        userTagDict = client.list_user_tags(UserName=u)
        for tag in userTagDict['Tags']:
            if tag['Key'] == 'role' and tag['Value'] == 'contractor':
                contractors.append(u)
    return contractors


def checkContractors(contractors, createDates, client):
    # For each contractor, check how long their account has existed,
    # if that number is greater than 45 days remove them from all groups.
    # get current time and timezone.
    tz = datetime.datetime.now().astimezone().tzinfo
    currentTime = datetime.datetime.now(tz)
    for c in contractors:
        timeDifference = currentTime - createDates[c]
        if timeDifference.days > 45:
            groupList = client.list_groups_for_user(UserName=c)
            for group in groupList['Groups']:
                 try:
                    response = client.remove_user_from_group(GroupName=group['GroupName'], UserName=c)
                    print('Contractor {} removed from group {}'.format(c, group['GroupName']))
                except:
                    print('Contractor {} could not be removed from group {}'.format(c, group['GroupName']))
    return

def lambda_handler(event,context):
    print(event)
    # Run main function.
    main()

There’s a lot going on here, so let’s break it down by method:

  • main()_: Here we create an iam client object that we pass to our methods and we control the program flow.

  • getUsers(client): This method creates a ‘list_users’ paginator, which returns a paginated dictionary containing information about each user in the account.

  • listUsers(pages): This method accepts the pagination dictionary as an argument, then parses out and returns a list of user names.

  • getDates(pages): This also accepts the pagination dictionary as an argument, parsing out and returning a dictionary of usernames and user creation dates.

  • getContractors(users, client): This method accepts the list of users returned by listUsers() and checks each user in the list for the tags ‘role: contractor’. If a user has those tags, their username is appended to a list of contractors, which is returned.

  • checkContractors(contractors, createDates, client): This method accepts the list of contractors and the dictionary of creation dates as arguments, compares the creation date of each user in the list of contractors to the current date, then removes them from any groups they are members of, if they are over 45 days old.

  • lambda_handler(event, client): This function allows CloudWatch to trigger your Lambda function. It prints the event information it receives and calls the main() method.

Summary We have created a Lambda function that checks each user in an account and removes them from all groups if the following conditions are true:

  1. They have the tag role:contractor.
  2. They are older than 45 days.

This Lambda function was deployed using the Serverless Framework, simplifying the deployment process. We now have a Lambda function that checks our users for old contractors once per day, and automatically disables contractors older than 45 days.

This particular script may not save you more than 30 minutes over the course of a month, but once this principle is applied to other routine tasks that number will continue to grow like an automation snowball.


Orbit

Like what you read? Why not subscribe to the weekly Orbit newsletter and get content before everyone else?