mirror of
https://github.com/inspec/inspec
synced 2024-11-26 22:50:36 +00:00
aws_security_group: Query against other security group ids in allow_* matchers (#3576)
* add security-group to security-group rules * update docs * Add integration tests for security-group to security-group rules * rubocop fix * Add one security group rule, with position. * make control fit description Signed-off-by: Timothy van Zadelhoff <timothy.inspec@theothersolution.nl
This commit is contained in:
parent
8033134ebe
commit
5739cb2d6b
5 changed files with 297 additions and 5 deletions
|
@ -12,7 +12,6 @@ SGs are a networking construct which contain ingress and egress rules for networ
|
|||
|
||||
While this resource provides facilities for searching inbound and outbound rules on a variety of criteria, there is currently no support for performing matches based on:
|
||||
|
||||
* References to other Security Groups
|
||||
* References to VPC peers or other AWS services (that is, no support for searches based on 'prefix lists').
|
||||
|
||||
<br>
|
||||
|
@ -85,12 +84,36 @@ The following examples show how to use this InSpec audit resource.
|
|||
end
|
||||
|
||||
# If you have one rule with two CIDRs:
|
||||
it { should allow_out(ipv4_range: [ '10.7.23.12/32', '10.8.23.12/32' ] }
|
||||
it { should allow_out(ipv4_range: [ '10.7.23.12/32', '10.8.23.12/32' ]) }
|
||||
|
||||
# Expect exactly three rules.
|
||||
its('outbound_rules.count') { should cmp 3 }
|
||||
end
|
||||
|
||||
# Ensure that the canary_deployments Security Group only allows access from one specific security group id on port 443.
|
||||
describe aws_security_group(group_name: 'canary_deployments') do
|
||||
|
||||
it { should allow_in_only(port: 443, security_group: "sg-33334444") }
|
||||
end
|
||||
|
||||
# Ensure that one of the security groups in a list allows access from one of the groups in another list.
|
||||
# If you have two lists of groups, check if one of the groups in the first list allows access for one of the
|
||||
# groups in the second list.
|
||||
|
||||
control 'master to node access security group' do
|
||||
desc 'one of the node security groups should allow all access from:
|
||||
one of the master security groups, so masters can reach all nodes'
|
||||
describe.one do
|
||||
[ 'sg-11112222', 'sg-33334444' ].each do |nodeSG|
|
||||
[ 'sg-55556666', 'sg-77778888' ].each do |masterSG|
|
||||
describe aws_security_group(id: nodeSG) do
|
||||
it { should allow_in(security_group: masterSG) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Resource Parameters
|
||||
|
@ -260,6 +283,7 @@ The matchers accept a key-value list of search criteria. For a rule to match, i
|
|||
* position - A one-based index into the list of rules. If provided, this restricts the evaluation to the rule at that position. You may also use the special values `:first` and `:last`. `position` may also be used to enable `allow_in_only` and `allow_out_only` to work with multi-rule Security Groups.
|
||||
* protocol - Specifies the IP protocol. 'tcp', 'udp', and 'icmp' are some typical values. The string "-1" or 'any' is used to indicate any protocol.
|
||||
* to_port - Determines if a rule exists whose port range ends at the specified number. The word 'to_' does *not* relate to inbound/outbound directionality; it relates to the port range ("counting _to_"). `to_port` is an exact criterion; so if the rule allows 1000-2000 and you specify a `to_port` of 1999, it does not match.
|
||||
* security_group - Specifies a security-group id, to be checked as permissible origin (for `allow_in`) or destination (for `allow_out`) for traffic. Each AWS Security Group rule may have multiple allowed source or destination security groups.
|
||||
|
||||
describe aws_security_group(group_name: 'mixed-functionality-group') do
|
||||
# Allow RDP from defined range
|
||||
|
@ -283,6 +307,9 @@ The matchers accept a key-value list of search criteria. For a rule to match, i
|
|||
|
||||
# Do not allow unrestricted IPv4 access.
|
||||
it { should_not allow_in(ipv4_range: '0.0.0.0/0') }
|
||||
|
||||
# Allow unrestricted access from security-group.
|
||||
it { should allow_in(security_group: 'sg-11112222') }
|
||||
end
|
||||
|
||||
# Suppose you have a Group that should allow SSH and RDP from
|
||||
|
|
|
@ -41,9 +41,18 @@ class AwsSecurityGroup < Inspec.resource(1)
|
|||
private
|
||||
|
||||
def allow_only(rules, criteria)
|
||||
rules = allow__focus_on_position(rules, criteria)
|
||||
# allow_{in_out}_only require either a single-rule group, or you
|
||||
# to select a rule using position.
|
||||
return false unless rules.count == 1 || criteria.key?(:position)
|
||||
if criteria.key?(:security_group)
|
||||
if criteria.key?(:position)
|
||||
pos = criteria[:position] -1
|
||||
else
|
||||
pos = 0
|
||||
end
|
||||
return false unless rules[pos].key?(:user_id_group_pairs) && rules[pos][:user_id_group_pairs].count == 1
|
||||
end
|
||||
criteria[:exact] = true
|
||||
allow(rules, criteria)
|
||||
end
|
||||
|
@ -58,6 +67,7 @@ class AwsSecurityGroup < Inspec.resource(1)
|
|||
matched &&= allow__match_protocol(rule, criteria)
|
||||
matched &&= allow__match_ipv4_range(rule, criteria)
|
||||
matched &&= allow__match_ipv6_range(rule, criteria)
|
||||
matched &&= allow__match_security_group(rule, criteria)
|
||||
matched
|
||||
end
|
||||
end
|
||||
|
@ -67,6 +77,7 @@ class AwsSecurityGroup < Inspec.resource(1)
|
|||
:from_port,
|
||||
:ipv4_range,
|
||||
:ipv6_range,
|
||||
:security_group,
|
||||
:port,
|
||||
:position,
|
||||
:protocol,
|
||||
|
@ -187,6 +198,13 @@ class AwsSecurityGroup < Inspec.resource(1)
|
|||
match_ipv4_or_6_range(rule, criteria)
|
||||
end
|
||||
|
||||
def allow__match_security_group(rule, criteria)
|
||||
return true unless criteria.key?(:security_group)
|
||||
query = criteria[:security_group]
|
||||
return false unless rule[:user_id_group_pairs]
|
||||
rule[:user_id_group_pairs].any? { |group| query == group[:group_id] }
|
||||
end
|
||||
|
||||
def validate_params(raw_params)
|
||||
recognized_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
|
|
|
@ -222,6 +222,38 @@ output "ec2_security_group_alpha_group_name" {
|
|||
value = "${aws_security_group.alpha.name}"
|
||||
}
|
||||
|
||||
# Create another security group
|
||||
# in the default VPC
|
||||
resource "aws_security_group" "beta" {
|
||||
name = "${terraform.env}-beta"
|
||||
description = "SG beta"
|
||||
vpc_id = "${data.aws_vpc.default.id}"
|
||||
}
|
||||
|
||||
output "ec2_security_group_beta_group_id" {
|
||||
value = "${aws_security_group.beta.id}"
|
||||
}
|
||||
|
||||
output "ec2_security_group_beta_group_name" {
|
||||
value = "${aws_security_group.beta.name}"
|
||||
}
|
||||
|
||||
# Create third security group
|
||||
# in the default VPC
|
||||
resource "aws_security_group" "gamma" {
|
||||
name = "${terraform.env}-gamma"
|
||||
description = "SG gamma"
|
||||
vpc_id = "${data.aws_vpc.default.id}"
|
||||
}
|
||||
|
||||
output "ec2_security_group_gamma_group_id" {
|
||||
value = "${aws_security_group.gamma.id}"
|
||||
}
|
||||
|
||||
output "ec2_security_group_gamma_group_name" {
|
||||
value = "${aws_security_group.gamma.name}"
|
||||
}
|
||||
|
||||
# NOTE: AWS (in the console and CLI) creates SGs with a default
|
||||
# allow all egress. Terraform removes that rule, unless you specify it here.
|
||||
|
||||
|
@ -273,6 +305,44 @@ resource "aws_security_group_rule" "alpha_piv6_all_ports" {
|
|||
security_group_id = "${aws_security_group.alpha.id}"
|
||||
}
|
||||
|
||||
# Populate SG Beta with some rules
|
||||
resource "aws_security_group_rule" "beta_http_world" {
|
||||
type = "ingress"
|
||||
from_port = "80"
|
||||
to_port = "80"
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
security_group_id = "${aws_security_group.beta.id}"
|
||||
}
|
||||
|
||||
resource "aws_security_group_rule" "beta_ssh_in_alfa" {
|
||||
type = "ingress"
|
||||
from_port = "22"
|
||||
to_port = "22"
|
||||
protocol = "tcp"
|
||||
source_security_group_id = "${aws_security_group.alpha.id}"
|
||||
security_group_id = "${aws_security_group.beta.id}"
|
||||
}
|
||||
|
||||
resource "aws_security_group_rule" "beta_all_ports_in_gamma" {
|
||||
type = "ingress"
|
||||
from_port = "0"
|
||||
to_port = "65535"
|
||||
protocol = "tcp"
|
||||
source_security_group_id = "${aws_security_group.gamma.id}"
|
||||
security_group_id = "${aws_security_group.beta.id}"
|
||||
}
|
||||
|
||||
# Populate SG Gamma with a rule
|
||||
resource "aws_security_group_rule" "gamma_ssh_in_alfa" {
|
||||
type = "ingress"
|
||||
from_port = "22"
|
||||
to_port = "22"
|
||||
protocol = "tcp"
|
||||
source_security_group_id = "${aws_security_group.alpha.id}"
|
||||
security_group_id = "${aws_security_group.gamma.id}"
|
||||
}
|
||||
|
||||
#============================================================#
|
||||
# VPC Subnets
|
||||
#============================================================#
|
||||
|
|
|
@ -3,6 +3,8 @@ fixtures = {}
|
|||
'ec2_security_group_default_vpc_id',
|
||||
'ec2_security_group_default_group_id',
|
||||
'ec2_security_group_alpha_group_id',
|
||||
'ec2_security_group_beta_group_id',
|
||||
'ec2_security_group_gamma_group_id',
|
||||
'ec2_security_group_alpha_group_name',
|
||||
].each do |fixture_name|
|
||||
fixtures[fixture_name] = attribute(
|
||||
|
@ -74,4 +76,11 @@ control "aws_security_group matchers" do
|
|||
it { should allow_out(ipv6_range: ["2001:db8::/122"])}
|
||||
it { should allow_out(ipv6_range: ["2001:db8::/122"], from_port: 6000, to_port: 6007)}
|
||||
end
|
||||
describe aws_security_group(fixtures['ec2_security_group_beta_group_id']) do
|
||||
it { should allow_in(port: 22, security_group: fixtures['ec2_security_group_alpha_group_id']) }
|
||||
it { should allow_in(security_group: fixtures['ec2_security_group_gamma_group_id']) }
|
||||
end
|
||||
describe aws_security_group(fixtures['ec2_security_group_gamma_group_id']) do
|
||||
it { should allow_in_only(port: 22, security_group: fixtures['ec2_security_group_alpha_group_id']) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -123,6 +123,7 @@ class AwsSGSProperties < Minitest::Test
|
|||
:position,
|
||||
:protocol,
|
||||
:to_port,
|
||||
:security_group,
|
||||
].each do |criterion|
|
||||
# No errors here
|
||||
sg.allow_in?(criterion => 'dummy')
|
||||
|
@ -133,7 +134,7 @@ class AwsSGSProperties < Minitest::Test
|
|||
sg = AwsSecurityGroup.new('sg-aaaabbbb')
|
||||
rules = sg.inbound_rules
|
||||
assert_equal(0, rules.count)
|
||||
refute(sg.allow_in?()) # Should we test this - "open" crieria?
|
||||
refute(sg.allow_in?()) # Should we test this - "open" criteria?
|
||||
end
|
||||
|
||||
def test_matcher_allow_inbound_complex
|
||||
|
@ -200,14 +201,40 @@ class AwsSGSProperties < Minitest::Test
|
|||
# Test _only with a single rule group for IPv6
|
||||
sg = AwsSecurityGroup.new('sg-33334444')
|
||||
assert_equal(1, sg.inbound_rules.count, "count the number of rules for 1-rule ipv6 group")
|
||||
assert_equal(1, sg.inbound_rules_count, "Count the number of rule variants for 1-rule gipv6 roup")
|
||||
assert_equal(1, sg.inbound_rules_count, "Count the number of rule variants for 1-rule ipv6 group")
|
||||
assert(sg.allow_in_only?(ipv6_range: "::/0"), "Match IP range using _only on 1-rule ipv6 group")
|
||||
assert(sg.allow_in_only?(protocol: 'any'), "Match protocol using _only on 1-rule ipv6 group")
|
||||
refute(sg.allow_in_only?(port: 22), "no match port using _only on 1-rule ipv6 group")
|
||||
|
||||
# security-group
|
||||
sg = AwsSecurityGroup.new('sg-55556666')
|
||||
assert(sg.allow_in?(security_group: "sg-33334441"), "match on group-id")
|
||||
assert(sg.allow_in?(security_group: "sg-33334441", port: 22), "match on group-id, numeric port")
|
||||
assert(sg.allow_in?(security_group: "sg-33334441", port: "22"), "match on group-id, string port")
|
||||
assert(sg.allow_in?(security_group: "sg-33334441", to_port: "22", from_port: "22"), "match on group-id, to/from port")
|
||||
assert(sg.allow_in?(port: 9002, position: 3), "range matching on port with allow_in")
|
||||
refute(sg.allow_in_only?(port: 9002, position: 3), "no range matching on port with allow_in_only")
|
||||
refute(sg.allow_in_only?(security_group: "sg-33334441",), "no matching on group with allow_in_only when multiple group rules")
|
||||
assert(sg.allow_in_only?(from_port: 9001, to_port: 9003, position: 3), "exact range matching on port with allow_in_only")
|
||||
|
||||
# Test _only with a single rule group for security-group
|
||||
sg = AwsSecurityGroup.new('sg-33334441')
|
||||
assert_equal(1, sg.inbound_rules.count, "count the number of rules for 1-rule security-group")
|
||||
assert_equal(1, sg.inbound_rules_count, "Count the number of rule variants for 1-rule security-group")
|
||||
assert(sg.allow_in_only?(security_group: "sg-33334444"), "Match security-group using _only on 1-rule security-group")
|
||||
assert(sg.allow_in_only?(protocol: 'any',security_group: "sg-33334444"), "Match protocol using _only on 1-rule security-group")
|
||||
refute(sg.allow_in_only?(port: 22, security_group: "sg-33334444"), "no match port using _only on 1-rule security-group")
|
||||
|
||||
# Test _only with a single rule group for security-group with position pinning
|
||||
sg = AwsSecurityGroup.new('sg-33334442')
|
||||
assert(sg.allow_in_only?(security_group: "sg-33334444", position: 2), "Match security-group using _only with numerical position")
|
||||
assert(sg.allow_in_only?(protocol: 'any',security_group: "sg-33334444", position: 2), "Match protocol using _only on 1-rule security-group with numerical position")
|
||||
refute(sg.allow_in_only?(port: 22, security_group: "sg-33334444", position: 2), "no match port using _only on 1-rule security-group with numerical position")
|
||||
assert(sg.allow_in_only?(security_group: "sg-33334444", position: "2"), "Match security-group using _only with string position")
|
||||
assert(sg.allow_in_only?(security_group: "sg-33334444", position: :last), "Match security-group using _only with last position")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#=============================================================================#
|
||||
# Test Fixtures
|
||||
#=============================================================================#
|
||||
|
@ -320,6 +347,147 @@ module AwsMESGSB
|
|||
}),
|
||||
],
|
||||
ip_permissions_egress: [],
|
||||
}),
|
||||
OpenStruct.new({
|
||||
description: 'Open for group one group rule second position',
|
||||
group_id: 'sg-33334442',
|
||||
group_name: 'etha',
|
||||
vpc_id: 'vpc-12345678',
|
||||
ip_permissions: [
|
||||
OpenStruct.new({
|
||||
from_port: nil,
|
||||
to_port: nil,
|
||||
ip_protocol: "-1",
|
||||
ipv_6_ranges: [
|
||||
{cidr_ipv_6:"::/0"},
|
||||
]
|
||||
}),
|
||||
OpenStruct.new({
|
||||
from_port: nil,
|
||||
to_port: nil,
|
||||
ip_protocol: "-1",
|
||||
user_id_group_pairs: [
|
||||
OpenStruct.new({
|
||||
description: 'Open for group one rule second position',
|
||||
group_id: 'sg-33334444',
|
||||
group_name: 'delta',
|
||||
peering_status: "",
|
||||
user_id: "123456789012",
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
]
|
||||
}),
|
||||
],
|
||||
ip_permissions_egress: [],
|
||||
}),
|
||||
OpenStruct.new({
|
||||
description: 'Open for group one rule',
|
||||
group_id: 'sg-33334441',
|
||||
group_name: 'zeta',
|
||||
vpc_id: 'vpc-12345678',
|
||||
ip_permissions: [
|
||||
OpenStruct.new({
|
||||
from_port: nil,
|
||||
to_port: nil,
|
||||
ip_protocol: "-1",
|
||||
user_id_group_pairs: [
|
||||
OpenStruct.new({
|
||||
description: 'Open for group one rule',
|
||||
group_id: 'sg-33334444',
|
||||
group_name: 'delta',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
]
|
||||
}),
|
||||
],
|
||||
ip_permissions_egress: [],
|
||||
}),
|
||||
OpenStruct.new({
|
||||
description: 'Open for group',
|
||||
group_id: 'sg-55556666',
|
||||
group_name: 'epsilon',
|
||||
vpc_id: 'vpc-12345678',
|
||||
ip_permissions: [
|
||||
OpenStruct.new({
|
||||
from_port: 80,
|
||||
to_port: 443,
|
||||
ip_protocol: "-1",
|
||||
ip_ranges: [
|
||||
{cidr_ip:"0.0.0.0/0"},
|
||||
]
|
||||
}),
|
||||
OpenStruct.new({
|
||||
from_port: 22,
|
||||
to_port: 22,
|
||||
ip_protocol: "-1",
|
||||
user_id_group_pairs: [
|
||||
OpenStruct.new({
|
||||
description: 'Open for group rule 2',
|
||||
group_id: 'sg-33334441',
|
||||
group_name: 'zeta',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
]
|
||||
}),
|
||||
OpenStruct.new({
|
||||
from_port: 9001,
|
||||
to_port: 9003,
|
||||
ip_protocol: "-1",
|
||||
user_id_group_pairs: [
|
||||
OpenStruct.new({
|
||||
description: 'Open for group rule 3',
|
||||
group_id: 'sg-33334441',
|
||||
group_name: 'zeta',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
]
|
||||
}),
|
||||
OpenStruct.new({
|
||||
from_port: nil,
|
||||
to_port: nil,
|
||||
ip_protocol: "-1",
|
||||
user_id_group_pairs: [
|
||||
OpenStruct.new({
|
||||
description: 'allow all from multiple sg',
|
||||
group_id: 'sg-33334441',
|
||||
group_name: 'zeta',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
OpenStruct.new({
|
||||
description: 'allow all from multiple sg[2]',
|
||||
group_id: 'sg-33334442',
|
||||
group_name: 'etha',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
OpenStruct.new({
|
||||
description: 'allow all from multiple sg[3]',
|
||||
group_id: 'sg-11112222',
|
||||
group_name: 'theta',
|
||||
peering_status: "",
|
||||
user_id: '123456789012',
|
||||
vpc_id: "",
|
||||
vpc_peering_connection_id: ""
|
||||
}),
|
||||
]
|
||||
}),
|
||||
],
|
||||
ip_permissions_egress: [],
|
||||
}), ]
|
||||
|
||||
selected = fixtures.select do |sg|
|
||||
|
|
Loading…
Reference in a new issue