diff --git a/.travis.yml b/.travis.yml index 2710f52..289f383 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,14 @@ sudo: false language: ruby +cache: bundler rvm: - 2.3.4 script: - bundle install -- travis_wait bundle exec rake +- travis_wait 60 bundle exec rake env: global: - secure: c5kyaYSGrKMg3bsVMCnIYe2w/3jJ4rxkoIwM06YGLmw5lPW0twWgpwpuBsGf6WKvDLv0eQjGq3B0I1DqHmdYHq4dQ+PdsvWg0kQUpzWpKD2ccVXevyPeDs5pC1UcLtDUpRk0Rv1Hpo4v5kiT2zsmLx29Z6F4n7agtzqK5Q2vdPs= - secure: ipdvAzZ5EwTQui8CyINqRYLFNNe4ZyZP617xEmUS3YalhRMOEZ+NSq7BlqliE4IJPg8YlQjT5q35xFtREIr/KYS1tmU1/nFA68GC/9T+uJAsc3LSmUoYMn8Ot3654u1SjlZ86benyToZ2MgHglmHigS46SOoxBpqHrKMUW9RUz0= - AWS_REGION=ap-northeast-1 + - APPLY_WAIT=10 diff --git a/bin/miam b/bin/miam index 280fbad..1fd6425 100755 --- a/bin/miam +++ b/bin/miam @@ -51,9 +51,10 @@ ARGV.options do |opt| opt.on('' , '--split-more') { split = :more } opt.on('', '--format=FORMAT', [:ruby, :json]) {|v| format_passed = true; options[:format] = v } 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('' , '--target REGEXP') {|v| (options[:target] ||= []) << Regexp.new(v) } + opt.on('' , '--exclude REGEXP') {|v| (options[:exclude] ||= []) << Regexp.new(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 } opt.on('' , '--no-progress') { options[:no_progress] = true } opt.on('' , '--debug') { options[:debug] = true } diff --git a/lib/miam.rb b/lib/miam.rb index 54009d6..0ecd1e6 100644 --- a/lib/miam.rb +++ b/lib/miam.rb @@ -5,7 +5,7 @@ require 'singleton' require 'thread' -require 'aws-sdk-core' +require 'aws-sdk-iam' Aws.use_bundled_cert! require 'ruby-progressbar' diff --git a/lib/miam/client.rb b/lib/miam/client.rb index 014e0b1..ecd6b1f 100644 --- a/lib/miam/client.rb +++ b/lib/miam/client.rb @@ -2,7 +2,10 @@ class Miam::Client include Miam::Logger::Helper def initialize(options = {}) - @options = {:format => :ruby}.merge(options) + @options = { + format: :ruby, + exclude: [] + }.merge(options) aws_config = options.delete(:aws_config) || {} @iam = Aws::IAM::Client.new(aws_config) @sts = Aws::STS::Client.new(aws_config) @@ -58,6 +61,7 @@ def apply(file) def walk(file) expected = load_file(file) + @options[:exclude] += expected[:exclude] actual, group_users, instance_profile_roles = Miam::Exporter.export(@iam, @options) updated = pre_walk_managed_policies(expected[:policies], actual[:policies]) @@ -87,7 +91,7 @@ def walk_users(expected, actual, group_users) updated = walk_user(user_name, expected_attrs, actual_attrs) || updated else actual_attrs = @driver.create_user(user_name, expected_attrs) - access_key = @driver.create_access_key(user_name) + access_key = @driver.create_access_key(user_name) unless @options[:no_access_key] if access_key @password_manager.puts_password(user_name, access_key[:access_key_id], access_key[:secret_access_key]) @@ -255,12 +259,24 @@ def walk_role(role_name, expected_attrs, actual_attrs) log(:warn, "Role `#{role_name}`: 'path' cannot be updated", :color => :yellow) end - updated = walk_assume_role_policy(role_name, expected_attrs[:assume_role_policy_document], actual_attrs[:assume_role_policy_document]) + updated = walk_role_settings(role_name, {max_session_duration: expected_attrs[:max_session_duration]}, {max_session_duration: actual_attrs[:max_session_duration]}) + updated = walk_assume_role_policy(role_name, expected_attrs[:assume_role_policy_document], actual_attrs[:assume_role_policy_document]) || updated updated = walk_role_instance_profiles(role_name, expected_attrs[:instance_profiles], actual_attrs[:instance_profiles]) || updated updated = walk_attached_managed_policies(:role, role_name, expected_attrs[:attached_managed_policies], actual_attrs[:attached_managed_policies]) || updated walk_policies(:role, role_name, expected_attrs[:policies], actual_attrs[:policies]) || updated end + def walk_role_settings(role_name, expected_settings, actual_settings) + updated = false + + if expected_settings != actual_settings + @driver.update_role_settings(role_name, expected_settings, actual_settings) + updated = true + end + + updated + end + def walk_assume_role_policy(role_name, expected_assume_role_policy, actual_assume_role_policy) updated = false expected_assume_role_policy.sort_array! @@ -527,11 +543,11 @@ def target_matched?(name) result = true if @options[:exclude] - result &&= name !~ @options[:exclude] + result &&= @options[:exclude].all? {|r| name !~ r } end if @options[:target] - result &&= name =~ @options[:target] + result &&= @options[:target].any? {|r| name =~ r} end result diff --git a/lib/miam/driver.rb b/lib/miam/driver.rb index 7c9f07c..5c0c73d 100644 --- a/lib/miam/driver.rb +++ b/lib/miam/driver.rb @@ -178,6 +178,7 @@ def create_role(role_name, attrs) params = { :role_name => role_name, :assume_role_policy_document => encode_document(assume_role_policy_document), + :max_session_duration => attrs.fetch(:max_session_duration) } params[:path] = attrs[:path] if attrs[:path] @@ -189,6 +190,7 @@ def create_role(role_name, attrs) :assume_role_policy_document => assume_role_policy_document, :policies => {}, :attached_managed_policies => [], + :max_session_duration => attrs.fetch(:max_session_duration), } new_role_attrs[:path] = attrs[:path] if attrs[:path] @@ -237,6 +239,14 @@ def remove_role_from_instance_profiles(role_name, instance_profile_names) end end + def update_role_settings(role_name, new_settings, old_settings) + log(:info, "Update Role `#{role_name}` > Settings", :color => :green) + log(:info, Miam::Utils.diff(old_settings, new_settings, :color => @options[:color]), :color => false) + unless_dry_run do + @iam.update_role(new_settings.merge(role_name: role_name)) + end + end + def update_assume_role_policy(role_name, policy_document, old_policy_document) log(:info, "Update Role `#{role_name}` > AssumeRolePolicy", :color => :green) log(:info, Miam::Utils.diff(old_policy_document, policy_document, :color => @options[:color]), :color => false) diff --git a/lib/miam/dsl/context.rb b/lib/miam/dsl/context.rb index ba73f2c..42d15ba 100644 --- a/lib/miam/dsl/context.rb +++ b/lib/miam/dsl/context.rb @@ -12,7 +12,7 @@ def self.eval(dsl, path, options = {}) def initialize(path, options = {}, &block) @path = path @options = options - @result = {:users => {}, :groups => {}, :roles => {}, :instance_profiles => {}, :policies => {}} + @result = {:users => {}, :groups => {}, :roles => {}, :instance_profiles => {}, :policies => {}, :exclude => []} @context = Hashie::Mash.new( :path => path, @@ -41,6 +41,10 @@ def require(file) end end + def exclude(pattern) + @result[:exclude] << pattern + end + def user(name, user_options = {}, &block) name = name.to_s diff --git a/lib/miam/dsl/context/role.rb b/lib/miam/dsl/context/role.rb index 2c83bd6..e587705 100644 --- a/lib/miam/dsl/context/role.rb +++ b/lib/miam/dsl/context/role.rb @@ -4,7 +4,7 @@ class Miam::DSL::Context::Role def initialize(context, name, &block) @role_name = name @context = context.merge(:role_name => name) - @result = {:instance_profiles => [], :policies => {}, :attached_managed_policies => []} + @result = {:instance_profiles => [], :max_session_duration => 3600, :policies => {}, :attached_managed_policies => []} instance_eval(&block) end @@ -22,6 +22,10 @@ def instance_profiles(*profiles) @result[:instance_profiles].concat(profiles.map(&:to_s)) end + def max_session_duration(duration) + @result[:max_session_duration] = duration + end + def assume_role_policy_document if @result[:assume_role_policy_document] raise "Role `#{@role_name}` > AssumeRolePolicyDocument: already defined" diff --git a/lib/miam/dsl/converter.rb b/lib/miam/dsl/converter.rb index 6f4c77c..2b1a77e 100644 --- a/lib/miam/dsl/converter.rb +++ b/lib/miam/dsl/converter.rb @@ -95,6 +95,8 @@ def output_role(role_name, attrs) role #{role_name.inspect}, #{Miam::Utils.unbrace(role_options.inspect)} do #{output_role_instance_profiles(attrs[:instance_profiles])} + #{output_role_max_session_duration(attrs[:max_session_duration])} + #{output_assume_role_policy_document(attrs[:assume_role_policy_document])} #{output_policies(attrs[:policies])} @@ -122,6 +124,12 @@ def output_instance_profiles(instance_profiles) }.select {|i| i }.join("\n") end + def output_role_max_session_duration(max_session_duration) + <<-EOS.strip + max_session_duration #{max_session_duration} + EOS + end + def output_assume_role_policy_document(assume_role_policy_document) assume_role_policy_document = assume_role_policy_document.pretty_inspect assume_role_policy_document.gsub!("\n", "\n ").strip! @@ -196,11 +204,11 @@ def target_matched?(name) result = true if @options[:exclude] - result &&= name !~ @options[:exclude] + result &&= @options[:exclude].all? {|r| name !~ r} end if @options[:target] - result &&= name =~ @options[:target] + result &&= @options[:target].any? {|r| name =~ r} end result diff --git a/lib/miam/exporter.rb b/lib/miam/exporter.rb index 835be7d..a8bf1b0 100644 --- a/lib/miam/exporter.rb +++ b/lib/miam/exporter.rb @@ -144,6 +144,8 @@ def export_roles(roles, instance_profile_roles) instance_profiles = role.instance_profile_list.map {|i| i.instance_profile_name } policies = export_role_policies(role) attached_managed_policies = role.attached_managed_policies.map(&:policy_arn) + role_data = @iam.get_role(role_name: role_name).role + max_session_duration = role_data.max_session_duration @mutex.synchronize do instance_profiles.each do |instance_profile_name| @@ -159,6 +161,7 @@ def export_roles(roles, instance_profile_roles) :instance_profiles => instance_profiles, :policies => policies, :attached_managed_policies => attached_managed_policies, + :max_session_duration => max_session_duration, } progress diff --git a/lib/miam/password_manager.rb b/lib/miam/password_manager.rb index 9911f73..260f9c7 100644 --- a/lib/miam/password_manager.rb +++ b/lib/miam/password_manager.rb @@ -13,7 +13,7 @@ def initialize(output, options = {}) def identify(user, type, policy) password = mkpasswd(policy) - log(:info, "mkpasswd: #{password}") + log(:debug, "mkpasswd: #{password}") puts_password(user, type, password) password end diff --git a/lib/miam/version.rb b/lib/miam/version.rb index b8bf353..44110f2 100644 --- a/lib/miam/version.rb +++ b/lib/miam/version.rb @@ -1,3 +1,3 @@ module Miam - VERSION = '0.2.4.beta12' + VERSION = '0.2.4.beta18' end diff --git a/miam.gemspec b/miam.gemspec index 35f4802..bbdbc73 100644 --- a/miam.gemspec +++ b/miam.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.add_dependency 'aws-sdk-core', '>= 2.0.42' + spec.add_dependency 'aws-sdk-iam', '~> 1' spec.add_dependency 'ruby-progressbar' spec.add_dependency 'parallel' spec.add_dependency 'term-ansicolor' diff --git a/spec/miam/attach_detach_policy_spec.rb b/spec/miam/attach_detach_policy_spec.rb index a314cd8..964189e 100644 --- a/spec/miam/attach_detach_policy_spec.rb +++ b/spec/miam/attach_detach_policy_spec.rb @@ -1,7 +1,7 @@ describe 'attach/detach policy' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -94,7 +94,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>["Admin", "SES"], :attached_managed_policies=>[ "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess"], @@ -174,7 +174,7 @@ context 'when attach policy' do let(:update_policy_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -282,7 +282,7 @@ context 'when detach policy' do let(:update_policy_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( diff --git a/spec/miam/create_spec.rb b/spec/miam/create_spec.rb index 6a3b654..66cde9b 100644 --- a/spec/miam/create_spec.rb +++ b/spec/miam/create_spec.rb @@ -12,7 +12,7 @@ context 'when create user and group' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -88,7 +88,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>["Admin", "SES"], :attached_managed_policies=>[], :policies=> @@ -184,7 +184,7 @@ end end - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do include_template context.user_name end diff --git a/spec/miam/delete_spec.rb b/spec/miam/delete_spec.rb index 2b8b1bd..91d2be6 100644 --- a/spec/miam/delete_spec.rb +++ b/spec/miam/delete_spec.rb @@ -1,7 +1,7 @@ describe 'delete' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -74,7 +74,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>["Admin", "SES"], :attached_managed_policies=>[], :policies=> @@ -139,7 +139,7 @@ context 'when delete group' do let(:delete_group_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -351,7 +351,7 @@ context 'when delete instance_profile' do let(:delete_instance_profiles_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -432,7 +432,7 @@ context 'when delete role' do let(:delete_role_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -491,7 +491,7 @@ context 'when delete role and instance_profile' do let(:delete_role_and_instance_profile_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( diff --git a/spec/miam/exclude_spec.rb b/spec/miam/exclude_spec.rb new file mode 100644 index 0000000..c1801d3 --- /dev/null +++ b/spec/miam/exclude_spec.rb @@ -0,0 +1,252 @@ +describe 'exclude 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_bob) 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: [/bob/]) } + + it do + updated = apply(subject) { exclude_bob } + expect(updated).to be_falsey + end + end + + context 'when exclude a group, a role and an instance profile' do + let(:exclude_admin_and_my) 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: [/Admin/, /^my-/]) } + + it do + updated = apply(subject) { exclude_admin_and_my } + expect(updated).to be_falsey + end + end + + context 'when specifying exclude option in a DSL' do + let(:exclude_everything) do + <<-RUBY + exclude /.*/ + RUBY + end + + subject { client(exclude: []) } + + it do + updated = apply(subject) { exclude_everything } + expect(updated).to be_falsey + end + end +end diff --git a/spec/miam/hash_ext_spec.rb b/spec/miam/hash_ext_spec.rb index 2f81fce..3a8b07e 100644 --- a/spec/miam/hash_ext_spec.rb +++ b/spec/miam/hash_ext_spec.rb @@ -2,7 +2,7 @@ let(:hash) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>[], :policies=> {"S3"=> @@ -19,7 +19,7 @@ let(:expected_hash) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>[], :policies=> {"S3"=> diff --git a/spec/miam/ignore_login_profile_spec.rb b/spec/miam/ignore_login_profile_spec.rb index f65853e..4487100 100644 --- a/spec/miam/ignore_login_profile_spec.rb +++ b/spec/miam/ignore_login_profile_spec.rb @@ -1,7 +1,7 @@ describe 'ignore login profile' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true policy "S3" do @@ -18,7 +18,7 @@ let(:update_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>false policy "S3" do @@ -37,7 +37,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>[], :policies=> {"S3"=> diff --git a/spec/miam/rename_spec.rb b/spec/miam/rename_spec.rb index acc98b8..1c52ada 100644 --- a/spec/miam/rename_spec.rb +++ b/spec/miam/rename_spec.rb @@ -1,7 +1,7 @@ describe 'update' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -74,7 +74,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>["Admin", "SES"], :attached_managed_policies=>[], :policies=> @@ -139,7 +139,7 @@ context 'when rename user' do let(:rename_user_dsl) do <<-RUBY - user "bob2", :path=>"/devloper/", :renamed_from=>"bob" do + user "bob2", :path=>"/developer/", :renamed_from=>"bob" do login_profile :password_reset_required=>true groups( @@ -222,7 +222,7 @@ context 'when rename group' do let(:rename_group_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -306,7 +306,7 @@ context 'when rename without renamed_from' do let(:rename_without_renamed_from_dsl) do <<-RUBY - user "bob2", :path=>"/devloper/" do + user "bob2", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -391,7 +391,7 @@ context 'when rename role and instance_profile' do let(:rename_role_and_instance_profile_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( diff --git a/spec/miam/target_spec.rb b/spec/miam/target_spec.rb new file mode 100644 index 0000000..4b907e9 --- /dev/null +++ b/spec/miam/target_spec.rb @@ -0,0 +1,185 @@ +describe 'target 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 target a user' do + let(:target_bob) 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 + RUBY + end + + subject { client(target: [/bob/]) } + + it do + updated = apply(subject) { target_bob } + expect(updated).to be_falsey + end + end + + context 'when target a group, a role and an instance profile' do + let(:target_admin_and_my) do + <<-RUBY + group "Admin", :path=>"/admin/" do + policy "Admin" do + {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "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(target: [/Admin/, /^my-/]) } + + it do + updated = apply(subject) { target_admin_and_my } + expect(updated).to be_falsey + end + end +end diff --git a/spec/miam/update_spec.rb b/spec/miam/update_spec.rb index c1c6dcb..28b1460 100644 --- a/spec/miam/update_spec.rb +++ b/spec/miam/update_spec.rb @@ -1,7 +1,7 @@ describe 'update' do let(:dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -74,7 +74,7 @@ let(:expected) do {:users=> {"bob"=> - {:path=>"/devloper/", + {:path=>"/developer/", :groups=>["Admin", "SES"], :attached_managed_policies=>[], :policies=> @@ -122,6 +122,7 @@ "Principal"=>{"Service"=>"ec2.amazonaws.com"}, "Action"=>"sts:AssumeRole"}]}, :instance_profiles=>["my-instance-profile"], + :max_session_duration=>3600, :attached_managed_policies=>[], :policies=> {"role-policy"=> @@ -149,7 +150,7 @@ context 'when update policy' do let(:update_policy_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -236,7 +237,7 @@ context 'when update path' do let(:update_path_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -320,7 +321,7 @@ context 'when update path (role, instance_profile)' do let(:cannot_update_path_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -409,7 +410,7 @@ context 'when update assume_role_policy' do let(:update_assume_role_policy_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -492,7 +493,7 @@ context 'when update groups' do let(:update_groups_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -580,7 +581,7 @@ context 'when update login_profile' do let(:update_login_profile_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>false groups( @@ -663,7 +664,7 @@ context 'when delete login_profile' do let(:delete_login_profile_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do groups( "Admin", "SES" @@ -744,7 +745,7 @@ context 'when delete policy' do let(:delete_policy_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -807,7 +808,7 @@ context 'when update instance_profiles' do let(:update_instance_profiles_dsl) do <<-RUBY - user "bob", :path=>"/devloper/" do + user "bob", :path=>"/developer/" do login_profile :password_reset_required=>true groups( @@ -888,4 +889,89 @@ expect(export).to eq expected end end + + context 'when update role max_session_duration' do + let(:update_instance_profiles_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 + end + + user "mary", :path=>"/staff/" do + policy "S3" do + {"Statement"=> + [{"Action"=> + ["s3:Get*", + "s3:List*"], + "Effect"=>"Allow", + "Resource"=>"*"}]} + end + end + + group "Admin", :path=>"/admin/" do + policy "Admin" do + {"Statement"=>[{"Effect"=>"Allow", "Action"=>"*", "Resource"=>"*"}]} + end + end + + group "SES", :path=>"/ses/" do + policy "ses-policy" do + {"Statement"=> + [{"Effect"=>"Allow", "Action"=>"ses:SendRawEmail", "Resource"=>"*"}]} + end + end + + role "my-role", :path=>"/any/" do + instance_profiles( + "my-instance-profile" + ) + + max_session_policy 43200 + + 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 + end + + instance_profile "my-instance-profile", :path=>"/profile/" + RUBY + end + + subject { client } + + it do + updated = apply(subject) { update_instance_profiles_dsl } + expect(updated).to be_truthy + expected[:roles]["my-role"][:max_session_duration] = 43200 + expect(export).to eq expected + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 461c881..346dfcd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -67,14 +67,17 @@ def tempfile(content, options = {}) end def apply(cli = client) - tempfile(yield) do |f| + result = tempfile(yield) do |f| begin cli.apply(f.path) - rescue Aws::IAM::Errors::EntityTemporarilyUnmodifiable + rescue Aws::IAM::Errors::EntityTemporarilyUnmodifiable, Aws::IAM::Errors::Throttling, Aws::IAM::Errors::NoSuchEntity sleep 3 retry end end + + sleep ENV['APPLY_WAIT'].to_i + result end def export(options = {})