Class: AwsRotate::Key

Inherits:
Base
  • Object
show all
Defined in:
lib/aws_rotate/key.rb

Defined Under Namespace

Classes: GetIamUserError, MaxKeysError

Constant Summary collapse

MAX_KEYS =

Check if there are 2 keys, cannot rotate if there are 2 keys already. Raise error if there are 2 keys. Returns false if not at max limit

2
@@cache =
{}

Instance Method Summary collapse

Methods inherited from Base

#initialize

Methods included from AwsServices

#iam, #sts

Constructor Details

This class inherits a constructor from AwsRotate::Base

Instance Method Details

#aws_environment_variables_warningObject



145
146
147
148
149
150
151
152
# File 'lib/aws_rotate/key.rb', line 145

def aws_environment_variables_warning
  return unless ENV['AWS_ACCESS_KEY_ID'] || ENV['AWS_SECRET_ACCESS_KEY']

  puts <<~EOL
    WARN: The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables are also set in your shell.
    You must update those yourself. This tool only updates thethe keys in ~/.aws.
  EOL
end

#cache_access_keyObject



96
97
98
99
100
# File 'lib/aws_rotate/key.rb', line 96

def cache_access_key
  old_key_id = aws_configure_get(:aws_access_key_id)
  return unless old_key_id
  @@cache[old_key_id]
end

#check_max_keys_limitObject

Check if there are 2 keys, cannot rotate if there are 2 keys already. Display info message for user to reduce it to 1 key. Returns false if not at max limit Returns true if at max limit



85
86
87
88
89
90
91
92
93
# File 'lib/aws_rotate/key.rb', line 85

def check_max_keys_limit
  check_max_keys_limit!
rescue MaxKeysError
  puts <<~EOL.color(:red)
    This user #{@user} in the AWS_PROFILE=#{@profile} has 2 access keys. This is the max number of keys allowed.
    Please remove at least one of the keys so aws-rotate can rotate the key.
  EOL
  true # at max limit
end

#check_max_keys_limit!Object

Raises:



75
76
77
78
79
# File 'lib/aws_rotate/key.rb', line 75

def check_max_keys_limit!
  resp = iam.list_access_keys(user_name: @user)
  return false if resp..size < MAX_KEYS # not at max limit
  raise MaxKeysError
end

#create_access_keyObject

Returns:

#<struct Aws::IAM::Types::AccessKey
  user_name="tung",
  access_key_id="AKIAXZ6ODJLQUU6O3FD2",
  status="Active",
  secret_access_key="8eEnLLdR7gQE9fkFiDVuemi3qPf3mBMXxEXAMPLE",
  create_date=2019-08-13 21:14:35 UTC>>


111
112
113
114
115
116
117
118
119
120
121
# File 'lib/aws_rotate/key.rb', line 111

def create_access_key
  resp = iam.create_access_key
  key = resp.access_key

  # store in cache to help with multiple profiles using the same aws access key
  old_key_id = aws_configure_get(:aws_access_key_id)
  @@cache[old_key_id] = OldKey.new(old_key_id, key.access_key_id, key.secret_access_key)

  puts "Created new access key: #{key.access_key_id}"
  key
end

#delete_old_access_keyObject



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/aws_rotate/key.rb', line 129

def delete_old_access_key
  resp = iam.list_access_keys
  access_keys = resp.
  # Important: only delete if there are keys 2.  The reason this is possible is because multiple profiles can use
  # the same aws_access_key_id. In this case, an additional key is not created but we use the key from the @@cache
  return if access_keys.size <= 1

  old_key = access_keys.sort_by(&:create_date).first
  iam.delete_access_key(access_key_id: old_key.access_key_id)
  puts "Old access key deleted: #{old_key.access_key_id}"
end

#get_iam_userObject



31
32
33
34
35
36
37
# File 'lib/aws_rotate/key.rb', line 31

def get_iam_user
  get_iam_user!
rescue GetIamUserError
  message = @options[:noop] ? "Will not be able to update key" : "Unable to update key"
  puts "WARN: #{message} for AWS_PROFILE=#{@profile}".color(:yellow)
  return false
end

#get_iam_user!Object

Returns IAM username. Returns nil unless this profile is actually associated with an user. Skips assume role profiles.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/aws_rotate/key.rb', line 42

def get_iam_user!
  resp = sts.get_caller_identity
  arn = resp.arn
  # Example arns:
  #
  #    arn:aws:iam::112233445566:user/tung - iam user
  #    arn arn:aws:sts::112233445566:assumed-role/Admin/default_session - assume role
  #
  if arn.include?(':user/')
    arn.split('/').last
  end
rescue Aws::Errors::MissingRegionError => e
  puts "The AWS_PROFILE=#{@profile} may not exist. Please double check it.".color(:red)
  puts "#{e.class} #{e.message}"
  raise GetIamUserError
rescue Aws::STS::Errors::InvalidClientTokenId => e
  puts "The AWS_PROFILE=#{@profile} profile does not have access to IAM. Please double check it.".color(:red)
  puts "#{e.class} #{e.message}"
  raise GetIamUserError
rescue Aws::STS::Errors::SignatureDoesNotMatch => e
  puts "The AWS_PROFILE=#{@profile} profile seems to have invalid secret keys. Please double check it.".color(:red)
  puts "#{e.class} #{e.message}"
  raise GetIamUserError
rescue Aws::Errors::NoSourceProfileError => e
  puts "WARN: The AWS_PROFILE=#{@profile} profile does not have have access keys.".color(:yellow)
  puts "#{e.class} #{e.message}"
  raise GetIamUserError
end

#patience_messageObject



141
142
143
# File 'lib/aws_rotate/key.rb', line 141

def patience_message
  puts "Please note, it sometimes take a few seconds or even minutes before the new IAM access key is usable."
end

#runObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/aws_rotate/key.rb', line 6

def run
  # Note: It is nice to always call get_iam_user first as it'll check access. We rescue exceptions
  # and report errors early on. The noop check happens after this initial check.
  # Also with this we can filter for only the keys thats that have associated users and will be updated.
  # Only the profiles with IAM users will be shown as "Updating..."
  puts "AWS_PROFILE=#{ENV['AWS_PROFILE']}"
  @user = get_iam_user # will only rotate keys that belong to an actual IAM user
  return unless @user

  at_max = check_max_keys_limit
  return false if at_max

  message = "Updating access key for AWS_PROFILE=#{@profile}"
  message = "NOOP: #{message}" if @options[:noop]
  puts message.color(:green)
  return false if @options[:noop]

  key = cache_access_key || create_access_key
  update_aws_credentials_file(key.access_key_id, key.secret_access_key)
  delete_old_access_key
  patience_message
  aws_environment_variables_warning
  true
end

#update_aws_credentials_file(aws_access_key_id, aws_secret_access_key) ⇒ Object



123
124
125
126
127
# File 'lib/aws_rotate/key.rb', line 123

def update_aws_credentials_file(aws_access_key_id, aws_secret_access_key)
  aws_configure_set(aws_access_key_id: aws_access_key_id)
  aws_configure_set(aws_secret_access_key: aws_secret_access_key)
  puts "Updated profile #{@profile} in #{@credentials_path} with new key: #{aws_access_key_id}"
end