Managing compliance the Serverless way with AWS Config custom rules
Tags: serverless, compliance, awsconfig, lambd
Published: 2022-04-18 17:19:52

**AWS Config** is a tool for continuous monitoring and conducting compliance checks across your resources in AWS. With the growing services in an AWS account, managing security and conducting routine audits can become cumbersome. This is where AWS Config can do all the heavy lifting with a few clicks. With detailed reports of the configuration history, this service simplifies auditing and maintaining compliance with any internal guidelines you may have.

In this article, we will take a quick look at how to get started with AWS Config and also explore on how you can create your own custom rules that allow you to run evaluations against specific resources, along with using SNS for delivering changes in configurations.

## Overview:
- How to set up AWS Config
- SNS Event rule
- Brief about Conformance packs
- Creating custom AWS Config rule with Lambda
- Conclusion

## Getting started:

### Setting up AWS managed rules:

In the first instance of using the service, you will be asked to create rules either for AWS resources or for the third party resource apps that your account may have been integrated with (eg. DataDog, Jfrog, Stackery etc). This can always be altered later by going to Settings at the dashboard.


![AWS Config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nswc1fjvif49y4j90mqr.png)

After specifying the bucket name for delivering the Config reports, you can optionally choose to associate an SNS topic which will then trigger notifications whenever there are configuration changes to your resources that Config is able to capture. We will get to set this up a little later.


![Choose S3 bucket and SNS topic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnd3wlrcq74psaf9xyfp.png)

On the next page, you can handpick the AWS managed rules for the resources against which the evaluation should be done. These rules will be triggered in two ways (depending on rule type)

- By frequency (value between 1h to 24h)
- Configuration changes (either by **changes to some specific resource Id or changes to resources based on associated tags**)


![Rules for evaluation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fr0yn9by21ghljma7x0s.png)

## SNS Event rule:

For configuring the SNS event notifications, head over to the Eventbridge dashboard and set up the AWS event for Config.


![SNS Config event type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3bswzvfosw3qprjg8y5.png)

You can configure the event type and custom JSON message for that event type. For now, we will stick to a simple setup for triggering notifications when there is a configuration change for all resources.

Proceed to associate the SNS topic that was provided in the initial setup at the AWS Config dashboard.

## Conformance packs:

Conformance packs are preset templates containing rules and configurations that provide a general-purpose framework for security and operational governance checks. Think of them as starter templates/rules which you should ideally extend to build compliance regulations that are in line with the organization’s standards.

Here I have chosen the Serverless conformance pack that includes checks against the usual serverless resources: DynamoDb, API Gateway, Lambda and VPC endpoint setup to name some of the items in the list.


![Serverless conformance pack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5uju7yorm4lddz1i30a.png)

## Creating your own Config custom rule :

Let’s consider a specific scenario wherein you want to ensure that all Lambda functions belonging to a specific VPC do not have an open 0.0.0.0/0 outbound rule in the Security group.

On the rule creation page, choose the custom Lambda rule.


![Custom Lambda rule](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svveorqaahsjrekd1n6h.png)

Fill in the function ARN (you will need to create a function beforehand), the trigger type, and rule parameters (here I have specified as **vpcid**).


![Trigger type and Rule parameter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dceknvjflaah6l24378.png)

