aws-mfa 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []