aws-google 0.1.1 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03312ea0556bae7f422a1b33b26200316b1367c49c3953ee980595b8252417b3
4
- data.tar.gz: e2df7bb3a34014e0d8ff13157b53d6834a39cc0c0fc15933245f56b09ecfcd1e
3
+ metadata.gz: 1ec086b6871d43a2064a99974bb55fd3f1cd6bc0dc5a749a560f3a41f3d1a9e6
4
+ data.tar.gz: 0ff2652d74bf6eb73076d146a9c4ab6c65a6a16dba54b7ce2d4d0d00b3c95c6d
5
5
  SHA512:
6
- metadata.gz: ce872352fc5b0c54fe89cfceb87b94222e134cc8369d504302d8c0aa55a790cb03c9ffd281e7e70800ebba3bc18147f5ce39e856e3800c5969587170637ad156
7
- data.tar.gz: b349a3bfd15fff12184eb70ba32082238f792532d7cf49bae7de33e47039a818d1d7dad2fd19f360d52fae5bc654dfc095021992d891b4cf4bb9f373072a334f
6
+ metadata.gz: 3e168070dc1cf75b2d74cddd96b1add3db9a9c37e613f3ed4bddb5d14ce969152f41c0fe75b1954e958129b6b6eee92c6fc9797c6bb2924c4a6a9e4fa87b94bc
7
+ data.tar.gz: 8ba6d8a8c01926516c53d9bf7477a080b8d853a6f7b76e670378012e5660729d63e59424d7c0b98bd28ab0f3a83d3c20a1e80b546189d74ff5a4ef9bc87547d2
data/README.md CHANGED
@@ -21,8 +21,8 @@ Or install it yourself as:
21
21
  ## Usage
22
22
 
