class AwsRotate::Key

Constants

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

Public Instance Methods

aws_environment_variables_warning() click to toggle source
# 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_key() click to toggle source
# 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_limit() click to toggle source

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

# 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!() click to toggle source
# 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.access_key_metadata.size < MAX_KEYS # not at max limit
  raise MaxKeysError
end
create_access_key() click to toggle source

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>>
# 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_key() click to toggle source
# File lib/aws_rotate/key.rb, line 129
def delete_old_access_key
  resp = iam.list_access_keys
  access_keys = resp.access_key_metadata
  # 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_user() click to toggle source
# 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!() click to toggle source

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

# 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_message() click to toggle source
# 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
run() click to toggle source
# 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) click to toggle source
# 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

Private Instance Methods

aws_configure_get(k) click to toggle source
# File lib/aws_rotate/key.rb, line 162
def aws_configure_get(k)
  out = `aws configure get #{k} --profile #{@profile}` # use backtick to grab output
  out.strip!
  out == '' ? nil : out
end
aws_configure_set(options={}) click to toggle source

Use the aws cli to spare coding work from parsing it.

# File lib/aws_rotate/key.rb, line 157
def aws_configure_set(options={})
  k, v = options.keys.first, options.values.first
  sh "aws configure set #{k} #{v} --profile #{@profile}"
end