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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/bin/aws-mfa +6 -122
  3. data/lib/aws_mfa.rb +170 -0
  4. data/lib/aws_mfa/errors.rb +11 -0
  5. metadata +52 -10
@@ -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
@@ -1,126 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'json'
3
+ require 'aws_mfa'
4
4
 
5
- class AwsMfa
6
- def initialize
7
- @aws_config_dir = validate_aws_install
8
- end
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
@@ -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
@@ -0,0 +1,11 @@
1
+ class AwsMfa
2
+ class Errors
3
+
4
+ class Error < StandardError; end
5
+ class ConfigurationNotFound < Error; end
6
+ class CommandNotFound < Error; end
7
+ class DeviceNotFound < Error; end
8
+ class InvalidCode < Error; end
9
+
10
+ end
11
+ 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.2.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: 2014-09-30 00:00:00.000000000 Z
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: 1.8.23
86
+ rubygems_version: 2.4.5.1
45
87
  signing_key:
46
- specification_version: 3
88
+ specification_version: 4
47
89
  summary: Run AWS commands with MFA
48
90
  test_files: []