23
23
  - Visit the [Google API Console](https://console.developers.google.com/) to create/obtain OAuth 2.0 Client ID credentials (client ID and client secret) for an application in your Google account.
24
- - Create an AWS IAM Role with the desired IAM policies attached, and a 'trust relationship' (`AssumeRolePolicyDocument`) allowing the `sts:AssumeRoleWithWebIdentity` action to be permitted
25
- by your Google Client ID and a specific set of Google Account IDs:
24
+ - Create an AWS IAM Role with the desired IAM policies attached, and a ['trust policy'](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#term_trust-policy) ([`AssumeRolePolicyDocument`](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html)) allowing the [`sts:AssumeRoleWithWebIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) action with [Web Identity Federation condition keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif) authorizing
25
+ your Google Client ID (`accounts.google.com:aud`) and a specific set of Google Account IDs (`accounts.google.com:sub`):
26
26
 
27
27
  ```json
28
28
  {
@@ -36,9 +36,7 @@ by your Google Client ID and a specific set of Google Account IDs:
36
36
  "Action": "sts:AssumeRoleWithWebIdentity",
37
37
  "Condition": {
38
38
  "StringEquals": {
39
- "accounts.google.com:aud": "123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com"
40
- },
41
- "ForAnyValue:StringEquals": {
39
+ "accounts.google.com:aud": "123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com",
42
40
  "accounts.google.com:sub": [
43
41
  "000000000000000000000",
44
42
  "111111111111111111111"
@@ -50,35 +48,38 @@ by your Google Client ID and a specific set of Google Account IDs:
50
48
  }
51
49
  ```
52
50
 
53
- - In your Ruby code, construct an `Aws::Google` object by passing in the AWS role, client id and client secret:
51
+ - In your Ruby code, construct an `Aws::Google` object by passing the AWS `role_arn`, Google `client_id` and `client_secret`, either as constructor arguments or via the `Aws::Google.config` global defaults:
54
52
  ```ruby
55
53
  require 'aws/google'
56
54
 
57
- aws_role = 'arn:aws:iam::[AccountID]:role/[Role]'
58
- client_id = '123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com'
59
- client_secret = '01234567890abcdefghijklmn'
55
+ options = {
56
+ aws_role: 'arn:aws:iam::[AccountID]:role/[Role]',
57
+ client_id: '123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com',
58
+ client_secret: '01234567890abcdefghijklmn'
59
+ }
60
60
 
61
- role_credentials = Aws::Google.new(
62
- role_arn: aws_role,
63
- google_client_id: client_id,
64
- google_client_secret: client_secret
65
- )
61
+ # Pass constructor arguments:
62
+ credentials = Aws::Google.new(options)
63
+ puts Aws::STS::Client.new(credentials: credentials).get_caller_identity
66
64
 
67
- puts Aws::STS::Client.new(credentials: role_credentials).get_caller_identity
65
+ # Set global defaults:
66
+ Aws::Google.config = options
67
+ puts Aws::STS::Client.new.get_caller_identity
68
68
  ```
69
69
 
70
- - Or, set `Aws::Google.config` hash to add Google auth to the default credential provider chain:
70
+ - Or, add the properties to your AWS config profile ([`~/.aws/config`](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-where)) to use Google as the AWS credential provider without any changes to your application code:
71
71
 
72
- ```ruby
73
- Aws::Google.config = {
74
- role_arn: aws_role,
75
- google_client_id: client_id,
76
- google_client_secret: client_secret,
77
- }
78
-
79
- puts Aws::STS::Client.new.get_caller_identity
72
+ ```ini
73
+ [my_profile]
74
+ google =
75
+ role_arn = arn:aws:iam::[AccountID]:role/[Role]
76
+ client_id = 123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com
77
+ client_secret = 01234567890abcdefghijklmn
78
+ credential_process = aws-google
80
79
  ```
81
80
 
81
+ The extra `credential_process` config line tells AWS to [Source Credentials with an External Process](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html), in this case the `aws-google` script, which allows you to seamlessly use the same Google login configuration from non-Ruby SDKs (like the CLI).
82
+
82
83
  ## Development
83
84
 
84
85
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -23,11 +23,10 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency 'aws-sdk-core', '~> 3'
25
25
  spec.add_dependency 'google-api-client', '~> 0.23'
26
- spec.add_dependency 'launchy', '~> 2' # Peer dependency of Google::APIClient::InstalledAppFlow
26
+ spec.add_dependency 'launchy', '~> 2'
27
27
 
28
28
  spec.add_development_dependency 'activesupport', '~> 5'
29
- spec.add_development_dependency 'bundler', '~> 1'
30
- spec.add_development_dependency 'minitest', '~> 5.10'
29
+ spec.add_development_dependency 'minitest', '~> 5.14.2'
31
30
  spec.add_development_dependency 'mocha', '~> 1.5'
32
31
  spec.add_development_dependency 'rake', '~> 12'
33
32
  spec.add_development_dependency 'timecop', '~> 0.8'
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # CLI to retrieve AWS credentials in credential_process format.
4
+ # Ref: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html
5
+
6
+ require 'aws/google'
7
+ require 'time'
8
+ require 'json'
9
+
10
+ google = ::Aws::STS::Client.new.config.credentials
11
+ credentials = google.credentials
12
+ output = {
13
+ Version: 1,
14
+ AccessKeyId: credentials.access_key_id,
15
+ SecretAccessKey: credentials.secret_access_key,
16
+ SessionToken: credentials.session_token,
17
+ Expiration: Time.at(google.expiration.to_i).iso8601
18
+ }
19
+ puts output.to_json
@@ -1,6 +1,7 @@
1
1
  require_relative 'google/version'
2
2
  require 'aws-sdk-core'
3
3
  require_relative 'google/credential_provider'
4
+ require_relative 'google/cached_credentials'
4
5
 
5
6
  require 'googleauth'
6
7
  require 'google/api_client/auth/storage'
@@ -23,51 +24,43 @@ module Aws
23
24
  # constructed.
24
25
  class Google
25
26
  include ::Aws::CredentialProvider
26
- include ::Aws::RefreshingCredentials
27
+ include ::Aws::Google::CachedCredentials
27
28
 
28
29
  class << self
30
+ # Use `Aws::Google.config` to set default options for any instance of this provider.
29
31
  attr_accessor :config
30
32
  end
33
+ self.config = {}
31
34
 
32
35
  # @option options [required, String] :role_arn
33
36
  # @option options [String] :policy
34
37
  # @option options [Integer] :duration_seconds
35
38
  # @option options [String] :external_id
36
39
  # @option options [STS::Client] :client STS::Client to use (default: create new client)
37
- # @option options [String] :profile AWS Profile to store temporary credentials (default `default`)
38
40
  # @option options [String] :domain G Suite domain for account-selection hint
39
41
  # @option options [String] :online if `true` only a temporary access token will be provided,
40
42
  # a long-lived refresh token will not be created and stored on the filesystem.
41
43
  # @option options [String] :port port for local server to listen on to capture oauth browser redirect.
42
- # Defaults to an out-of-band authentication process.
43
- # @option options [::Google::Auth::ClientId] :google_id
44
+ # Defaults to 1234. Set to nil or 0 to use an out-of-band authentication process.
45
+ # @option options [String] :client_id Google client ID
46
+ # @option options [String] :client_secret Google client secret
44
47
  def initialize(options = {})
48
+ options = options.merge(self.class.config)
45
49
  @oauth_attempted = false
46
50
  @assume_role_params = options.slice(
47
51
  *Aws::STS::Client.api.operation(:assume_role_with_web_identity).
48
52
  input.shape.member_names
49
53
  )
50
54
 
51
- @profile = options[:profile] || ENV['AWS_DEFAULT_PROFILE'] || 'default'
52
55
  @google_id = ::Google::Auth::ClientId.new(
53
- options[:google_client_id],
54
- options[:google_client_secret]
56
+ options[:client_id],
57
+ options[:client_secret]
55
58
  )
56
59
  @client = options[:client] || Aws::STS::Client.new(credentials: nil)
57
60
  @domain = options[:domain]
58
61
  @online = options[:online]
59
- @port = options[:port]
60
-
61
- # Use existing AWS credentials stored in the shared config if available.
62
- # If this is `nil` or expired, #refresh will be called on the first AWS API service call
63
- # to generate AWS credentials derived from Google authentication.
64
- @expiration = Aws.shared_config.get('expiration', profile: @profile) rescue nil
65
- @mutex = Mutex.new
66
- if near_expiration?
67
- refresh!
68
- else
69
- @credentials = Aws.shared_config.credentials(profile: @profile) rescue nil
70
- end
62
+ @port = options[:port] || 1234
63
+ super
71
64
  end
72
65
 
73
66
  private
@@ -105,8 +98,18 @@ module Aws
105
98
  credentials.tap(&storage.method(:write_credentials))
106
99
  end
107
100
 
101
+ def silence_output
102
+ outs = [$stdout, $stderr]
103
+ clones = outs.map(&:clone)
104
+ outs.each { |io| io.reopen '/dev/null'}
105
+ yield
106
+ ensure
107
+ outs.each_with_index { |io, i| io.reopen(clones[i]) }
108
+ end
109
+
108
110
  def get_oauth_code(client, options)
109
- raise 'fallback' unless @port
111
+ raise 'fallback' unless @port && !@port.zero?
112
+
110
113
  require 'launchy'
111
114
  require 'webrick'
112
115
  code = nil
@@ -123,26 +126,29 @@ module Aws
123
126
  end
124
127
  trap('INT') { server.shutdown }
125
128
  client.redirect_uri = "http://localhost:#{@port}"
126
- launchy = Launchy.open(client.authorization_uri(options).to_s)
127
- server_thread = Thread.new do
128
- begin
129
- server.start
130
- ensure server.shutdown
129
+ silence_output do
130
+ launchy = Launchy.open(client.authorization_uri(options).to_s)
131
+ server_thread = Thread.new do
132
+ begin
133
+ server.start
134
+ ensure server.shutdown
135
+ end
136
+ end
137
+ while server_thread.alive?
138
+ raise 'fallback' if !launchy.alive? && !launchy.value.success?
139
+
140
+ sleep 0.1
131
141
  end
132
- end
133
- while server_thread.alive?
134
- raise 'fallback' if !launchy.alive? && !launchy.value.success?
135
- sleep 0.1
136
142
  end
137
143
  code || raise('fallback')
138
144
  rescue StandardError
139
145
  trap('INT', 'DEFAULT')
140
146
  # Fallback to out-of-band authentication if browser launch failed.
141
147
  client.redirect_uri = 'oob'
142
- url = client.authorization_uri(options)
143
- print "\nOpen the following URL in a browser and enter the " \
144
- "resulting code after authorization:\n#{url}\n> "
145
- gets
148
+ return ENV['OAUTH_CODE'] if ENV['OAUTH_CODE']
149
+
150
+ raise RuntimeError, 'Open the following URL in a browser to get a code,' \
151
+ "export to $OAUTH_CODE and rerun:\n#{client.authorization_uri(options)}", []
146
152
  end
147
153
 
148
154
  def refresh
@@ -168,7 +174,7 @@ module Aws
168
174
  role_session_name: token_params['email']
169
175
  )
170
176
  )
171
- rescue Signet::AuthorizationError => e
177
+ rescue Signet::AuthorizationError, Aws::STS::Errors::ExpiredTokenException
172
178
  retry if (@google_client = google_oauth)
173
179
  raise
174
180
  rescue Aws::STS::Errors::AccessDenied => e
@@ -176,7 +182,7 @@ module Aws
176
182
  raise e, "\nYour Google ID does not have access to the requested AWS Role. Ask your administrator to provide access.
177
183
  Role: #{@assume_role_params[:role_arn]}
178
184
  Email: #{token_params['email']}
179
- Google ID: #{token_params['sub']}", e.backtrace
185
+ Google ID: #{token_params['sub']}", []
180
186
  end
181
187
 
182
188
  c = assume_role.credentials
@@ -186,35 +192,8 @@ Google ID: #{token_params['sub']}", e.backtrace
186
192
  c.session_token
187
193
  )
188
194
  @expiration = c.expiration.to_i
189
- write_credentials
190
- end
191
-
192
- # Write credentials and expiration to AWS credentials file.
193
- def write_credentials
194
- # AWS CLI is needed because writing AWS credentials is not supported by the AWS Ruby SDK.
195
- return unless system('which aws >/dev/null 2>&1')
196
- %w[
197
- access_key_id
198
- secret_access_key
199
- session_token
200
- ].map {|x| ["aws_#{x}", @credentials.send(x)]}.
201
- to_h.
202
- merge(expiration: @expiration).each do |key, value|
203
- system("aws configure set #{key} #{value} --profile #{@profile}")
204
- end
205
- end
206
- end
207
-
208
- # Patch Aws::SharedConfig to allow fetching arbitrary keys from the shared config.
209
- module SharedConfigGetKey
210
- def get(key, opts = {})
211
- profile = opts.delete(:profile) || @profile_name
212
- if @parsed_config && (prof_config = @parsed_config[profile])
213
- prof_config[key]
214
- end
215
195
  end
216
196
  end
217
- Aws::SharedConfig.prepend SharedConfigGetKey
218
197
 
219
198
  # Extend ::Google::APIClient::Storage to write {type: 'authorized_user'} to credentials,
220
199
  # as required by Google's default credentials loader.
@@ -0,0 +1,47 @@
1
+ module Aws
2
+ class Google
3
+ Aws::SharedConfig.config_reader :expiration
4
+
5
+ # Mixin module extending `RefreshingCredentials` that caches temporary credentials
6
+ # in the credentials file, so a single session can be reused across multiple processes.
7
+ # The temporary credentials are saved to a separate profile with a '_session' suffix.
8
+ module CachedCredentials
9
+ include RefreshingCredentials
10
+
11
+ # @option options [String] :profile AWS Profile to store temporary credentials (default `default`)
12
+ def initialize(options = {})
13
+ # Use existing AWS credentials stored in the shared session config if available.
14
+ # If this is `nil` or expired, #refresh will be called on the first AWS API service call
15
+ # to generate AWS credentials derived from Google authentication.
16
+ @mutex = Mutex.new
17
+
18
+ @profile = options[:profile] || ENV['AWS_PROFILE'] || ENV['AWS_DEFAULT_PROFILE'] || 'default'
19
+ @session_profile = @profile + '_session'
20
+ @expiration = Aws.shared_config.expiration(profile: @session_profile) rescue nil
21
+ @credentials = Aws.shared_config.credentials(profile: @session_profile) rescue nil
22
+ refresh_if_near_expiration
23
+ end
24
+
25
+ def refresh_if_near_expiration
26
+ if near_expiration?
27
+ @mutex.synchronize do
28
+ if near_expiration?
29
+ refresh
30
+ write_credentials
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ # Write credentials and expiration to AWS credentials file.
37
+ def write_credentials
38
+ # AWS CLI is needed because writing AWS credentials is not supported by the AWS Ruby SDK.
39
+ return unless system('which aws >/dev/null 2>&1')
40
+ Aws::SharedCredentials::KEY_MAP.transform_values(&@credentials.method(:send)).
41
+ merge(expiration: @expiration).each do |key, value|
42
+ system("aws configure set #{key} #{value} --profile #{@session_profile}")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -3,16 +3,39 @@ module Aws
3
3
  # Inserts GoogleCredentials into the default AWS credential provider chain.
4
4
  # Google credentials will only be used if Aws::Google.config is set before initialization.
5
5
  module CredentialProvider
6
- # Insert google_credentials as the second-to-last credentials provider
7
- # (in front of instance profile, which makes an http request).
6
+ # Insert google_credentials as the third-to-last credentials provider
7
+ # (in front of process credentials and instance_profile credentials).
8
8
  def providers
9
- super.insert(-2, [:google_credentials, {}])
9
+ super.insert(-3, [:google_credentials, {}])
10
10
  end
11
11
 
12
12
  def google_credentials(options)
13
- (config = Google.config) && Google.new(options.merge(config))
13
+ profile_name = determine_profile_name(options)
14
+ if Aws.shared_config.config_enabled?
15
+ Aws.shared_config.google_credentials_from_config(profile: profile_name)
16
+ end
17
+ rescue Errors::NoSuchProfileError
18
+ nil
14
19
  end
15
20
  end
16
21
  ::Aws::CredentialProviderChain.prepend CredentialProvider
22
+
23
+ module GoogleSharedCredentials
24
+ def google_credentials_from_config(opts = {})
25
+ google_opts = {}
26
+ if @config_enabled && @parsed_config
27
+ p = opts[:profile] || @profile_name
28
+ google_opts.merge!(@parsed_config.
29
+ fetch(p, {}).fetch('google', {}).
30
+ transform_keys(&:to_sym)
31
+ )
32
+ end
33
+ google_opts.merge!(::Aws::Google.config)
34
+ if google_opts.has_key?(:role_arn)
35
+ Google.new(google_opts)
36
+ end
37
+ end
38
+ end
39
+ ::Aws::SharedConfig.prepend GoogleSharedCredentials
17
40
  end
18
41
  end
@@ -1,5 +1,5 @@
1
1
  module Aws
2
2
  class Google
3
- VERSION = '0.1.1'.freeze
3
+ VERSION = '0.1.7'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Jordan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-12 00:00:00.000000000 Z
11
+ date: 2020-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-core
@@ -66,34 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '5'
69
- - !ruby/object:Gem::Dependency
70
- name: bundler
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: minitest
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - "~>"
88
74
  - !ruby/object:Gem::Version
89
- version: '5.10'
75
+ version: 5.14.2
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
- version: '5.10'
82
+ version: 5.14.2
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: mocha
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -153,7 +139,8 @@ dependencies:
153
139
  description: Use Google OAuth as an AWS credential provider.
154
140
  email:
155
141
  - will@code.org
156
- executables: []
142
+ executables:
143
+ - aws-google
157
144
  extensions: []
158
145
  extra_rdoc_files: []
159
146
  files:
@@ -166,7 +153,9 @@ files:
166
153
  - aws-google.gemspec
167
154
  - bin/console
168
155
  - bin/setup
156
+ - exe/aws-google
169
157
  - lib/aws/google.rb
158
+ - lib/aws/google/cached_credentials.rb
170
159
  - lib/aws/google/credential_provider.rb
171
160
  - lib/aws/google/version.rb
172
161
  homepage: https://github.com/code-dot-org/aws-google
@@ -189,8 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
178
  - !ruby/object:Gem::Version
190
179
  version: '0'
191
180
  requirements: []
192
- rubyforge_project:
193
- rubygems_version: 2.7.4
181
+ rubygems_version: 3.1.2
194
182
  signing_key:
195
183
  specification_version: 4
196
184
  summary: Use Google OAuth as an AWS credential provider