Skip to content

Commit b9e486c

Browse files
p-mongop
andauthored
Fix RUBY-2421 Auth mechanism properties not downcased when client is created with host and Ruby options with symbol key (#2110)
* Check caller identity after assuming role * check connectivity via mongo shell * reference aws docs for sts errors * run server status instead of ismaster to catch auth errors * line count should suffice * expand aws auth notes * RUBY-2421 test case for auth mechanism properties not getting downcased * RUBY-2421 account for keys possibly being either symbols or strings at this point * if options are strings they need to go into a document derivative * fix again * redo options type conversions once again * the test suite needs auth mechanism properties downcased when testing objects below the client in hierarchy * try converting back to hash * Revert "try converting back to hash" This reverts commit b50ff8d. * symbolize keys here * handle explicit nil values * RUBY-2425 deal with wildcard arns * fix connection tests * only sts arns are modified * we no longer write nil passwords into the client Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent 0a28656 commit b9e486c

13 files changed

+142
-45
lines changed

.evergreen/lib/server_setup.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
class ServerSetup
66
def setup_aws_auth
77
arn = env!('MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN')
8-
puts "Adding AWS-mapped user #{arn}"
8+
puts "Adding AWS-mapped user #{wildcard_arn(arn)} for #{arn}"
99
create_aws_user(arn)
1010

1111
puts 'Setup done'
@@ -27,14 +27,27 @@ def setup_tags
2727

2828
private
2929

30+
# Creates an appropriate AWS mapped user for the provided ARN.
31+
#
32+
# The mapped user does not use the specified ARN directly but instead
33+
# uses a derived wildcard ARN. Because of this, multiple ARNs can map
34+
# to the same user.
3035
def create_aws_user(arn)
3136
bootstrap_client.use('$external').database.users.create(
32-
arn,
37+
wildcard_arn(arn),
3338
roles: [{role: 'root', db: 'admin'}],
3439
write_concern: {w: :majority, wtimeout: 5000},
3540
)
3641
end
3742

43+
def wildcard_arn(arn)
44+
if arn.start_with?('arn:aws:sts::')
45+
arn.sub(%r,/[^/]+\z,, '/*')
46+
else
47+
arn
48+
end
49+
end
50+
3851
def require_env_vars(vars)
3952
vars.each do |var|
4053
unless env?(var)
@@ -64,8 +77,8 @@ def client
6477
end
6578

6679
def bootstrap_client
67-
@bootstrap_client ||= Mongo::Client.new(%w(localhost),
68-
user: 'bootstrap', password: 'bootstrap',
80+
@bootstrap_client ||= Mongo::Client.new(ENV['MONGODB_URI'] || %w(localhost),
81+
user: 'bootstrap', password: 'bootstrap', auth_mech: :scram, auth_mech_properties: nil,
6982
)
7083
end
7184
end

.evergreen/run-tests-aws-auth.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ case "$AUTH" in
4141
# request resolve to. It is hardcoded in
4242
# https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_aws/aws_e2e_assume_role.js
4343
# and is not given as an Evergreen variable.
44+
# Note: the asterisk at the end is manufactured by the server and not
45+
# obtained from STS. See https://jira.mongodb.org/browse/RUBY-2425.
4446
export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="arn:aws:sts::557821124784:assumed-role/authtest_user_assume_role/*"
4547
;;
4648

.evergreen/run-tests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ elif test "$AUTH" = aws-assume-role; then
225225
export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
226226
export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
227227
export AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN
228+
229+
aws sts get-caller-identity
228230

229231
hosts="`uri_escape $MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID`:`uri_escape $MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY`@$hosts"
230232

@@ -269,6 +271,10 @@ fi
269271

270272
export MONGODB_URI="mongodb://$hosts/?serverSelectionTimeoutMS=30000$uri_options"
271273

274+
if echo "$AUTH" |grep -q ^aws-assume-role; then
275+
$BINDIR/mongo "$MONGODB_URI" --eval 'db.runCommand({serverStatus: 1})' |wc
276+
fi
277+
272278
set_fcv
273279

274280
if test "$TOPOLOGY" = replica-set && ! echo "$MONGODB_VERSION" |fgrep -q 2.6; then

lib/mongo/client.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,7 @@ def initialize(addresses_or_uri, options = nil)
463463
@srv_records = nil
464464
end
465465

466-
options = Options::Redacted.new(Hash[options.map do |k, v|
467-
if k == 'auth_mech_properties'
468-
v = Hash[v.map { |pk, pv| [pk.downcase, pv] }]
469-
end
470-
[k, v]
471-
end])
466+
options = self.class.canonicalize_ruby_options(options)
472467

473468
# Special handling for sdam_proc as it is only used during client
474469
# construction
@@ -486,6 +481,12 @@ def initialize(addresses_or_uri, options = nil)
486481
end
487482
options = merged_options
488483

484+
options.keys.each do |k|
485+
if options[k].nil?
486+
options.delete(k)
487+
end
488+
end
489+
489490
@options = validate_new_options!(options)
490491
=begin WriteConcern object support
491492
if @options[:write_concern].is_a?(WriteConcern::Base)
@@ -774,7 +775,6 @@ def read_concern
774775
options[:read_concern]
775776
end
776777

777-
778778
# Get the write concern for this client. If no option was provided, then a
779779
# default single server acknowledgement will be used.
780780
#
@@ -1027,6 +1027,23 @@ def with_session(options = {}, &block)
10271027
end
10281028
end
10291029

1030+
class << self
1031+
# Lowercases auth mechanism properties, if given, in the specified
1032+
# options, then converts the options to an instance of Options::Redacted.
1033+
#
1034+
# @api private
1035+
def canonicalize_ruby_options(options)
1036+
Options::Redacted.new(Hash[options.map do |k, v|
1037+
if k == :auth_mech_properties || k == 'auth_mech_properties'
1038+
if v
1039+
v = Hash[v.map { |pk, pv| [pk.downcase, pv] }]
1040+
end
1041+
end
1042+
[k, v]
1043+
end])
1044+
end
1045+
end
1046+
10301047
private
10311048

10321049
# Create a new encrypter object using the client's auto encryption options

spec/NOTES.aws-auth.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,16 @@ problem. Below are some of the puzzling responses I encountered:
145145
line) but the value is otherwise completely valid. This error has no relation
146146
to the "session token" or "security token" as used with temporary AWS
147147
credentials.
148-
- *The security token included in the request is invalid*: this error is
149-
produced when the AWS access key id, as specified in the scope part of the
150-
`Authorization` header, is not a valid access key id. In the case of
151-
non-temporary credentials being used for authentication, the error refers to
152-
a "security token" but the authentication process does not actually use a
153-
security token as this term is used in the AWS documentation describing
154-
temporary credentials.
148+
- *The security token included in the request is invalid*: this error can be
149+
produced in several circumstances:
150+
- When the AWS access key id, as specified in the scope part of the
151+
`Authorization` header, is not a valid access key id. In the case of
152+
non-temporary credentials being used for authentication, the error refers to
153+
a "security token" but the authentication process does not actually use a
154+
security token as this term is used in the AWS documentation describing
155+
temporary credentials.
156+
- When using temporary credentials and the security token is not provided
157+
in the STS request at all (x-amz-security-token header).
155158
- *Signature expired: 20200317T000000Z is now earlier than 20200317T222541Z
156159
(20200317T224041Z - 15 min.)*: This error happens when `x-amz-date` header
157160
value is the formatted date (`YYYYMMDD`) rather than the ISO8601 formatted
@@ -168,6 +171,8 @@ problem. Below are some of the puzzling responses I encountered:
168171
produced when temporary credentials are used and the credentials have
169172
expired.
170173

174+
See also [AWS documentation for STS error messages](https://docs.aws.amazon.com/STS/latest/APIReference/CommonErrors.html).
175+
171176
### Resources
172177

173178
Generally I found Amazon's own documentation to be the best for implementing

spec/integration/client_authentication_options_spec.rb

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -464,20 +464,37 @@
464464
end
465465

466466
context 'with client options' do
467-
let(:client_opts) { { auth_mech_properties: auth_mechanism_properties } }
467+
[:auth_mech_properties, 'auth_mech_properties'].each do |key|
468468

469-
include_examples 'correctly sets auth mechanism properties on the client'
469+
context "using #{key.class} keys" do
470+
let(:client_opts) { { key => auth_mechanism_properties } }
470471

471-
context 'when options are given in mixed case' do
472-
let(:auth_mechanism_properties) do
473-
{
474-
service_NAME: service_name,
475-
canonicalize_host_NAME: canonicalize_host_name,
476-
service_REALM: service_realm,
477-
}.freeze
478-
end
472+
include_examples 'correctly sets auth mechanism properties on the client'
473+
474+
context 'when options are given in mixed case' do
475+
let(:auth_mechanism_properties) do
476+
{
477+
service_NAME: service_name,
478+
canonicalize_host_NAME: canonicalize_host_name,
479+
service_REALM: service_realm,
480+
}.freeze
481+
end
482+
483+
context 'using URI and options' do
479484

480-
include_examples 'correctly sets auth mechanism properties on the client'
485+
let(:client) { new_local_client_nmio(uri, client_opts) }
486+
487+
include_examples 'correctly sets auth mechanism properties on the client'
488+
end
489+
490+
context 'using host and options' do
491+
492+
let(:client) { new_local_client_nmio(['localhost'], client_opts) }
493+
494+
include_examples 'correctly sets auth mechanism properties on the client'
495+
end
496+
end
497+
end
481498
end
482499
end
483500
end

spec/integration/connection_pool_populator_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
let(:options) { {} }
55

66
let(:server_options) do
7-
SpecConfig.instance.test_options.merge(options).merge(SpecConfig.instance.auth_options)
7+
Mongo::Utils.shallow_symbolize_keys(Mongo::Client.canonicalize_ruby_options(
8+
SpecConfig.instance.all_test_options,
9+
)).update(options)
810
end
911

1012
let(:address) do

spec/mongo/client_construction_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,18 @@
15351535
end
15361536
end
15371537
end
1538+
1539+
context ':auth_mech_properties option' do
1540+
context 'is nil' do
1541+
let(:options) do
1542+
{auth_mech_properties: nil}
1543+
end
1544+
1545+
it 'creates the client without the option' do
1546+
client.options.should_not have_key(:auth_mech_properties)
1547+
end
1548+
end
1549+
end
15381550
end
15391551

15401552
context 'when making a block client' do

spec/mongo/server/connection_pool_spec.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
let(:options) { {} }
66

77
let(:server_options) do
8-
SpecConfig.instance.ssl_options.merge(SpecConfig.instance.compressor_options)
9-
.merge(SpecConfig.instance.retry_writes_options).merge(SpecConfig.instance.auth_options)
10-
.merge(options)
8+
Mongo::Utils.shallow_symbolize_keys(Mongo::Client.canonicalize_ruby_options(
9+
SpecConfig.instance.all_test_options,
10+
)).tap do |opts|
11+
opts.delete(:min_pool_size)
12+
opts.delete(:max_pool_size)
13+
opts.delete(:wait_queue_timeout)
14+
end.update(options)
1115
end
1216

1317
let(:address) do

spec/mongo/server/connection_spec.rb

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,9 @@ class ConnectionSpecTestException < Exception; end
252252
require_auth
253253

254254
let(:server_options) do
255-
SpecConfig.instance.test_options.merge(monitoring_io: false).
256-
merge(SpecConfig.instance.auth_options)
255+
Mongo::Client.canonicalize_ruby_options(
256+
SpecConfig.instance.all_test_options,
257+
).update(monitoring_io: false)
257258
end
258259

259260
let(:exception) do
@@ -593,10 +594,12 @@ class ConnectionSpecTestException < Exception; end
593594
server,
594595
SpecConfig.instance.test_options.merge(
595596
:database => SpecConfig.instance.test_user.database,
596-
).merge(SpecConfig.instance.credentials_or_external_user(
597-
user: SpecConfig.instance.test_user.name,
598-
password: SpecConfig.instance.test_user.password,
599-
))
597+
).merge(Mongo::Utils.shallow_symbolize_keys(Mongo::Client.canonicalize_ruby_options(
598+
SpecConfig.instance.credentials_or_external_user(
599+
user: SpecConfig.instance.test_user.name,
600+
password: SpecConfig.instance.test_user.password,
601+
),
602+
)))
600603
).tap do |connection|
601604
connection.connect!
602605
end
@@ -1103,7 +1106,7 @@ class ConnectionSpecTestException < Exception; end
11031106
:user => SpecConfig.instance.test_user.name,
11041107
:password => SpecConfig.instance.test_user.password,
11051108
:database => SpecConfig.instance.test_db,
1106-
:auth_mech => :mongodb_cr
1109+
:auth_mech => :mongodb_cr,
11071110
)
11081111
end
11091112

0 commit comments

Comments
 (0)