This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch master
in repository lib389.
commit dce79c5621e4cb344fa4932c45ccd6c7e34817cc
Author: William Brown <firstyear(a)redhat.com>
Date: Wed Jun 7 17:38:06 2017 +1000
Ticket 66 - expand healthcheck for Directory Server
Bug Description: In order to aid admins, we should be able to offer
advice about the status of their servers.
Fix Description: This expands the healthcheck command to include
encryption and password options. It changes the command name (from
lint to healthcheck), and add's generic wrappers to run these
more easily.
https://pagure.io/lib389/issue/66
Author: wibrown
Review by: ilias95, spichugi (Thanks!)
---
cli/dsconf | 4 +-
cli/dsctl | 2 +-
lib389/_mapped_object.py | 11 +++-
lib389/backend.py | 43 +++----------
lib389/cli_conf/{lint.py => health.py} | 36 +++++++----
lib389/config.py | 25 ++++++++
lib389/lint.py | 111 +++++++++++++++++++++++++++++++++
lib389/tests/healthcheck_test.py | 42 +++++++++++++
8 files changed, 221 insertions(+), 53 deletions(-)
diff --git a/cli/dsconf b/cli/dsconf
index a114afb..168ecdf 100755
--- a/cli/dsconf
+++ b/cli/dsconf
@@ -21,7 +21,7 @@ from lib389._constants import DN_CONFIG, DN_DM
from lib389.cli_conf import backend as cli_backend
from lib389.cli_conf import plugin as cli_plugin
from lib389.cli_conf import schema as cli_schema
-from lib389.cli_conf import lint as cli_lint
+from lib389.cli_conf import health as cli_health
from lib389.cli_conf.plugins import memberof as cli_memberof
from lib389.cli_base import disconnect_instance, connect_instance
@@ -62,7 +62,7 @@ if __name__ == '__main__':
cli_backend.create_parser(subparsers)
cli_schema.create_parser(subparsers)
- cli_lint.create_parser(subparsers)
+ cli_health.create_parser(subparsers)
cli_plugin.create_parser(subparsers)
cli_memberof.create_parser(subparsers)
diff --git a/cli/dsctl b/cli/dsctl
index bfaba4a..fed1a96 100755
--- a/cli/dsctl
+++ b/cli/dsctl
@@ -21,7 +21,7 @@ from lib389.cli_ctl import instance as cli_instance
from lib389.cli_ctl import dbtasks as cli_dbtasks
from lib389.cli_base import disconnect_instance
-log = logging.getLogger("dsadm")
+log = logging.getLogger("dsctl")
if __name__ == '__main__':
diff --git a/lib389/_mapped_object.py b/lib389/_mapped_object.py
index c307cf2..361b479 100644
--- a/lib389/_mapped_object.py
+++ b/lib389/_mapped_object.py
@@ -96,6 +96,7 @@ class DSLdapObject(DSLogging):
self._must_attributes = None
# attributes, we don't want to compare
self._compare_exclude = ['entryid']
+ self._lint_functions = None
def __unicode__(self):
val = self._dn
@@ -463,8 +464,14 @@ class DSLdapObject(DSLogging):
You should return an array of these dicts, on None if there are no errors.
"""
- return None
-
+ if not self._lint_functions:
+ return None
+ results = []
+ for fn in self._lint_functions:
+ result = fn()
+ if result:
+ results.append(result)
+ return results
# A challenge of this, is how do we manage indexes? They have two naming attribunes....
diff --git a/lib389/backend.py b/lib389/backend.py
index aa540e5..1affe13 100644
--- a/lib389/backend.py
+++ b/lib389/backend.py
@@ -24,6 +24,8 @@ from lib389.monitor import MonitorBackend
# This is for sample entry creation.
from lib389.configurations import get_sample_entries
+from lib389.lint import DSBLE0001
+
class BackendLegacy(object):
proxied_methods = 'search_s getEntry'.split()
@@ -391,6 +393,7 @@ class Backend(DSLdapObject):
self._must_attributes = ['nsslapd-suffix', 'cn']
self._create_objectclasses = ['top', 'extensibleObject',
BACKEND_OBJECTCLASS_VALUE]
self._protected = False
+ self._lint_functions = [self._lint_mappingtree]
# Check if a mapping tree for this suffix exists.
self._mts = MappingTrees(self._instance)
@@ -480,7 +483,7 @@ class Backend(DSLdapObject):
# The super will actually delete ourselves.
super(Backend, self).delete()
- def lint(self):
+ def _lint_mappingtree(self):
"""
Backend lint
@@ -488,48 +491,18 @@ class Backend(DSLdapObject):
* missing mapping tree entries for the backend
* missing indcies if we are local and have log access?
"""
- results = []
- # return ["Hello!",]
# Check for the missing mapping tree.
suffix = ensure_str(self.get_attr_val('nsslapd-suffix'))
bename = self.get_attr_val('cn')
- # This should change to the mapping tree objects later ....
try:
mt = self._mts.get(suffix)
if mt.get_attr_val('nsslapd-backend') != ensure_bytes(bename) and
mt.get_attr_val('nsslapd-state') != ensure_bytes('backend') :
raise ldap.NO_SUCH_OBJECT("We have a matching suffix, but not a
backend or correct database name.")
except ldap.NO_SUCH_OBJECT:
- results.append({
- 'dsle': 'DSBLE0001',
- 'severity': 'MEDIUM',
- 'items' : [bename, ],
- 'detail' : """
-This backend may be missing the correct mapping tree references. Mapping Trees allow
-the directory server to determine which backend an operation is routed to in the
-abscence of other information. This is extremely important for correct functioning
-of LDAP ADD for example.
-
-A correct Mapping tree for this backend must contain the suffix name, the database name
-and be a backend type. IE:
-
-cn=o3Dexample,cn=mapping tree,cn=config
-cn: o=example
-nsslapd-backend: userRoot
-nsslapd-state: backend
-objectClass: top
-objectClass: extensibleObject
-objectClass: nsMappingTree
-
- """,
- 'fix' : """
-Either you need to create the mapping tree, or you need to repair the related
-mapping tree. You will need to do this by hand by editing cn=config, or stopping
-the instance and editing dse.ldif.
- """
- })
- if len(results) == 0:
- return None
- return results
+ result = DSBLE0001
+ result['items'] = [bename, ]
+ return result
+ return None
def get_monitor(self):
monitor = MonitorBackend(instance=self._instance, dn= "cn=monitor,%s" %
self._dn, batch=self._batch)
diff --git a/lib389/cli_conf/lint.py b/lib389/cli_conf/health.py
similarity index 52%
rename from lib389/cli_conf/lint.py
rename to lib389/cli_conf/health.py
index 31c5cf0..02aef14 100644
--- a/lib389/cli_conf/lint.py
+++ b/lib389/cli_conf/health.py
@@ -9,12 +9,20 @@
import argparse
from lib389.backend import Backend, Backends
+from lib389.config import Encryption, Config
-LINT_OBJECTS = [
- Backends
+# These get all instances, then check them all.
+CHECK_MANY_OBJECTS = [
+ Backends,
]
-def _format_lint_output(log, result):
+# These get single instances and check them.
+CHECK_OBJECTS = [
+ Config,
+ Encryption,
+]
+
+def _format_check_output(log, result):
log.info("==== DS Lint Error: %s ====" % result['dsle'])
log.info(" Severity: %s " % result['severity'])
log.info(" Affects:")
@@ -25,25 +33,27 @@ def _format_lint_output(log, result):
log.info(" Resolution:")
log.info(result['fix'])
-def lint_run(inst, basedn, log, args):
+def health_check_run(inst, basedn, log, args):
log.info("Beginning lint report, this could take a while ...")
report = []
- for lo in LINT_OBJECTS:
+ for lo in CHECK_MANY_OBJECTS:
log.info("Checking %s ..." % lo.__name__)
lo_inst = lo(inst)
for clo in lo_inst.list():
result = clo.lint()
if result is not None:
report += result
- log.info("Lint complete!")
+ for lo in CHECK_OBJECTS:
+ log.info("Checking %s ..." % lo.__name__)
+ lo_inst = lo(inst)
+ result = lo_inst.lint()
+ if result is not None:
+ report += result
+ log.info("Healthcheck complete!")
for item in report:
- _format_lint_output(log, item)
+ _format_check_output(log, item)
def create_parser(subparsers):
- lint_parser = subparsers.add_parser('lint', help="Check for
configuration issues in your Directory Server instance.")
-
- subcommands = lint_parser.add_subparsers(help="action")
-
- run_lint_parser = subcommands.add_parser('run', help="Run a lint report
on your Directory Server instance. This is a safe, Read Only operation.")
- run_lint_parser.set_defaults(func=lint_run)
+ run_healthcheck_parser = subparsers.add_parser('healthcheck', help="Run
a healthcheck report on your Directory Server instance. This is a safe, read only
operation.")
+ run_healthcheck_parser.set_defaults(func=health_check_run)
diff --git a/lib389/config.py b/lib389/config.py
index 01b731e..0723214 100644
--- a/lib389/config.py
+++ b/lib389/config.py
@@ -20,7 +20,9 @@ import ldap
from lib389._constants import *
from lib389 import Entry
from lib389._mapped_object import DSLdapObject
+from lib389.utils import ensure_bytes, ensure_str
+from lib389.lint import DSCLE0001, DSCLE0002, DSELE0001
class Config(DSLdapObject):
"""
@@ -52,6 +54,7 @@ class Config(DSLdapObject):
]
self._compare_exclude = self._compare_exclude + config_compare_exclude
self._rdn_attribute = 'cn'
+ self._lint_functions = [self._lint_hr_timestamp, self._lint_passwordscheme]
@property
def dn(self):
@@ -180,6 +183,21 @@ class Config(DSLdapObject):
fields = 'nsslapd-security nsslapd-ssl-check-hostname'.split()
return self._instance.getEntry(DN_CONFIG, attrlist=fields)
+ def _lint_hr_timestamp(self):
+ hr_timestamp =
self.get_attr_val('nsslapd-logging-hr-timestamps-enabled')
+ if ensure_bytes('on') != hr_timestamp:
+ return DSCLE0001
+ pass # nsslapd-logging-hr-timestamps-enabled
+
+ def _lint_passwordscheme(self):
+ allowed_schemes = ['SSHA512', 'PBKDF2_SHA256']
+ password_scheme = self.get_attr_val('passwordStorageScheme')
+ root_scheme = self.get_attr_val('nsslapd-rootpwstoragescheme')
+ u_password_scheme = ensure_str(password_scheme)
+ u_root_scheme = ensure_str(root_scheme)
+ if u_root_scheme not in allowed_schemes or u_password_scheme not in
allowed_schemes:
+ return DSCLE0002
+ return None
class Encryption(DSLdapObject):
"""
@@ -196,12 +214,19 @@ class Encryption(DSLdapObject):
self._rdn_attribute = 'cn'
self._must_attributes = ['cn']
self._protected = True
+ self._lint_functions = [self._lint_check_tls_version]
def create(self, rdn=None, properties={'cn': 'encryption',
'nsSSLClientAuth': 'allowed'}):
if rdn is not None:
self._log.debug("dn on cn=encryption is not None. This is a
mistake.")
super(Encryption, self).create(properties=properties)
+ def _lint_check_tls_version(self):
+ tls_min = self.get_attr_val('sslVersionMin');
+ if tls_min < ensure_bytes('TLS1.1'):
+ return DSELE0001
+ return None
+
class RSA(DSLdapObject):
"""
Manage the "cn=RSA,cn=encryption,cn=config" object
diff --git a/lib389/lint.py b/lib389/lint.py
new file mode 100644
index 0000000..404cff8
--- /dev/null
+++ b/lib389/lint.py
@@ -0,0 +1,111 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+# A set of constants defining the lint errors we can return to a caller.
+# as well as some functions to help process them.
+
+
+DSBLE0001 = {
+ 'dsle': 'DSBLE0001',
+ 'severity': 'MEDIUM',
+ 'items' : [],
+ 'detail' : """
+This backend may be missing the correct mapping tree references. Mapping Trees allow
+the directory server to determine which backend an operation is routed to in the
+abscence of other information. This is extremely important for correct functioning
+of LDAP ADD for example.
+
+A correct Mapping tree for this backend must contain the suffix name, the database name
+and be a backend type. IE:
+
+cn=o3Dexample,cn=mapping tree,cn=config
+cn: o=example
+nsslapd-backend: userRoot
+nsslapd-state: backend
+objectClass: top
+objectClass: extensibleObject
+objectClass: nsMappingTree
+
+ """,
+ 'fix' : """
+Either you need to create the mapping tree, or you need to repair the related
+mapping tree. You will need to do this by hand by editing cn=config, or stopping
+the instance and editing dse.ldif.
+ """
+}
+
+DSCLE0001 = {
+ 'dsle' : 'DSCLE0001',
+ 'severity' : 'LOW',
+ 'items': ['cn=config', ],
+ 'detail' : """
+nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
+
+[07/Jun/2017:17:15:58 +1000]
+
+to
+
+[07/Jun/2017:17:15:58.716117312 +1000]
+
+This actually provides a performance improvement. Additionally, this setting will be
+removed in a future release.
+ """,
+ 'fix' : """
+Set nsslapd-logging-hr-timestamps-enabled to on.
+ """
+}
+
+DSCLE0002 = {
+ 'dsle': 'DSCLE0002',
+ 'severity': 'HIGH',
+ 'items' : ['cn=config', ],
+ 'detail' : """
+Password storage schemes in Directory Server define how passwords are hashed via a
+one-way mathematical function for storage. Knowing the hash it is difficult to gain
+the input, but knowing the input you can easily compare the hash.
+
+Many hashes are well known for cryptograhpic verification properties, but are
+designed to be *fast* to validate. This is the opposite of what we desire for password
+storage. In the unlikely event of a disclosure, you want hashes to be *difficult* to
+verify, as this adds a cost of work to an attacker.
+
+In Directory Server, we offer one hash suitable for this (PBKDF2_SHA256) and one hash
+for "legacy" support (SSHA512).
+
+Your configuration does not use these for password storage or the root password storage
+scheme.
+ """,
+ 'fix': """
+Perform a configuration reset of the values:
+
+passwordStorageScheme
+nsslapd-rootpwstoragescheme
+
+IE, stop Directory Server, and in dse.ldif delete these two lines. When Directory Server
+is started, they will use the server provided defaults that are secure.
+ """
+}
+
+DSELE0001 = {
+ 'dsle': 'DSELE0001',
+ 'severity': 'MEDIUM',
+ 'items' : ['cn=encryption,cn=config', ],
+ 'detail': """
+This Directory Server may not be using strong TLS protocol versions. TLS1.0 is known to
+have a number of issues with the protocol. Please see:
+
+https://tools.ietf.org/html/rfc7457
+
+It is advised you set this value to the maximum possible.
+ """,
+ 'fix' : """
+set cn=encryption,cn=config sslVersionMin to a version greater than TLS1.0
+ """
+}
+
+
diff --git a/lib389/tests/healthcheck_test.py b/lib389/tests/healthcheck_test.py
new file mode 100644
index 0000000..94711eb
--- /dev/null
+++ b/lib389/tests/healthcheck_test.py
@@ -0,0 +1,42 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+import pytest
+import ldap
+
+from lib389.topologies import topology_st
+
+from lib389.lint import *
+
+def test_hc_backend_mt(topology_st):
+ mt = topology_st.standalone.mappingtrees.get('userRoot')
+ # We have to remove the MT from a backend.
+ mt.delete()
+ be = topology_st.standalone.backends.get('userRoot')
+ result = be._lint_mappingtree()
+ # We have to check this by name, not object, as the result changes
+ # the affected dn in the result.
+ assert result['dsle'] == 'DSBLE0001'
+
+def test_hc_encryption(topology_st):
+ topology_st.standalone.encryption.set('sslVersionMin', 'TLS1.0')
+ result = topology_st.standalone.encryption._lint_check_tls_version()
+ assert result == DSELE0001
+
+def test_hc_config(topology_st):
+ # Check the HR timestamp
+ topology_st.standalone.config.set('nsslapd-logging-hr-timestamps-enabled',
'off')
+ result = topology_st.standalone.config._lint_hr_timestamp()
+ assert result == DSCLE0001
+
+ # Check the password scheme check.
+ topology_st.standalone.config.set('passwordStorageScheme', 'SSHA')
+ result = topology_st.standalone.config._lint_passwordscheme()
+ assert result == DSCLE0002
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.