Advanced Auditing with AWS Config

CIS Benchmarks for AWS

The CIS Benchmarks for AWS are available from both AWS and and provide guidance for configuring security features for a variety of AWS systems. There are additional benchmarks for other systems - such as CentOS, Apache, and Nginx. The CIS benchmarks for AWS targets the following AWS technologies:

  • IAM
  • Config
  • CloudTrail
  • CloudWatch
  • SNS
  • S3
  • VPC

We'll cover specific examples of the benchmarks further down, but you can also see the linked PDF above for details as well!

Enable AWS Config

Like most really good security features AWS provides (like CloudTrail), AWS makes it really simple to enable.

In the console:

  1. Log into the AWS Console.
  2. Click "Get Started Now".
  3. On the displayed settings page, ensure you're recording for all supported resources.
  4. On the displayed settings page, ensure you're recording for supported global resources.
  5. Select your S3 bucket. You can create a bucket, choose a bucket, or choose a bucket from another account.
  6. Select whether to enable SNS. This is optional.
  7. If you enabled SNS, select the topic you want to send the events to.
  8. For the role, chose or enable a role for the event recording to S3.
  9. If the region supports rules, click next.
  10. Click save.

Using the AWS CLI:

aws configservice subscribe --s3-bucket my-config-bucket --sns-topic arn:aws:sns:us-east-1:012345678912:my-config-notice --iam-role arn:aws:iam::012345678912:role/myConfigRole

Note that with the CLI you'll need to do the additional work of creating the required S3 bucket, IAM role, and SNS topic ahead of enabling the service.

Creating AWS Config Rules

First, why "AWS Config Rules"? I'll be honest, I mostly like it for the visibility and enforcement aspect. While regular AWS Config will record the state of your resources it doesn't offer you easy visibility or rules for your resources. But as a stand alone, AWS Config is really powerful to monitor changes to your environment. As an example, you could look up the resource timeline for a security group attached to your Elastic Load Balancer. As the network interfaces change for the ELB (during a scale up, scale down, or even just software update), you'll see the network interface relationships update for the Security Group.

What AWS Config Rules brings to the table is the ability to evaluate a resource against a set of criteria. For example, you might evaluate an IAM user for having an MFA device attached. Or the AWS account to verify password policy configuration. This is what makes AWS Config Rules powerful, that and the easy to review dashboard that it's equipped with (and compared to some of the GUIs presented by AWS, it's beautiful).

AWS Config Rules supports two types of rules as of this writing. The first type that I want to talk about are the "Triggered Rules". These triggered rules are applied upon detection of a change to a resource. It's my preferred way to target a specific resource type and evaluate it against my criteria. A classic example is the one of the IAM user with MFA that I referred to previously. When updates are performed to the IAM user, the updates would trigger a check of the various defined criteria for the updated resource.

Vastly different from triggered rules, AWS Config Rules provides scheduled or periodic rules. I like to use scheduled rules to evaluate global resources on my account. Resources like an MFA on the root account, or CloudTrail being enabled.

Rules, whether triggered or scheduled, have three valid results:

  1. COMPLIANT
  2. NON_COMPLIANT
  3. NOT_APPLICABLE

The COMPLIANT and NON_COMPLIANT results simply indicate success or failure evaluations of your criteria. The NOT_APPLICABLE result indicates that the criteria was not applied against the presented resource.

A special note on the NOT_APPLICABLE result: If you're seeing a lot of these you need to adjust your rules as it's probably costing you unnecessary executions.

Next up, it's time for some examples!

Example AWS Config Rule for MFA required for IAM users

For our first example, how hard is it to create a rule that evaluates whether or not an IAM user has an MFA device associated with their user? The flow generally looks like:

  1. A user or system creates or modifies an IAM user.
  2. AWS records the event.
  3. Every ten minutes or so, AWS Config receives a 'snapshot' of the recorded events.
  4. When a snapshot is received AWS Config evaluates whether any rules apply to any resources or tags in the snapshot.
  5. If a match is found, AWS Config Rules will execute your AWS Lambda Function.

So in the case of our IAM user rule, AWS Config is looking for AWS::IAM::User resources. Once one is discovered it's passed through to AWS Lambda. The actual logic of our rule (written in Node.js) would be fairly short and simple, like:

