Skip to content

Commit

Permalink
Merge pull request #2713 from DataDog/yann/mongo-auth
Browse files Browse the repository at this point in the history
[mongodb] authenticate with X.509
  • Loading branch information
yannmh committed Sep 6, 2016
2 parents aa572f4 + 9b1075b commit e44e902
Showing 1 changed file with 71 additions and 25 deletions.
96 changes: 71 additions & 25 deletions checks.d/mongo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# stdlib
import re
import time
import urllib

# 3p
import pymongo
Expand Down Expand Up @@ -549,6 +550,38 @@ def _normalize(self, metric_name, submit_method, prefix):
metric_prefix=metric_prefix, metric_suffix=metric_suffix
)

def _authenticate(self, database, username, password, use_x509):
"""
Authenticate to the database.
Available mechanisms:
* Username & password
* X.509
More information:
https://api.mongodb.com/python/current/examples/authentication.html
"""
authenticated = False
try:
# X.509
if use_x509:
self.log.debug(
u"Authenticate `%s` to `%s` using `MONGODB-X509` mechanism",
username, database
)
authenticated = database.authenticate(username, mechanism='MONGODB-X509')

# Username & password
else:
authenticated = database.authenticate(username, password)

except pymongo.errors.PyMongoError as e:
self.log.error(
u"Authentication failed due to invalid credentials or configuration issues. %s", e
)

return authenticated

def check(self, instance):
"""
Returns a dictionary that looks a lot like what's sent back by
Expand All @@ -571,8 +604,7 @@ def total_seconds(td):
if 'server' not in instance:
raise Exception("Missing 'server' in mongo config")

server = instance['server']

# x.509 authentication
ssl_params = {
'ssl': instance.get('ssl', None),
'ssl_keyfile': instance.get('ssl_keyfile', None),
Expand All @@ -586,35 +618,44 @@ def total_seconds(td):
del ssl_params[key]

# Configuration a URL, mongodb://user:pass@server/db
server = instance['server']
parsed = pymongo.uri_parser.parse_uri(server)
username = parsed.get('username')
password = parsed.get('password')
db_name = parsed.get('database')
clean_server_name = server.replace(password, "*" * 5) if password is not None else server
additional_metrics = instance.get('additional_metrics', [])

if not db_name:
self.log.debug('No MongoDB database found in URI. Defaulting to admin.')
db_name = 'admin'
clean_server_name = server.replace(password, "*" * 5) if password else server

tags = instance.get('tags', [])
# de-dupe tags to avoid a memory leak
tags = list(set(tags))
service_check_tags = [
"db:%s" % db_name
]
service_check_tags.extend(tags)
# Add the `server` tag to the metrics' tags only (it's added in the backend for service checks)
tags.append('server:%s' % clean_server_name)
if ssl_params:
username_uri = u"{}@".format(urllib.quote(username))
clean_server_name = clean_server_name.replace(username_uri, "")

# Get the list of metrics to collect
additional_metrics = instance.get('additional_metrics', [])

collect_tcmalloc_metrics = 'tcmalloc' in additional_metrics
metrics_to_collect = self._get_metrics_to_collect(
server,
additional_metrics
)

# Tagging
tags = instance.get('tags', [])
# ...de-dupe tags to avoid a memory leak
tags = list(set(tags))

if not db_name:
self.log.info('No MongoDB database found in URI. Defaulting to admin.')
db_name = 'admin'

service_check_tags = [
"db:%s" % db_name
]
service_check_tags.extend(tags)

# ...add the `server` tag to the metrics' tags only
# (it's added in the backend for service checks)
tags.append('server:%s' % clean_server_name)

nodelist = parsed.get('nodelist')
if nodelist:
host = nodelist[0][0]
Expand All @@ -624,11 +665,6 @@ def total_seconds(td):
"port:%s" % port
]

do_auth = True
if username is None or password is None:
self.log.debug("Mongo: cannot extract username and password from config %s" % server)
do_auth = False

timeout = float(instance.get('timeout', DEFAULT_TIMEOUT)) * 1000
try:
cli = pymongo.mongo_client.MongoClient(
Expand All @@ -646,8 +682,18 @@ def total_seconds(td):
tags=service_check_tags)
raise

if do_auth and not db.authenticate(username, password):
message = "Mongo: cannot connect with config %s" % server
# Authenticate
do_auth = True
use_x509 = ssl_params and not password

if not username:
self.log.debug(
u"A username is required to authenticate to `%s`", server
)
do_auth = False

if do_auth and not self._authenticate(db, username, password, use_x509):
message = u"Mongo: cannot connect with config `%s`" % clean_server_name
self.service_check(
self.SERVICE_CHECK_NAME,
AgentCheck.CRITICAL,
Expand Down Expand Up @@ -698,7 +744,7 @@ def total_seconds(td):
**ssl_params)
db = cli[db_name]

if do_auth and not db.authenticate(username, password):
if do_auth and not self._authenticate(db, username, password, use_x509):
message = ("Mongo: cannot connect with config %s" % server)
self.service_check(
self.SERVICE_CHECK_NAME,
Expand Down

0 comments on commit e44e902

Please sign in to comment.