aws_iam_policy resource (#184)

Signed-off-by: Rony Xavier <rx294@nyu.edu>
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Rony Xavier 2018-01-26 15:21:49 -05:00 committed by Clinton Wolfe
parent 2d6bb1b84a
commit f09d4f5266
4 changed files with 484 additions and 0 deletions

View file

@ -0,0 +1,139 @@
---
title: About the aws_iam_policy Resource
---
# aws_iam_policy
Use the `aws_iam_policy` InSpec audit resource to test properties of a single managed AWS IAM Policy.
A policy is an entity in AWS that, when attached to an identity or resource, defines their permissions. AWS evaluates these policies when a principal, such as a user, makes a request. Permissions in the policies determine whether the request is allowed or denied.
Each IAM Policy is uniquely identified by either its policy_name or arn.
<br>
## Syntax
An `aws_iam_policy` resource block identifies a policy by policy name.
# Find a policy by name
describe aws_iam_policy('AWSSupportAccess') do
it { should exist }
end
# Find a customer-managed by name
describe aws_iam_policy('customer-managed-policy') do
it { should exist }
end
# Hash syntax for policy name
describe aws_iam_policy(policy_name: 'AWSSupportAccess') do
it { should exist }
end
<br>
## Examples
The following examples show how to use this InSpec audit resource.
### Test that a policy does exist
describe aws_iam_policy('AWSSupportAccess') do
it { should exist }
end
### Test that a policy is attached to at least one entity
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached }
end
<br>
## Properties
### arn
"The ARN identifier of the specified policy. An ARN uniquely identifies the policy within AWS."
describe aws_iam_policy('AWSSupportAccess') do
its('arn') { should cmp "arn:aws:iam::aws:policy/AWSSupportAccess" }
end
### default_version_id
The default_version_id value of the specified policy.
describe aws_iam_policy('AWSSupportAccess') do
its('default_version_id') { should cmp "v1" }
end
### attachment_count
The count of attached entities for the specified policy.
describe aws_iam_policy('AWSSupportAccess') do
its('attachment_count') { should cmp 1 }
end
### attached_users
The list of usernames of the users attached to the policy.
describe aws_iam_policy('AWSSupportAccess') do
its('attached_users') { should include "test-user" }
end
### attached_groups
The list of groupnames of the groups attached to the policy.
describe aws_iam_policy('AWSSupportAccess') do
its('attached_groups') { should include "test-group" }
end
### attached_roles
The list of rolenames of the roles attached to the policy.
describe aws_iam_policy('AWSSupportAccess') do
its('attached_roles') { should include "test-role" }
end
## Matchers
This InSpec audit resource has the following special matchers. For a full list of available matchers (such as `exist`) please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
### be_attached
The test will pass if the identified policy is attached to at least one IAM user, group, or role.
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached }
end
### be_attached_to_user(USERNAME)
The test will pass if the identified policy attached the specified user.
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached_to_user(USERNAME) }
end
### be_attached_to_role(ROLENAME)
The test will pass if the identified policy attached the specified role.
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached_to_role(ROLENAME) }
end
### be_attached_to_group(GROUPNAME)
The test will pass if the identified policy attached the specified group.
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached_to_group(GROUPNAME) }
end

114
libraries/aws_iam_policy.rb Normal file
View file

@ -0,0 +1,114 @@
class AwsIamPolicy < Inspec.resource(1)
name 'aws_iam_policy'
desc 'Verifies settings for individual AWS IAM Policy'
example "
describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached }
end
"
include AwsResourceMixin
attr_reader :arn, :default_version_id, :attachment_count
def to_s
"Policy #{@policy_name}"
end
def attached?
!attachment_count.zero?
end
def attached_users
return @attached_users if defined? @attached_users
fetch_attached_entities
@attached_users
end
def attached_groups
return @attached_groups if defined? @attached_groups
fetch_attached_entities
@attached_groups
end
def attached_roles
return @attached_roles if defined? @attached_roles
fetch_attached_entities
@attached_roles
end
def attached_to_user?(user_name)
attached_users.include?(user_name)
end
def attached_to_group?(group_name)
attached_groups.include?(group_name)
end
def attached_to_role?(role_name)
attached_roles.include?(role_name)
end
private
def validate_params(raw_params)
validated_params = check_resource_param_names(
raw_params: raw_params,
allowed_params: [:policy_name],
allowed_scalar_name: :policy_name,
allowed_scalar_type: String,
)
if validated_params.empty?
raise ArgumentError, "You must provide the parameter 'policy_name' to aws_iam_policy."
end
validated_params
end
def fetch_from_aws
backend = AwsIamPolicy::BackendFactory.create
criteria = { max_items: 1000 } # maxItems max value is 1000
resp = backend.list_policies(criteria)
@policy = resp.policies.detect do |policy|
policy.policy_name == @policy_name
end
@exists = !@policy.nil?
return unless @exists
@arn = @policy[:arn]
@default_version_id = @policy[:default_version_id]
@attachment_count = @policy[:attachment_count]
end
def fetch_attached_entities
unless @exists
@attached_groups = nil
@attached_users = nil
@attached_roles = nil
return
end
backend = AwsIamPolicy::BackendFactory.create
criteria = { policy_arn: arn }
resp = backend.list_entities_for_policy(criteria)
@attached_groups = resp.policy_groups.map(&:group_name)
@attached_users = resp.policy_users.map(&:user_name)
@attached_roles = resp.policy_roles.map(&:role_name)
end
class Backend
class AwsClientApi
BackendFactory.set_default_backend(self)
def list_policies(criteria)
AWSConnection.new.iam_client.list_policies(criteria)
end
def list_entities_for_policy(criteria)
AWSConnection.new.iam_client.list_entities_for_policy(criteria)
end
end
end
end

View file

@ -0,0 +1,29 @@
control "aws_iam_policy recall" do
describe aws_iam_policy("AWSSupportAccess") do
it { should exist }
end
describe aws_iam_policy(policy_name: "AWSSupportAccess") do
it { should exist }
end
end
control "aws_iam_policy properties" do
describe aws_iam_policy("AdministratorAccess") do
its('arn') { should cmp "arn:aws:iam::aws:policy/AdministratorAccess" }
its('default_version_id') { should cmp 'v1' }
its('attachment_count') { should cmp 1 }
its('attached_users') { should include "test-fixture-maker" }
its('attached_groups') { should be_empty }
its('attached_roles') { should be_empty }
end
end
control "aws_iam_policy matchers" do
describe aws_iam_policy("AdministratorAccess") do
it { should be_attached }
end
describe aws_iam_policy("AdministratorAccess") do
it { should be_attached_to_user("test-fixture-maker") }
end
end

View file

@ -0,0 +1,202 @@
require 'helper'
require 'aws_iam_policy'
# 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
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
end
#=============================================================================#
# Test Fixtures
#=============================================================================#
module MAIPSB
class Empty < AwsIamPolicy::Backend
def list_policies(query)
OpenStruct.new(policies: [])
end
end
class Basic < AwsIamPolicy::Backend
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: 'v2',
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
end
end