if (resourceType == 'AWS::IAM::User') {
    iam.listMFADevices({ UserName: invokingEvent.configurationItem.resourceName }, function(err, mfa) {
        var compliance = 'NON_COMPLIANT';
        if (!err) {
            if (mfa.MFADevices.length > 0) {
                compliance = 'COMPLIANT';
            }
        } else {
            console.log(err);
        }
        putEval(event,context,resType,resId,compliance,timestamp);
    });
} else {
    putEval(event,context,resType,resId,'NOT_APPLICABLE',timestamp);
}

The first line of the evaluation is just a check to confirm the resource type. Our next step is to get the IAM user's resource name, which we can retrieve from the invoking event. By default, everything is NON_COMPLIANT because we only want things to be COMPLIANT if they meet a set of criteria. In this case we're just calling the listMFADevices action on the IAM user detected by the invoking event. We're then checking the length of the resulting devices, which is 0 if an MFA device is not present. If it's more than 0, then it's COMPLIANT.

Fairly simple right? I mean, that in and of itself was only 14 lines of code. There is a little bit of wrapping you can't see here, but it's the standard AWS Lambda style code wrappings. Which reminds me...

For the code complete example, check out the available GitHub repositiory: link

Example AWS Config Rule for ensuring AWS Config is enabled

I originally wrote this a while back as a periodic rule, but since then AWS has released CloudTrail as a supported resource type. This example parses the AWS Config snapshot and evaluates whether a multi-region trail is active.

readSnapshot(s3, s3key, s3bucket, function(err, snapshot) {
    if (err === null) {
        var compliance = 'NON_COMPLIANT';
        for (var i = 0; i < snapshot.configurationItems.length; i++) {
            var item = snapshot.configurationItems[i];
            if (item.resourceType === 'AWS::CloudTrail::Trail') {
                if (item.configuration.isMultiRegionTrail) {
                    compliance = 'COMPLIANT';
                }
            }
        }
        putEval(event,context,resType,resId,compliance,timestamp);
    } else {
        context.fail(err);
    }
});

In a similar vein to our previous example, this one is 16 lines long! Again, there is a little bit of wrapping you can't see here, but it's the standard AWS Lambda style code wrappings. Well other than this readSnapshot function. That function is fairly simple, it just reads the source snapshot from S3 and lets us parse it.

function readSnapshot(s3client, s3key, s3bucket, callback) {
    var params = {
        Key: s3key,
        Bucket: s3bucket
    };
    var buffer = "";
    s3client.getObject(params)
        .createReadStream()
        .pipe(zlib.createGunzip())
        .on('data', function(chunk) {
            buffer = buffer + chunk;
        })
        .on('end', function() {
            callback(null, JSON.parse(buffer));
        })
        .on('error', function(err) {
            callback(err, null);
        });
}

It's also longer than our actual rule! 19 lines! Anyways...

For the code complete example, check out the available GitHub repositiory: link

Example AWS Config Rules for ensuring VPC Flow Logging is enabled

Similar to our first example, this example only makes

ec2.describeFlowLogs({ Filter: [ { Name: 'resource-id', Values: [ resourceId ] } ] }, function(err, data) {
    var compliance = 'NON_COMPLIANT';
    if (!err) {
        if (data.FlowLogStatus == 'ACTIVE') {
            compliance = 'COMPLIANT';
        }
    }
    putEval(event,context,resType,resId,compliance,timestamp);
});

This is our last and shortest example - only 9 lines long! Again, there is a little bit of wrapping you can't see here, but it's the standard AWS Lambda style code wrappings. Which reminds me...

For the code complete example, check out the available GitHub repositiory: link

GitHub Repository with starter templates

I've prepared a fully working example in a GitHub repository for your usage. You'll need to follow the directions in the README to apply it to your targeted AWS environment. The results are shown in the next section though!

Resulting AWS Config Dashboard

Results! If you've followed along in the post, you've probably wondered what's so cool about applying some code against some AWS resources? Well...

Here's a quick visual example of the overview for rule statuses:

And for a visual example of a specific rule status:

If you think this is cool: check out AWS CloudWatch Events! You can have a variety of events trigger code executions. There are a lot of cool options and tricks! Such as API calls to disable AWS CloudTrail re-enables it.