mirror of
https://github.com/inspec/inspec
synced 2024-11-23 13:13:22 +00:00
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:
parent
2d6bb1b84a
commit
f09d4f5266
4 changed files with 484 additions and 0 deletions
139
docs/resources/aws_iam_policy.md
Normal file
139
docs/resources/aws_iam_policy.md
Normal 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
114
libraries/aws_iam_policy.rb
Normal 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
|
29
test/integration/default/verify/controls/aws_iam_policy.rb
Normal file
29
test/integration/default/verify/controls/aws_iam_policy.rb
Normal 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
|
202
test/unit/resources/aws_iam_policy_test.rb
Normal file
202
test/unit/resources/aws_iam_policy_test.rb
Normal 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
|
Loading…
Reference in a new issue