Understanding Singular and Plural InSpec resources

InSpec enables you to automate compliance by expressing the expected state of many things. A common need is to find a group of things, then examine them in detail.

If you are using the cloud, you know that cloud security is paramount, but difficult to keep enforced. Even if you have your infrastructure creation automated, it is still possible, through error or malice, for an individual using other means (such as a web UI) to make changes to your infrastructure that may go undetected.

Positive tests – “this thing should exist, and be configured in this manner” are often available using the infrastructure provisioning tools.  But from a compliance and security standpoint, it is often the negative tests that are of more concern – “This and only this should exist; nothing should be configured in this insecure manner.”  Provisioning tools are generally not able to detect such things.

InSpec addresses this problem by loosely categorizing its resources into two groups: resources that are able to list all members of a resource type, and resources that are able to examine a resource type in detail. We call these plural and singular resources, and name them accordingly.

For example, in your AWS environment, you may wish to check your firewall rules, called “security groups.” InSpec provides an “aws_security_groups” resource that specializes in listing and filtering the security groups, as well as an “aws_security_group” resource that allows you to audit an individual group in-depth.

control "No group should allow in port 22" do
  aws_security_groups.security_group_ids do |security_group_id|
    describe aws_security_group(security_group_id) do
      it { should_not allow_in port: 22 }
    end
  end
end

InSpec’s output will treat this as one control, with one test for each security group. If a security group violates the policy by allowing in port 22, that individual test will fail, with clear output identifying the security group by ID.

But plural resources don’t have to enumerate all members of the resource type. All plural resources support powerful filtering mechanisms.

For example, you might want to ensure that all IAM users on your AWS account have multi-factor authentication:

describe aws_iam_users.where(have_mfa_enabled?: false) do
  it { should_not exist }
end

This is a common pattern using a plural resource to express non-existence. It is faster than the plural-singular construction above, but the output when failing is less clear (it will indicate that there is at least one user without MFA, but not which). One way around that is this idiom: list the matching usernames, and expect the list to be empty. If the list isn’t empty, the offending username(s) will be shown.

describe aws_iam_users.where(have_mfa_enabled?: false) do
  it { should_not exist }
  its('usernames') { should cmp [] } # Less readable, but failure output is better
end

You can also execute arbitrary code in the where block. Suppose your AWS networking setup is configured such that your application VPCs all have IP address blocks that begin with 10, while your admin VPCs begin with 172. You can select them using aws_vpcs, and have Ruby’s IPAddr class perform the subnet calculations.

require 'ipaddr'
# List all application VPCs
aws_vpcs.where 
  { IPAddr.new('10.0.0.0/8').include?(cidr_block) }.vpc_ids do | vpc_id|
  # Find the default security group in the VPC
  describe aws_security_group(group_name: 'default', vpc_id: vpc_id) do
    # Make sure it does not allow in any traffic by default
    it { should_not allow_in() } 
  end
end

With the recent release of InSpec 2.2, several enhancements have been made to the facility that supports the “plural” resources, improving consistency, performance, and fixing some bugs and unpredictable behavior.  

  • Lazy Column Loading. A plural resource may now defer populating columns until they are accessed. The aws_iam_users resource has been updated to take advantage of this feature, and its performance is greatly improved.
  • Developer Documentation. The support library is now fully documented, allowing project developers and community contributors to understand it more quickly and leverage less-well-known features.
  • Standardized Features. All plural resources now receive the where, raw_data, entries, count, and exist? methods automatically. This reduces copy-pasting when making a new plural resource, and allows InSpec users to leverage prior knowledge: “If it ends in s, I can call where and count…”
  • Several Bug Fixes. Several defects and surprising behaviors have been corrected, primarily around exception handling and validation.
  • Enable Future Self-Documentation. By more clearly differentiating the intent of each property in the resource codebase, we have cleared the path to being able to generate documentation from the code itself.

If you’d like to check out singular and plural resources working together, try out some of these resources:

aws_security_group / aws_security_groups
package / packages
aws_ec2_instance / aws_ec2_instances
Author Clinton Wolfe

Clinton Wolfe is a software engineer at Chef, helping build the cloud functionality of InSpec. In a previous life, he was a DevOps consultant, which meant he voluntarily chose to go into heavily siloed, dysfunctional organizations and try to get them to talk to each other with as few stabbings as possible. He is especially interested in testable infrastructure, and the processes needed to support quality and compliance throughout the application lifecycle.