module AwsSu

Set up the AWS authentication environment for a user who has an ID in a master account and is allowed to switch to a role in another account.

Typical usage scenario:

require 'aws_su'

class RunAwsSu
include AwsSu
end

run_aws_su = RunAwsSu.new
run_aws_su.authenticate(
  profile: 'ds-nonprod',
  duration: '28800',
  region: 'eu-west-2'
)
run_aws_su.ec2_client.describe_vpcs

also sets up current shell so system calls don't need further authentication:

system('aws ec2 describe-vpcs --region eu-west-2')

It is assumed that the region is set in the first profile in .aws/config, e.g.

[profile master]
region=eu-west-2

or it can be set in the call to authenticate() as shown above

Constants

AWS_CONFIG_FILE
AWS_SUDO_FILE
DURATION
VERSION

Public Instance Methods

authenticate(options = {}) click to toggle source

Authenticate user for the session @param options Hash {

duration: 'AWS role session timeout',
region: AWS region,
profile: Name of profile in .aws/config to use

}

# File lib/aws_su.rb, line 57
def authenticate(options = {})
  @session = "aws-su-session-#{Time.now.to_i}"
  @profile = options[:profile]
  @duration = options[:duration].nil? ? DURATION : options[:duration]
  @token_ttl = calculate_session_expiry(@duration)

  region = AWSConfig.profiles.first[1][:region]
  region = AWSConfig.profiles[@profile][:region] unless
      AWSConfig.profiles[@profile][:region].nil?
  @region = options[:region].nil? ? region : options[:region]
  raise('Unable to determine region') if @region.nil?

  assume_role
end
ec2_client() click to toggle source

Configure the ec2 client

# File lib/aws_su.rb, line 73
def ec2_client
  Aws::EC2::Client.new
end
elb_client() click to toggle source

Configure the elb client

# File lib/aws_su.rb, line 78
def elb_client
  Aws::ElasticLoadBalancing::Client.new
end
iam_client() click to toggle source

Configure the IAM client

# File lib/aws_su.rb, line 83
def iam_client
  Aws::IAM::Client.new
end
s3_client() click to toggle source

Configure the S3 client

# File lib/aws_su.rb, line 88
def s3_client
  Aws::S3::Client.new
end
sqs_client() click to toggle source

SQS Client

# File lib/aws_su.rb, line 93
def sqs_client
  Aws::SQS::Client.new
end
sts_client() click to toggle source

STS

# File lib/aws_su.rb, line 98
def sts_client
  Aws::STS::Client.new(
    credentials: load_secrets,
    region: @region
  )
end

Private Instance Methods

assume_role(duration = DURATION) click to toggle source

Assume a role @param duration A string integer representing the role session duration

# File lib/aws_su.rb, line 109
def assume_role(duration = DURATION)
  if session_valid?
    # Recover persisted session and use that to update AWS.config
    config = Aws.config.update(
      credentials: Aws::Credentials.new(
        parse_access_key,
        parse_secret_access_key,
        parse_session_token
      )
    )
    export_config_to_environment(config)
  else
    # Session has expired so auth again
    assume_role_mfa(duration)
    export_aws_sudo_file
  end
end
assume_role_mfa(duration, mfa_code = nil) click to toggle source

Assume a role using an MFA Token

# File lib/aws_su.rb, line 128
def assume_role_mfa(duration, mfa_code = nil)
  mfa_code = prompt_for_mfa_code if mfa_code.nil?
  delete_sudo_file
  role_creds = sts_client.assume_role(
    role_arn: AWSConfig[@profile]['role_arn'],
    role_session_name: @session,
    duration_seconds: duration.to_i,
    serial_number: AWSConfig[@profile]['mfa_serial'],
    token_code: mfa_code.to_s
  )
  update_aws_config(role_creds)
  persist_aws_su(role_creds)
end
calculate_session_expiry(duration = DURATION) click to toggle source

Calculate the session expiration # @param duration A string integer representing the role session duration

# File lib/aws_su.rb, line 144
def calculate_session_expiry(duration = DURATION)
  (Time.now + duration.to_i).strftime('%Y-%m-%d %H:%M:%S')
end
delete_sudo_file() click to toggle source

Delete the AWS sudo file

# File lib/aws_su.rb, line 149
def delete_sudo_file
  File.delete(AWS_SUDO_FILE) if File.exist?(AWS_SUDO_FILE)
end
export_aws_sudo_file() click to toggle source

Get the values for AWS secrets etc and export them to the environment

