aws-mfa 0.2.0 → 0.3.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 +7 -0
- data/bin/aws-mfa +6 -122
- data/lib/aws_mfa.rb +170 -0
- data/lib/aws_mfa/errors.rb +11 -0
- metadata +52 -10
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7acea0de7d157dcacffc45761829147d3928f4ff
|
4
|
+
data.tar.gz: 18a947ec9f627b2e303585149c68e0a3393510b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b6a4b52a6a3ce238876ddffdcf9ea3d428e9c75e41069a9394be4fe7b477d9971fb0d42d7841d6e9503724096d5a446d20727ff124e9ec969fa506172de7db04
|
7
|
+
data.tar.gz: 4adc1bc7e1171f840e855264f05eec5ccda076404fddbb8e95f55ef945d9ce222af1cb79d20701fd5b3e1a4818cd204373ccc24c2abd9d4f77dbd49b1d3f446b
|
data/bin/aws-mfa
CHANGED
@@ -1,126 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'aws_mfa'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def validate_aws_install
|
11
|
-
unless which('aws')
|
12
|
-
puts 'Could not find the aws command'
|
13
|
-
exit 1
|
14
|
-
end
|
15
|
-
|
16
|
-
if ENV['AWS_CREDENTIAL_FILE']
|
17
|
-
aws_config_file = ENV['AWS_CREDENTIAL_FILE']
|
18
|
-
aws_config_dir = File.dirname(aws_config_file)
|
19
|
-
else
|
20
|
-
aws_config_dir = File.join(ENV['HOME'], '.aws')
|
21
|
-
aws_config_file = File.join(aws_config_dir, 'config')
|
22
|
-
end
|
23
|
-
|
24
|
-
unless File.readable?(aws_config_file)
|
25
|
-
puts 'Aws configuration not found. You must run `aws cli configure`'
|
26
|
-
exit 1
|
27
|
-
end
|
28
|
-
|
29
|
-
aws_config_dir
|
30
|
-
end
|
31
|
-
|
32
|
-
# http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
|
33
|
-
def which(cmd)
|
34
|
-
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
35
|
-
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
36
|
-
exts.each { |ext|
|
37
|
-
exe = File.join(path, "#{cmd}#{ext}")
|
38
|
-
return exe if File.executable? exe
|
39
|
-
}
|
40
|
-
end
|
41
|
-
return nil
|
42
|
-
end
|
43
|
-
|
44
|
-
def load_arn
|
45
|
-
arn_file = File.join(@aws_config_dir, 'mfa_device')
|
46
|
-
|
47
|
-
if File.readable?(arn_file)
|
48
|
-
arn = File.read(arn_file)
|
49
|
-
else
|
50
|
-
puts 'Fetching MFA devices for your account...'
|
51
|
-
mfa_devices = JSON.parse(`aws iam list-mfa-devices`).fetch('MFADevices')
|
52
|
-
if mfa_devices.any?
|
53
|
-
arn = mfa_devices.first.fetch('SerialNumber')
|
54
|
-
puts "Using MFA device #{arn}. To change this in the future edit #{arn_file}."
|
55
|
-
File.open(arn_file, 'w') { |f| f.print arn }
|
56
|
-
else
|
57
|
-
abort 'No MFA devices were found for your account'
|
58
|
-
end
|
59
|
-
end
|
60
|
-
arn
|
61
|
-
end
|
62
|
-
|
63
|
-
def get_credentials(arn)
|
64
|
-
credentials_file = File.join(@aws_config_dir, 'mfa_credentials')
|
65
|
-
|
66
|
-
if File.readable?(credentials_file) && token_not_expired?(credentials_file)
|
67
|
-
credentials = JSON.parse(File.read(credentials_file))
|
68
|
-
else
|
69
|
-
puts 'Enter the 6-digit code from your MFA device:'
|
70
|
-
code = $stdin.gets.chomp
|
71
|
-
unless code =~ /^\d{6}$/
|
72
|
-
puts 'That is an invalid MFA code'
|
73
|
-
exit 1
|
74
|
-
end
|
75
|
-
ENV.delete('AWS_SECRET_ACCESS_KEY')
|
76
|
-
ENV.delete('AWS_ACCESS_KEY_ID')
|
77
|
-
ENV.delete('AWS_SESSION_TOKEN')
|
78
|
-
ENV.delete('AWS_SECURITY_TOKEN')
|
79
|
-
credentials_raw = `aws sts get-session-token --serial-number #{arn} --token-code #{code}`
|
80
|
-
credentials = JSON.parse(credentials_raw)
|
81
|
-
File.open(credentials_file, 'w') { |f| f.print credentials_raw }
|
82
|
-
end
|
83
|
-
|
84
|
-
credentials['Credentials']
|
85
|
-
end
|
86
|
-
|
87
|
-
def token_not_expired?(credentials_file)
|
88
|
-
# default is 12 hours
|
89
|
-
expiration_period = 12 * 60 * 60
|
90
|
-
mtime = File.stat(credentials_file).mtime
|
91
|
-
now = Time.new
|
92
|
-
if now - mtime < expiration_period
|
93
|
-
true
|
94
|
-
else
|
95
|
-
false
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def print_credentials(credentials)
|
100
|
-
puts "export AWS_SECRET_ACCESS_KEY='#{credentials['SecretAccessKey']}'"
|
101
|
-
puts "export AWS_ACCESS_KEY_ID='#{credentials['AccessKeyId']}'"
|
102
|
-
puts "export AWS_SESSION_TOKEN='#{credentials['SessionToken']}'"
|
103
|
-
puts "export AWS_SECURITY_TOKEN='#{credentials['SessionToken']}'"
|
104
|
-
end
|
105
|
-
|
106
|
-
def export_credentials(credentials)
|
107
|
-
ENV['AWS_SECRET_ACCESS_KEY'] = credentials['SecretAccessKey']
|
108
|
-
ENV['AWS_ACCESS_KEY_ID'] = credentials['AccessKeyId']
|
109
|
-
ENV['AWS_SESSION_TOKEN'] = credentials['SessionToken']
|
110
|
-
ENV['AWS_SECURITY_TOKEN'] = credentials['SessionToken']
|
111
|
-
end
|
112
|
-
|
113
|
-
def execute
|
114
|
-
arn = load_arn
|
115
|
-
credentials = get_credentials(arn)
|
116
|
-
if ARGV.empty?
|
117
|
-
print_credentials(credentials)
|
118
|
-
else
|
119
|
-
export_credentials(credentials)
|
120
|
-
exec(*ARGV)
|
121
|
-
end
|
122
|
-
end
|
5
|
+
begin
|
6
|
+
aws_mfa = AwsMfa.new
|
7
|
+
aws_mfa.execute
|
8
|
+
rescue AwsMfa::Errors::Error => e
|
9
|
+
abort e.message
|
123
10
|
end
|
124
|
-
|
125
|
-
aws_mfa = AwsMfa.new
|
126
|
-
aws_mfa.execute
|
data/lib/aws_mfa.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'aws_mfa/errors'
|
3
|
+
|
4
|
+
class AwsMfa
|
5
|
+
attr_reader :aws_config_dir
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
validate_aws_installed
|
9
|
+
@aws_config_dir = find_aws_config
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate_aws_installed
|
13
|
+
raise Errors::CommandNotFound, 'Could not find the aws command' unless which('aws')
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_aws_config
|
17
|
+
if ENV['AWS_CREDENTIAL_FILE']
|
18
|
+
aws_config_file = ENV['AWS_CREDENTIAL_FILE']
|
19
|
+
aws_config_dir = File.dirname(aws_config_file)
|
20
|
+
else
|
21
|
+
aws_config_dir = File.join(ENV['HOME'], '.aws')
|
22
|
+
aws_config_file = File.join(aws_config_dir, 'config')
|
23
|
+
end
|
24
|
+
|
25
|
+
unless File.readable?(aws_config_file)
|
26
|
+
raise Errors::ConfigurationNotFound, 'Aws configuration not found. You must run `aws configure`'
|
27
|
+
end
|
28
|
+
|
29
|
+
aws_config_dir
|
30
|
+
end
|
31
|
+
|
32
|
+
# http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
|
33
|
+
def which(cmd)
|
34
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
35
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
36
|
+
exts.each { |ext|
|
37
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
38
|
+
return exe if File.executable? exe
|
39
|
+
}
|
40
|
+
end
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_arn(profile='default')
|
45
|
+
arn_file_name = 'mfa_device'
|
46
|
+
if (! profile.eql? 'default')
|
47
|
+
arn_file_name = "#{profile}_#{arn_file_name}"
|
48
|
+
end
|
49
|
+
arn_file = File.join(aws_config_dir, arn_file_name)
|
50
|
+
|
51
|
+
if File.readable?(arn_file)
|
52
|
+
arn = load_arn_from_file(arn_file)
|
53
|
+
else
|
54
|
+
arn = load_arn_from_aws(profile)
|
55
|
+
write_arn_to_file(arn_file, arn)
|
56
|
+
end
|
57
|
+
|
58
|
+
arn
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_arn_from_file(arn_file)
|
62
|
+
File.read(arn_file)
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_arn_from_aws(profile='default')
|
66
|
+
puts 'Fetching MFA devices for your account...'
|
67
|
+
if mfa_devices(profile).any?
|
68
|
+
mfa_devices(profile).first.fetch('SerialNumber')
|
69
|
+
else
|
70
|
+
raise Errors::DeviceNotFound, 'No MFA devices were found for your account'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def mfa_devices(profile='default')
|
75
|
+
@mfa_devices ||= JSON.parse(`aws --profile #{profile} --output json iam list-mfa-devices`).fetch('MFADevices')
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_arn_to_file(arn_file, arn)
|
79
|
+
File.open(arn_file, 'w') { |f| f.print arn }
|
80
|
+
puts "Using MFA device #{arn}. To change this in the future edit #{arn_file}."
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_credentials(arn, profile='default')
|
84
|
+
credentials_file_name = 'mfa_credentials'
|
85
|
+
if (! profile.eql? 'default')
|
86
|
+
credentials_file_name = "#{profile}_#{credentials_file_name}"
|
87
|
+
end
|
88
|
+
credentials_file = File.join(aws_config_dir, credentials_file_name)
|
89
|
+
|
90
|
+
if File.readable?(credentials_file) && token_not_expired?(credentials_file)
|
91
|
+
credentials = load_credentials_from_file(credentials_file)
|
92
|
+
else
|
93
|
+
credentials = load_credentials_from_aws(arn, profile)
|
94
|
+
write_credentials_to_file(credentials_file, credentials)
|
95
|
+
end
|
96
|
+
|
97
|
+
JSON.parse(credentials).fetch('Credentials')
|
98
|
+
end
|
99
|
+
|
100
|
+
def load_credentials_from_file(credentials_file)
|
101
|
+
File.read(credentials_file)
|
102
|
+
end
|
103
|
+
|
104
|
+
def load_credentials_from_aws(arn, profile='default')
|
105
|
+
code = request_code_from_user
|
106
|
+
unset_environment
|
107
|
+
`aws --profile #{profile} --output json sts get-session-token --serial-number #{arn} --token-code #{code}`
|
108
|
+
end
|
109
|
+
|
110
|
+
def write_credentials_to_file(credentials_file, credentials)
|
111
|
+
File.open(credentials_file, 'w') { |f| f.print credentials }
|
112
|
+
end
|
113
|
+
|
114
|
+
def request_code_from_user
|
115
|
+
puts 'Enter the 6-digit code from your MFA device:'
|
116
|
+
code = $stdin.gets.chomp
|
117
|
+
raise Errors::InvalidCode, 'That is an invalid MFA code' unless code =~ /^\d{6}$/
|
118
|
+
code
|
119
|
+
end
|
120
|
+
|
121
|
+
def unset_environment
|
122
|
+
ENV.delete('AWS_SECRET_ACCESS_KEY')
|
123
|
+
ENV.delete('AWS_ACCESS_KEY_ID')
|
124
|
+
ENV.delete('AWS_SESSION_TOKEN')
|
125
|
+
ENV.delete('AWS_SECURITY_TOKEN')
|
126
|
+
end
|
127
|
+
|
128
|
+
def token_not_expired?(credentials_file)
|
129
|
+
# default is 12 hours
|
130
|
+
expiration_period = 12 * 60 * 60
|
131
|
+
mtime = File.stat(credentials_file).mtime
|
132
|
+
now = Time.new
|
133
|
+
if now - mtime < expiration_period
|
134
|
+
true
|
135
|
+
else
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def print_credentials(credentials)
|
141
|
+
puts "export AWS_SECRET_ACCESS_KEY='#{credentials['SecretAccessKey']}'"
|
142
|
+
puts "export AWS_ACCESS_KEY_ID='#{credentials['AccessKeyId']}'"
|
143
|
+
puts "export AWS_SESSION_TOKEN='#{credentials['SessionToken']}'"
|
144
|
+
puts "export AWS_SECURITY_TOKEN='#{credentials['SessionToken']}'"
|
145
|
+
end
|
146
|
+
|
147
|
+
def export_credentials(credentials)
|
148
|
+
ENV['AWS_SECRET_ACCESS_KEY'] = credentials['SecretAccessKey']
|
149
|
+
ENV['AWS_ACCESS_KEY_ID'] = credentials['AccessKeyId']
|
150
|
+
ENV['AWS_SESSION_TOKEN'] = credentials['SessionToken']
|
151
|
+
ENV['AWS_SECURITY_TOKEN'] = credentials['SessionToken']
|
152
|
+
end
|
153
|
+
|
154
|
+
def execute
|
155
|
+
profile = 'default'
|
156
|
+
profile_index = ARGV.index('--profile')
|
157
|
+
if (!profile_index.nil?)
|
158
|
+
profile = ARGV.delete_at(profile_index + 1)
|
159
|
+
ARGV.delete_at(profile_index)
|
160
|
+
end
|
161
|
+
arn = load_arn(profile)
|
162
|
+
credentials = load_credentials(arn, profile)
|
163
|
+
if ARGV.empty?
|
164
|
+
print_credentials(credentials)
|
165
|
+
else
|
166
|
+
export_credentials(credentials)
|
167
|
+
exec(*ARGV)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
metadata
CHANGED
@@ -1,16 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-mfa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Brian Pitts
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
11
|
+
date: 2016-02-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fakefs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
14
55
|
description: Run AWS commands with MFA
|
15
56
|
email: brian.pitts@lonelyplanet.com
|
16
57
|
executables:
|
@@ -19,30 +60,31 @@ extensions: []
|
|
19
60
|
extra_rdoc_files: []
|
20
61
|
files:
|
21
62
|
- bin/aws-mfa
|
63
|
+
- lib/aws_mfa.rb
|
64
|
+
- lib/aws_mfa/errors.rb
|
22
65
|
homepage: http://www.github.com/lonelyplanet/aws-mfa
|
23
66
|
licenses:
|
24
67
|
- Apache-2.0
|
68
|
+
metadata: {}
|
25
69
|
post_install_message:
|
26
70
|
rdoc_options: []
|
27
71
|
require_paths:
|
28
72
|
- lib
|
29
73
|
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
-
none: false
|
31
74
|
requirements:
|
32
|
-
- -
|
75
|
+
- - ">="
|
33
76
|
- !ruby/object:Gem::Version
|
34
77
|
version: '0'
|
35
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
-
none: false
|
37
79
|
requirements:
|
38
|
-
- -
|
80
|
+
- - ">="
|
39
81
|
- !ruby/object:Gem::Version
|
40
82
|
version: '0'
|
41
83
|
requirements:
|
42
84
|
- aws-cli
|
43
85
|
rubyforge_project:
|
44
|
-
rubygems_version:
|
86
|
+
rubygems_version: 2.4.5.1
|
45
87
|
signing_key:
|
46
|
-
specification_version:
|
88
|
+
specification_version: 4
|
47
89
|
summary: Run AWS commands with MFA
|
48
90
|
test_files: []
|