From 7850202878af76a86acacca4891502c80fe83ead Mon Sep 17 00:00:00 2001 From: n0ts Date: Mon, 29 Apr 2019 08:21:01 +0900 Subject: [PATCH] Support exclude-path --- README.md | 1 + bin/miam | 1 + lib/miam/client.rb | 32 +++-- spec/miam/exclude_path_spec.rb | 237 +++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 spec/miam/exclude_path_spec.rb diff --git a/README.md b/README.md index 84ab9f9..6e9e145 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Usage: miam [options] --export-concurrency N --target REGEXP --exclude REGEXP + --exclude-path PATH --ignore-login-profile --no-color --no-progress diff --git a/bin/miam b/bin/miam index 1fd6425..24fee0d 100755 --- a/bin/miam +++ b/bin/miam @@ -53,6 +53,7 @@ ARGV.options do |opt| opt.on('' , '--export-concurrency N', Integer) {|v| options[:export_concurrency] = v } opt.on('' , '--target REGEXP') {|v| (options[:target] ||= []) << Regexp.new(v) } opt.on('' , '--exclude REGEXP') {|v| (options[:exclude] ||= []) << Regexp.new(v) } + opt.on('' , '--exclude-path PATH') {|v| (options[:exclude_path] ||= []) << v } opt.on('' , '--ignore-login-profile') { options[:ignore_login_profile] = true } opt.on('' , '--no-access-key') { options[:no_access_key] = true } opt.on('' , '--no-color') { options[:color] = false } diff --git a/lib/miam/client.rb b/lib/miam/client.rb index ecd6b1f..cd3227d 100644 --- a/lib/miam/client.rb +++ b/lib/miam/client.rb @@ -4,7 +4,8 @@ class Miam::Client def initialize(options = {}) @options = { format: :ruby, - exclude: [] + exclude: [], + exclude_path: [], }.merge(options) aws_config = options.delete(:aws_config) || {} @iam = Aws::IAM::Client.new(aws_config) @@ -82,7 +83,7 @@ def walk_users(expected, actual, group_users) updated = scan_rename(:user, expected, actual, group_users) expected.each do |user_name, expected_attrs| - next unless target_matched?(user_name) + next unless target_matched?(user_name, expected_attrs) actual_attrs = actual.delete(user_name) @@ -103,7 +104,7 @@ def walk_users(expected, actual, group_users) end actual.each do |user_name, attrs| - next unless target_matched?(user_name) + next unless target_matched?(user_name, attrs) @driver.delete_user(user_name, attrs) @@ -179,7 +180,7 @@ def walk_groups(expected, actual, actual_users, group_users) updated = scan_rename(:group, expected, actual, group_users) expected.each do |group_name, expected_attrs| - next unless target_matched?(group_name) + next unless target_matched?(group_name, expected_attrs) actual_attrs = actual.delete(group_name) @@ -194,7 +195,7 @@ def walk_groups(expected, actual, actual_users, group_users) end actual.each do |group_name, attrs| - next unless target_matched?(group_name) + next unless target_matched?(group_name, attrs) users_in_group = group_users.delete(group_name) || [] @driver.delete_group(group_name, attrs, users_in_group) @@ -218,7 +219,7 @@ def walk_roles(expected, actual, instance_profile_roles) updated = false expected.each do |role_name, expected_attrs| - next unless target_matched?(role_name) + next unless target_matched?(role_name, expected_attrs) actual_attrs = actual.delete(role_name) @@ -232,7 +233,7 @@ def walk_roles(expected, actual, instance_profile_roles) end actual.each do |role_name, attrs| - next unless target_matched?(role_name) + next unless target_matched?(role_name, attrs) instance_profile_names = [] @@ -279,6 +280,7 @@ def walk_role_settings(role_name, expected_settings, actual_settings) def walk_assume_role_policy(role_name, expected_assume_role_policy, actual_assume_role_policy) updated = false + expected_assume_role_policy.sort_array! actual_assume_role_policy.sort_array! @@ -333,7 +335,7 @@ def walk_instance_profiles(expected, actual, actual_roles, instance_profile_role updated = false expected.each do |instance_profile_name, expected_attrs| - next unless target_matched?(instance_profile_name) + next unless target_matched?(instance_profile_name, expected_attrs) actual_attrs = actual.delete(instance_profile_name) @@ -347,7 +349,7 @@ def walk_instance_profiles(expected, actual, actual_roles, instance_profile_role end actual.each do |instance_profile_name, attrs| - next unless target_matched?(instance_profile_name) + next unless target_matched?(instance_profile_name, attrs) roles_in_instance_profile = instance_profile_roles.delete(instance_profile_name) || [] @driver.delete_instance_profile(instance_profile_name, attrs, roles_in_instance_profile) @@ -478,7 +480,8 @@ def pre_walk_managed_policies(expected, actual) updated = false expected.each do |policy_name, expected_attrs| - next unless target_matched?(policy_name) + next unless target_matched?(policy_name, expected_attrs) + actual_attrs = actual.delete(policy_name) if actual_attrs @@ -513,7 +516,8 @@ def post_walk_managed_policies(actual) updated = false actual.each do |policy_name, actual_attrs| - next unless target_matched?(policy_name) + next unless target_matched?(policy_name, actual_attrs) + @driver.delete_managed_policy(policy_name, actual_attrs[:path]) updated = true end @@ -539,13 +543,17 @@ def load_file(file) end end - def target_matched?(name) + def target_matched?(name, attrs) result = true if @options[:exclude] result &&= @options[:exclude].all? {|r| name !~ r } end + if @options[:exclude_path] + result &&= @options[:exclude_path].select{|v| attrs[:path] == v }.empty? if attrs.key? :path + end + if @options[:target] result &&= @options[:target].any? {|r| name =~ r} end diff --git a/spec/miam/exclude_path_spec.rb b/spec/miam/exclude_path_spec.rb new file mode 100644 index 0000000..10ad907 --- /dev/null +++ b/spec/miam/exclude_path_spec.rb @@ -0,0 +1,237 @@ +describe 'exclude path option' do + let(:dsl) do + <<-RUBY + user "bob", :path=>"/developer/" do + login_profile :password_reset_required=>true + + groups( + "Admin", + "SES" + ) + + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + user "mary", :path=>"/staff/" do + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + group "Admin", :path=>"/admin/" do + policy "Admin" do + {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + group "SES", :path=>"/ses/" do + policy "ses-policy" do + {"Statement"=> + [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + role "my-role", :path=>"/any/" do + instance_profiles( + "my-instance-profile" + ) + + assume_role_policy_document do + {"Version"=>"2012-10-17", + "Statement"=> + [{"Sid"=>"", + "Effect"=>"Allow", + "Principal"=>{"Service"=>"ec2.amazonaws.com"}, + "Action"=>"sts:AssumeRole"}]} + end + + policy "role-policy" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + instance_profile "my-instance-profile", :path=>"/profile/" + RUBY + end + + before(:each) do + apply { dsl } + end + + context 'when exclude a user' do + let(:exclude_path_admin) do + <<-RUBY + user "mary", :path=>"/staff/" do + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + group "Admin", :path=>"/admin/" do + policy "Admin" do + {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + group "SES", :path=>"/ses/" do + policy "ses-policy" do + {"Statement"=> + [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + role "my-role", :path=>"/any/" do + instance_profiles( + "my-instance-profile" + ) + + assume_role_policy_document do + {"Version"=>"2012-10-17", + "Statement"=> + [{"Sid"=>"", + "Effect"=>"Allow", + "Principal"=>{"Service"=>"ec2.amazonaws.com"}, + "Action"=>"sts:AssumeRole"}]} + end + + policy "role-policy" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + instance_profile "my-instance-profile", :path=>"/profile/" + RUBY + end + + subject { client(exclude_path_admin: ['/admin/'] ) } + + it do + updated = apply(subject) { exclude_path_admin } + expect(updated).to be_falsey + end + end + + context 'when exclude a group, a role and an instance profile' do + let(:exclude_path_developer_and_staff) do + <<-RUBY + user "bob", :path=>"/developer/" do + login_profile :password_reset_required=>true + + groups( + "Admin", + "SES" + ) + + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + user "mary", :path=>"/staff/" do + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + + group "SES", :path=>"/ses/" do + policy "ses-policy" do + {"Statement"=> + [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]} + end + + attached_managed_policies( + "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess" + ) + end + RUBY + end + + subject { client(exclude_path_developer_and_staff: ['/developer/', '/staff/']) } + + it do + updated = apply(subject) { exclude_path_developer_and_staff } + expect(updated).to be_falsey + end + end +end