Skip to content

Commit b95a4d8

Browse files
committed
PYTHON-1201 - Test GSSAPI and PLAIN authentication
1 parent d2a5cd8 commit b95a4d8

File tree

3 files changed

+149
-50
lines changed

3 files changed

+149
-50
lines changed

.evergreen/config.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,16 @@ functions:
336336
${PREPARE_SHELL}
337337
PYTHON_BINARY=${PYTHON_BINARY} C_EXTENSIONS=${C_EXTENSIONS} AUTH=${AUTH} SSL=${SSL} MONGODB_URI="${MONGODB_URI}" sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
338338
339+
"run enterprise auth tests":
340+
- command: shell.exec
341+
type: test
342+
params:
343+
silent: true
344+
working_dir: "src"
345+
script: |
346+
# DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
347+
PYTHON_BINARY=${PYTHON_BINARY} SASL_HOST=${sasl_host} SASL_PORT=${sasl_port} SASL_USER=${sasl_user} SASL_PASS=${sasl_pass} SASL_DB=${sasl_db} PRINCIPAL=${principal} GSSAPI_DB=${gssapi_db} KEYTAB_BASE64=${keytab_base64} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} sh ${PROJECT_DIRECTORY}/.evergreen/run-enterprise-auth-tests.sh
348+
339349
"cleanup":
340350
- command: shell.exec
341351
params:
@@ -603,6 +613,15 @@ tasks:
603613
TOPOLOGY: "sharded_cluster"
604614
- func: "run tests"
605615

616+
- name: "test-enterprise-auth"
617+
tags: ["enterprise-auth"]
618+
commands:
619+
- func: "bootstrap mongo-orchestration"
620+
vars:
621+
VERSION: "latest"
622+
TOPOLOGY: "server"
623+
- func: "run enterprise auth tests"
624+
606625
# }}}
607626

608627

@@ -1160,6 +1179,13 @@ buildvariants:
11601179
add_tasks:
11611180
- "test-3.0-standalone"
11621181

1182+
- matrix_name: "test-linux-enterprise-auth"
1183+
matrix_spec: {"python-version": "*", auth: "auth"}
1184+
display_name: "Enterprise Auth Linux ${python-version}"
1185+
run_on: ubuntu1604-test
1186+
tasks:
1187+
- name: "test-enterprise-auth"
1188+
11631189
# Platform notes
11641190
# i386 builds of OpenSSL or Cyrus SASL are not available
11651191
# Ubuntu14.04 only supports 2.6+ with SSL
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
3+
# Don't trace to avoid secrets showing up in the logs
4+
set -o errexit
5+
6+
echo "Running enterprise authentication tests"
7+
8+
PYTHON_VERSION=$(${PYTHON_BINARY} -c 'import sys; sys.stdout.write(str(sys.version_info[0]))')
9+
PLATFORM="$(${PYTHON_BINARY} -c 'import platform; print(platform.system())')"
10+
11+
export DB_USER="bob"
12+
export DB_PASSWORD="pwd123"
13+
14+
# There is no kerberos package for Jython, but we do want to test PLAIN.
15+
if [ ${PLATFORM} != "Java" ]; then
16+
# PyMongo 2.x doesn't support GSSAPI on Windows.
17+
if [ "Windows_NT" != "$OS" ]; then
18+
echo "Writing keytab"
19+
echo ${KEYTAB_BASE64} | base64 -d > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
20+
echo "Running kinit"
21+
kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p ${PRINCIPAL}
22+
echo "Setting GSSAPI variables"
23+
export GSSAPI_HOST=${SASL_HOST}
24+
export GSSAPI_PORT=${SASL_PORT}
25+
export GSSAPI_PRINCIPAL=${PRINCIPAL}
26+
fi
27+
EXTRA_ARGS=""
28+
else
29+
# Keep Jython 2.5 from running out of memory.
30+
EXTRA_ARGS="-J-XX:-UseGCOverheadLimit -J-Xmx4096m"
31+
fi
32+
33+
# Set verbose test output flag.
34+
if [ "$PYTHON_VERSION" = "3" ]; then
35+
# With Python 3, the tests do not accept a "--verbosity=2" flag.
36+
TEST_VERBOSITY="-v"
37+
else
38+
# With Python 2, the tests accepts a "-v" flag but only "--verbosity=2"
39+
# causes the verbose output we want.
40+
TEST_VERBOSITY="--verbosity=2"
41+
fi
42+
43+
echo "Running tests"
44+
${PYTHON_BINARY} setup.py clean
45+
${PYTHON_BINARY} $EXTRA_ARGS setup.py nosetests $TEST_VERBOSITY

