Auditing AWS cloud resources with Chef InSpec

Continuing from the preceding blog, ‘Auditing OS Level Resources with Chef InSpec’ which delved into utilizing the Chef InSpec open-source tool for testing individual servers via OS level resources. Now, we embark into the domain of Chef InSpec’s facility in cloud environments. Chef InSpec extends its support to cloud platforms like AWS, Azure, and GCP. Referring to insights shared in our earlier blog, Qxf2’s clients predominantly embrace Infrastructure as Code and heavily lean towards the AWS cloud platform for deployments. A few of our clients also opt for the Azure cloud platform. This post takes through the capabilities of Chef InSpec tailored to the AWS cloud platform.


1.Prerequisites

To allow InSpec to talk AWS
-> InSpec installed.
-> AWS console access.
-> AWS IAM user with “ReadOnlyAccess” is all you need to execute tests.
-> Configure your AWS CLI

2.Profile for AWS

To verify the configuration of AWS resources, initialize an inspec profile with target platform as AWS:

inspec init profile --platform aws blog_profile

The above command creates standalone structure with “controls” and “inspec.yml” file. You can verify inspec.yml for platform support ‘AWS’ under supports section.

Command to verify AWS profile
inspec detect -t aws://<region>/<aws_profile_name>
Output of the Platform details

inspec detect


3.Tests to verify AWS resources

3.1. AWS S3 Resource:

Amazon S3 bucket is object storage service used to store and retrieve the data. This audit resource 'aws_s3_bucket' tests properties of a single AWS S3 bucket.
Example: The below provided code tests S3 bucket existence, access and the tags attached to the S3 bucket.
Before writing test add the below steps to “inspec.yml”

atrribute:
- name: bucketname
  description: 's3 bucket name'
  required: true
  value: $TEST_BUCKET
  type: string

In this YAML block, you’re defining an attribute named ‘bucketname’, this attribute used to pass the information to your Chef InSpec profile during runtime. The YAML file describes an S3 bucket name and the attribute marked as required (required: true). The attribute type set to of type string (type: string). The value for this attribute set to a variable named $TEST_BUCKET.

bucketname = attribute('bucketname')
 
control "AWS S3" do
  impact 0.7
  title "Check AWS Backend S3 Buckets"
 
  describe aws_s3_bucket(bucket_name: bucketname) do
    it { should exist }
    it { should_not be_public }
    its('tags') { should include("Name" => "InSpec")}
  end
end

This bucketname = attribute('bucketname') retrieves the value of the bucket name. The be_public matcher tests if the bucket has potentially insecure access controls. In summary, the code snippet sets up an attribute in inspec.yml to dynamically provide an S3 bucket name and verifies the existence, not accessible to public and attached tags information.

Command to execute:
inspec exec blog_profile -t aws://<region>/<aws_profile_name> --input bucketname="<bucket name>"

Sample run of s3 bucket resource test:
S3_bucket_output_new


3.2. AWS SNS:

An Amazon SNS is managed service for communication. SNS(Simple Notification Service) used to allow messaging between decoupled microservices applications.
Example: Verify the configuration of an AWS SNS (Simple Notification Service) topic and its associated subscription.

describe aws_sns_topic('arn:aws:sns:us-east-1:121777381368:my-topic-name') do
    it { should exist }
end
describe aws_sns_subscription(subscription_arn: 'arn:aws:sns:us-east-1:121777381368:my-topic-name:ef2ddf44-bd3c-41dd-a750-72380deba2a2') do
    its('endpoint') { should cmp '[email protected]' }
end

The code validates the existence of an SNS topic and then verifies the attributes of a specific subscription associated with that topic. This helps ensure that the SNS configuration aligns with the expected values and settings.


3.3. AWS SQS Resource:

