lib/aws_su.rb in aws_su-0.1.0 vs lib/aws_su.rb in aws_su-0.1.1
- old
+ new
@@ -1,13 +1,43 @@
# frozen_string_literal: true
require 'aws_config'
require 'matches'
require 'awsecrets'
-require 'aws_sudo/version'
+require 'aws_su/version'
-# Set up the AWS authentication environment
+# 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
+#
module AwsSu
class Error < StandardError; end
AWS_SUDO_FILE = Dir.home + '/.awssudo'
AWS_CONFIG_FILE = Dir.home + '/.aws/config'
@@ -17,15 +47,25 @@
@session = nil # Name of the active session
@duration = DURATION # Session duration in seconds
@master_config = Awsecrets.load # AWS config for master account
# Authenticate user for the session
- def authenticate(profile, duration = DURATION)
- @session = "awssudo-session-#{Time.now.to_i}"
- @token_ttl = calculate_session_expiry(duration)
- @profile = profile
- @duration = duration
+ # @param options Hash {
+ # duration: 'AWS role session timeout',
+ # region: AWS region,
+ # profile: Name of profile in .aws/config to use
+ # }
+ 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 = options[:region].nil? ? region : options[:region]
+ raise('Unable to determine region') if @region.nil?
+
export_aws_sudo_file
assume_role
end
# Configure the ec2 client
@@ -46,25 +86,28 @@
# Configure the S3 client
def s3_client
Aws::S3::Client.new
end
- # Get an STS client so we can request a session token
+ # SQS Client
+ def sqs_client
+ Aws::SQS::Client.new
+ end
+
+ # STS
def sts_client
Aws::STS::Client.new(
- credentials: load_secrets,
- region: 'eu-west-2'
+ credentials: load_secrets,
+ region: @region
)
end
private
+
# Assume a role
- # @param duration A string integer representing the session duration
+ # @param duration A string integer representing the role session duration
def assume_role(duration = DURATION)
- # For the benefit of anything downstream we are running
- export_aws_sudo_file
-
if session_valid?
# Recover persisted session and use that to update AWS.config
Aws.config.update(
credentials: Aws::Credentials.new(
parse_access_key,
@@ -72,29 +115,32 @@
parse_session_token
)
)
else
# Session has expired so auth again
- assume_role_with_mfa_token(duration)
+ assume_role_mfa(duration)
end
+ # For the benefit of anything downstream we are running
+ export_aws_sudo_file
end
# Assume a role using an MFA Token
- def assume_role_with_mfa_token(duration, mfa_code = nil)
+ def assume_role_mfa(duration, mfa_code = nil)
mfa_code = prompt_for_mfa_code if mfa_code.nil?
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
+ 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_sudo(role_creds)
+ persist_aws_su(role_creds)
end
# Calculate the session expiration
+ # # @param duration A string integer representing the role session duration
def calculate_session_expiry(duration = DURATION)
(Time.now + duration.to_i).strftime('%Y-%m-%d %H:%M:%S')
end
# Get the values for AWS secrets etc and export them to the environment
@@ -146,24 +192,10 @@
File.readlines(AWS_SUDO_FILE).each do |line|
return line.split('=')[1].chomp if line.include?('AWS_SECRET_ACCESS_KEY')
end
end
- # Recover the role_arn from the AWS config file
- def parse_role_arn
- File.readlines(AWS_CONFIG_FILE).each do |line|
- return line.split('=')[1].chomp if line.include?('role_arn')
- end
- end
-
- # Recover the mfa serial number from AWS config file
- def parse_mfa_serial
- File.readlines(AWS_CONFIG_FILE).each do |line|
- return line.split('=')[1].chomp if line.include?('mfa_serial')
- end
- end
-
# Parse the session token from awssudo
def parse_session_token
File.readlines(AWS_SUDO_FILE).each do |line|
return line.split('=')[1].chomp if line.include?('AWS_SESSION_TOKEN')
end
@@ -175,17 +207,19 @@
return line.split('=')[1].chomp if line.include?('AWS_TOKEN_TTL')
end
end
# Persist the config to the awssudo file
- def persist_aws_sudo(config, file = AWS_SUDO_FILE)
- File.open(file, 'w') do |file|
- file.puts('AWS_ACCESS_KEY_ID=' + config.credentials.access_key_id)
- file.puts('AWS_SECRET_ACCESS_KEY=' + config.credentials.secret_access_key)
- file.puts('AWS_SESSION_TOKEN=' + config.credentials.session_token)
- file.puts('AWS_SECURITY_TOKEN=' + config.credentials.session_token)
- file.puts('AWS_TOKEN_TTL=' + @token_ttl)
- file.puts('AWS_PROFILE=' + @profile)
+ # @param config Credentials from assume role to persist
+ # @param file The temporary secrets file ~/.awssudo
+ 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 the user to supply and MFA code
def prompt_for_mfa_code