awsudo 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/electronicarts/awsudo.svg?branch=master)](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
|