test/test_auth.py

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
from test.test_pooling_base import get_pool
4343
from test.test_replica_set_client import TestReplicaSetClientBase
4444
from test.test_threads import AutoAuthenticateThreads
45-
from test.utils import (is_mongos,
45+
from test.utils import (delay,
46+
is_mongos,
4647
remove_all_users,
4748
assertRaisesExactly,
4849
one,
@@ -54,13 +55,14 @@
5455
# YOU MUST RUN KINIT BEFORE RUNNING GSSAPI TESTS.
5556
GSSAPI_HOST = os.environ.get('GSSAPI_HOST')
5657
GSSAPI_PORT = int(os.environ.get('GSSAPI_PORT', '27017'))
57-
PRINCIPAL = os.environ.get('PRINCIPAL')
58+
GSSAPI_PRINCIPAL = os.environ.get('GSSAPI_PRINCIPAL')
59+
GSSAPI_DB = os.environ.get('GSSAPI_DB', 'test')
5860

5961
SASL_HOST = os.environ.get('SASL_HOST')
6062
SASL_PORT = int(os.environ.get('SASL_PORT', '27017'))
6163
SASL_USER = os.environ.get('SASL_USER')
6264
SASL_PASS = os.environ.get('SASL_PASS')
63-
SASL_DB = os.environ.get('SASL_DB', '$external')
65+
SASL_DB = os.environ.get('SASL_DB', '$external')
6466

6567

6668
def setUpModule():
@@ -81,99 +83,123 @@ class AutoAuthenticateThread(threading.Thread):
8183
"""Used in testing threaded authentication.
8284
"""
8385

84-
def __init__(self, database):
86+
def __init__(self, collection):
8587
super(AutoAuthenticateThread, self).__init__()
86-
self.database = database
87-
self.success = True
88+
self.collection = collection
89+
self.success = False
8890

8991
def run(self):
90-
try:
91-
self.database.command('dbstats')
92-
except OperationFailure:
93-
self.success = False
92+
assert self.collection.find_one({'$where': delay(1)}) is not None
93+
self.success = True
9494

9595

9696
class TestGSSAPI(unittest.TestCase):
9797

9898
def setUp(self):
9999
if not HAVE_KERBEROS:
100100
raise SkipTest('Kerberos module not available.')
101-
if not GSSAPI_HOST or not PRINCIPAL:
102-
raise SkipTest('Must set GSSAPI_HOST and PRINCIPAL to test GSSAPI')
101+
if not GSSAPI_HOST or not GSSAPI_PRINCIPAL:
102+
raise SkipTest(
103+
'Must set GSSAPI_HOST and GSSAPI_PRINCIPAL to test GSSAPI')
103104

104105
def test_gssapi_simple(self):
105106

106107
client = MongoClient(GSSAPI_HOST, GSSAPI_PORT)
108+
db = client[GSSAPI_DB]
107109
# Without gssapiServiceName
108-
self.assertTrue(client.test.authenticate(PRINCIPAL,
109-
mechanism='GSSAPI'))
110-
client.database_names()
110+
self.assertTrue(db.authenticate(GSSAPI_PRINCIPAL, mechanism='GSSAPI'))
111+
db.collection.find_one()
111112
uri = ('mongodb://%s@%s:%d/?authMechanism='
112-
'GSSAPI' % (quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT))
113+
'GSSAPI' % (
114+
quote_plus(GSSAPI_PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT))
113115
client = MongoClient(uri)
114-
client.database_names()
116+
client[GSSAPI_DB].collection.find_one()
115117

116118
# With gssapiServiceName
117-
self.assertTrue(client.test.authenticate(PRINCIPAL,
118-
mechanism='GSSAPI',
119-
gssapiServiceName='mongodb'))
120-
client.database_names()
119+
client = MongoClient(GSSAPI_HOST, GSSAPI_PORT)
120+
db = client[GSSAPI_DB]
121+
self.assertTrue(db.authenticate(GSSAPI_PRINCIPAL,
122+
mechanism='GSSAPI',
123+
gssapiServiceName='mongodb'))
124+
db.collection.find_one()
121125
uri = ('mongodb://%s@%s:%d/?authMechanism='
122-
'GSSAPI;gssapiServiceName=mongodb' % (quote_plus(PRINCIPAL),
123-
GSSAPI_HOST, GSSAPI_PORT))
126+
'GSSAPI;gssapiServiceName=mongodb' % (
127+
quote_plus(GSSAPI_PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT))
124128
client = MongoClient(uri)
125-
client.database_names()
129+
client[GSSAPI_DB].collection.find_one()
126130
uri = ('mongodb://%s@%s:%d/?authMechanism='
127131
'GSSAPI;authMechanismProperties=SERVICE_NAME:mongodb' % (
128-
quote_plus(PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT))
132+
quote_plus(GSSAPI_PRINCIPAL), GSSAPI_HOST, GSSAPI_PORT))
129133
client = MongoClient(uri)
130-
client.database_names()
134+
client[GSSAPI_DB].collection.find_one()
131135

132136
set_name = client.admin.command('ismaster').get('setName')
133137
if set_name:
134138
client = MongoReplicaSetClient(GSSAPI_HOST,
135139
port=GSSAPI_PORT,
136140
replicaSet=set_name)
141+
db = client[GSSAPI_DB]
137142
# Without gssapiServiceName
138-
self.assertTrue(client.test.authenticate(PRINCIPAL,
139-
mechanism='GSSAPI'))
140-
client.database_names()
143+
self.assertTrue(db.authenticate(GSSAPI_PRINCIPAL,
144+
mechanism='GSSAPI'))
145+
db.collection.find_one()
141146
uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet'
142-
'=%s' % (quote_plus(PRINCIPAL),
147+
'=%s' % (quote_plus(GSSAPI_PRINCIPAL),
143148
GSSAPI_HOST, GSSAPI_PORT, str(set_name)))
144149
client = MongoReplicaSetClient(uri)
145-
client.database_names()
150+
client[GSSAPI_DB].collection.find_one()
146151

147152
# With gssapiServiceName
148-
self.assertTrue(client.test.authenticate(PRINCIPAL,
149-
mechanism='GSSAPI',
150-
gssapiServiceName='mongodb'))
151-
client.database_names()
153+
client = MongoReplicaSetClient(GSSAPI_HOST,
154+
port=GSSAPI_PORT,
155+
replicaSet=set_name)
156+
db = client[GSSAPI_DB]
157+
self.assertTrue(db.authenticate(
158+
GSSAPI_PRINCIPAL,
159+
mechanism='GSSAPI',
160+
gssapiServiceName='mongodb'))
161+
db.collection.find_one()
152162
uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet'
153-
'=%s;gssapiServiceName=mongodb' % (quote_plus(PRINCIPAL),
154-
GSSAPI_HOST,
155-
GSSAPI_PORT,
156-
str(set_name)))
163+
'=%s;gssapiServiceName=mongodb' % (
164+
quote_plus(GSSAPI_PRINCIPAL),
165+
GSSAPI_HOST,
166+
GSSAPI_PORT,
167+
str(set_name)))
157168
client = MongoReplicaSetClient(uri)
158-
client.database_names()
169+
client[GSSAPI_DB].collection.find_one()
159170
uri = ('mongodb://%s@%s:%d/?authMechanism=GSSAPI;replicaSet=%s;'
160171
'authMechanismProperties=SERVICE_NAME:mongodb' % (
161-
quote_plus(PRINCIPAL),
162-
GSSAPI_HOST, GSSAPI_PORT, str(set_name)))
172+
quote_plus(GSSAPI_PRINCIPAL),
173+
GSSAPI_HOST,
174+
GSSAPI_PORT,
175+
str(set_name)))
163176
client = MongoReplicaSetClient(uri)
164-
client.database_names()
177+
client[GSSAPI_DB].collection.find_one()
165178

