Create IAM policies using AWS Lambda

In this article let’s look into how we can Create IAM policies using AWS Lambda and attach it to an existing IAM role during deployment using terraform.

Imagine a scenario where you are working in a multi AWS account environment and the lambda in the main account needs permissions on other accounts ( for assuming role) to execute the tasks.

In this scenario the Lambda service role need permission on other accounts. but we don’t want to pre-configure the cross account role ARN’s hard coding in the policies. the reason being the number of accounts in your organization will only grow in the future and every-time there is a new account and if your lambda needs to execute the code in this new account, you will be updating the IAM role again and again( manually changing policy code and redeploying) and it will be a maintenance nightmare.

Refer to this AWS documentation to understand how lambda cross account permissions work

Creating IAM policy using Lambda and DynamoDB is one way to handle this scenario.

Refer to this git repo for the complete python create IAM policy lambda example code.
Terraform commands to run the project are provided in the git repo README file.

Step #1

Create a DynamoDB table ( this step is not included in the repo) called ACCOUNT-TAGS in the main account (shared services account) to capture all the account numbers in the organization and relevant account tags as shown below.
A consistent tagging is very important in many ways in the cloud computing and this is one of the examples showing how it can really benefit us.

accountId      accountTag   otherColumns
123456701      DEV-ACCT      
098765432      QA-ACCT
989798766      PRD-ACCT

In the next step we see how this table is going to be useful in building IAM policies.

Our final goal is to add a policy like shown below to the lambda execution role dynamically ( deploy time)

	"Effect": "Allow",
	"Action": "sts:AssumeRole",
	"Resource": [

Step #2

Configure your S3 back end state files locations for each environment in the terraform back end files. – refer to the git repo backends folder as shown below

bucket = "qa-terraform-state-bkt"
key    = "lambdas/iam-policy-lambda.tfstate"
region = "us-east-1"

Step #3

Understand what AWS resources this repo is creating . the value of “ENV” depends on your workspace, it depends on environment variable which is based on your workspace environment , refer to this article to understand the terraform workspaces . possible values are “SBX”, “DEV”, “QA” and “PRD”

  • ENV–OTHER-LAMBDA-ROLE : This is a dummy role we are creating which could be a lambda execution role for an imaginary lambda “guardduty_to_s3“. this role has S3 read only permissions and CW log permissions to begin with and later we will add Cross Account permissions to this role via our python Lambda
  • ENV-IAM-CREATE-POLICY-LAMBDA : This is the lambda which does the job, it scans the DynamoDB table ACCOUNT-TAGS and builds the cross account permissions at deploy time and performs the following tasks int the order
    • Build the cross account permissions policy document
    • Detach the policy if already exists & attached to the OTHER-LAMBDA-ROLE
    • Delete the policy
    • Create new policy with same name
    • finally attach the policy to the OTHER-LAMBDA-ROLE
  • Execute the lambda ( call lambda ) using terraform aws_lambda_invocation resource.

Step #4

  • Login to AWS console and verify if the Role OTHER-LAMBDA-ROLE has a new policy named “ENV-CROSS-ACCT-LAMBDA-POLICY” is attached to it.

OTHER-LAMBDA-ROLE before and after executing the lambda

This role was initially created with just one policy – “OTHER-LAMBDA-POLICY”.

Create IAM policies using AWS Lambda

and then a new policy called “CROSS-ACCT-LAMBDA-POLICY” is attached using our python Lambda and DynamoDB table.

Create IAM policy lambda example

Create IAM policies using AWS Lambda Python example code explanation

Read all the variables from environment variables as opposed to hard coding them in the lambda code.

#read important environment variables. - configured through terraform
dynamodb        = boto3.resource("dynamodb")
dynamo_table    = os.environ['MOSAIC_ACCOUNTS_TABLE']
role_name       = os.environ['IAM_ROLE_NAME']
policy_name     = os.environ['POLICY_NAME']
policy_arn      = os.environ['POLICY_ARN']

Read (scan) DynamoDB table and build IAM policy document

table = dynamodb.Table(dynamo_table)    
    response = table.scan()['Items']
    # this string variable will be added to the policyStr which is a policy Document.
    role_arns = "" 
    for item in response:
        accountID = item["accountId"]
        iam_arn = "arn:aws:iam::"+accountID+":role/"+item["accountTag"]+"-CROSS-ACCOUNT-LAMBDA-ROLE"
        print (iam_arn)
        role_arns += "\""+iam_arn+"\","

Create a IAM policy string in JSON format with proper escape characters.

 policyStr = "{\r\n    \"Version\": \"2012-10-17\",\r\n    \"Statement\": [\r\n        {\r\n            \"Effect\": \"Allow\",\r\n            \"Action\": \"sts:AssumeRole\",\r\n            \"Resource\": [\r\n  "+role_arns+" \r\n            ]\r\n        }\r\n    ]\r\n}"

Get handle to the IAM role ( an existing role – OTHER-LAMBDA-ROLE)

iam = boto3.resource('iam')
role = iam.Role(role_name)

Detach and Delete the policy if it already exist, This is required as we can’t update an existing policy and hence this is the only way to update an existing Role’s permissions.

#detach policy if already exist
        role_res = role.detach_policy(PolicyArn=policy_arn)
    except Exception as e:
        print("An exception occurred detaching policy ", e)
    #delete the policy if already exists
    except Exception as e:
        print("An exception occurred deleting policy ", e)

Create IAM policy using the policy document (a JSON string) created above

#Create new Policy
    response = iam_client.create_policy(
        Description="Test Policy Creation from Lambda function"
#attach the policy to Lambda role
role_res = role.attach_policy(PolicyArn=policy_arn)

Finally execute the lambda ( once ) immediately after creating using Terraform – essentially we are doing all this in a single terrform apply command.
We have added depends_on argument for the invocation resource because we want to make sure the lambda creation is complete before we invoke it.

#Invoke lambda function
data "aws_lambda_invocation" "invoke_lambda" {
  function_name = local.iam_policy_lambda_name
  depends_on    =[aws_lambda_function.create-iam-policy-lambda]
  input         = <<JSON

This Lambda can be invoked as needed basis and it updates the CROSS account permissions as per the data from the DynamoDB table.

External references


Leave a Reply

Your email address will not be published. Required fields are marked *