Skip to content

Commit 5137401

Browse files
authored
RUBY-1949 Add X.509 authentication integration tests (#1553)
* RUBY-1949 Add X.509 authentication integration tests * Ensure auth source for x509 auth is always $external * Handle x509 auth failing in x509 test when x509 auth is used globally * Remove X509_AUTH_REQUIRED as it is no longer used
1 parent 33c6121 commit 5137401

32 files changed

+657
-94
lines changed

.evergreen/config.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@ functions:
361361
${PREPARE_SHELL}
362362
KERBEROS_REQUIRED=1 .evergreen/run-tests-with-kerberos.sh
363363
364+
"run tests with X.509 authentication":
365+
- command: shell.exec
366+
type: test
367+
params:
368+
shell: bash
369+
working_dir: "src"
370+
script: |
371+
${PREPARE_SHELL}
372+
.evergreen/run-x509-tests.sh
373+
364374
"run tests via mlaunch":
365375
- command: shell.exec
366376
type: test
@@ -524,6 +534,9 @@ tasks:
524534
commands:
525535
- func: "bootstrap mongo-orchestration"
526536
- func: "run tests with kerberos"
537+
- name: "x509-tests"
538+
commands:
539+
- func: "run tests with X.509 authentication"
527540

528541
axes:
529542
- id: "mongodb-version"
@@ -930,6 +943,17 @@ buildvariants:
930943
tasks:
931944
- name: "kerberos-tests"
932945

946+
matrix_name: "x509-tests"
947+
matrix_spec:
948+
auth-and-ssl: "auth-and-ssl"
949+
ruby: "ruby-2.6"
950+
mongodb-version: "4.2"
951+
topology: standalone
952+
os: rhel70
953+
display_name: "X.509 Tests"
954+
tasks:
955+
- name: "x509-tests"
956+
933957
-
934958
matrix_name: "enterprise-auth-tests-rhel"
935959
matrix_spec:

.evergreen/run-local-tls-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ setup_ruby
2323
install_deps
2424

2525
arch=ubuntu1404
26+
# Last server version built for Ubuntu 14.04 is apparently 4.0.9
2627
version=4.0.9
2728
prepare_server $arch $version
2829

.evergreen/run-x509-tests.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/bin/bash
2+
3+
set -o xtrace # Write all commands first to stderr
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
. `dirname "$0"`/functions.sh
7+
8+
set_fcv
9+
set_env_vars
10+
11+
setup_ruby
12+
13+
install_deps
14+
15+
arch=rhel70
16+
version=4.2.0
17+
prepare_server $arch $version
18+
19+
install_mlaunch
20+
21+
export dbdir="$MONGO_ORCHESTRATION_HOME"/db
22+
mkdir -p "$dbdir"
23+
mlaunch --dir "$dbdir" --binarypath "$BINDIR" --single \
24+
--sslMode requireSSL \
25+
--sslPEMKeyFile spec/support/certificates/server.pem \
26+
--sslCAFile spec/support/certificates/ca.crt \
27+
--sslClientCertificate spec/support/certificates/client.pem \
28+
--auth --username bootstrap --password bootstrap \
29+
--setParameter enableTestCommands=1
30+
31+
create_user_cmd="`cat <<'EOT'
32+
db.getSiblingDB("$external").runCommand(
33+
{
34+
createUser: "C=US,ST=New York,L=New York City,O=MongoDB,OU=x509,CN=localhost",
35+
roles: [
36+
{ role: "dbAdminAnyDatabase", db: "admin" },
37+
{ role: "readWriteAnyDatabase", db: "admin" },
38+
{ role: "userAdminAnyDatabase", db: "admin" },
39+
{ role: "clusterAdmin", db: "admin" },
40+
],
41+
writeConcern: { w: "majority" , wtimeout: 5000 },
42+
}
43+
)
44+
EOT
45+
`"
46+
47+
"$BINDIR"/mongo --tls \
48+
--tlsCAFile spec/support/certificates/ca.crt \
49+
--tlsCertificateKeyFile spec/support/certificates/client-x509.pem \
50+
-u bootstrap -p bootstrap \
51+
--eval "$create_user_cmd"
52+
53+
export MONGODB_URI="mongodb://localhost:27017/?tls=true&"\
54+
"tlsCAFile=spec/support/certificates/ca.crt&"\
55+
"tlsCertificateKeyFile=spec/support/certificates/client-x509.pem&"\
56+
"authMechanism=MONGODB-X509"
57+
58+
bundle exec rake
59+
60+
test_status=$?
61+
echo "TEST STATUS"
62+
echo ${test_status}
63+
64+
kill_jruby
65+
66+
mlaunch stop --dir "$dbdir"
67+
68+
exit ${test_status}

Rakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace :spec do
2929
task :prepare do
3030
$: << File.join(File.dirname(__FILE__), 'spec')
3131

32+
require 'support/utils'
3233
require 'support/spec_setup'
3334
SpecSetup.new.run
3435
end
@@ -40,6 +41,7 @@ namespace :spec do
4041
# Since this task is usually used for troubleshooting of test suite
4142
# configuration, leave driver log level at the default of debug to
4243
# have connection diagnostics printed during handshakes and such.
44+
require 'support/utils'
4345
require 'support/spec_config'
4446
require 'support/client_registry'
4547
SpecConfig.instance.print_summary

docs/tutorials/ruby-driver-authentication.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,11 @@ The mechanism can be explicitly set with the credentials:
7777
auth_mech: :mongodb_cr )
7878

7979

80-
Client Certificate (x509)
81-
`````````````````````````
82-
*Requires MongoDB v2.6 or greater.*
80+
Client Certificate (X.509)
81+
``````````````````````````
8382

8483
The driver presents an X.509 certificate during SSL negotiation.
85-
The Client Certificate (x509) mechanism authenticates a username
84+
The MONGODB-X509 authentication mechanism authenticates a username
8685
derived from the distinguished subject name of this certificate.
8786

8887
This authentication method requires the use of SSL connections with

docs/tutorials/ruby-driver-create-client.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ by concatenating them after the leaf server and client certificates, respectivel
741741
``:ssl_cert_object`` Ruby option, which takes an instance of
742742
``OpenSSL::X509::Certificate``, does not support certificate chains.
743743

744-
The Ruby driver performs strict X509 certificate verification, which requires
744+
The Ruby driver performs strict X.509 certificate verification, which requires
745745
that both of the following fields are set in the intermediate certificate(s):
746746

747747
- X509v3 Basic Constraints: CA: TRUE -- Can sign certificates

lib/mongo/auth.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ def initialize(user, used_mechanism: nil, message: nil)
123123
else
124124
''
125125
end
126-
msg = "User #{user.name}#{specified_mechanism} is not authorized to access #{user.database} (auth source: #{user.auth_source})#{used_mechanism}"
126+
used_user = if user.mechanism == :mongodb_x509
127+
'Client certificate'
128+
else
129+
"User #{user.name}"
130+
end
131+
msg = "#{used_user}#{specified_mechanism} is not authorized to access #{user.database} (auth source: #{user.auth_source})#{used_mechanism}"
127132
if message
128133
msg += ': ' + message
129134
end

lib/mongo/auth/user.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def sasl_prepped_password
159159
# @since 2.0.0
160160
def initialize(options)
161161
@database = options[:database] || Database::ADMIN
162-
@auth_source = options[:auth_source] || @database
162+
@auth_source = options[:auth_source] || self.class.default_auth_source(options)
163163
@name = options[:user]
164164
@password = options[:password] || options[:pwd]
165165
@mechanism = options[:auth_mech]
@@ -205,6 +205,20 @@ def spec
205205
#
206206
# @return [ String ] The client key for the user.
207207
attr_reader :client_key
208+
209+
# Generate default auth source based on the URI and options
210+
#
211+
# @api private
212+
def self.default_auth_source(options)
213+
case options[:auth_mech]
214+
when :gssapi, :mongodb_x509
215+
'$external'
216+
when :plain
217+
options[:database] || '$external'
218+
else
219+
options[:database] || Database::ADMIN
220+
end
221+
end
208222
end
209223
end
210224
end

lib/mongo/auth/x509.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
module Mongo
1818
module Auth
1919

20-
# Defines behavior for x.509 authentication.
20+
# Defines behavior for X.509 authentication.
2121
#
2222
# @since 2.0.0
2323
class X509
@@ -39,6 +39,16 @@ class X509
3939
#
4040
# @since 2.0.0
4141
def initialize(user)
42+
# The only valid database for X.509 authentication is $external.
43+
if user.auth_source != '$external'
44+
user_name_msg = if user.name
45+
" #{user.name}"
46+
else
47+
''
48+
end
49+
raise Auth::InvalidConfiguration, "User#{user_name_msg} specifies auth source '#{user.auth_source}', but the only valid auth source for X.509 is '$external'"
50+
end
51+
4252
@user = user
4353
end
4454

lib/mongo/auth/x509/conversation.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module Mongo
1616
module Auth
1717
class X509
1818

19-
# Defines behavior around a single x.509 conversation between the
19+
# Defines behavior around a single X.509 conversation between the
2020
# client and server.
2121
#
2222
# @since 2.0.0
@@ -34,7 +34,7 @@ class Conversation
3434
# @return [ User ] user The user for the conversation.
3535
attr_reader :user
3636

37-
# Finalize the x.509 conversation. This is meant to be iterated until
37+
# Finalize the X.509 conversation. This is meant to be iterated until
3838
# the provided reply indicates the conversation is finished.
3939
#
4040
# @example Finalize the conversation.
@@ -50,23 +50,32 @@ def finalize(reply)
5050
validate!(reply)
5151
end
5252

53-
# Start the x.509 conversation. This returns the first message that
53+
# Start the X.509 conversation. This returns the first message that
5454
# needs to be sent to the server.
5555
#
5656
# @example Start the conversation.
5757
# conversation.start
5858
#
5959
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
6060
#
61-
# @return [ Protocol::Query ] The first x.509 conversation message.
61+
# @return [ Protocol::Query ] The first X.509 conversation message.
6262
#
6363
# @since 2.0.0
6464
def start(connection = nil)
6565
login = LOGIN.merge(mechanism: X509::MECHANISM)
6666
login[:user] = user.name if user.name
6767
if connection && connection.features.op_msg_enabled?
6868
selector = login
69-
selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source
69+
# The only valid database for X.509 authentication is $external.
70+
if user.auth_source != '$external'
71+
user_name_msg = if user.name
72+
" #{user.name}"
73+
else
74+
''
75+
end
76+
raise Auth::InvalidConfiguration, "User#{user_name_msg} specifies auth source '#{user.auth_source}', but the only valid auth source for X.509 is '$external'"
77+
end
78+
selector[Protocol::Msg::DATABASE_IDENTIFIER] = '$external'
7079
cluster_time = connection.mongos? && connection.cluster_time
7180
selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time
7281
Protocol::Msg.new([], {}, selector)

0 commit comments

Comments
 (0)