AWS SQS queue service used to send, store and receive messages between components. Use aws_sqs_queue audit resource for testing its properties of single SQS queue service.
Example: Test the SQS queue exists, delay time, queue is a FIFO queue and has a visibility timeout of 300 seconds.

describe aws_sqs_queue(queue_url: 'https://sqs.ap-southeast-2.amazonaws.com/1212121/MyQueue') do
  it { should exist }
  its('delay_seconds') { should be 0 }
  its('is_fifo_queue') { should be false }
  its('visibility_timeout') { should be 300 }
end

The aws_sqs_queue resource validates the presence of SQS queue using its URL. The it { should exist } assertion confirms its existence, while its('delay_seconds') { should eq 0 } test the delay time a specific attribute.The its('is_fifo_queue') { should be false } tests if queue is fifo and its('visibility_timeout') { should be 300 } tests visibility timeout of the message in seconds.


3.4. AWS Security Groups Resource:

Security Groups acts like firewall that controls the traffic allowed to and from in your “Virtual Private Network(VPN)”
Example: The test verifies the existence of Security Group, tests specific incoming ports (such as 80, 443, 22, 7687, and 7474) are allowed, validates that the security group is associated with a designated VPC (Virtual Private Cloud).

describe aws_security_group(group_id: 'sg-12345678') do
  it { should exist }
  it { should allow_in(port: 80) }
  it { should allow_in(port: 443) }
  it { should allow_in(port: 22) }
  it { should allow_in(port: 7687) }
  it { should allow_in(port: 7474) }
  its ('vpc_id') { should eq 'vpc-12345678' }
end

3.5. AWS Lambda Resource:

AWS Lambda function event-driven, serverless provided as part of Amazon Web Services.
Example: The test verifies the existence of Lambda, tests handler, Lambda version, runtime version and last modified. Please add the below lines to the ‘inspec.yml’.

Inputs:
- name: lambdaname
  description:  'lambda name'
  type: string

In this inspec.yml block, you’re defining an input (attribute) named ‘lambdaname’. This attribute is used to pass the Lambda function name to your Chef InSpec profile during runtime. It is described with a description stating that it represents a Lambda function name. Its type is specified as a string.

# Set a for an input.
lambdaname =  input('lambdaname')
 
# verify AWS Lambda Configuration
title "lambda config test"
 
# you add controls here
control "aws-lambda-config" do                      
  impact 1.0                               
  title "verify lambda configuration"                                       
  describe aws_lambda(lambdaname) do
    it { should exist}
    its ('handler') { should eq 'main.on_event' }
    its ('version') { should eq '$LATEST' }
    its ('runtime') { should eq 'python3.9' }
    its('last_modified') { should_not be_nil }
  end
end

The lambdaname = input('lambdaname') this line assigns the value of the lambdaname attribute from the input (attribute) defined in the inspec.yml file to the local variable lambdaname. The impact 1.0 Specifies the impact level of the control. Here, an impact of 1.0 indicates high importance.
In summary, this code dynamically fetches the Lambda function name using the input function, allowing you to provide the value during runtime. The Chef InSpec control then tests the configuration aspects of the specified AWS Lambda function using the aws_lambda resource and various assertions. The “title” and “impact” attributes provide additional context about the purpose and importance of the tests being performed.

Command to execute tests under blog_profile:
inspec exec blog_profile -t aws://<region>/<aws_profile_name> --input bucketname="<S3 bucket name>" lambdaname=<"LAMBDA_FUNCTION_NAME">

Note: This command will execute the S3 bucket and Lambda tests.


3.6. AWS EBS Volume Resource:

An Amazon EBS(Elastic block-level Storage) Volume that can attach your instance is a durable, block-level storage device.
Example: The tests verifies existence, confirms that it is encrypted, checks that its size is greater than 50 GB, and ensures that the volume’s input/output operations per second (IOPS) is set to exactly 300.

describe aws_ebs_volume('vol-055a1d9e9b5619d78') do
    it { should exist }
    it { should be_encrypted }
    its('size') { should be > 50 }
    its('iops') { should cmp 300 }
