Bug Description: If you run any of the CLI tools using "-v", and set a password,
that password will be displayed in clear text in the console.
Fix Description: Create an internal list of sensitive attributes to filter, and
mask them in the operation debug logging. But still allow the
password to be seen if you set the env variable DEBUGGING=true
We also still print the root DN password if it is a container
installation.
https://pagure.io/389-ds-base/issue/50251
Reviewed by: spichugi, firstyear, and mhonek (Thanks!!!)
Gbp-Pq: Name CVE-2019-10224.patch
REPLICATION_TRANSPORT = RA_TRANSPORT_PROT
REPLICATION_TIMEOUT = RA_TIMEOUT
+# Attributes that should be masked from logging output
+SENSITIVE_ATTRS = ['userpassword',
+ 'nsslapd-rootpw',
+ 'nsds5replicacredentials',
+ 'nsmultiplexorcredentials']
+
TRANS_STARTTLS = "starttls"
TRANS_SECURE = "secure"
TRANS_NORMAL = "normal"
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
-import re
import six
import logging
import ldif
from lib389._constants import *
from lib389.properties import *
-from lib389.utils import ensure_str, ensure_bytes, ensure_list_bytes
+from lib389.utils import (ensure_str, ensure_bytes, ensure_list_bytes, display_log_data)
MAJOR, MINOR, _, _, _ = sys.version_info
log = logging.getLogger(__name__)
+
class FormatDict(cidict):
def __getitem__(self, name):
if name in self:
def update(self, dct):
"""Update passthru to the data attribute."""
- log.debug("update dn: %r with %r" % (self.dn, dct))
+ log.debug("updating dn: {}".format(self.dn))
for k, v in list(dct.items()):
if isinstance(v, list) or isinstance(v, tuple):
self.data[k] = v
else:
self.data[k] = [v]
+ log.debug("updated dn: {} with {}".format(self.dn, display_log_data(dct)))
def __repr__(self):
"""Convert the Entry to its LDIF representation"""
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import os
import ldap
import ldap.dn
from ldap import filter as ldap_filter
import logging
import json
from functools import partial
-
from lib389._entry import Entry
from lib389._constants import DIRSRV_STATE_ONLINE, SER_ROOT_DN, SER_ROOT_PW
from lib389.utils import (
ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str,
- ensure_list_int
+ ensure_list_int, display_log_value, display_log_data
)
# This function filter and term generation provided thanks to
action_txt = "UNKNOWN"
if value is None or len(value) < 512:
- self._log.debug("%s set %s: (%r, %r)" % (self._dn, action_txt, key, value))
+ self._log.debug("%s set %s: (%r, %r)" % (self._dn, action_txt, key, display_log_value(key, value)))
else:
self._log.debug("%s set %s: (%r, value too large)" % (self._dn, action_txt, key))
if self._instance.state != DIRSRV_STATE_ONLINE:
"""
assert(len(self._create_objectclasses) > 0)
basedn = ensure_str(basedn)
- self._log.debug('Checking "%s" under %s : %s' % (rdn, basedn, properties))
+ self._log.debug('Checking "%s" under %s : %s' % (rdn, basedn, display_log_data(properties)))
# Add the objectClasses to the properties
(dn, valid_props) = self._validate(rdn, properties, basedn)
# Check if the entry exists or not? .add_s is going to error anyway ...
- self._log.debug('Validated dn %s : valid_props %s' % (dn, valid_props))
+ self._log.debug('Validated dn {}'.format(dn))
exists = False
e.update({'objectclass': ensure_list_bytes(self._create_objectclasses)})
e.update(valid_props)
# We rely on exceptions here to indicate failure to the parent.
- self._log.debug('Creating entry %s : %s' % (dn, e))
self._instance.add_ext_s(e, serverctrls=self._server_controls, clientctrls=self._client_controls)
+ self._log.debug('Created entry %s : %s' % (dn, display_log_data(e.data)))
# If it worked, we need to fix our instance dn
self._dn = dn
return self
backend['suffix'] = val
break
else:
- print("The suffix \"{}\" is not a valid DN")
+ print("The suffix \"{}\" is not a valid DN".format(val))
continue
else:
backend['suffix'] = suffix
else:
self.log.debug("Skipping default SASL maps - no backend found!")
+ # We need to log the password when containerised
+ if self.containerised:
+ self.log.debug("Root DN password: {}".format(slapd['root_password']))
+
# Complete.
# Change the root password finally
ds_instance.config.set('nsslapd-rootpw',
log.info("content: %r" % ret)
+@pytest.mark.parametrize('data', [
+ ({'userpaSSwoRd': '1234', 'nsslaPd-rootpw': '5678', 'regularAttr': 'originalvalue'},
+ {'userpaSSwoRd': '********', 'nsslaPd-rootpw': '********', 'regularAttr': 'originalvalue'}),
+ ({'userpassword': ['1', '2', '3'], 'nsslapd-rootpw': ['x']},
+ {'userpassword': ['********', '********', '********'], 'nsslapd-rootpw': ['********']})
+])
+def test_get_log_data(data):
+ before, after = data
+ assert display_log_data(before) == after
+
+
if __name__ == "__main__":
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s -v %s" % CURRENT_FILE)
from lib389.dseldif import DSEldif
from lib389._constants import (
DEFAULT_USER, VALGRIND_WRAPPER, DN_CONFIG, CFGSUFFIX, LOCALHOST,
- ReplicaRole, CONSUMER_REPLICAID
+ ReplicaRole, CONSUMER_REPLICAID, SENSITIVE_ATTRS
)
from lib389.properties import (
SER_HOST, SER_USER_ID, SER_GROUP_ID, SER_STRICT_HOSTNAME_CHECKING, SER_PORT,
MAJOR, MINOR, _, _, _ = sys.version_info
+DEBUGGING = os.getenv('DEBUGGING', default=False)
+
log = logging.getLogger(__name__)
#
insts.sort()
return insts
+
def get_user_is_ds_owner():
# Check if we have permission to administer the DS instance. This is required
# for some tasks such as installing, killing, or editing configs for the
return False
+def display_log_value(attr, value, hide_sensitive=True):
+ # Mask all the sensitive attribute values
+ if DEBUGGING or not hide_sensitive:
+ return value
+ else:
+ if attr.lower() in SENSITIVE_ATTRS:
+ if type(value) in (list, tuple):
+ return list(map(lambda _: '********', value))
+ else:
+ return '********'
+ else:
+ return value
+
+
+def display_log_data(data, hide_sensitive=True):
+ # Take a dict and mask all the sensitive data
+ return {a: display_log_value(a, v, hide_sensitive) for a, v in data.items()}
\ No newline at end of file