AWS DevOps Blog

How to Centrally Manage AWS Config Rules across Multiple AWS Accounts

AWS Config Rules allow you to codify policies and best practices for your organization and evaluate configuration changes to AWS resources against these policies. If you manage multiple AWS accounts, you might want to centrally govern and define these policies for all of the AWS accounts in your organization. With appropriate authorization, you can create a Config rule in one account that uses an AWS Lambda function owned by another account. Such a setup allows you to maintain a single copy of the Lambda function. You do not have to duplicate source code across accounts.

In this post, I will show you how to create Config rules with appropriate cross-account Lambda function authorization. I’ll use a central account that I refer to as the admin-account to create a Lambda function. All of the other accounts then point to the Lambda function owned by the admin-account to create a Config rule. Let’s call one of these accounts the managed-account. This setup allows you to maintain tight control over the source code and eliminates the need to create a copy of Lambda functions in all of the accounts. You no longer have to deploy updates to the Lambda function in these individual accounts.

We will complete these steps for the setup:

  1. Create a Lambda function for a cross-account Config rule in the admin-account.
  2. Authorize Config Rules in the managed-account to invoke a Lambda function in the admin-account.
  3. Create an IAM role in the managed-account to pass to the Lambda function.
  4. Add a policy and trust relationship to the IAM role in the managed-account.
  5. Pass the IAM role from the managed-account to the Lambda function.

Step 1: Create a Lambda Function for a Cross-Account Config Rule

Let’s first create a Lambda function in the admin-account. In this example, the Lambda function checks if log file validation is enabled for all of the AWS CloudTrail trails. Enabling log file validation helps you determine whether a log file was modified or deleted after CloudTrail delivered it. For more information about CloudTrail log file validation, see Validating CloudTrail Log File Integrity.

Note: This rule is an example only. You do not need to create this specific rule to set up cross-account Config rules. You can apply the concept illustrated here to any new or existing Config rule.

To get started, in the AWS Lambda console, choose the config-rule-change-triggered blueprint.

 

Next, modify the evaluateCompliance function and the handler invoked by Lambda. Leave the rest of the blueprint code as is.

function evaluateCompliance(configurationItem, ruleParameters) {
    checkDefined(configurationItem, 'configurationItem');
    checkDefined(configurationItem.configuration, 'configurationItem.configuration');
    checkDefined(ruleParameters, 'ruleParameters');
    //Check if the resource is of type CloudTrail
    if ('AWS::CloudTrail::Trail' !== configurationItem.resourceType) {
        return 'NOT_APPLICABLE';
    }
    //If logfileValidation is enabled, then the trail is compliant
    else if (configurationItem.configuration.logFileValidationEnabled) {
        return 'COMPLIANT';
    }
    else {
        return 'NON_COMPLIANT';
    }
}

In this code snippet, we first ensure that the evaluation is being performed for a trail. Then we check whether the LogFileValidationEnabled property of the trail is set to true. If log file validation is enabled, the trail is marked compliant. Otherwise, the trail is marked noncompliant.

Because this Lambda function is created for reporting evaluation results in the managed-account, the Lambda function will need to be able to call the PutEvaluations Config API (and other APIs, if needed) on the managed-account. We’ll pass the ARN of an IAM role in the managed-account to this Lambda function as a rule parameter. We will need to add a few lines of code to the Lambda function’s handler in order to assume the IAM role passed on by the Config rule in the managed-account:

exports.handler = (event, context, callback) => {
    event = checkDefined(event, 'event');
    const invokingEvent = JSON.parse(event.invokingEvent);
    const ruleParameters = JSON.parse(event.ruleParameters);
    const configurationItem = checkDefined(invokingEvent.configurationItem, 'invokingEvent.configurationItem');
    let compliance = 'NOT_APPLICABLE';
    const putEvaluationsRequest = {}; 
    if (isApplicable(invokingEvent.configurationItem, event)) {
        // Invoke the compliance checking function.
        compliance = evaluateCompliance(invokingEvent.configurationItem, ruleParameters);
    } 
    // Put together the request that reports the evaluation status
    // Note that we're choosing to report this evaluation against the resource that was passed in.
    // You can choose to report this against any other resource type supported by Config 

    putEvaluationsRequest.Evaluations = [{
        ComplianceResourceType: configurationItem.resourceType,
        ComplianceResourceId: configurationItem.resourceId,
        ComplianceType: compliance,
        OrderingTimestamp: configurationItem.configurationItemCaptureTime
    }];
    putEvaluationsRequest.ResultToken = event.resultToken;
    // Assume the role passed from the managed-account
    aws.config.credentials = new aws.TemporaryCredentials({RoleArn: ruleParameters.executionRole});
    let config = new aws.ConfigService({});
    // Invoke the Config API to report the result of the evaluation
    config.putEvaluations(putEvaluationsRequest, callback);
};

