Skip to content

Commit 4a5e0f6

Browse files
authored
PYTHON-3313 Cache AWS Credentials Where Possible (#982)
1 parent 775c020 commit 4a5e0f6

File tree

5 files changed

+80
-2
lines changed

5 files changed

+80
-2
lines changed

.evergreen/run-mongodb-aws-ecs-test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ authtest () {
3030
$PYTHON -m pip install --upgrade wheel setuptools pip
3131
cd src
3232
$PYTHON -m pip install '.[aws]'
33-
$PYTHON test/auth_aws/test_auth_aws.py
33+
$PYTHON test/auth_aws/test_auth_aws.py -v
3434
cd -
3535
}
3636

.evergreen/run-mongodb-aws-test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ authtest () {
6161
. venvaws/bin/activate
6262
fi
6363
python -m pip install '.[aws]'
64-
python test/auth_aws/test_auth_aws.py
64+
python test/auth_aws/test_auth_aws.py -v
6565
deactivate
6666
rm -rf venvaws
6767
}

doc/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ PyMongo 4.3 brings a number of improvements including:
2626
now allow for new types of events (such as DDL and C2C replication events)
2727
to be recorded with the new parameter ``show_expanded_events``
2828
that can be passed to methods such as :meth:`~pymongo.collection.Collection.watch`.
29+
- PyMongo now internally caches AWS credentials that it fetches from AWS
30+
endpoints, to avoid rate limitations. The cache is cleared when the
31+
credentials expire or an error is encountered.
2932

3033
Bug fixes
3134
.........

pymongo/auth_aws.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ def __init__(self, credentials):
2727

2828
_HAVE_MONGODB_AWS = False
2929

30+
try:
31+
from pymongo_auth_aws.auth import set_cached_credentials, set_use_cached_credentials
32+
33+
# Enable credential caching.
34+
set_use_cached_credentials(True)
35+
except ImportError:
36+
37+
def set_cached_credentials(creds):
38+
pass
39+
40+
3041
import bson
3142
from bson.binary import Binary
3243
from bson.son import SON
@@ -88,7 +99,13 @@ def _authenticate_aws(credentials, sock_info):
8899
# SASL complete.
89100
break
90101
except PyMongoAuthAwsError as exc:
102+
# Clear the cached credentials if we hit a failure in auth.
103+
set_cached_credentials(None)
91104
# Convert to OperationFailure and include pymongo-auth-aws version.
92105
raise OperationFailure(
93106
"%s (pymongo-auth-aws version %s)" % (exc, pymongo_auth_aws.__version__)
94107
)
108+
except Exception:
109+
# Clear the cached credentials if we hit a failure in auth.
110+
set_cached_credentials(None)
111+
raise

test/auth_aws/test_auth_aws.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
sys.path[0:0] = [""]
2222

23+
from pymongo_auth_aws import AwsCredential, auth
24+
2325
from pymongo import MongoClient
2426
from pymongo.errors import OperationFailure
2527
from pymongo.uri_parser import parse_uri
@@ -53,6 +55,62 @@ def test_connect_uri(self):
5355
with MongoClient(self.uri) as client:
5456
client.get_database().test.find_one()
5557

58+
def setup_cache(self):
59+
if os.environ.get("AWS_ACCESS_KEY_ID", None) or "@" in self.uri:
60+
self.skipTest("Not testing cached credentials")
61+
if not hasattr(auth, "set_cached_credentials"):
62+
self.skipTest("Cached credentials not available")
63+
64+
# Ensure cleared credentials.
65+
auth.set_cached_credentials(None)
66+
self.assertEqual(auth.get_cached_credentials(), None)
67+
68+
client = MongoClient(self.uri)
69+
client.get_database().test.find_one()
70+
client.close()
71+
return auth.get_cached_credentials()
72+
73+
def test_cache_credentials(self):
74+
creds = self.setup_cache()
75+
self.assertIsNotNone(creds)
76+
77+
def test_cache_about_to_expire(self):
78+
creds = self.setup_cache()
79+
client = MongoClient(self.uri)
80+
self.addCleanup(client.close)
81+
82+
# Make the creds about to expire.
83+
creds = auth.get_cached_credentials()
84+
assert creds is not None
85+
86+
creds = AwsCredential(creds.username, creds.password, creds.token, lambda x: True)
87+
auth.set_cached_credentials(creds)
88+
89+
client.get_database().test.find_one()
90+
new_creds = auth.get_cached_credentials()
91+
self.assertNotEqual(creds, new_creds)
92+
93+
def test_poisoned_cache(self):
94+
creds = self.setup_cache()
95+
96+
client = MongoClient(self.uri)
97+
self.addCleanup(client.close)
98+
99+
# Poison the creds with invalid password.
100+
assert creds is not None
101+
creds = AwsCredential("a" * 24, "b" * 24, "c" * 24)
102+
auth.set_cached_credentials(creds)
103+
104+
with self.assertRaises(OperationFailure):
105+
client.get_database().test.find_one()
106+
107+
# Make sure the cache was cleared.
108+
self.assertEqual(auth.get_cached_credentials(), None)
109+
110+
# The next attempt should generate a new cred and succeed.
111+
client.get_database().test.find_one()
112+
self.assertNotEqual(auth.get_cached_credentials(), None)
113+
56114

57115
class TestAWSLambdaExamples(unittest.TestCase):
58116
def test_shared_client(self):

0 commit comments

Comments
 (0)