inspec/test/unit/resources/aws_iam_policy_test.rb
Clinton Wolfe cdfb325ca3 Apply chefstyle -a, and manually remove duplicate requires
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
2020-12-21 13:25:04 -05:00

455 lines
20 KiB
Ruby

require "helper"
require "inspec/resource"
require "resources/aws/aws_iam_policy"
require "resource_support/aws"
# 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
%w{
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