Skip to content

Commit 2d504aa

Browse files
authored
Add bearer auth / token providers (#2746)
1 parent 6621ae7 commit 2d504aa

File tree

20 files changed

+1223
-16
lines changed

20 files changed

+1223
-16
lines changed

build_tools/aws-sdk-code-generator/lib/aws-sdk-code-generator/plugin_list.rb

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def each(&block)
2020
def compute_plugins(options)
2121
plugins = {}
2222
plugins.update(options[:async_client] ? default_async_plugins : default_plugins)
23-
plugins.update(signature_plugins(options.fetch(:signature_version)))
23+
plugins.update(signature_plugins(options))
2424
plugins.update(protocol_plugins(options.fetch(:protocol)))
2525
plugins.update(options.fetch(:add_plugins))
2626
options.fetch(:remove_plugins).each do |plugin_name|
@@ -96,15 +96,21 @@ def protocol_plugins(protocol)
9696
}[protocol]
9797
end
9898

99-
def signature_plugins(signature_version)
100-
case signature_version
101-
when 'v4'
102-
{ 'Aws::Plugins::SignatureV4' => "#{core_plugins}/signature_v4.rb" }
103-
when 'v2'
104-
{ 'Aws::Plugins::SignatureV2' => "#{core_plugins}/signature_v2.rb" }
105-
else
106-
{}
99+
def signature_plugins(options)
100+
auth_types = [options.fetch(:signature_version)]
101+
auth_types += options[:api]['operations'].map { |_n, o| o['authtype'] }.compact
102+
plugins = {}
103+
auth_types.each do |auth_type|
104+
case auth_type
105+
when 'v4'
106+
plugins['Aws::Plugins::SignatureV4'] = "#{core_plugins}/signature_v4.rb"
107+
when 'v2'
108+
plugins['Aws::Plugins::SignatureV2'] = "#{core_plugins}/signature_v2.rb"
109+
when 'bearer'
110+
plugins['Aws::Plugins::BearerAuthorization'] = "#{core_plugins}/bearer_authorization.rb"
111+
end
107112
end
113+
plugins
108114
end
109115

