Module: Awskeyring::Awsapi

Defined in:
lib/awskeyring/awsapi.rb

Overview

AWS API methods for Awskeyring

Constant Summary collapse

ADMIN_POLICY =

Admin policy as json

{
  Version: '2012-10-17',
  Statement: [{
    Action: '*',
    Resource: '*',
    Effect: 'Allow'
  }]
}.to_json.freeze
AWS_SIGNIN_URL =

AWS Signin url

'https://signin.aws.amazon.com/federation'
AWS_ENV_VARS =

AWS Env vars

%w[
  AWS_ACCOUNT_NAME
  AWS_ACCOUNT_ID
  AWS_ACCESS_KEY_ID
  AWS_ACCESS_KEY
  AWS_CREDENTIAL_EXPIRATION
  AWS_SECRET_ACCESS_KEY
  AWS_SECRET_KEY
  AWS_SECURITY_TOKEN
  AWS_SESSION_TOKEN
].freeze
TWELVE_HOUR =

Twelve hours in seconds

(60 * 60 * 12)
ONE_HOUR =

One hour in seconds

(60 * 60 * 1)
ONE_DAY =

Days in seconds

(24 * 60 * 60)

Class Method Summary collapse

Class Method Details

.gen_test_credentials(account:) ⇒ Hash

Generate test credentials for AWS

Returns:

  • (Hash)

    with the new credentials key The aws_access_key_id secret The aws_secret_access_key expiry expiry time



192
193
194
195
196
197
198
199
200
201
# File 'lib/awskeyring/awsapi.rb', line 192

def self.gen_test_credentials(account:)
  {
    account: ,
    key: "AKIA#{Array.new(16) { [*'A'..'Z', *'2'..'7'].sample }.join}",
    secret: SecureRandom.base64(30),
    token: nil,
    expiry: nil,
    role: nil
  }
end

.get_account_id(key:) ⇒ String

Get the account number from an access key

Parameters:

  • key (String)

    The aws_access_key_id

Returns:

  • (String)

    Account number



259
260
261
262
263
264
# File 'lib/awskeyring/awsapi.rb', line 259

def self.(key:)
  padded_no = key[3..12]
  mask = (2 << 39) - 1
  decimal = (decode(padded_no) >> 4) & mask
  decimal.to_s.rjust(12, '0')
end

.get_cred_json(key:, secret:, token:, expiry:) ⇒ String

Genarates AWS CLI compatible JSON see credential_process in AWS Docs

Parameters:

  • key (String)

    The aws_access_key_id

  • secret (String)

    The aws_secret_access_key

  • token (String)

    The aws_session_token

  • expiry (String)

    expiry time

Returns:

  • (String)

    credential_process json



112
113
114
115
116
117
118
119
120
# File 'lib/awskeyring/awsapi.rb', line 112

def self.get_cred_json(key:, secret:, token:, expiry:)
  JSON.pretty_generate(
    Version: 1,
    AccessKeyId: key,
    SecretAccessKey: secret,
    SessionToken: token,
    Expiration: expiry
  )
end

.get_credentials_from_file(account:) ⇒ Hash

Retrieve credentials from the AWS Credentials file

Parameters:

  • account (String)

    the profile name wanted

Returns:

  • (Hash)

    with the new credentials key The aws_access_key_id secret The aws_secret_access_key token The aws_session_token expiry expiry time



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/awskeyring/awsapi.rb', line 174

def self.get_credentials_from_file(account:)
  creds = Aws::SharedCredentials.new(profile_name: )
  {
    account: ,
    key: creds.credentials.access_key_id,
    secret: creds.credentials.secret_access_key,
    token: creds.credentials.session_token,
    expiry: Time.new + TWELVE_HOUR,
    role: nil
  }
end

.get_env_array(params = {}) ⇒ Hash

Generates Environment Variables for the AWS CLI

Parameters:

  • params (Hash) (defaults to: {})

    including [String] account The aws account name [String] key The aws_access_key_id [String] secret The aws_secret_access_key [String] token The aws_session_token

Returns:

  • (Hash)

    env_var hash



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/awskeyring/awsapi.rb', line 130

