aws-google 0.1.1 → 0.1.7

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 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