# File lib/aws_su.rb, line 154
def export_aws_sudo_file
  return unless File.exists?(AWS_SUDO_FILE)

  File.readlines(AWS_SUDO_FILE).each do |line|
    case line
    when MatchesAwsAccessKeyId
      ENV['AWS_ACCESS_KEY_ID'] = line.split('=')[1].strip
    when MatchesAwsSecretAccessKey
      ENV['AWS_SECRET_ACCESS_KEY'] = line.split('=')[1].strip
    when MatchesAwsSessionToken
      ENV['AWS_SESSION_TOKEN'] = line.split('=')[1].strip
    when MatchesAwsSecurityToken
      ENV['AWS_SECURITY_TOKEN'] = line.split('=')[1].strip
    when MatchesAwsTokenEtl
      ENV['AWS_TOKEN_TTL'] = line.split('=')[1].strip
    # when MatchesAwsProfile
    #   ENV['AWS_PROFILE'] = line.split('=')[1].strip
    end
  end
end
export_config_to_environment(config) click to toggle source

Export the AWS values to the ENV

# File lib/aws_su.rb, line 176
def export_config_to_environment(config)
  ENV['AWS_ACCESS_KEY_ID'] = config[:credentials].access_key_id
  ENV['AWS_SECRET_ACCESS_KEY'] = config[:credentials].secret_access_key
  ENV['AWS_SESSION_TOKEN'] = config[:credentials].session_token
  ENV['AWS_SECURITY_TOKEN'] = config[:credentials].session_token
  ENV['AWS_TOKEN_TTL'] = @token_ttl
  ENV['AWS_DEFAULT_REGION'] = @region
end
load_secrets() click to toggle source

Load the user's AWS Secrets

# File lib/aws_su.rb, line 186
def load_secrets
  Awsecrets.load
end
parse_access_key() click to toggle source

Parse the secret access key from awssudo

# File lib/aws_su.rb, line 191
def parse_access_key
  File.readlines(AWS_SUDO_FILE).each do |line|
    return line.split('=')[1].chomp if line.include?('AWS_ACCESS_KEY')
  end
end
parse_secret_access_key() click to toggle source

Parse the secret access key from awssudo

# File lib/aws_su.rb, line 198
def parse_secret_access_key
  File.readlines(AWS_SUDO_FILE).each do |line|
    return line.split('=')[1].chomp if line.include?('AWS_SECRET_ACCESS_KEY')
  end
end
parse_session_token() click to toggle source

Parse the session token from awssudo

# File lib/aws_su.rb, line 205
def parse_session_token
  File.readlines(AWS_SUDO_FILE).each do |line|
    return line.split('=')[1].chomp if line.include?('AWS_SESSION_TOKEN')
  end
end
parse_ttl_timeout() click to toggle source

Recover the persisted session timeout from AWSSUDO file

# File lib/aws_su.rb, line 212
def parse_ttl_timeout
  File.readlines(AWS_SUDO_FILE).each do |line|
    return line.split('=')[1].chomp if line.include?('AWS_TOKEN_TTL')
  end
end
persist_aws_su(config, file = AWS_SUDO_FILE) click to toggle source

Persist the config to the awssudo file @param config Credentials from assume role to persist @param file The temporary secrets file ~/.awssudo

# File lib/aws_su.rb, line 221
def persist_aws_su(config, file = AWS_SUDO_FILE)
  File.open(file, 'w') do |f|
    f.puts('AWS_ACCESS_KEY_ID=' + config.credentials.access_key_id)
    f.puts('AWS_SECRET_ACCESS_KEY=' + config.credentials.secret_access_key)
    f.puts('AWS_SESSION_TOKEN=' + config.credentials.session_token)
    f.puts('AWS_SECURITY_TOKEN=' + config.credentials.session_token)
    f.puts('AWS_TOKEN_TTL=' + @token_ttl)
    f.puts('AWS_PROFILE=' + @profile)
  end
end
prompt_for_mfa_code() click to toggle source

Prompt the user to supply and MFA code

# File lib/aws_su.rb, line 233
def prompt_for_mfa_code
  puts 'Enter MFA code: '
  mfa_token_code = gets.chomp
  return mfa_token_code unless mfa_token_code.nil? || mfa_token_code.empty?
  raise(Error, 'No code supplied, aborting')
end
session_valid?() click to toggle source

See if we have a valid session or if it has expired

# File lib/aws_su.rb, line 241
def session_valid?
  return false unless File.exists?(AWS_SUDO_FILE)
  File.readlines(AWS_SUDO_FILE).each do |line|
    next unless line.include?('AWS_TOKEN_TTL')
    aws_token_ttl = line.split('=')[1]
    return true if Time.parse(aws_token_ttl) > Time.now
    return false
  end
  false
end
update_aws_config(role_creds) click to toggle source

Update the Aws.config hash

# File lib/aws_su.rb, line 253
def update_aws_config(role_creds)
  Aws.config.update(
    credentials: Aws::Credentials.new(
      role_creds.credentials.access_key_id,
      role_creds.credentials.secret_access_key,
      role_creds.credentials.session_token
    )
  )
end