aws-google 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 76723a29a160f6d7326a3329e08e6316da4e8e224013ef400321cd7780e46340
4
+ data.tar.gz: 2cac963af78ade133bccdafa257bb8bc691a794afa0f1f0172f920ecd5c335a9
5
+ SHA512:
6
+ metadata.gz: 4a2aa252e1108cd54bc562ab4cf2f5c54732f9a66ce86b57fbfd96c43a799c9792d9adb38661488cf00667959a47b091def2a47fc72af55fa2e5d66bd2b9ebf3
7
+ data.tar.gz: 457f3dadbf5bb0b00059576ebc19daeb93a4ddd55867f49d6dab121a17a7041dc87c86fb46b8cc65d77115dbe27cb1a387340344204be7c804c01a82008f9459
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,13 @@
1
+ Copyright 2019 Code.org
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,92 @@
1
+ # Aws::Google [![Build Status](https://travis-ci.com/code-dot-org/aws-google.svg?branch=master)](https://travis-ci.com/code-dot-org/aws-google)
2
+
3
+ Use Google OAuth as an AWS Credential Provider.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's `Gemfile`:
8
+
9
+ ```ruby
10
+ gem 'aws-google'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install aws-google
20
+
21
+ ## Usage
22
+
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:
26
+
27
+ ```json
28
+ {
29
+ "Version": "2012-10-17",
30
+ "Statement": [
31
+ {
32
+ "Effect": "Allow",
33
+ "Principal": {
34
+ "Federated": "accounts.google.com"
35
+ },
36
+ "Action": "sts:AssumeRoleWithWebIdentity",
37
+ "Condition": {
38
+ "StringEquals": {
39
+ "accounts.google.com:aud": "123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com"
40
+ },
41
+ "ForAnyValue:StringEquals": {
42
+ "accounts.google.com:sub": [
43
+ "000000000000000000000",
44
+ "111111111111111111111"
45
+ ]
46
+ }
47
+ }
48
+ }
49
+ ]
50
+ }
51
+ ```
52
+
53
+ - In your Ruby code, construct an `Aws::Google` object by passing in the AWS role, client id and client secret:
54
+ ```ruby
55
+ aws_role = 'arn:aws:iam::[AccountID]:role/[Role]'
56
+ client_id = '123456789012-abcdefghijklmnopqrstuvwzyz0123456.apps.googleusercontent.com'
57
+ client_secret = '01234567890abcdefghijklmn'
58
+
59
+ role_credentials = Aws::Google.new(
60
+ role_arn: aws_role,
61
+ google_client_id: client_id,
62
+ google_client_secret: client_secret
63
+ )
64
+
65
+ puts Aws::STS::Client.new(credentials: role_credentials).get_caller_identity
66
+ ```
67
+
68
+ - Or, set `Aws::Google.config` hash to add Google auth to the default credential provider chain:
69
+
70
+ ```ruby
71
+ Aws::Google.config = {
72
+ role_arn: aws_role,
73
+ google_client_id: client_id,
74
+ google_client_secret: client_secret,
75
+ }
76
+
77
+ puts Aws::STS::Client.new.get_caller_identity
78
+ ```
79
+
80
+ ## Development
81
+
82
+ 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.
83
+
84
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
85
+
86
+ ## Contributing
87
+
88
+ Bug reports and pull requests are welcome on GitHub at https://github.com/code-dot-org/aws-google.
89
+
90
+ ## License
91
+
92
+ The gem is available as open source under the terms of the [Apache 2.0 License](http://opensource.org/licenses/apache-2.0).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'aws/google/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'aws-google'
7
+ spec.version = Aws::Google::VERSION
8
+ spec.authors = ['Will Jordan']
9
+ spec.email = ['will@code.org']
10
+
11
+ spec.summary = 'Use Google OAuth as an AWS credential provider'
12
+ spec.description = 'Use Google OAuth as an AWS credential provider.'
13
+ spec.homepage = 'https://github.com/code-dot-org/aws-google'
14
+ spec.license = 'Apache-2.0'
15
+
16
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'aws-sdk-core', '~> 3'
25
+ spec.add_dependency 'google-api-client', '~> 0.23'
26
+ spec.add_dependency 'launchy', '~> 2' # Peer dependency of Google::APIClient::InstalledAppFlow
27
+
28
+ spec.add_development_dependency 'activesupport', '~> 5'
29
+ spec.add_development_dependency 'bundler', '~> 1'
30
+ spec.add_development_dependency 'minitest', '~> 5.10'
31
+ spec.add_development_dependency 'mocha', '~> 1.5'
32
+ spec.add_development_dependency 'rake', '~> 12'
33
+ spec.add_development_dependency 'timecop', '~> 0.8'
34
+ spec.add_development_dependency 'webmock', '~> 3.3'
35
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'aws/google'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,193 @@
1
+ require_relative 'google/version'
2
+ require 'aws-sdk-core'
3
+ require_relative 'google/credential_provider'
4
+
5
+ require 'googleauth'
6
+ require 'google/api_client/auth/storage'
7
+ require 'google/api_client/auth/storages/file_store'
8
+
9
+ module Aws
10
+ # An auto-refreshing credential provider that works by assuming
11
+ # a role via {Aws::STS::Client#assume_role_with_web_identity},
12
+ # using an ID token derived from a Google refresh token.
13
+ #
14
+ # role_credentials = Aws::Google.new(
15
+ # role_arn: aws_role,
16
+ # google_client_id: client_id,
17
+ # google_client_secret: client_secret
18
+ # )
19
+ #
20
+ # ec2 = Aws::EC2::Client.new(credentials: role_credentials)
21
+ #
22
+ # If you omit `:client` option, a new {Aws::STS::Client} object will be
23
+ # constructed.
24
+ class Google
25
+ include ::Aws::CredentialProvider
26
+ include ::Aws::RefreshingCredentials
27
+
28
+ class << self
29
+ attr_accessor :config
30
+ end
31
+
32
+ # @option options [required, String] :role_arn
33
+ # @option options [String] :policy
34
+ # @option options [Integer] :duration_seconds
35
+ # @option options [String] :external_id
36
+ # @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
+ # @option options [String] :domain G Suite domain for account-selection hint
39
+ # @option options [String] :online if `true` only a temporary access token will be provided,
40
+ # a long-lived refresh token will not be created and stored on the filesystem.
41
+ # @option options [::Google::Auth::ClientId] :google_id
42
+ def initialize(options = {})
43
+ @oauth_attempted = false
44
+ @assume_role_params = options.slice(
45
+ *Aws::STS::Client.api.operation(:assume_role_with_web_identity).
46
+ input.shape.member_names
47
+ )
48
+
49
+ @profile = options[:profile] || ENV['AWS_DEFAULT_PROFILE'] || 'default'
50
+ @google_id = ::Google::Auth::ClientId.new(
51
+ options[:google_client_id],
52
+ options[:google_client_secret]
53
+ )
54
+ @client = options[:client] || Aws::STS::Client.new(credentials: nil)
55
+ @domain = options[:domain]
56
+ @online = options[:online]
57
+
58
+ # Use existing AWS credentials stored in the shared config if available.
59
+ # If this is `nil` or expired, #refresh will be called on the first AWS API service call
60
+ # to generate AWS credentials derived from Google authentication.
61
+ @expiration = Aws.shared_config.get('expiration', profile: @profile) rescue nil
62
+ @mutex = Mutex.new
63
+ if near_expiration?
64
+ refresh!
65
+ else
66
+ @credentials = Aws.shared_config.credentials(profile: @profile) rescue nil
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # Use cached Application Default Credentials if available,
73
+ # otherwise fallback to creating new Google credentials through browser login.
74
+ def google_client
75
+ @google_client ||= (::Google::Auth.get_application_default rescue nil) || google_oauth
76
+ end
77
+
78
+ # Create an OAuth2 Client using Google's default browser-based OAuth InstalledAppFlow.
79
+ # Store cached credentials to the standard Google Application Default Credentials location.
80
+ # Ref: http://goo.gl/IUuyuX
81
+ # @return [Signet::OAuth2::Client]
82
+ def google_oauth
83
+ return nil if @oauth_attempted
84
+ @oauth_attempted = true
85
+
86
+ path = "#{ENV['HOME']}/.config/#{::Google::Auth::CredentialsLoader::WELL_KNOWN_PATH}"
87
+ FileUtils.mkdir_p(File.dirname(path))
88
+ storage = GoogleStorage.new(::Google::APIClient::FileStore.new(path))
89
+
90
+ options = {
91
+ client_id: @google_id.id,
92
+ client_secret: @google_id.secret,
93
+ scope: %w[openid email]
94
+ }
95
+ uri_options = {include_granted_scopes: true}
96
+ uri_options[:hd] = @domain if @domain
97
+ uri_options[:access_type] = 'online' if @online
98
+
99
+ require 'google/api_client/auth/installed_app'
100
+ if defined?(Launchy) && Launchy::Application::Browser.new.app_list.any?
101
+ ::Google::APIClient::InstalledAppFlow.new(options).authorize(storage, uri_options)
102
+ else
103
+ credentials = ::Google::Auth::UserRefreshCredentials.new(
104
+ options.merge(redirect_uri: 'urn:ietf:wg:oauth:2.0:oob')
105
+ )
106
+ url = credentials.authorization_uri(uri_options)
107
+ print 'Open the following URL in the browser and enter the ' \
108
+ "resulting code after authorization:\n#{url}\n> "
109
+ credentials.code = gets
110
+ credentials.fetch_access_token!
111
+ credentials.tap(&storage.method(:write_credentials))
112
+ end
113
+ end
114
+
115
+ def refresh
116
+ assume_role = begin
117
+ client = google_client
118
+ return unless client
119
+
120
+ begin
121
+ tries ||= 2
122
+ id_token = client.id_token
123
+ # Decode the JWT id_token to use the Google email as the AWS role session name.
124
+ token_params = JWT.decode(id_token, nil, false).first
125
+ rescue JWT::DecodeError, JWT::ExpiredSignature
126
+ # Refresh and retry once if token is expired or invalid.
127
+ client.refresh!
128
+ raise if (tries -= 1).zero?
129
+ retry
130
+ end
131
+
132
+ @client.assume_role_with_web_identity(
133
+ @assume_role_params.merge(
134
+ web_identity_token: id_token,
135
+ role_session_name: token_params['email']
136
+ )
137
+ )
138
+ rescue Signet::AuthorizationError => e
139
+ retry if (@google_client = google_oauth)
140
+ raise
141
+ rescue Aws::STS::Errors::AccessDenied => e
142
+ retry if (@google_client = google_oauth)
143
+ raise e, "\nYour Google ID does not have access to the requested AWS Role. Ask your administrator to provide access.
144
+ Role: #{@assume_role_params[:role_arn]}
145
+ Email: #{token_params['email']}
146
+ Google ID: #{token_params['sub']}", e.backtrace
147
+ end
148
+
149
+ c = assume_role.credentials
150
+ @credentials = Aws::Credentials.new(
151
+ c.access_key_id,
152
+ c.secret_access_key,
153
+ c.session_token
154
+ )
155
+ @expiration = c.expiration.to_i
156
+ write_credentials
157
+ end
158
+
159
+ # Write credentials and expiration to AWS credentials file.
160
+ def write_credentials
161
+ # AWS CLI is needed because writing AWS credentials is not supported by the AWS Ruby SDK.
162
+ return unless system('which aws >/dev/null 2>&1')
163
+ %w[
164
+ access_key_id
165
+ secret_access_key
166
+ session_token
167
+ ].map {|x| ["aws_#{x}", @credentials.send(x)]}.
168
+ to_h.
169
+ merge(expiration: @expiration).each do |key, value|
170
+ system("aws configure set #{key} #{value} --profile #{@profile}")
171
+ end
172
+ end
173
+ end
174
+
175
+ # Patch Aws::SharedConfig to allow fetching arbitrary keys from the shared config.
176
+ module SharedConfigGetKey
177
+ def get(key, opts = {})
178
+ profile = opts.delete(:profile) || @profile_name
179
+ if @parsed_config && (prof_config = @parsed_config[profile])
180
+ prof_config[key]
181
+ end
182
+ end
183
+ end
184
+ Aws::SharedConfig.prepend SharedConfigGetKey
185
+
186
+ # Extend ::Google::APIClient::Storage to write {type: 'authorized_user'} to credentials,
187
+ # as required by Google's default credentials loader.
188
+ class GoogleStorage < ::Google::APIClient::Storage
189
+ def credentials_hash
190
+ super.merge(type: 'authorized_user')
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,18 @@
1
+ module Aws
2
+ class Google
3
+ # Inserts GoogleCredentials into the default AWS credential provider chain.
4
+ # Google credentials will only be used if Aws::Google.config is set before initialization.
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).
8
+ def providers
9
+ super.insert(-2, [:google_credentials, {}])
10
+ end
11
+
12
+ def google_credentials(options)
13
+ (config = Google.config) && Google.new(options.merge(config))
14
+ end
15
+ end
16
+ ::Aws::CredentialProviderChain.prepend CredentialProvider
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Aws
2
+ class Google
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-google
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Will Jordan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: google-api-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.23'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.23'
41
+ - !ruby/object:Gem::Dependency
42
+ name: launchy
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
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
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mocha
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '12'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '12'
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.8'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.8'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.3'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.3'
153
+ description: Use Google OAuth as an AWS credential provider.
154
+ email:
155
+ - will@code.org
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".travis.yml"
162
+ - Gemfile
163
+ - LICENSE.txt
164
+ - README.md
165
+ - Rakefile
166
+ - aws-google.gemspec
167
+ - bin/console
168
+ - bin/setup
169
+ - lib/aws/google.rb
170
+ - lib/aws/google/credential_provider.rb
171
+ - lib/aws/google/version.rb
172
+ homepage: https://github.com/code-dot-org/aws-google
173
+ licenses:
174
+ - Apache-2.0
175
+ metadata:
176
+ allowed_push_host: https://rubygems.org
177
+ post_install_message:
178
+ rdoc_options: []
179
+ require_paths:
180
+ - lib
181
+ required_ruby_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ required_rubygems_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ requirements: []
192
+ rubyforge_project:
193
+ rubygems_version: 2.7.4
194
+ signing_key:
195
+ specification_version: 4
196
+ summary: Use Google OAuth as an AWS credential provider
197
+ test_files: []