166179
def test_gssapi_threaded(self):
167180

168181
# Use auto_start_request=True to make sure each thread
169182
# uses a different socket.
170183
client = MongoClient(GSSAPI_HOST, auto_start_request=True)
171-
self.assertTrue(client.test.authenticate(PRINCIPAL,
172-
mechanism='GSSAPI'))
184+
db = client[GSSAPI_DB]
185+
self.assertTrue(db.authenticate(GSSAPI_PRINCIPAL,
186+
mechanism='GSSAPI'))
187+
188+
# Need one document in the collection. AutoAuthenticateThread does
189+
# collection.find_one with a 1-second delay, forcing it to check out
190+
# multiple sockets from the pool concurrently, proving that
191+
# auto-authentication works with GSSAPI.
192+
collection = db.test
193+
if collection.count() == 0:
194+
try:
195+
collection.drop()
196+
collection.insert_one({'_id': 1})
197+
except OperationFailure:
198+
raise SkipTest("User must be able to write.")
173199

174200
threads = []
175201
for _ in xrange(4):
176-
threads.append(AutoAuthenticateThread(client.foo))
202+
threads.append(AutoAuthenticateThread(collection))
177203
for thread in threads:
178204
thread.start()
179205
for thread in threads:
@@ -186,13 +212,15 @@ def test_gssapi_threaded(self):
186212
client = MongoReplicaSetClient(GSSAPI_HOST,
187213
replicaSet=set_name,
188214
read_preference=preference)
189-
self.assertTrue(client.test.authenticate(PRINCIPAL,
190-
mechanism='GSSAPI'))
191-
self.assertTrue(client.foo.command('dbstats'))
215+
db = client[GSSAPI_DB]
216+
self.assertTrue(db.authenticate(GSSAPI_PRINCIPAL,
217+
mechanism='GSSAPI'))
218+
collection = db.test
219+
collection.find_one()
192220

193221
threads = []
194222
for _ in xrange(4):
195-
threads.append(AutoAuthenticateThread(client.foo))
223+
threads.append(AutoAuthenticateThread(collection))
196224
for thread in threads:
197225
thread.start()
198226
for thread in threads:

0 commit comments

Comments
 (0)