110116
def core_plugins
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"version":"2.0",
3+
"metadata":{
4+
"endpointPrefix":"svc",
5+
"protocol": "rest-json",
6+
"signatureVersion":"bearer"
7+
},
8+
"operations": {
9+
"BearerAuth": {
10+
"name": "BearerAuth",
11+
"http": {
12+
"method": "POST",
13+
"requestUri": "/"
14+
},
15+
"input": {"shape": "Request"}
16+
},
17+
"NoAuth": {
18+
"name": "NoAuth",
19+
"http": {
20+
"method": "POST",
21+
"requestUri": "/"
22+
},
23+
"input": {"shape": "Request"},
24+
"authtype":"none"
25+
},
26+
"Sigv4Auth": {
27+
"name": "Sigv4Auth",
28+
"http": {
29+
"method": "POST",
30+
"requestUri": "/"
31+
},
32+
"input": {"shape": "Request"},
33+
"authtype":"v4"
34+
}
35+
},
36+
"shapes": {
37+
"Request": {
38+
"type": "structure",
39+
"members": {
40+
"Value": {
41+
"shape": "Value"
42+
}
43+
}
44+
},
45+
"Value": {"type": "string"}
46+
}
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"version":"2.0",
3+
"metadata":{
4+
"endpointPrefix":"svc",
5+
"protocol": "rest-json",
6+
"signatureVersion":"v4"
7+
},
8+
"operations": {
9+
"BearerAuth": {
10+
"name": "BearerAuth",
11+
"http": {
12+
"method": "POST",
13+
"requestUri": "/"
14+
},
15+
"input": {"shape": "Request"},
16+
"authtype":"bearer"
17+
},
18+
"NoAuth": {
19+
"name": "NoAuth",
20+
"http": {
21+
"method": "POST",
22+
"requestUri": "/"
23+
},
24+
"input": {"shape": "Request"},
25+
"authtype":"none"
26+
},
27+
"Sigv4Auth": {
28+
"name": "Sigv4Auth",
29+
"http": {
30+
"method": "POST",
31+
"requestUri": "/"
32+
},
33+
"input": {"shape": "Request"}
34+
}
35+
},
36+
"shapes": {
37+
"Request": {
38+
"type": "structure",
39+
"members": {
40+
"Value": {
41+
"shape": "Value"
42+
}
43+
}
44+
},
45+
"Value": {"type": "string"}
46+
}
47+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../spec_helper'
4+
5+
describe 'Client Interface:' do
6+
describe 'SignatureVersion: bearer' do
7+
before(:all) do
8+
SpecHelper.generate_service(['BearerAuth'], multiple_files: false)
9+
end
10+
11+
let(:token) { 'token' }
12+
13+
let(:token_provider) { Aws::StaticTokenProvider.new(token) }
14+
15+
let(:client) do
16+
BearerAuth::Client.new(
17+
region: 'us-west-2',
18+
stub_responses: true,
19+
token_provider: token_provider,
20+
endpoint: 'https://svc.us-west-2.amazonaws.com'
21+
)
22+
end
23+
24+
describe '#bearer_auth' do
25+
it 'sets the Authorization header' do
26+
client.stub_responses(:bearer_auth, -> (context) {
27+
expect(context.http_request.headers['Authorization']).to eq('Bearer token')
28+
})
29+
client.bearer_auth
30+
end
31+
end
32+
33+
describe '#sigv_4_auth' do
34+
it 'sets the Authorization header' do
35+
client.stub_responses(:sigv_4_auth, -> (context) {
36+
expect(context.http_request.headers['Authorization']).to include('AWS4-HMAC-SHA256')
37+
})
38+
client.sigv_4_auth
39+
end
40+
end
41+
42+
describe '#no_auth' do
43+
it 'does not set the Authorization header' do
44+
client.stub_responses(:no_auth, -> (context) {
45+
expect(context.http_request.headers.key?('Authorization')).to be_falsey
46+
})
47+
client.no_auth
48+
end
49+
end
50+
end
51+
52+
describe 'SignatureVersion: v4' do
53+
before(:all) do
54+
SpecHelper.generate_service(['V4WithBearer'], multiple_files: false)
55+
end
56+
57+
let(:token) { 'token' }
58+
59+
let(:token_provider) { Aws::StaticTokenProvider.new(token) }
60+
61+
let(:client) do
62+
V4WithBearer::Client.new(
63+
region: 'us-west-2',
64+
stub_responses: true,
65+
token_provider: token_provider,
66+
endpoint: 'https://svc.us-west-2.amazonaws.com'
67+
)
68+
end
69+
70+
describe '#bearer_auth' do
71+
it 'sets the Authorization header' do
72+
resp = client.bearer_auth
73+
expect(resp.context.http_request.headers['Authorization']).to eq('Bearer token')
74+
end
75+
end
76+
77+
describe '#sigv_4_auth' do
78+
it 'sets the Authorization header' do
79+
resp = client.sigv_4_auth
80+
expect(resp.context.http_request.headers['Authorization']).to include('AWS4-HMAC-SHA256')
81+
end
82+
end
83+
84+
describe '#no_auth' do
85+
it 'does not set the Authorization header' do
86+
resp = client.no_auth
87+
expect(resp.context.http_request.headers.key?('Authorization')).to be_falsey
88+
end
89+
end
90+
end
91+
92+
end

gems/aws-sdk-core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Unreleased Changes
22
------------------
33

4+
* Feature - Add support for Bearer Token Authentication and TokenProviders.
45
* Issue - Validate that `_X_AMZN_TRACE_ID` ENV value contains only valid, non-control characters.
56

67
3.133.0 (2022-08-22)

gems/aws-sdk-core/lib/aws-sdk-core.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
require_relative 'aws-sdk-core/process_credentials'
2121
require_relative 'aws-sdk-core/sso_credentials'
2222

23+
# tokens and token providers
24+
require_relative 'aws-sdk-core/token'
25+
require_relative 'aws-sdk-core/token_provider'
26+
require_relative 'aws-sdk-core/static_token_provider'
27+
require_relative 'aws-sdk-core/refreshing_token'
28+
require_relative 'aws-sdk-core/sso_token_provider'
29+
require_relative 'aws-sdk-core/token_provider_chain'
30+
require_relative 'aws-sdk-core/plugins/bearer_authorization'
31+
2332
# client modules
2433

2534
require_relative 'aws-sdk-core/client_stubs'

gems/aws-sdk-core/lib/aws-sdk-core/errors.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,19 @@ class InvalidProcessCredentialsPayload < RuntimeError; end
210210
# Raised when SSO Credentials are invalid
211211
class InvalidSSOCredentials < RuntimeError; end
212212