end

3.7. AWS EC2 Instance Resource:

An Amazon EC2 is virtual service in Amazon web services terminology which can be used to provision a computer server within the AWS cloud.
Example: Test the instance is running, verifying image id, testing tags and the availability zone of an EC2 instance for the provided EC2 instance named ‘my-instance’.

describe aws_ec2_instances(name: 'my-instance') do
    it { should be_running }
    its('image_id') { should eq 'ami-033a6e7ae709da8ad' }
    its('tags') { should include( "Name"=>"test","Name1"=>"test-1","Name2"=>"test-2","Name3"=>"test-3") }
    its('availability_zone') { should eq 'us-east-1' }
end

3.8. AWS VPC Resource:

Use aws_vpc audit resource to test the properties of single VPC and the CIDR Block that is used within the VPC.
Example: Verify that a VPC with ID “vpc-0123456789abcdef0” exists and has specific CIDR blocks.

describe aws_vpc(vpc_id: 'vpc-0123456789abcdef0') do
  it { should exist }
  its('cidr_block') { should cmp '10.0.0.0/16' }
end

3.9. AWS Elastic IP (EIP) Resource:

An Elastic IP address is a static IPv4 address designed for dynamic cloud computing.
Example: Test whether an Elastic IP exists and is correctly associated with the expected EC2 instance.

elastic_ip = ENV['ELASTIC_IP']
instance_id = ENV['INSTANCE_ID']
 
control 'Test AWS EC2 instance by providing elastic ip' do
        impact 1.0
        title 'Verify Elastic IP and associated instance ID'
        desc 'Ensure Elastic IP exists and it is associated with the instance ID'
        describe aws_ec2_eip(public_ip: elastic_ip) do
                it { should exist }
                its('instance_id') { should eq instance_id }
        end
end

The “elastic_ip” and “instance_id” retrieve the values of environment variables named ‘ELASTIC_IP’ and ‘INSTANCE_ID’ during the runtime. The use of environment variables allows flexibility in testing different instances and Elastic IPs.


3.10.AWS IAM User:

An IAM user is an entity in AWS. IAM user is used to interact with AWS console access provided depends on the user permissions.
Example: Test the IAM user ‘johny’ exists and don’t have access key and inline policies attached to the IAM user.

describe aws_iam_user('johny') do
  it                         { should exist }
  its('access_keys')         { should be_empty }
  its('inline_policy_names') { should be_empty }
end

4.Conclusion

Infrastructure, especially incorrectly configured infrastructure, is a source of problems. With tools like Chef InSpec, testers now have a way to test for bugs and misconfigurations. I hope the examples in this post help you write tests for your infrastructure hosted on AWS.

Hire Qxf2 testers for your infrastructure testing

Hiring Qxf2 for testing infrastructure brings you skilled experts who make sure your systems work smoothly and securely. We find issues before they become problems, ensuring your tech setup is dependable. Contact us to know more.


References:

https://qxf2.com/blog/auditing-os-level-resources-with-chef-inspec-beginners-guide/

5 thoughts on “Auditing AWS cloud resources with Chef InSpec

    1. Indeed, we had the option to utilize boto3, but in a previous client engagement, we used Chef InSpec. Unfortunately, at that time, we didn’t have sufficient time to delve deeper into its capabilities. We recognized the opportunity to further explore Chef InSpec by creating tests for our AWS-hosted resources

      1. Hello,

        How to get this added to a lambda to run this ? Created a lambda layer to include gem ? but its not working. Any help would be appreciated .

      2. Hi,
        Can you please be more specific about your requirement. You want to audit your AWS resources from a Lambda?

  1. Hello,

    How to get this added to a lambda to run this ? Created a lambda layer to include gem ? but its not working. Any help would be appreciated .

Leave a Reply

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