Basic fields for aws_vpcs (#2930)

* Update singular implementation to avoid use of inner object
* Update docs and tests for 3 new filters and properties on aws_vpcs
* Implement new filters and properties; one failing test due to odd FilterTable behavior
* changes to avoid bug 2929

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2018-04-12 15:48:55 -04:00 committed by Jared Quick
parent b0f34ffd21
commit 745ff32c80
8 changed files with 262 additions and 68 deletions

View file

@ -20,23 +20,94 @@ Every AWS account has at least one VPC, the "default" VPC, in every region.
An `aws_vpcs` resource block uses an optional filter to select a group of VPCs and then tests that group.
# The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
# Since you always have at least one VPC, this will always pass.
describe aws_vpcs do
it { should exist }
end
# Insist that all VPCs use the same DHCP option set.
describe aws_vpcs.where { dhcp_options_id != 'dopt-12345678' } do
it { should_not exist }
end
<br>
## Examples
The following examples show how to use this InSpec audit resource.
As this is the initial release of `aws_vpcs`, its limited functionality precludes examples.
### Check for a Particular VPC ID
describe aws_vpcs do
its('vpc_ids') { should include 'vpc-12345678' }
end
### Use the VPC IDs to Get a List of Default Security Groups
aws_vpcs.vpc_ids.each do |vpc_id|
describe aws_security_group(vpc_id: vpc_id, group_name: 'default') do
it { should_not allow_in(port: 22) }
end
end
<br>
## Filter Criteria
### cidr_block
Filters the results to include only those VPCs that match the given IPv4 range. This is a string value.
# We shun the 10.0.0.0/8 space
describe aws_vpcs.where { cidr_block.start_with?('10') } do
it { should_not exist }
end
### dhcp_option_id
Filters the results to include only those VPCs that have the given DHCP Option Set.
# Insist on one DHCP option set for all VPCs.
describe aws_vpcs.where { dhcp_options_id != 'dopt-12345678' } do
it { should_not exist }
end
## Properties
### cidr_blocks
The cidr_blocks property provides a list of the CIDR blocks that the matched VPCs serve as strings.
describe aws_vpcs do
# This is simple array membership checking - not subnet membership
its('cidr_blocks') { should include '179.0.0.0/16' }
end
### dhcp_options_ids
The dhcp_option_set_ids property provides a de-duplicated list of the DHCP Option Set IDs that the matched VPCs use when assigning IPs to resources.
describe aws_vpcs do
its('dhcp_options_ids') { should include 'dopt-12345678' }
end
### vpc_ids
The vpc_ids property provides a list of the IDs of the matched VPCs.
describe aws_vpcs do
its('vpc_ids') { should include 'vpc-12345678' }
end
# Get a list of all VPC IDs
aws_vpcs.vpc_ids.each do |vpc_id|
# Do something with vpc_id
end
## Matchers
This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://www.inspec.io/docs/reference/matchers/).
### exists

View file

@ -15,11 +15,8 @@ class AwsVpc < Inspec.resource(1)
"VPC #{vpc_id}"
end
[:cidr_block, :dhcp_options_id, :state, :vpc_id, :instance_tenancy, :is_default].each do |property|
define_method(property) do
@vpc[property]
end
end
attr_reader :cidr_block, :dhcp_options_id, :instance_tenancy, :is_default,\
:state, :vpc_id
alias default? is_default
@ -51,9 +48,16 @@ class AwsVpc < Inspec.resource(1)
resp = backend.describe_vpcs({ filters: [filter] })
@vpc = resp.vpcs[0].to_h
@vpc_id = @vpc[:vpc_id]
@exists = !@vpc.empty?
vpc = resp.vpcs[0].to_h
@exists = !vpc.empty?
return unless @exists
@cidr_block = vpc[:cidr_block]
@dhcp_options_id = vpc[:dhcp_options_id]
@instance_tenancy = vpc[:instance_tenancy]
@is_default = vpc[:is_default]
@state = vpc[:state]
@vpc_id = vpc[:vpc_id]
end
class Backend

View file

@ -13,7 +13,13 @@ class AwsVpcs < Inspec.resource(1)
# Underlying FilterTable implementation.
filter = FilterTable.create
filter.add_accessor(:entries)
.add_accessor(:where)
.add(:exists?) { |x| !x.entries.empty? }
.add(:cidr_blocks, field: :cidr_block)
.add(:vpc_ids, field: :vpc_id)
# We need a dummy here, so FilterTable will define and populate the dhcp_options_id field
filter.add(:dummy, field: :dhcp_options_id)
.add(:dhcp_options_ids) { |obj| obj.entries.map(&:dhcp_options_id).uniq }
filter.connect(self, :table)
def validate_params(raw_params)
@ -29,7 +35,8 @@ class AwsVpcs < Inspec.resource(1)
end
def fetch_from_api
@table = BackendFactory.create(inspec_runner).describe_vpcs.to_h[:vpcs]
describe_vpcs_response = BackendFactory.create(inspec_runner).describe_vpcs
@table = describe_vpcs_response.to_h[:vpcs].map(&:to_h)
end
class Backend

View file

@ -10,24 +10,23 @@
# alpha | debian | N | t2.micro
# beta | centos | Y | t2.small
resource "aws_instance" "alpha" {
ami = "${data.aws_ami.debian.id}"
instance_type = "t2.micro"
tags {
Name = "${terraform.env}.alpha"
Name = "${terraform.env}.alpha"
X-Project = "inspec"
}
}
resource "aws_instance" "beta" {
ami = "${data.aws_ami.centos.id}"
instance_type = "t2.small"
ami = "${data.aws_ami.centos.id}"
instance_type = "t2.small"
iam_instance_profile = "${aws_iam_instance_profile.profile_for_ec2_with_role.name}"
tags {
Name = "${terraform.env}.beta"
Name = "${terraform.env}.beta"
X-Project = "inspec"
}
}
@ -76,7 +75,7 @@ EOF
}
resource "aws_iam_instance_profile" "profile_for_ec2_with_role" {
name = "${terraform.env}.profile_for_ec2_with_role"
name = "${terraform.env}.profile_for_ec2_with_role"
role = "${aws_iam_role.role_for_ec2_with_role.name}"
}
@ -88,6 +87,7 @@ output "ec2_instance_has_role_id" {
output "ec2_instance_type_t2_micro_id" {
value = "${aws_instance.alpha.id}"
}
output "ec2_instance_type_t2_small_id" {
value = "${aws_instance.beta.id}"
}
@ -96,8 +96,8 @@ output "ec2_instance_type_t2_small_id" {
# Debian
data "aws_ami" "debian" {
most_recent = true
owners = ["679593333241"]
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
@ -110,21 +110,23 @@ data "aws_ami" "debian" {
}
filter {
name = "root-device-type"
name = "root-device-type"
values = ["ebs"]
}
}
output "ec2_ami_id_debian" {
value = "${data.aws_ami.debian.id}"
}
output "ec2_instance_debian_id" {
value = "${aws_instance.alpha.id}"
}
# Centos
data "aws_ami" "centos" {
most_recent = true
owners = ["679593333241"]
most_recent = true
owners = ["679593333241"]
filter {
name = "name"
@ -137,13 +139,15 @@ data "aws_ami" "centos" {
}
filter {
name = "root-device-type"
name = "root-device-type"
values = ["ebs"]
}
}
output "ec2_ami_id_centos" {
value = "${data.aws_ami.centos.id}"
}
output "ec2_instance_centos_id" {
value = "${aws_instance.beta.id}"
}
@ -159,7 +163,7 @@ data "aws_vpc" "default" {
data "aws_security_group" "default" {
vpc_id = "${data.aws_vpc.default.id}"
name = "default"
name = "default"
}
output "ec2_security_group_default_vpc_id" {
@ -174,6 +178,18 @@ resource "aws_vpc" "non_default" {
cidr_block = "172.32.0.0/16"
}
output "vpc_default_vpc_id" {
value = "${data.aws_vpc.default.id}"
}
output "vpc_default_vpc_cidr_block" {
value = "${data.aws_vpc.default.cidr_block}"
}
output "vpc_default_dhcp_options_id" {
value = "${data.aws_vpc.default.dhcp_options_id}"
}
output "vpc_non_default_id" {
value = "${aws_vpc.non_default.id}"
}
@ -186,6 +202,10 @@ output "vpc_non_default_instance_tenancy" {
value = "${aws_vpc.non_default.instance_tenancy}"
}
output "vpc_non_default_dhcp_options_id" {
value = "${aws_vpc.non_default.dhcp_options_id}"
}
# Create a security group with a known description
# in the default VPC
resource "aws_security_group" "alpha" {
@ -207,43 +227,42 @@ output "ec2_security_group_alpha_group_name" {
# Populate SG Alpha with some rules
resource "aws_security_group_rule" "alpha_http_world" {
type = "ingress"
from_port = "80"
to_port = "80"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
type = "ingress"
from_port = "80"
to_port = "80"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = "${aws_security_group.alpha.id}"
}
resource "aws_security_group_rule" "alpha_ssh_in" {
type = "ingress"
from_port = "22"
to_port = "22"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24"]
type = "ingress"
from_port = "22"
to_port = "22"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24"]
security_group_id = "${aws_security_group.alpha.id}"
}
resource "aws_security_group_rule" "alpha_x11" {
description = "Only allow X11 out for some reason"
type = "egress"
from_port = "6000"
to_port = "6007"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24", "10.3.2.0/24"]
description = "Only allow X11 out for some reason"
type = "egress"
from_port = "6000"
to_port = "6007"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24", "10.3.2.0/24"]
security_group_id = "${aws_security_group.alpha.id}"
}
resource "aws_security_group_rule" "alpha_all_ports" {
type = "ingress"
from_port = "0"
to_port = "65535"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24"]
type = "ingress"
from_port = "0"
to_port = "65535"
protocol = "tcp"
cidr_blocks = ["10.1.2.0/24"]
security_group_id = "${aws_security_group.alpha.id}"
}
#============================================================#
# VPC Subnets
#============================================================#

View file

@ -1,9 +1,12 @@
fixtures = {}
[
'ec2_security_group_default_vpc_id',
'vpc_default_vpc_id',
'vpc_default_cidr_block',
'vpc_default_dhcp_options_id',
'vpc_non_default_id',
'vpc_non_default_cidr_block',
'vpc_non_default_instance_tenancy'
'vpc_non_default_instance_tenancy',
'vpc_non_default_dhcp_options_id',
].each do |fixture_name|
fixtures[fixture_name] = attribute(
fixture_name,
@ -13,7 +16,7 @@ fixtures = {}
end
control "aws_vpc recall" do
describe aws_vpc(fixtures['ec2_security_group_default_vpc_id']) do
describe aws_vpc(fixtures['vpc_default_vpc_id']) do
it { should exist}
end
@ -36,11 +39,11 @@ control "aws_vpc properties" do
its('state') { should eq 'available' }
its('cidr_block') { should eq fixtures['vpc_non_default_cidr_block']}
its('instance_tenancy') { should eq fixtures['vpc_non_default_instance_tenancy']}
# TODO: figure out how to access the dhcp_options_id
its('dhcp_options_id') { should eq fixtures['vpc_non_default_dhcp_options_id']}
end
describe aws_vpc do
its('vpc_id') { should eq fixtures['ec2_security_group_default_vpc_id'] }
its('vpc_id') { should eq fixtures['vpc_default_vpc_id'] }
end
end
@ -49,7 +52,7 @@ control "aws_vpc matchers" do
it { should be_default }
end
describe aws_vpc(fixtures['ec2_security_group_default_vpc_id']) do
describe aws_vpc(fixtures['vpc_default_vpc_id']) do
it { should be_default }
end

View file

@ -1,5 +1,46 @@
fixtures = {}
[
'vpc_default_vpc_id',
'vpc_default_cidr_block',
'vpc_default_dhcp_options_id',
'vpc_non_default_id',
'vpc_non_default_cidr_block',
'vpc_non_default_instance_tenancy',
'vpc_non_default_dhcp_options_id',
].each do |fixture_name|
fixtures[fixture_name] = attribute(
fixture_name,
default: "default.#{fixture_name}",
description: 'See ../build/ec2.tf',
)
end
control "aws_vpcs recall" do
describe aws_vpcs do
it { should exist }
its('vpc_ids') { should include fixtures['vpc_default_vpc_id'] }
its('vpc_ids') { should include fixtures['vpc_non_default_vpc_id'] }
its('entries.count') { should cmp 2 }
end
end
control "aws_vpcs filtering" do
describe aws_vpcs.where { cidr_block.starts_with? '179'} do
its('vpc_ids.first') { should cmp fixtures['vpc_non_default_vpc_id'] }
end
describe aws_vpcs.where { dhcp_option_set_id == fixtures['vpc_default_dhcp_option_set_id']} do
its('vpc_ids') { should include fixtures['vpc_default_vpc_id']}
end
end
control "aws_vpcs properties" do
describe aws_vpcs do
its('vpc_ids') { should include fixtures['vpc_default_vpc_id'] }
its('vpc_ids') { should include fixtures['vpc_non_default_vpc_id'] }
its('cidr_blocks') { should include fixtures['vpc_default_cidr_block']}
its('cidr_blocks') { should include fixtures['vpc_non_default_cidr_block']}
its('dhcp_option_set_ids') { should include fixtures['vpc_default_dhcp_option_set_id']}
its('dhcp_option_set_ids') { should include fixtures['vpc_non_default_dhcp_option_set_id']}
end
end

View file

@ -71,7 +71,8 @@ class AwsVpcPropertiesTest < Minitest::Test
def test_property_vpc_id
assert_equal('vpc-aaaabbbb', AwsVpc.new('vpc-aaaabbbb').vpc_id)
assert_nil(AwsVpc.new(vpc_id: 'vpc-00000000').vpc_id)
# Even on a miss, identifiers should be preserved
assert_equal('vpc-00000000', AwsVpc.new(vpc_id: 'vpc-00000000').vpc_id)
end
def test_property_cidr_block

View file

@ -1,4 +1,5 @@
require 'helper'
require 'ipaddr'
# MAVPB = MockAwsVpcsPluralBackend
# Abbreviation not used outside this file
@ -23,30 +24,77 @@ end
#=============================================================================#
# Search / Recall
# Filter Criteria
#=============================================================================#
class AwsVpcsRecallEmptyTest < Minitest::Test
def setup
AwsVpcs::BackendFactory.select(MAVPB::Empty)
end
def test_search_miss_via_empty_vpcs
refute AwsVpcs.new.exists?
end
end
class AwsVpcsRecallBasicTest < Minitest::Test
class AwsVpcsFilterCriteriaTest < Minitest::Test
def setup
AwsVpcs::BackendFactory.select(MAVPB::Basic)
end
def test_search_hit_via_empty_filter
def test_filter_with_no_criteria
assert AwsVpcs.new.exists?
end
def test_filter_with_vpc_id
hit = AwsVpcs.new.where(:vpc_id => 'vpc-aaaabbbb')
assert(hit.exists?)
miss = AwsVpcs.new.where(:vpc_id => 'vpc-09876543')
refute(miss.exists?)
end
def test_filter_with_cidr_block
hit = AwsVpcs.new.where(:cidr_block => '10.0.0.0/16')
assert(hit.exists?)
# This triggers a bug/misfeature in FilterTable - see https://github.com/chef/inspec/issues/2929
# hit = AwsVpcs.new.where { IPAddr.new('10.0.0.0/8').include? cidr_block }
hit = AwsVpcs.new.where { cidr_block.start_with? '10' }
assert(hit.exists?)
assert_equal(2, hit.entries.count)
miss = AwsVpcs.new.where(:cidr_block => '11.0.0.0/16')
refute(miss.exists?)
end
def test_filter_with_dhcp_options_id
hit = AwsVpcs.new.where(:dhcp_options_id => 'dopt-aaaabbbb')
assert(hit.exists?)
miss = AwsVpcs.new.where(:dhcp_options_id => 'dopt-12345678')
refute(miss.exists?)
end
end
#=============================================================================#
# Properties
#=============================================================================#
class AwsVpcsProperties < Minitest::Test
def setup
AwsVpcs::BackendFactory.select(MAVPB::Basic)
@vpcs = AwsVpcs.new
end
def test_property_vpc_ids
assert_includes(@vpcs.vpc_ids, 'vpc-aaaabbbb')
assert_includes(@vpcs.vpc_ids, 'vpc-12344321')
refute_includes(@vpcs.vpc_ids, nil)
end
def test_property_cidr_blocks
assert_includes(@vpcs.cidr_blocks, '10.0.0.0/16')
assert_includes(@vpcs.cidr_blocks, '10.1.0.0/16')
refute_includes(@vpcs.cidr_blocks, nil)
end
def test_property_dhcp_options_ids
assert_includes(@vpcs.dhcp_options_ids, 'dopt-aaaabbbb')
# Should be de-duplicated
assert_equal(1, @vpcs.dhcp_options_ids.count)
end
end
#=============================================================================#
# Test Fixtures
#=============================================================================#
@ -70,7 +118,7 @@ module MAVPB
}),
OpenStruct.new({
cidr_block: '10.1.0.0/16',
dhcp_options_id: 'dopt-43211234',
dhcp_options_id: 'dopt-aaaabbbb', # Same as vpc-aaaabbbb
state: 'available',
vpc_id: 'vpc-12344321',
instance_tenancy: 'default',