inspec/test/unit/resources/aws_iam_policy_test.rb
Clinton Wolfe 44c0fd2e4f
Accept symbols and downcased criteria in aws_iam_policy have_statement matcher (#3129)
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
2018-06-21 14:19:56 -04:00

452 lines
No EOL
20 KiB
Ruby

require 'helper'
# MAIPSB = MockAwsIamPolicySingularBackend
# Abbreviation not used outside this file
#=============================================================================#
# Constructor Tests
#=============================================================================#
class AwsIamPolicyConstructorTest < Minitest::Test
def setup
AwsIamPolicy::BackendFactory.select(MAIPSB::Empty)
end
def test_rejects_empty_params
assert_raises(ArgumentError) { AwsIamPolicy.new }
end
def test_accepts_policy_name_as_scalar
AwsIamPolicy.new('test-policy-1')
end
def test_accepts_policy_name_as_hash
AwsIamPolicy.new(policy_name: 'test-policy-1')
end
def test_rejects_unrecognized_params
assert_raises(ArgumentError) { AwsIamPolicy.new(shoe_size: 9) }
end
end
#=============================================================================#
# Search / Recall
#=============================================================================#
class AwsIamPolicyRecallTest < Minitest::Test
def setup
AwsIamPolicy::BackendFactory.select(MAIPSB::Basic)
end
def test_search_hit_via_scalar_works
assert AwsIamPolicy.new('test-policy-1').exists?
end
def test_search_hit_via_hash_works
assert AwsIamPolicy.new(policy_name: 'test-policy-1').exists?
end
def test_search_miss_is_not_an_exception
refute AwsIamPolicy.new(policy_name: 'non-existant').exists?
end
end
#=============================================================================#
# Properties
#=============================================================================#
class AwsIamPolicyPropertiesTest < Minitest::Test
def setup
AwsIamPolicy::BackendFactory.select(MAIPSB::Basic)
end
def test_property_arn
assert_equal('arn:aws:iam::aws:policy/test-policy-1', AwsIamPolicy.new('test-policy-1').arn)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').arn)
end
def test_property_default_version_id
assert_equal('v1', AwsIamPolicy.new('test-policy-1').default_version_id)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').default_version_id)
end
def test_property_attachment_count
assert_equal(3, AwsIamPolicy.new('test-policy-1').attachment_count)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attachment_count)
end
def test_property_attached_users
assert_equal(['test-user'], AwsIamPolicy.new('test-policy-1').attached_users)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_users)
end
def test_property_attached_groups
assert_equal(['test-group'], AwsIamPolicy.new('test-policy-1').attached_groups)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_groups)
end
def test_property_attached_roles
assert_equal(['test-role'], AwsIamPolicy.new('test-policy-1').attached_roles)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_roles)
end
def test_property_policy
policy = AwsIamPolicy.new('test-policy-1').policy
assert_kind_of(Hash, policy)
assert(policy.key?('Statement'), "test-policy-1 should have a Statement key when unpacked")
assert_equal(1, policy['Statement'].count, "test-policy-1 should have 1 statements when unpacked")
assert_nil(AwsIamPolicy.new('non-existant').policy)
end
def test_property_statement_count
assert_nil(AwsIamPolicy.new('non-existant').statement_count)
assert_equal(1, AwsIamPolicy.new('test-policy-1').statement_count)
assert_equal(2, AwsIamPolicy.new('test-policy-2').statement_count)
assert_equal(1, AwsIamPolicy.new('test-policy-3').statement_count)
end
end
#=============================================================================#
# Matchers
#=============================================================================#
class AwsIamPolicyMatchersTest < Minitest::Test
def setup
AwsIamPolicy::BackendFactory.select(MAIPSB::Basic)
end
def test_matcher_attached_positive
assert AwsIamPolicy.new('test-policy-1').attached?
end
def test_matcher_attached_negative
refute AwsIamPolicy.new('test-policy-2').attached?
end
def test_matcher_attached_to_user_positive
assert AwsIamPolicy.new('test-policy-1').attached_to_user?('test-user')
end
def test_matcher_attached_to_user_negative
refute AwsIamPolicy.new('test-policy-2').attached_to_user?('test-user')
end
def test_matcher_attached_to_group_positive
assert AwsIamPolicy.new('test-policy-1').attached_to_group?('test-group')
end
def test_matcher_attached_to_group_negative
refute AwsIamPolicy.new('test-policy-2').attached_to_group?('test-group')
end
def test_matcher_attached_to_role_positive
assert AwsIamPolicy.new('test-policy-1').attached_to_role?('test-role')
end
def test_matcher_attached_to_role_negative
refute AwsIamPolicy.new('test-policy-2').attached_to_role?('test-role')
end
def test_have_statement_when_policy_does_not_exist
assert_nil AwsIamPolicy.new('nonesuch').has_statement?('Effect' => 'foo')
end
def test_have_statement_when_provided_no_criteria
AwsIamPolicy.new('test-policy-1').has_statement?
end
def test_have_statement_when_provided_acceptable_criteria
{
'Action' => 'dummy',
'Effect' => 'Deny', # This has restictions on the value provided
'Resource' => 'dummy',
'Sid' => 'dummy',
}.each do |criterion, test_value|
[
criterion,
criterion.downcase,
criterion.to_sym,
criterion.downcase.to_sym
].each do |variant|
AwsIamPolicy.new('test-policy-1').has_statement?(variant => test_value)
end
end
end
def test_have_statement_when_provided_unimplemented_criteria
[
'Conditional',
'NotAction',
'NotPrincipal',
'NotResource',
'Principal'
].each do |criterion|
ex = assert_raises(ArgumentError) {AwsIamPolicy.new('test-policy-1').has_statement?(criterion => 'dummy')}
assert_match(/not supported/, ex.message)
end
end
def test_have_statement_when_provided_unrecognized_criteria
ex = assert_raises(ArgumentError) {AwsIamPolicy.new('test-policy-1').has_statement?('foo' => 'dummy')}
assert_match(/Unrecognized/, ex.message)
end
def test_have_statement_when_sid_is_provided
['Sid', 'sid', :Sid, :sid].each do |variant|
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'beta01'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'CloudWatchEventsFullAccess'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'IAMPassRoleForCloudWatchEvents'))
refute(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'beta01'))
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => /eta/))
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => /CloudWatch/))
refute(AwsIamPolicy.new('test-policy-2').has_statement?(variant => /eta/))
end
end
def test_have_statement_when_effect_is_provided
['Effect','effect',:Effect,:effect].each do |variant|
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'Deny'))
refute(AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'Allow'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'Allow'))
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'Disallow') }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'allow') }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?(variant => :Allow) }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?(variant => :allow) }
end
end
def test_have_statement_when_action_is_provided
['Action', 'action', :Action, :action].each do |variant|
# Able to match a simple string action when multiple statements present
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'iam:PassRole'))
# Able to match a wildcard string action
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'events:*'))
# Do not match a wildcard when using strings
refute(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'events:EnableRule'))
# Do match when using a regex
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => /^events\:/))
# Able to match one action when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'ec2:DescribeSubnets'))
# Do not match if only one action specified as an array when the statement has an array of actions
refute(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['ec2:DescribeSubnets']))
# Do match if two actions specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['ec2:DescribeSubnets', 'ec2:DescribeSecurityGroups']))
# Do match setwise if two actions specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['ec2:DescribeSecurityGroups', 'ec2:DescribeSubnets']))
# Do match if only one regex action specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => /^ec2\:Describe/))
# Do match if one regex action specified in an array when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => [/^ec2\:Describe/]))
# Able to match a degenerate policy doc in which there is exactly one statement as a hash.
assert(AwsIamPolicy.new('test-policy-3').has_statement?(variant => 'acm:GetCertificate'))
# Don't explode, and also don't match, if a policy has a statement without an Action
refute(AwsIamPolicy.new('test-policy-4').has_statement?(variant => 'iam:*'))
end
end
def test_have_statement_when_resource_is_provided
['Resource', 'resource', :Resource, :resource].each do |variant|
# Able to match a simple string resource when multiple statements present
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'arn:aws:iam::*:role/AWS_Events_Invoke_Targets'))
# Able to match a wildcard string resource
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => '*'))
# Do not match a wildcard when using strings
refute(AwsIamPolicy.new('test-policy-2').has_statement?(variant => 'arn:aws:events:us-east-1:123456789012:rule/my-rule'))
# Do match when using a regex
assert(AwsIamPolicy.new('test-policy-2').has_statement?(variant => /AWS_Events_Invoke_Targets$/))
# Able to match one resource when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => 'arn:aws:ec2:::*'))
# Do not match if only one resource specified as an array when the statement has an array of resources
refute(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['arn:aws:ec2:::*']))
# Do match if two resources specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['arn:aws:ec2:::*', '*']))
# Do match setwise if two resources specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => ['*', 'arn:aws:ec2:::*']))
# Do match if only one regex resource specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => /^arn\:aws\:ec2/))
# Do match if one regex resource specified in an array when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?(variant => [/\*/]))
# Able to match a degenerate policy doc in which there is exactly one statement as a hash.
assert(AwsIamPolicy.new('test-policy-3').has_statement?(variant => '*'))
end
end
end
#=============================================================================#
# Test Fixtures
#=============================================================================#
module MAIPSB
class Empty < AwsBackendBase
def list_policies(query)
OpenStruct.new(policies: [])
end
end
class Basic < AwsBackendBase
def list_policies(query)
fixtures = [
OpenStruct.new({
policy_name: 'test-policy-1',
arn: 'arn:aws:iam::aws:policy/test-policy-1',
default_version_id: 'v1',
attachment_count: 3,
is_attachable: true,
}),
OpenStruct.new({
policy_name: 'test-policy-2',
arn: 'arn:aws:iam::aws:policy/test-policy-2',
default_version_id: 'v1',
attachment_count: 0,
is_attachable: false,
}),
OpenStruct.new({
policy_name: 'test-policy-3',
arn: 'arn:aws:iam::aws:policy/test-policy-3',
default_version_id: 'v1',
attachment_count: 0,
is_attachable: true,
}),
OpenStruct.new({
policy_name: 'test-policy-4',
arn: 'arn:aws:iam::aws:policy/test-policy-4',
default_version_id: 'v1',
attachment_count: 0,
is_attachable: false,
}),
]
OpenStruct.new({ policies: fixtures })
end
def list_entities_for_policy(query)
policy = {}
policy['arn:aws:iam::aws:policy/test-policy-1'] =
{
policy_groups: [
OpenStruct.new({
group_name: 'test-group',
group_id: 'AIDAIJ3FUBXLZ4VXV34LE',
}),
],
policy_users: [
OpenStruct.new({
user_name: 'test-user',
user_id: 'AIDAIJ3FUBXLZ4VXV34LE',
}),
],
policy_roles: [
OpenStruct.new({
role_name: 'test-role',
role_id: 'AIDAIJ3FUBXLZ4VXV34LE',
}),
],
}
policy['arn:aws:iam::aws:policy/test-policy-2'] =
{
policy_groups: [],
policy_users: [],
policy_roles: [],
}
OpenStruct.new( policy[query[:policy_arn]] )
end
def get_policy_version(query)
fixtures = {
'arn:aws:iam::aws:policy/test-policy-1' => {
'v1' => OpenStruct.new(
# This is the integration test fixture "beta"
# {
# "Version"=>"2012-10-17",
# "Statement"=> [
# {
# "Sid"=>"beta01",
# "Action"=>["ec2:DescribeSubnets", "ec2:DescribeSecurityGroups"],
# "Effect"=>"Deny",
# "Resource"=>["arn:aws:ec2:::*", "*"]
# }
# ]
# }
document: '%7B%0A%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%22Sid%22%3A%20%22beta01%22%2C%0A%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22ec2%3ADescribeSubnets%22%2C%0A%20%20%20%20%20%20%20%20%22ec2%3ADescribeSecurityGroups%22%0A%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%22Effect%22%3A%20%22Deny%22%2C%0A%20%20%20%20%20%20%22Resource%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22arn%3Aaws%3Aec2%3A%3A%3A%2A%22%2C%0A%20%20%20%20%20%20%20%20%22%2A%22%0A%20%20%20%20%20%20%5D%0A%20%20%20%20%7D%0A%20%20%5D%0A%7D%0A'
)
},
'arn:aws:iam::aws:policy/test-policy-2' => {
'v1' => OpenStruct.new(
# This is AWS-managed CloudWatchEventsFullAccess
# {
# "Version"=>"2012-10-17",
# "Statement"=> [
# {
# "Sid"=>"CloudWatchEventsFullAccess",
# "Effect"=>"Allow",
# "Action"=>"events:*",
# "Resource"=>"*"
# },
# {
# "Sid"=>"IAMPassRoleForCloudWatchEvents",
# "Effect"=>"Allow",
# "Action"=>"iam:PassRole",
# "Resource"=>"arn:aws:iam::*:role/AWS_Events_Invoke_Targets"
# }
# ]
# }
document: '%7B%0A%20%20%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sid%22%3A%20%22CloudWatchEventsFullAccess%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%22events%3A%2A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sid%22%3A%20%22IAMPassRoleForCloudWatchEvents%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%22iam%3APassRole%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22arn%3Aaws%3Aiam%3A%3A%2A%3Arole%2FAWS_Events_Invoke_Targets%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%0A%7D'
)
},
'arn:aws:iam::aws:policy/test-policy-3' => {
'v1' => OpenStruct.new(
# This is AWS-managed AWSCertificateManagerReadOnly
# {
# "Version": "2012-10-17",
# "Statement": {
# "Effect": "Allow",
# "Action": [
# "acm:DescribeCertificate",
# "acm:ListCertificates",
# "acm:GetCertificate",
# "acm:ListTagsForCertificate"
# ],
# "Resource": "*"
# }
# }
document: '%7B%0A%20%20%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%20%20%22Statement%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22acm%3ADescribeCertificate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22acm%3AListCertificates%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22acm%3AGetCertificate%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22acm%3AListTagsForCertificate%22%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%7D%0A%7D',
)
},
'arn:aws:iam::aws:policy/test-policy-4' => {
'v1' => OpenStruct.new(
# This is arn:aws:iam::aws:policy/PowerUserAccess
# {
# "Version": "2012-10-17",
# "Statement": [
# {
# "Effect": "Allow",
# "NotAction": [
# "iam:*",
# "organizations:*"
# ],
# "Resource": "*"
# },
# {
# "Effect": "Allow",
# "Action": [
# "iam:CreateServiceLinkedRole",
# "iam:DeleteServiceLinkedRole",
# "iam:ListRoles",
# "organizations:DescribeOrganization"
# ],
# "Resource": "*"
# }
# ]
# }
document: '%7B%0A%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%22NotAction%22%3A%20%5B%22iam%3A%2A%22%2C%20%22organizations%3A%2A%22%5D%2C%0A%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%7D%2C%7B%0A%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%22iam%3ACreateServiceLinkedRole%22%2C%0A%20%20%20%20%20%20%20%20%20%20%22iam%3ADeleteServiceLinkedRole%22%2C%0A%20%20%20%20%20%20%20%20%20%20%22iam%3AListRoles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%22organizations%3ADescribeOrganization%22%0A%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%7D%0A%20%20%5D%0A%7D',
)
},
}
pv = fixtures.dig(query[:policy_arn], query[:version_id])
return OpenStruct.new(policy_version: pv) if pv
raise Aws::IAM::Errors::NoSuchEntity.new(nil, nil)
end
end
end