Skip to content

Commit 1e2a4d3

Browse files
p-mongop
andcommitted
RUBY-2342 Implement proper AWS auth region calculation (#2032)
Co-authored-by: Oleg Pudeyev <oleg@bsdpower.com>
1 parent d526223 commit 1e2a4d3

File tree

5 files changed

+125
-35
lines changed

5 files changed

+125
-35
lines changed

lib/mongo/auth/aws/request.rb

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,13 @@ def initialize(access_key_id:, secret_access_key:, session_token: nil,
6161
%i(access_key_id secret_access_key host server_nonce).each do |arg|
6262
value = instance_variable_get("@#{arg}")
6363
if value.nil? || value.empty?
64-
raise ArgumentError, "Value for #{arg} is required"
64+
raise Error::InvalidServerAuthResponse, "Value for '#{arg}' is required"
6565
end
6666
end
67+
68+
if host && host.length > 255
69+
raise Error::InvalidServerAuthHost, "Value for 'host' is too long: #{@host}"
70+
end
6771
end
6872

6973
# @return [ String ] access_key_id The access key id.
@@ -98,8 +102,28 @@ def formatted_date
98102

99103
# @return [ String ] region The region of the host, derived from the host.
100104
def region
101-
# TODO implement region derivation when SPEC-1646 is done.
102-
'us-east-1'
105+
# Common case
106+
if host == 'sts.amazonaws.com'
107+
return 'us-east-1'
108+
end
109+
110+
if host.start_with?('.')
111+
raise Error::InvalidServerAuthHost, "Host begins with a period: #{host}"
112+
end
113+
if host.end_with?('.')
114+
raise Error::InvalidServerAuthHost, "Host ends with a period: #{host}"
115+
end
116+
117+
parts = host.split('.')
118+
if parts.any? { |part| part.empty? }
119+
raise Error::InvalidServerAuthHost, "Host has an empty component: #{host}"
120+
end
121+
122+
if parts.length == 1
123+
'us-east-1'
124+
else
125+
parts[1]
126+
end
103127
end
104128

105129
# Returns the scope of the request, per the AWS signature V4 specification.

lib/mongo/error.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ def add_label(label)
190190
require 'mongo/error/invalid_nonce'
191191
require 'mongo/error/invalid_replacement_document'
192192
require 'mongo/error/invalid_server_auth_response'
193+
# Subclass of InvalidServerAuthResponse
194+
require 'mongo/error/invalid_server_auth_host'
193195
require 'mongo/error/invalid_server_preference'
194196
require 'mongo/error/invalid_session'
195197
require 'mongo/error/invalid_signature'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (C) 2020 MongoDB Inc.
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.
14+
15+
module Mongo
16+
class Error
17+
18+
# Raised when the server returned an invalid Host value in AWS auth.
19+
class InvalidServerAuthHost < InvalidServerAuthResponse
20+
end
21+
end
22+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
require 'lite_spec_helper'
2+
3+
AWS_REGION_TEST_CASES = {
4+
'sts.amazonaws.com' => 'us-east-1',
5+
'sts.us-west-2.amazonaws.com' => 'us-west-2',
6+
'sts.us-west-2.amazonaws.com.ch' => 'us-west-2',
7+
'example.com' => 'com',
8+
'localhost' => 'us-east-1',
9+
'sts..com' => Mongo::Error::InvalidServerAuthHost,
10+
'.amazonaws.com' => Mongo::Error::InvalidServerAuthHost,
11+
'sts.amazonaws.' => Mongo::Error::InvalidServerAuthHost,
12+
'' => Mongo::Error::InvalidServerAuthResponse,
13+
'x' * 256 => Mongo::Error::InvalidServerAuthHost,
14+
}
15+
16+
describe 'AWS auth region tests' do
17+
18+
AWS_REGION_TEST_CASES.each do |host, expected_region|
19+
context "host '#{host}'" do
20+
let(:request) do
21+
Mongo::Auth::Aws::Request.new(access_key_id: 'access_key_id',
22+
secret_access_key: 'secret_access_key',
23+
session_token: 'session_token',
24+
host: host,
25+
server_nonce: 'server_nonce',
26+
)
27+
end
28+
29+
if expected_region.is_a?(String)
30+
it 'derives expected region' do
31+
request.region.should == expected_region
32+
end
33+
else
34+
it 'fails with an error' do
35+
lambda do
36+
request.region
37+
end.should raise_error(expected_region)
38+
end
39+
end
40+
end
41+
end
42+
end
Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
require 'spec_helper'
22

3-
describe Mongo::Auth::Aws::Request do
3+
describe Mongo::Auth::Aws::Request do
44

5-
describe "#formatted_time" do
6-
context "when time is provided and frozen" do
5+
describe "#formatted_time" do
6+
context "when time is provided and frozen" do
77
let(:original_time) { Time.at(1592399523).freeze }
8-
let(:request) do
9-
described_class.new(access_key_id: 'access_key_id',
10-
secret_access_key: 'secret_access_key',
11-
session_token: 'session_token',
12-
host: 'host',
8+
let(:request) do
9+
described_class.new(access_key_id: 'access_key_id',
10+
secret_access_key: 'secret_access_key',
11+
session_token: 'session_token',
12+
host: 'host',
1313
server_nonce: 'server_nonce',
1414
time: original_time
1515
)
@@ -24,12 +24,12 @@
2424
end
2525
end
2626

27-
context "when time is not provided" do
28-
let(:request) do
29-
described_class.new(access_key_id: 'access_key_id',
30-
secret_access_key: 'secret_access_key',
31-
session_token: 'session_token',
32-
host: 'host',
27+
context "when time is not provided" do
28+
let(:request) do
29+
described_class.new(access_key_id: 'access_key_id',
30+
secret_access_key: 'secret_access_key',
31+
session_token: 'session_token',
32+
host: 'host',
3333
server_nonce: 'server_nonce'
3434
)
3535
end
@@ -40,37 +40,37 @@
4040
end
4141
end
4242

43-
describe "#signature" do
44-
context "when time is provided and frozen" do
43+
describe "#signature" do
44+
context "when time is provided and frozen" do
4545
let(:original_time) { Time.at(1592399523).freeze }
46-
let(:request) do
47-
described_class.new(access_key_id: 'access_key_id',
48-
secret_access_key: 'secret_access_key',
49-
session_token: 'session_token',
50-
host: 'host',
46+
let(:request) do
47+
described_class.new(access_key_id: 'access_key_id',
48+
secret_access_key: 'secret_access_key',
49+
session_token: 'session_token',
50+
host: 'host',
5151
server_nonce: 'server_nonce',
5252
time: original_time
5353
)
5454
end
55-
56-
it 'doesn\'t raise error on signature' do
55+
56+
it 'doesn\'t raise error on signature' do
5757
expect { request.signature }.to_not raise_error
5858
end
5959
end
6060

61-
context "when time is not provided" do
62-
let(:request) do
63-
described_class.new(access_key_id: 'access_key_id',
64-
secret_access_key: 'secret_access_key',
65-
session_token: 'session_token',
66-
host: 'host',
61+
context "when time is not provided" do
62+
let(:request) do
63+
described_class.new(access_key_id: 'access_key_id',
64+
secret_access_key: 'secret_access_key',
65+
session_token: 'session_token',
66+
host: 'host',
6767
server_nonce: 'server_nonce'
6868
)
6969
end
70-
71-
it 'doesn\'t raise error on signature' do
70+
71+
it 'doesn\'t raise error on signature' do
7272
expect { request.signature }.to_not raise_error
7373
end
74-
end
74+
end
7575
end
7676
end

0 commit comments

Comments
 (0)