```
const configClient = require("aws-sdk/clients/configservice"); 
const ec2Client = require("aws-sdk/clients/ec2");
const lambdaClient = require("aws-sdk/clients/lambda");

const ec2 = new ec2Client({ region: process.env.AWS_REGION });
const lambda = new lambdaClient({ region: process.env.AWS_REGION });
const config = new configClient(); 
const COMPLIANCE_STATES = {
  COMPLIANT: "COMPLIANT",
  NON_COMPLIANT: "NON_COMPLIANT",
  NOT_APPLICABLE: "NOT_APPLICABLE",
};

// Checks whether the invoking event is ScheduledNotification
function isScheduledNotification(invokingEvent) {
  return invokingEvent.messageType === "ScheduledNotification";
}

// Evaluates the configuration of the egress rule in the security group
const evaluateCompliance = async (vpcId) => {
  const getLambdaInVPC = await ec2
    .describeNetworkInterfaces({
      Filters: [{ Name: "vpc-id", Values: [vpcId] }],
    })
    .promise();
  const lambdaList = [];

  getLambdaInVPC.NetworkInterfaces.forEach((item) => {
    lambdaList.push(
      item.Description.split("AWS Lambda VPC ENI-")[1].split("-")[0]
    );
  });
  if (lambdaList) {
    const uniqueLambdaNames = [...new Set(lambdaList)];

    const getLambdaSGId = [];
    for (const item in uniqueLambdaNames) {
      const lambdaInfo = await lambda
        .getFunction({ FunctionName: uniqueLambdaNames[item] })
        .promise();
      lambdaInfo.Configuration.VpcConfig.SecurityGroupIds.forEach((item) => {
        getLambdaSGId.push(item);
      });
    }
    const uniqueSGId = [...new Set(getLambdaSGId)];

    let complianceSGList = [];
    uniqueSGId.forEach((id) => {
        complianceSGList.push({
            Id: id,
            Compliance: COMPLIANCE_STATES.COMPLIANT,
          })
    });
    const checkSGEgressInternetRule = await ec2
      .describeSecurityGroups({
        GroupIds: [...uniqueSGId],
        Filters: [{ Name: "egress.ip-permission.cidr", Values: ["0.0.0.0/0"] }],
      })
      .promise();

    if (checkSGEgressInternetRule.SecurityGroups) {
      checkSGEgressInternetRule.SecurityGroups.forEach((item) => {
        const indexToUpdate = complianceSGList.findIndex((obj => obj.Id == item.GroupId));
        complianceSGList[indexToUpdate].Compliance = COMPLIANCE_STATES.NON_COMPLIANT;
      });
      console.log(JSON.stringify(complianceSGList))
      return complianceSGList;
    }
  } else {
    return [{ Id: vpcId }, { Compliance: COMPLIANCE_STATES.NOT_APPLICABLE }];
  }
};

exports.handler = async (event, context) => {
  // Parses the invokingEvent and ruleParameters values, which contain JSON objects passed as strings.
  console.log(JSON.stringify(event));
  const invokingEvent = JSON.parse(event.invokingEvent);
  const ruleParameters = JSON.parse(event.ruleParameters);


  if (isScheduledNotification(invokingEvent)) {
    // Passes the vpcid from the config rule parameter
    const checkCompliance = await evaluateCompliance(ruleParameters.vpcid);
    const putEvaluationsRequest = {
      ResultToken: event.resultToken,
    };
    putEvaluationsRequest.Evaluations = [];
    checkCompliance.forEach((item) => {
      putEvaluationsRequest.Evaluations.push({
        ComplianceResourceType: "AWS::EC2::SecurityGroup",
        ComplianceResourceId: item.Id,
        ComplianceType: item.Compliance,
        OrderingTimestamp: new Date(),
      });
    });
    // Sends the evaluation results to AWS Config.
    const configResponse = await config.putEvaluations(putEvaluationsRequest).promise();
    return configResponse
  } else {
    console.log("Not a scheduled event");
  }
};
```

The logic is essentially doing this,

- Pull all Lambda functions associated with the provided VPC ID (part of the incoming Config event as **rule parameter**)
- Get the Security group details.
- Filter those Security group IDs that have an outbound rule to 0.0.0.0/0.
- Return the evaluation result back to Config.

When you manually invoke this rule’s evaluation, any changes to security group will trigger an SNS event. You can review the results at Config dashboard and see the list of those Security group IDs that are non-compliant.

We can also set up an automated remediation response when the evaluation fails. In this case, choosing the _CloseSecurityGroup_ action from the list.


![Automatic remediation after compliance failure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02fzpgwdt41r7ijzacx0.png)

If in case a preset remediation action doesn’t exist, we could leverage the SNS event as a trigger for another Lambda function which will then carry out the remediation action for us, thereby **automating the whole process of event detection and remediation without any manual intervention**. The flow would then look something like this,


![Config event flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/io2aqt88seuupmrcomax.jpeg)

## Conclusion:

Compliance setups can get complicated as the number of services expands within an AWS account. With Config, it’s also possible to aggregate evaluation reports across multiple accounts which simplifies auditing when there are distributed resources in different regions belonging to other accounts.

Different business units within an organization may have varied audit requirements and it can certainly get a whole lot more difficult to control and manage. Especially in cases when an organization is wanting to maintain strict compliance as per PCI standards, AWS Config can definitely be of great help in offloading those internal audits and security management tasks.