awsudo 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +27 -15
- data/bin/aws-agent +31 -18
- data/bin/awsudo +46 -15
- data/lib/awsudo.rb +25 -76
- data/lib/awsudo/identity_provider.rb +91 -0
- data/lib/awsudo/identity_providers.rb +12 -0
- data/lib/awsudo/identity_providers/adfs.rb +16 -0
- data/lib/awsudo/identity_providers/okta.rb +69 -0
- metadata +31 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55636d46f188efba965077b306ccd5c99df46923
|
4
|
+
data.tar.gz: dcec6bd5c2684851a1d6c010882658fd6d270b43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 942140a15481c643d3a8ee3dd95ba492cd2a4f6aedcd329b3ff98a4bb4f0c94a2799943da35321558b472768cc8fe968bc53eea211d8a4d7c3a2e363920cabb9
|
7
|
+
data.tar.gz: 3c6b0b2f230b8c4cbd97e44dc5ed597d1214038d93a004d724c20790a0194fabd91412f3b3aa981630fa18f28d9685e30d476d3780b279a80db2a6175870e4a5
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
awsudo + aws-agent
|
2
2
|
==================
|
3
3
|
|
4
|
+
[](https://travis-ci.org/electronicarts/awsudo)
|
5
|
+
|
4
6
|
Overview
|
5
7
|
------------
|
6
8
|
|
@@ -15,17 +17,17 @@ to use.
|
|
15
17
|
Synopsis
|
16
18
|
------------
|
17
19
|
|
18
|
-
|
20
|
+
awsudo {role-name | role-arn} command
|
19
21
|
|
20
|
-
|
22
|
+
aws-agent
|
21
23
|
|
22
24
|
Requirements
|
23
25
|
------------
|
24
26
|
|
25
27
|
* UNIX, UNIX-like or GNU/Linux operating system
|
26
28
|
* SAML compliant federation service
|
27
|
-
* ruby 1
|
28
|
-
*
|
29
|
+
* ruby 2.1 or above
|
30
|
+
* rubygems: aws-sdk, nokogiri
|
29
31
|
|
30
32
|
Install
|
31
33
|
------------
|
@@ -37,33 +39,43 @@ Configuration
|
|
37
39
|
|
38
40
|
awsudo and aws-agent expect a configuration file named .awsudo in your home directory
|
39
41
|
containing the values for your identity provider login url and the SAML provider name
|
40
|
-
configured in AWS.
|
42
|
+
configured in AWS.
|
43
|
+
|
44
|
+
Example for AD FS:
|
45
|
+
|
46
|
+
IDP = adfs
|
47
|
+
IDP_LOGIN_URL = https://sts.example.com/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices
|
48
|
+
SAML_PROVIDER_NAME = adfs
|
41
49
|
|
42
|
-
|
43
|
-
|
50
|
+
Example for Okta:
|
51
|
+
|
52
|
+
IDP = okta
|
53
|
+
IDP_LOGIN_URL = https://example.okta.com/app/example/abc123/sso/saml
|
54
|
+
SAML_PROVIDER_NAME = okta
|
55
|
+
API_ENDPOINT = https://example.okta.com/api/v1
|
44
56
|
|
45
57
|
In addition to .awsudo, you can create .aws-roles in your home directory to map
|
46
58
|
IAM roles ARNs to more easy to remember alias names, one per line, separated by spaces. Example:
|
47
59
|
|
48
|
-
|
60
|
+
myaccount-admin arn:aws:iam::123456789012:role/myaccount-admin
|
49
61
|
|
50
62
|
Examples
|
51
63
|
------------
|
52
64
|
|
53
65
|
### awsudo
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
$ awsudo arn:aws:iam::123456789012:role/myaccount-admin aws ec2 describe-tags --region us-west-2
|
68
|
+
|
69
|
+
$ awsudo myaccount-admin aws ec2 describe-instances --region us-east-1
|
58
70
|
|
59
71
|
awsudo will ask your federated credentials every time. To avoid this use aws-agent as follows:
|
60
72
|
|
61
73
|
### aws-agent
|
62
74
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
75
|
+
$ aws-agent
|
76
|
+
Login: username
|
77
|
+
Password:
|
78
|
+
AWS_AUTH_SOCK=/var/folders/xz/lx178g0d0rb36x95446zwgd80000gp/T/aws-20150623-20990-58v1c4/agent; export AWS_AUTH_SOCK;
|
67
79
|
|
68
80
|
then execute the commands printed by aws-agent. awsudo will now ask for temporary credentials to aws-agent.
|
69
81
|
|
data/bin/aws-agent
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
|
2
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
3
3
|
|
4
|
-
require 'awsudo'
|
5
4
|
require 'logger'
|
6
5
|
require 'socket'
|
7
6
|
require 'tmpdir'
|
8
7
|
|
8
|
+
require 'awsudo'
|
9
|
+
|
9
10
|
def usage
|
10
11
|
warn <<EOS
|
11
12
|
Usage:
|
@@ -15,36 +16,48 @@ EOS
|
|
15
16
|
exit 1
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
AWSUDO.
|
19
|
+
LOG_FILENAME = File.join(ENV['HOME'], ".aws-agent.log")
|
20
|
+
CONFIG_FILENAME = File.join(ENV['HOME'], '.awsudo')
|
21
|
+
SUPPORTED_IDPS = AWSUDO::IdentityProviders.constants
|
21
22
|
|
22
|
-
|
23
|
-
logger = Logger.new(LOGFILE, "weekly")
|
23
|
+
logger = Logger.new(LOG_FILENAME, "weekly")
|
24
24
|
logger.progname = "aws-agent"
|
25
25
|
logger.level = Logger::WARN
|
26
26
|
|
27
|
-
|
27
|
+
config = AWSUDO.load_config(CONFIG_FILENAME)
|
28
|
+
idpname = config['IDP'].to_s.capitalize.to_sym
|
29
|
+
unless SUPPORTED_IDPS.include?(idpname)
|
30
|
+
warn "`#{config['IDP']}' is not a supported identity provider"
|
31
|
+
exit 4
|
32
|
+
end
|
33
|
+
username, password = AWSUDO.ask_for_credentials
|
34
|
+
idp = AWSUDO::IdentityProviders.new(idpname, config, username, password)
|
35
|
+
idp.logger = logger
|
28
36
|
|
29
37
|
socket_dir = Dir.mktmpdir("aws-")
|
30
38
|
socket_name = File.join(socket_dir, "agent")
|
31
|
-
|
39
|
+
|
40
|
+
case ENV['SHELL'].split('/').last
|
41
|
+
when 'csh', 'tcsh'
|
42
|
+
puts "setenv AWS_AUTH_SOCK #{socket_name}"
|
43
|
+
when 'fish'
|
44
|
+
puts "set -gx AWS_AUTH_SOCK #{socket_name}"
|
45
|
+
else
|
46
|
+
puts "AWS_AUTH_SOCK=#{socket_name}; export AWS_AUTH_SOCK;"
|
47
|
+
end
|
48
|
+
|
32
49
|
Process.daemon
|
33
50
|
$0 = 'aws-agent'
|
34
51
|
Process.setrlimit(Process::RLIMIT_CORE, 0, 0)
|
35
52
|
UNIXServer.open(socket_name) do |socket|
|
36
53
|
loop do
|
37
54
|
Thread.new(socket.accept) do |client|
|
38
|
-
logger.debug "
|
39
|
-
logger.debug "connection accepted: #{socket.inspect}"
|
55
|
+
logger.debug "Thread started"
|
56
|
+
logger.debug {"connection accepted: #{socket.inspect}"}
|
40
57
|
begin
|
41
|
-
|
42
|
-
logger.debug "role received: #{
|
43
|
-
|
44
|
-
logger.debug "role ARN resolved: #{role_arn}"
|
45
|
-
saml_assertion = AWSUDO.get_saml_assertion(username, password)
|
46
|
-
credentials = AWSUDO.assume_role_with_saml(saml_assertion, role_arn)
|
47
|
-
client.puts credentials.to_h.to_json
|
58
|
+
role_arn = client.gets.strip
|
59
|
+
logger.debug {"role ARN received: #{role_arn}"}
|
60
|
+
client.puts idp.assume_role(role_arn).to_json
|
48
61
|
rescue => e
|
49
62
|
logger.error e
|
50
63
|
error = {:error => e}.to_json
|
data/bin/awsudo
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
|
2
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
3
3
|
|
4
4
|
require 'awsudo'
|
5
5
|
|
6
|
+
CONFIG_FILENAME = File.join(ENV['HOME'], '.awsudo')
|
7
|
+
ROLES_FILENAME = File.join(ENV['HOME'], '.aws-roles')
|
8
|
+
SOCKETNAME = ENV['AWS_AUTH_SOCK']
|
9
|
+
SUPPORTED_IDPS = AWSUDO::IdentityProviders.constants
|
10
|
+
|
6
11
|
def usage
|
7
12
|
warn <<-EOS
|
8
13
|
Usage:
|
@@ -12,23 +17,49 @@ Usage:
|
|
12
17
|
exit 1
|
13
18
|
end
|
14
19
|
|
15
|
-
|
20
|
+
def find_role_arn(rolename)
|
21
|
+
return nil if rolename =~ /\s/ || rolename.empty?
|
22
|
+
line = File.readlines(ROLES_FILENAME).find do |line|
|
23
|
+
line =~ /^#{rolename}\s+arn:aws:iam::\d+:role\/\S+\s*$/
|
24
|
+
end
|
25
|
+
return nil if line.nil?
|
26
|
+
arn = line.split(/\s+/)[1]
|
27
|
+
arn
|
28
|
+
end
|
16
29
|
|
17
|
-
|
18
|
-
|
19
|
-
|
30
|
+
def cuddle
|
31
|
+
begin; yield; rescue; warn $!; end
|
32
|
+
end
|
33
|
+
|
34
|
+
usage if ARGV.size < 2
|
20
35
|
|
21
36
|
role = ARGV.shift
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
37
|
+
role_arn = role =~ /^arn:aws:iam::\d+:role\/\S+$/ ? role : find_role_arn(role)
|
38
|
+
|
39
|
+
if role_arn.nil?
|
40
|
+
warn "`#{role}' is not a valid role"
|
41
|
+
exit 2
|
42
|
+
end
|
43
|
+
|
44
|
+
keys = cuddle do
|
45
|
+
AWSUDO.assume_role_with_agent(role_arn, SOCKETNAME) unless SOCKETNAME.nil?
|
46
|
+
end || cuddle do
|
47
|
+
config = AWSUDO.load_config(CONFIG_FILENAME)
|
48
|
+
idpname = config['IDP'].to_s.capitalize.to_sym
|
49
|
+
unless SUPPORTED_IDPS.include?(idpname)
|
50
|
+
raise "`#{config['IDP']}' is not a supported identity provider"
|
28
51
|
end
|
29
52
|
|
30
|
-
|
31
|
-
|
32
|
-
|
53
|
+
username, password = AWSUDO.ask_for_credentials
|
54
|
+
idp = AWSUDO::IdentityProviders.new(idpname, config, username, password)
|
55
|
+
idp.assume_role(role_arn)
|
56
|
+
end || exit(3)
|
57
|
+
|
58
|
+
%w(access_key_id secret_access_key session_token).each do |keyname|
|
59
|
+
ENV['AWS_' + keyname.upcase] = keys[keyname] || keys[keyname.to_sym]
|
60
|
+
end
|
61
|
+
|
62
|
+
# for boto2
|
63
|
+
ENV['AWS_SECURITY_TOKEN'] = keys['session_token'] || keys[:session_token]
|
33
64
|
|
34
|
-
exec *ARGV if ARGV.size > 0
|
65
|
+
cuddle { exec *ARGV if ARGV.size > 0 }
|
data/lib/awsudo.rb
CHANGED
@@ -1,27 +1,35 @@
|
|
1
|
-
# Copyright (C) 2015 Electronic Arts Inc. All rights reserved.
|
1
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
2
2
|
|
3
|
-
require 'aws-sdk'
|
4
3
|
require 'io/console'
|
5
4
|
require 'json'
|
6
|
-
require '
|
7
|
-
require 'net/https'
|
8
|
-
require 'rexml/document'
|
5
|
+
require 'logger'
|
9
6
|
require 'socket'
|
10
7
|
require 'uri'
|
11
8
|
|
12
9
|
module AWSUDO
|
13
|
-
|
10
|
+
@logger = Logger.new(STDERR)
|
11
|
+
@logger.level = Logger::WARN
|
14
12
|
|
15
13
|
class << self
|
16
|
-
|
14
|
+
attr_accessor :logger
|
17
15
|
end
|
18
16
|
|
19
|
-
def self.
|
20
|
-
|
21
|
-
|
17
|
+
def self.assume_role_with_agent(role_arn, socket_name)
|
18
|
+
logger.debug {"role_arn: <#{role_arn}>"}
|
19
|
+
logger.debug {"socket_name: <#{socket_name}>"}
|
20
|
+
keys = UNIXSocket.open(socket_name) do |client|
|
21
|
+
client.puts role_arn
|
22
|
+
response = client.gets
|
23
|
+
logger.debug {"response: <#{response}>"}
|
24
|
+
raise "Connection closed by peer" if response.nil?
|
25
|
+
JSON.parse(response.strip)
|
26
|
+
end
|
27
|
+
|
28
|
+
raise keys['error'] unless keys['error'].nil?
|
29
|
+
keys
|
22
30
|
end
|
23
31
|
|
24
|
-
def self.
|
32
|
+
def self.ask_for_credentials
|
25
33
|
fd = IO.sysopen("/dev/tty", "w")
|
26
34
|
console = IO.new(fd,"w")
|
27
35
|
console.print "Login: "
|
@@ -32,70 +40,11 @@ module AWSUDO
|
|
32
40
|
[username, password]
|
33
41
|
end
|
34
42
|
|
35
|
-
def self.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
response = client.gets
|
40
|
-
raise "Connection closed by peer" if response.nil?
|
41
|
-
JSON.parse(response.strip)
|
42
|
-
end
|
43
|
-
|
44
|
-
raise credentials['error'] if credentials['error']
|
45
|
-
credentials
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.assume_role_using_password(role)
|
49
|
-
username, password = get_federated_credentials
|
50
|
-
saml_assertion = get_saml_assertion(username, password)
|
51
|
-
role_arn = resolve_role(role)
|
52
|
-
assume_role_with_saml(saml_assertion, role_arn).to_h
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.assume_role(role)
|
56
|
-
assume_role_using_agent(role) rescue assume_role_using_password(role)
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.get_saml_assertion(username, password)
|
60
|
-
uri = URI.parse(idp_login_url)
|
61
|
-
|
62
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
63
|
-
http.use_ssl = true
|
64
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
65
|
-
|
66
|
-
req = Net::HTTP::Post.new(uri.request_uri)
|
67
|
-
req.set_form_data({'username' => username, 'password' => password})
|
68
|
-
res = http.request(req)
|
69
|
-
|
70
|
-
raise "Authentication failed" if res['Location'].nil?
|
71
|
-
uri = URI.parse(res['Location'])
|
72
|
-
req = Net::HTTP::Get.new(uri.request_uri)
|
73
|
-
req['Cookie'] = res['Set-Cookie']
|
74
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
75
|
-
http.use_ssl = true
|
76
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
77
|
-
res = http.request(req)
|
78
|
-
|
79
|
-
doc = REXML::Document.new(res.body)
|
80
|
-
REXML::XPath.first(doc, '/html/body/form/input[@name = "SAMLResponse"]/@value').to_s
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.assume_role_with_saml(saml_assertion, role_arn)
|
84
|
-
principal_arn = "#{role_arn[/^arn:aws:iam::\d+:/]}saml-provider/#{saml_provider_name}"
|
85
|
-
sts = Aws::STS::Client.new(credentials: Aws::Credentials.new('a', 'b', 'c'), region: 'us-east-1')
|
86
|
-
sts.assume_role_with_saml(
|
87
|
-
role_arn: role_arn,
|
88
|
-
principal_arn: principal_arn,
|
89
|
-
saml_assertion: saml_assertion).credentials
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.resolve_role(role, roles_filename = AWS_ROLES)
|
93
|
-
return role if role =~ /^arn:aws:iam::\d+:role\/\S+$/
|
94
|
-
raise "`#{role}' is not a valid role" if role =~ /\s/
|
95
|
-
line = File.readlines(roles_filename).find {|line| line =~ /^#{role}\s+arn:aws:iam::\d+:role\/\S+\s*$/ }
|
96
|
-
raise "`#{role}' is not a valid role" if line.nil?
|
97
|
-
role_arn = line.split(/\s+/)[1]
|
98
|
-
raise "`#{role}' is not a valid role" if role_arn.nil?
|
99
|
-
role_arn
|
43
|
+
def self.load_config(filename)
|
44
|
+
config = Hash[*File.read(filename).scan(/^\s*(\w+)\s*=\s*(.*)\s*$/).flatten]
|
45
|
+
logger.debug { "config: <#{config.inspect}>" }
|
46
|
+
config
|
100
47
|
end
|
101
48
|
end
|
49
|
+
|
50
|
+
require 'awsudo/identity_providers'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
2
|
+
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 'base64'
|
5
|
+
require 'net/http'
|
6
|
+
require 'net/https'
|
7
|
+
require 'nokogiri'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
require 'awsudo'
|
11
|
+
|
12
|
+
module AWSUDO
|
13
|
+
class IdentityProvider
|
14
|
+
attr_accessor :idp_login_url, :saml_provider_name
|
15
|
+
attr_accessor :username, :password, :logger
|
16
|
+
|
17
|
+
def self.sts
|
18
|
+
return @sts unless @sts.nil?
|
19
|
+
@sts = Aws::STS::Client.new(
|
20
|
+
credentials: Aws::Credentials.new('a', 'b', 'c'),
|
21
|
+
region: 'us-east-1')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.new_from_config(config, username, password)
|
25
|
+
new(config['IDP_LOGIN_URL'], config['SAML_PROVIDER_NAME'],
|
26
|
+
username, password)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(url, name, username, password)
|
30
|
+
@idp_login_url = url
|
31
|
+
@saml_provider_name = name
|
32
|
+
@username = username
|
33
|
+
@password = password
|
34
|
+
@logger = AWSUDO.logger
|
35
|
+
begin
|
36
|
+
URI.parse @idp_login_url
|
37
|
+
rescue
|
38
|
+
raise "`#{@idp_login_url.inspect}' is not a valid IDP login URL"
|
39
|
+
end
|
40
|
+
if @saml_provider_name.nil? || @saml_provider_name.strip.empty?
|
41
|
+
raise "`#{@saml_provider_name.inspect}' is not a valid SAML provider name"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def saml_request
|
46
|
+
raise "should be implemented by subclass"
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_saml_response
|
50
|
+
req = saml_request
|
51
|
+
res = nil
|
52
|
+
uri = URI.parse(idp_login_url)
|
53
|
+
|
54
|
+
loop do
|
55
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
56
|
+
http.use_ssl = true
|
57
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
58
|
+
|
59
|
+
res = http.request(req)
|
60
|
+
logger.debug {"Location: <#{res['Location']}>"}
|
61
|
+
logger.debug {"Headers: <#{res.to_hash.inspect}>"}
|
62
|
+
logger.debug {"Body: <#{res.body.inspect}>"}
|
63
|
+
|
64
|
+
break if res['Location'].nil?
|
65
|
+
|
66
|
+
uri = URI.parse(res['Location'])
|
67
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
68
|
+
req['Cookie'] = res['Set-Cookie']
|
69
|
+
end
|
70
|
+
|
71
|
+
doc = Nokogiri::HTML(res.body)
|
72
|
+
doc.xpath('/html/body//form/input[@name = "SAMLResponse"]/@value').to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def assume_role(role_arn)
|
76
|
+
logger.debug {"role_arn: <#{role_arn}>"}
|
77
|
+
base_arn = role_arn[/^arn:aws:iam::\d+:/]
|
78
|
+
principal_arn = "#{base_arn}saml-provider/#{saml_provider_name}"
|
79
|
+
logger.debug {"principal_arn: <#{principal_arn}>"}
|
80
|
+
saml_assertion = get_saml_response
|
81
|
+
logger.debug {"saml_assertion: <#{Base64.decode64 saml_assertion}>"}
|
82
|
+
if saml_assertion.empty?
|
83
|
+
raise 'Unable to get SAML assertion (failed authentication?)'
|
84
|
+
end
|
85
|
+
self.class.sts.assume_role_with_saml(
|
86
|
+
role_arn: role_arn,
|
87
|
+
principal_arn: principal_arn,
|
88
|
+
saml_assertion: saml_assertion).credentials.to_h
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
2
|
+
|
3
|
+
module AWSUDO
|
4
|
+
module IdentityProviders
|
5
|
+
def self.new(idpname, config, username, password)
|
6
|
+
self.const_get(idpname).new_from_config(config, username, password)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'awsudo/identity_providers/adfs.rb'
|
12
|
+
require 'awsudo/identity_providers/okta.rb'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
2
|
+
|
3
|
+
require 'awsudo/identity_provider'
|
4
|
+
|
5
|
+
module AWSUDO
|
6
|
+
module IdentityProviders
|
7
|
+
class Adfs < IdentityProvider
|
8
|
+
def saml_request
|
9
|
+
uri = URI.parse(idp_login_url)
|
10
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
11
|
+
req.set_form_data({'username' => username, 'password' => password})
|
12
|
+
req
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright (C) 2015-2017 Electronic Arts Inc. All rights reserved.
|
2
|
+
|
3
|
+
require 'awsudo/identity_provider'
|
4
|
+
|
5
|
+
module AWSUDO
|
6
|
+
module IdentityProviders
|
7
|
+
class Okta < IdentityProvider
|
8
|
+
attr_accessor :api_endpoint
|
9
|
+
|
10
|
+
def self.new_from_config(config, username, password)
|
11
|
+
new(config['IDP_LOGIN_URL'], config['SAML_PROVIDER_NAME'],
|
12
|
+
config['API_ENDPOINT'], username, password)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(url, name, endpoint, username, password)
|
16
|
+
super(url, name, username, password)
|
17
|
+
@api_endpoint = endpoint
|
18
|
+
logger.debug "api_endpoint: <#{@api_endpoint}>"
|
19
|
+
begin
|
20
|
+
URI.parse(@api_endpoint)
|
21
|
+
rescue
|
22
|
+
raise "`#{@api_endpoint.inspect}' is not a valid API endpoint"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def authenticate
|
27
|
+
payload = {
|
28
|
+
'username' => username,
|
29
|
+
'password' => password,
|
30
|
+
'options' => {
|
31
|
+
'multiOptionalFactorEnroll' => false,
|
32
|
+
'warnBeforePasswordExpired' => false
|
33
|
+
}
|
34
|
+
}.to_json
|
35
|
+
uri = URI.parse(api_endpoint + '/authn')
|
36
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
37
|
+
http.use_ssl = true
|
38
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
39
|
+
|
40
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
41
|
+
req.content_type = 'application/json'
|
42
|
+
req['Accept'] = 'application/json'
|
43
|
+
req.body = payload
|
44
|
+
logger.debug {"payload: <#{req.body.inspect}>"}
|
45
|
+
res = http.request(req)
|
46
|
+
logger.debug {"Headers: <#{res.to_hash.inspect}>"}
|
47
|
+
logger.debug {"Body: <#{res.body.inspect}>"}
|
48
|
+
result = JSON.parse(res.body)
|
49
|
+
|
50
|
+
case result['status']
|
51
|
+
when 'SUCCESS'
|
52
|
+
return result['sessionToken']
|
53
|
+
when 'MFA_REQUIRED'
|
54
|
+
raise 'MFA required'
|
55
|
+
else
|
56
|
+
raise 'Authentication failed'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def saml_request
|
61
|
+
session_token = authenticate
|
62
|
+
uri = URI.parse(idp_login_url)
|
63
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
64
|
+
req.set_form_data({'onetimetoken' => session_token})
|
65
|
+
req
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awsudo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerardo Santana Gomez Garrido
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
27
41
|
description: |
|
28
42
|
awsudo enables users to execute commands that make API calls to AWS under the
|
29
43
|
security context of an IAM role. The IAM role is assumed only upon successful
|
@@ -39,15 +53,20 @@ executables:
|
|
39
53
|
extensions: []
|
40
54
|
extra_rdoc_files: []
|
41
55
|
files:
|
42
|
-
- bin/aws-agent
|
43
|
-
- bin/awsudo
|
44
|
-
- lib/awsudo.rb
|
45
|
-
- LICENSE
|
46
56
|
- CHANGELOG.md
|
47
57
|
- CONTRIBUTING.md
|
58
|
+
- LICENSE
|
48
59
|
- README.md
|
60
|
+
- bin/aws-agent
|
61
|
+
- bin/awsudo
|
62
|
+
- lib/awsudo.rb
|
63
|
+
- lib/awsudo/identity_provider.rb
|
64
|
+
- lib/awsudo/identity_providers.rb
|
65
|
+
- lib/awsudo/identity_providers/adfs.rb
|
66
|
+
- lib/awsudo/identity_providers/okta.rb
|
49
67
|
homepage: https://github.com/electronicarts/awsudo
|
50
|
-
licenses:
|
68
|
+
licenses:
|
69
|
+
- BSD-3-Clause
|
51
70
|
metadata: {}
|
52
71
|
post_install_message:
|
53
72
|
rdoc_options: []
|
@@ -55,17 +74,17 @@ require_paths:
|
|
55
74
|
- lib
|
56
75
|
required_ruby_version: !ruby/object:Gem::Requirement
|
57
76
|
requirements:
|
58
|
-
- -
|
77
|
+
- - ">="
|
59
78
|
- !ruby/object:Gem::Version
|
60
79
|
version: '0'
|
61
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
81
|
requirements:
|
63
|
-
- -
|
82
|
+
- - ">="
|
64
83
|
- !ruby/object:Gem::Version
|
65
84
|
version: '0'
|
66
85
|
requirements: []
|
67
86
|
rubyforge_project:
|
68
|
-
rubygems_version: 2.
|
87
|
+
rubygems_version: 2.6.11
|
69
88
|
signing_key:
|
70
89
|
specification_version: 4
|
71
90
|
summary: executes a command with the permissions given by an AWS IAM role
|