def self.get_env_array(params = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize
  env_var = {}
  env_var['AWS_DEFAULT_REGION'] = 'us-east-1' unless region

  params[:expiration] = Time.at(params[:expiry]).iso8601 unless params[:expiry].nil?
  params[:account_name] = params.delete(:account)
  params[:account_id] = (key: params[:key]) unless params[:key].nil?

  AWS_ENV_VARS.each do |var_name|
    params.each_key do |param_name|
      if var_name.include?(param_name.to_s.upcase) && !params[param_name].nil?
        env_var[var_name] = params[param_name]
      end
    end
  end

  env_var
end

.get_login_url(key:, secret:, token:, path:, user:) ⇒ String

Retrieves an AWS Console login url

Parameters:

  • key (String)

    The aws_access_key_id

  • secret (String)

    The aws_secret_access_key

  • token (String)

    The aws_session_token

  • user (String)

    The local username

  • path (String)

    within the Console to access

Returns:

  • (String)

    login_url to access



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/awskeyring/awsapi.rb', line 211

def self.(key:, secret:, token:, path:, user:)
  console_url = "https://console.aws.amazon.com/#{path}/home"

  unless token
    cred = get_token({ key: key, secret: secret, user: user, duration: TWELVE_HOUR })
    key = cred[:key]
    secret = cred[:secret]
    token = cred[:token]
  end

  session_json = {
    sessionId: key,
    sessionKey: secret,
    sessionToken: token
  }.to_json

  destination_param = "&Destination=#{CGI.escape(console_url)}"

  "#{AWS_SIGNIN_URL}?Action=login#{token_param(session_json: session_json)}#{destination_param}"
end

.get_token(params = {}) ⇒ Hash

Retrieves a temporary session token from AWS

Parameters:

  • params (Hash) (defaults to: {})

    including key The aws_access_key_id secret The aws_secret_access_key user The local username mfa The users MFA arn code The MFA code duration time in seconds until expiry role_arn ARN of the role to assume

Returns:

  • (Hash)

    with the new credentials key The aws_access_key_id secret The aws_secret_access_key token The aws_session_token expiry expiry time



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/awskeyring/awsapi.rb', line 63

def self.get_token(params = {}) # rubocop:disable  Metrics/AbcSize, Metrics/MethodLength
  ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
  sts = Aws::STS::Client.new(access_key_id: params[:key], secret_access_key: params[:secret])

  params[:mfa] = nil unless params[:code]
  begin
    response =
      if params[:role_arn]
        sts.assume_role(
          duration_seconds: params[:duration].to_i,
          role_arn: params[:role_arn],
          role_session_name: params[:user],
          serial_number: params[:mfa],
          token_code: params[:code]
        )
      elsif params[:code]
        sts.get_session_token(
          duration_seconds: params[:duration].to_i,
          serial_number: params[:mfa],
          token_code: params[:code]
        )
      else
        sts.get_federation_token(
          name: params[:user],
          policy: ADMIN_POLICY,
          duration_seconds: params[:duration]
        )
      end
  rescue Aws::STS::Errors::AccessDenied => e
    warn e
    exit 1
  end

  {
    key: Awskeyring::Validate.session_key(response.credentials[:access_key_id]),
    secret: Awskeyring::Validate.secret_access_key(response.credentials[:secret_access_key]),
    token: response.credentials[:session_token],
    expiry: response.credentials[:expiration]
  }
end

.regionString

Get the current region

Returns:

  • (String)

    current configured region



249
250
251
252
253
# File 'lib/awskeyring/awsapi.rb', line 249

def self.region
  keys = %w[AWS_REGION AMAZON_REGION AWS_DEFAULT_REGION]
  region = ENV.values_at(*keys).compact.first
  region || Aws.shared_config.region(profile: 'default')
end

.retry_backoff(&block) ⇒ Object

Retry the call with backoff

Parameters:

  • block (Block)

    the block to retry.



316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/awskeyring/awsapi.rb', line 316

def self.retry_backoff(&block)
  retries ||= 1
  begin
    yield block
  rescue Aws::IAM::Errors::InvalidClientTokenId => e
    if retries < 4
      sleep 2**retries
      retries += 1
      retry
    end
    warn e.message
    exit 1
  end
end

.rotate(account:, key:, secret:, key_message:) ⇒ String

Rotates the AWS access keys

Parameters:

  • key (String)

    The aws_access_key_id

  • secret (String)

    The aws_secret_access_key

  • account (String)

    the associated account name.

Returns:

  • (String)

    key The aws_access_key_id

  • (String)

    secret The aws_secret_access_key

  • (String)

    account the associated account name.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/awskeyring/awsapi.rb', line 287

def self.rotate(account:, key:, secret:, key_message:) # rubocop:disable  Metrics/MethodLength
  ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
  iam = Aws::IAM::Client.new(access_key_id: key, secret_access_key: secret)

  if iam.list_access_keys[:access_key_metadata].length > 1
    warn key_message
    exit 1
  end

  new_key = iam.create_access_key[:access_key]
  iam = Aws::IAM::Client.new(
    access_key_id: new_key[:access_key_id],
    secret_access_key: new_key[:secret_access_key]
  )
  retry_backoff do
    iam.delete_access_key(
      access_key_id: key
    )
  end
  {
    account: ,
    key: new_key[:access_key_id],
    secret: new_key[:secret_access_key]
  }
end

.verify_cred(key:, secret:, token: nil) ⇒ Object

Verify Credentials are active and valid

Parameters:

  • key (String)

    The aws_access_key_id

  • secret (String)

    The aws_secret_access_key

  • token (String) (defaults to: nil)

    The aws_session_token



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/awskeyring/awsapi.rb', line 154

def self.verify_cred(key:, secret:, token: nil)
  begin
    ENV['AWS_DEFAULT_REGION'] = 'us-east-1' unless region
    sts = Aws::STS::Client.new(access_key_id: key, secret_access_key: secret, session_token: token)
    sts.get_caller_identity
  rescue Aws::Errors::ServiceError => e
    warn e
    exit 1
  end
  true
end