213+
# Raised when SSO Token is invalid
214+
class InvalidSSOToken < RuntimeError; end
215+
216+
# Raised when a client is unable to sign a request because
217+
# the bearer token is not configured or available
218+
class MissingBearerTokenError < RuntimeError
219+
def initialize(*args)
220+
msg = 'unable to sign request without token set'
221+
super(msg)
222+
end
223+
end
224+
225+
213226
# Raised when there is a circular reference in chained
214227
# source_profiles
215228
class SourceProfileCircularReferenceError < RuntimeError; end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
module Aws
4+
# @api private
5+
module Plugins
6+
# @api private
7+
class BearerAuthorization < Seahorse::Client::Plugin
8+
9+
option(:token_provider,
10+
required: false,
11+
doc_type: 'Aws::TokenProvider',
12+
docstring: <<-DOCS
13+
A Bearer Token Provider. This can be an instance of any one of the
14+
following classes:
15+
16+
* `Aws::StaticTokenProvider` - Used for configuring static, non-refreshing
17+
tokens.
18+
19+
* `Aws::SSOTokenProvider` - Used for loading tokens from AWS SSO using an
20+
access token generated from `aws login`.
21+
22+
When `:token_provider` is not configured directly, the `Aws::TokenProviderChain`
23+
will be used to search for tokens configured for your profile in shared configuration files.
24+
DOCS
25+
) do |config|
26+
if config.stub_responses
27+
StaticTokenProvider.new('token')
28+
else
29+
TokenProviderChain.new(config).resolve
30+
end
31+
end
32+
33+
34+
def add_handlers(handlers, cfg)
35+
bearer_operations =
36+
if cfg.api.metadata['signatureVersion'] == 'bearer'
37+
# select operations where authtype is either not set or is bearer
38+
cfg.api.operation_names.select do |o|
39+
!cfg.api.operation(o)['authtype'] || cfg.api.operation(o)['authtype'] == 'bearer'
40+
end
41+
else # service is not bearer auth
42+
# select only operations where authtype is explicitly bearer
43+
cfg.api.operation_names.select do |o|
44+
cfg.api.operation(o)['authtype'] == 'bearer'
45+
end
46+
end
47+
handlers.add(Handler, step: :sign, operations: bearer_operations)
48+
end
49+
50+
class Handler < Seahorse::Client::Handler
51+
def call(context)
52+
if context.http_request.endpoint.scheme != 'https'
53+
raise ArgumentError, 'Unable to use bearer authorization on non https endpoint.'
54+
end
55+
56+
token_provider = context.config.token_provider
57+
if token_provider && token_provider.set?
58+
context.http_request.headers['Authorization'] = "Bearer #{token_provider.token.token}"
59+
else
60+
raise Errors::MissingBearerTokenError
61+
end
62+
@handler.call(context)
63+
end
64+
end
65+
end
66+
end
67+
end

gems/aws-sdk-core/lib/aws-sdk-core/plugins/signature_v4.rb

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module Plugins
77
# @api private
88
class SignatureV4 < Seahorse::Client::Plugin
99

10+
V4_AUTH = %w[v4 v4-unsigned-payload v4-unsigned-body]
11+
1012
option(:sigv4_signer) do |cfg|
1113
SignatureV4.build_signer(cfg)
1214
end
@@ -32,13 +34,16 @@ class SignatureV4 < Seahorse::Client::Plugin
3234
end
3335

3436
option(:unsigned_operations) do |cfg|
35-
cfg.api.operation_names.inject([]) do |unsigned, operation_name|
36-
if cfg.api.operation(operation_name)['authtype'] == 'none' ||
37-
cfg.api.operation(operation_name)['authtype'] == 'custom'
38-
# Unsign requests that has custom apigateway authorizer as well
39-
unsigned << operation_name
40-
else
41-
unsigned
37+
if cfg.api.metadata['signatureVersion'] == 'v4'
38+
# select operations where authtype is set and is not v4
39+
cfg.api.operation_names.select do |o|
40+
cfg.api.operation(o)['authtype'] && !V4_AUTH.include?(cfg.api.operation(o)['authtype'])
41+
end
42+
else # service is not v4 auth
43+
# select all operations where authtype is not v4
44+
# (includes operations with no explicit authtype)
45+
cfg.api.operation_names.select do |o|
46+
!V4_AUTH.include?(cfg.api.operation(o)['authtype'])
4247
end
4348
end
4449
end

0 commit comments

Comments
 (0)