In this code snippet, the ARN of the IAM role in the managed-account is passed to this Lambda function as a rule parameter called executionRole. The highlighted lines of code are used to assume the role in the managed-account. Finally, we select the appropriate role (in the admin-account) and save the function.


Make a note of the IAM role in admin-account assigned to the Lambda function and the ARN of the Lambda function. We’ll need to refer these later. You can find the ARN of the Lambda function in the upper-right corner of the AWS Lambda console.

Step 2: Authorize Config Rules in Other Accounts to Invoke a Lambda Function in Your Account

Because the Lambda function we just created will be invoked by the managed-account, we need to add resource policies to allow the managed-account to perform this action. Resource policies to Lambda functions can be applied only through the AWS CLI or SDKs.

Here’s a CLI command you can use to add the resource policy for the managed-account:

$ aws lambda add-permission 
  --function-name cloudtrailLogValidationEnabled 
  --region <region> 
  --statement-id <id> 
  --action "lambda:InvokeFunction" 
  --principal config.amazonaws.com 
  --source-account <managed-account> 

This statement allows only the principal config.amazonaws.com (AWS Config) for the specified source-account to perform the InvokeFunction action on AWS Lambda functions. If more than one account will invoke the Lambda function, each account must be authorized.

Step 3: Create an IAM Role to Pass to the Lambda Function

Next, we need to create an IAM role in the managed-account that can be assumed by the Lambda function. If you want to use an existing role, you can skip to step 4.

Sign in to the AWS IAM console of one of the managed-accounts. In the left navigation, choose Roles, and then choose Create New Role.

On the Set Role Name page, type a name for the role:

Because we are creating this role for cross-account access between the AWS accounts we own, on the Select Role Type page, select Role for Cross-Account Access:

After we choose this option, we must type the account number of the account to which we want to allow access. In our case, we will type the account number of the admin-account.

After we complete this step, we can attach policies to the role. We will skip this step for now. Choose Next Step to review and create the role.

Step 4: Add Policy and Trust Relationships to the IAM Role

From the IAM console of the managed-account, choose the IAM role that the Lambda function will assume, and then click it to modify the role:

We now see options to modify permissions and trust relationships. This IAM role must have, at minimum, permission to call the PutEvaluations Config API in the managed-account. You can attach an existing managed policy or create an inline policy to grant permission to the role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "config:PutEvaluations"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

This policy only allows PutEvaluations action on AWS Config service. You might want to extend permission for the role to perform other actions, depending on the evaluation logic you implement in the Lambda function.

We also need to ensure that the trust relationship is set up correctly. If you followed the steps in this post to create the role, you will see the admin-account has already been added as a trusted entity. This trust policy allows any entity in the admin-account to assume the role.

You can edit the trust relationship to restrict permission only to the role in the admin-account:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<admin-account>:role/lambda_config_role"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Here, lambda_config_role is the role we assigned to the Lambda function we created in the admin-account.

Step 5: Pass the IAM Role to the Lambda Function

The last step involves creating a custom rule in the managed-account. In the AWS Config console of the managed-account, follow the steps to create a custom Config rule. On the rule creation page, we will provide a name and description and paste the ARN of the Lambda function we created in the admin-account:

Because we want this rule to be triggered upon changes to CloudTrail trails, for Trigger type, select Configuration changes. For Scope of changes, select Resources. For Resources, add CloudTrail:Trail. Finally, add the executionRole rule parameter and paste the ARN of the IAM role: arn:aws:iam::<managed-account>:role/config-rule-admin.

 

Save your changes and then create the rule. After the rule is evaluated, inspect the results:

In this example, there are two CloudTrail trails, one of which is noncompliant. Upon further inspection, we find that the noncompliant trail does not have log file validation enabled:

After we enable log file validation, the rule will be evaluated again and the trail will be marked compliant.

In case you are managing multiple AWS accounts, you may want an easy way to create the Config rule and IAM role in all the accounts in your organization. This can be achieved by using the AWS CloudFormation template I have provided here. Before using this CloudFormation template, replace the admin-account placeholder with the account number of the AWS account you plan to use for centrally managing the Lambda function. Once the Config rule and IAM role are set up in all the managed accounts, you can simply modify the Lambda function in the admin-account to add further checks.

Conclusion

In this blog post, I showed how you can create AWS Config Rules that use Lambda functions with cross-account authorization. This setup allows you to centrally manage the Config rules and associated Lambda functions and retain control over the source code. As an alternative to this approach, you can use a CloudFormation template to create and update Config rules and associated Lambda functions in the managed accounts. The cross-account authorization we set up for the Lambda function in this blog post can also be extended to perform actions beyond reporting evaluation results. To do this, you need to add permission for the relevant APIs in the managed accounts.

We welcome your feedback! Leave comments in the section below or contact us on the AWS Config forum.