|
|
c384d7 |
From 09326585a5561480d44beb508af2cb1da52bfff6 Mon Sep 17 00:00:00 2001
|
|
|
c384d7 |
From: Mark Reynolds <mreynolds@redhat.com>
|
|
|
c384d7 |
Date: Mon, 18 Nov 2019 12:02:39 -0500
|
|
|
c384d7 |
Subject: [PATCH] Issue 50701 - Add additional healthchecks to dsconf
|
|
|
c384d7 |
|
|
|
c384d7 |
Description: New checks and several design changes have been implemented
|
|
|
c384d7 |
|
|
|
c384d7 |
Design changes:
|
|
|
c384d7 |
- Moved to a "yield" design, where a lint function can return multiple results
|
|
|
c384d7 |
- Revised the lint report so it's easier to read and distiguish between multiple
|
|
|
c384d7 |
errors
|
|
|
c384d7 |
- Revised most lint errors to include CLI examples on how to fix the issue
|
|
|
c384d7 |
|
|
|
c384d7 |
New Checks:
|
|
|
c384d7 |
- Check TLS certs for expired/expiring
|
|
|
c384d7 |
- Add RI plugin checks for missing indexes for RI member attributes
|
|
|
c384d7 |
- Added Disk Space check
|
|
|
c384d7 |
- Add Virtual Attribute index check
|
|
|
c384d7 |
- Add replication agmt status check
|
|
|
c384d7 |
- Add replication conflict entry check
|
|
|
c384d7 |
- File System checks (/etc/revolv.conf, and NSS pin files)
|
|
|
c384d7 |
- Replication changelog trimming
|
|
|
c384d7 |
|
|
|
c384d7 |
relates: https://pagure.io/389-ds-base/issue/50701
|
|
|
c384d7 |
|
|
|
c384d7 |
Reviewed by: firstyear, mhonek, tbordaz, and spichugi (Thanks!!!!)
|
|
|
c384d7 |
|
|
|
c384d7 |
add suggested changes
|
|
|
c384d7 |
|
|
|
c384d7 |
Improved the replication agreement health checks to use the new
|
|
|
c384d7 |
state levels (red, amber, green), and we use that to generate
|
|
|
c384d7 |
different reports.
|
|
|
c384d7 |
|
|
|
c384d7 |
Also improved report example autofilling of the values, so the exact
|
|
|
c384d7 |
commands can be copied and pasted.
|
|
|
c384d7 |
|
|
|
c384d7 |
Added a changelog trimming check as well.
|
|
|
c384d7 |
|
|
|
c384d7 |
Updated the help section to wanr that htehealthcheck feature should
|
|
|
c384d7 |
only be run on the local instance
|
|
|
c384d7 |
|
|
|
c384d7 |
Moved healthcheck to dsctl and added file permission checks
|
|
|
c384d7 |
---
|
|
|
c384d7 |
src/lib389/cli/dsconf | 2 -
|
|
|
c384d7 |
src/lib389/cli/dsctl | 10 +-
|
|
|
c384d7 |
src/lib389/lib389/_mapped_object.py | 6 +-
|
|
|
c384d7 |
src/lib389/lib389/agreement.py | 67 +++++--
|
|
|
c384d7 |
src/lib389/lib389/backend.py | 122 +++++++++---
|
|
|
c384d7 |
src/lib389/lib389/cli_base/dsrc.py | 6 +-
|
|
|
c384d7 |
src/lib389/lib389/cli_conf/health.py | 62 ------
|
|
|
c384d7 |
src/lib389/lib389/cli_ctl/health.py | 123 ++++++++++++
|
|
|
c384d7 |
src/lib389/lib389/config.py | 18 +-
|
|
|
c384d7 |
src/lib389/lib389/dseldif.py | 43 +++-
|
|
|
c384d7 |
src/lib389/lib389/lint.py | 287 +++++++++++++++++++++++----
|
|
|
c384d7 |
src/lib389/lib389/monitor.py | 14 ++
|
|
|
c384d7 |
src/lib389/lib389/nss_ssl.py | 35 +++-
|
|
|
c384d7 |
src/lib389/lib389/plugins.py | 46 ++++-
|
|
|
c384d7 |
src/lib389/lib389/properties.py | 1 +
|
|
|
c384d7 |
src/lib389/lib389/replica.py | 70 +++++++
|
|
|
c384d7 |
16 files changed, 746 insertions(+), 166 deletions(-)
|
|
|
c384d7 |
delete mode 100644 src/lib389/lib389/cli_conf/health.py
|
|
|
c384d7 |
create mode 100644 src/lib389/lib389/cli_ctl/health.py
|
|
|
c384d7 |
|
|
|
c384d7 |
diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf
|
|
|
c384d7 |
index 6e3ef19c3..5143756c8 100755
|
|
|
c384d7 |
--- a/src/lib389/cli/dsconf
|
|
|
c384d7 |
+++ b/src/lib389/cli/dsconf
|
|
|
c384d7 |
@@ -21,7 +21,6 @@ from lib389.cli_conf import backend as cli_backend
|
|
|
c384d7 |
from lib389.cli_conf import directory_manager as cli_directory_manager
|
|
|
c384d7 |
from lib389.cli_conf import plugin as cli_plugin
|
|
|
c384d7 |
from lib389.cli_conf import schema as cli_schema
|
|
|
c384d7 |
-from lib389.cli_conf import health as cli_health
|
|
|
c384d7 |
from lib389.cli_conf import monitor as cli_monitor
|
|
|
c384d7 |
from lib389.cli_conf import saslmappings as cli_sasl
|
|
|
c384d7 |
from lib389.cli_conf import pwpolicy as cli_pwpolicy
|
|
|
c384d7 |
@@ -80,7 +79,6 @@ cli_backup.create_parser(subparsers)
|
|
|
c384d7 |
cli_chaining.create_parser(subparsers)
|
|
|
c384d7 |
cli_config.create_parser(subparsers)
|
|
|
c384d7 |
cli_directory_manager.create_parsers(subparsers)
|
|
|
c384d7 |
-cli_health.create_parser(subparsers)
|
|
|
c384d7 |
cli_monitor.create_parser(subparsers)
|
|
|
c384d7 |
cli_plugin.create_parser(subparsers)
|
|
|
c384d7 |
cli_pwpolicy.create_parser(subparsers)
|
|
|
c384d7 |
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
|
|
|
c384d7 |
index 31e906b7d..8b86629ac 100755
|
|
|
c384d7 |
--- a/src/lib389/cli/dsctl
|
|
|
c384d7 |
+++ b/src/lib389/cli/dsctl
|
|
|
c384d7 |
@@ -16,14 +16,17 @@ import sys
|
|
|
c384d7 |
import signal
|
|
|
c384d7 |
import os
|
|
|
c384d7 |
from lib389.utils import get_instance_list
|
|
|
c384d7 |
-from lib389.cli_base import _get_arg, setup_script_logger, disconnect_instance
|
|
|
c384d7 |
from lib389 import DirSrv
|
|
|
c384d7 |
from lib389.cli_ctl import instance as cli_instance
|
|
|
c384d7 |
from lib389.cli_ctl import dbtasks as cli_dbtasks
|
|
|
c384d7 |
-from lib389.cli_base import disconnect_instance, setup_script_logger
|
|
|
c384d7 |
-from lib389.cli_base import format_error_to_dict
|
|
|
c384d7 |
from lib389.cli_ctl import tls as cli_tls
|
|
|
c384d7 |
+from lib389.cli_ctl import health as cli_health
|
|
|
c384d7 |
from lib389.cli_ctl.instance import instance_remove_all
|
|
|
c384d7 |
+from lib389.cli_base import (
|
|
|
c384d7 |
+ _get_arg,
|
|
|
c384d7 |
+ disconnect_instance,
|
|
|
c384d7 |
+ setup_script_logger,
|
|
|
c384d7 |
+ format_error_to_dict)
|
|
|
c384d7 |
from lib389._constants import DSRC_CONTAINER
|
|
|
c384d7 |
|
|
|
c384d7 |
parser = argparse.ArgumentParser()
|
|
|
c384d7 |
@@ -54,6 +57,7 @@ if not os.path.exists(DSRC_CONTAINER):
|
|
|
c384d7 |
cli_instance.create_parser(subparsers)
|
|
|
c384d7 |
cli_dbtasks.create_parser(subparsers)
|
|
|
c384d7 |
cli_tls.create_parser(subparsers)
|
|
|
c384d7 |
+cli_health.create_parser(subparsers)
|
|
|
c384d7 |
|
|
|
c384d7 |
argcomplete.autocomplete(parser)
|
|
|
c384d7 |
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
|
|
|
c384d7 |
index e331b3b27..4da112d25 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/_mapped_object.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/_mapped_object.py
|
|
|
c384d7 |
@@ -978,9 +978,9 @@ class DSLdapObject(DSLogging):
|
|
|
c384d7 |
return None
|
|
|
c384d7 |
results = []
|
|
|
c384d7 |
for fn in self._lint_functions:
|
|
|
c384d7 |
- result = fn()
|
|
|
c384d7 |
- if result:
|
|
|
c384d7 |
- results.append(result)
|
|
|
c384d7 |
+ for result in fn():
|
|
|
c384d7 |
+ if result is not None:
|
|
|
c384d7 |
+ results.append(result)
|
|
|
c384d7 |
return results
|
|
|
c384d7 |
|
|
|
c384d7 |
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
|
|
|
c384d7 |
index a0d4597ec..93fd72895 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/agreement.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/agreement.py
|
|
|
c384d7 |
@@ -105,6 +105,9 @@ class Agreement(DSLdapObject):
|
|
|
c384d7 |
time.sleep(2)
|
|
|
c384d7 |
return (done, error)
|
|
|
c384d7 |
|
|
|
c384d7 |
+ def get_name(self):
|
|
|
c384d7 |
+ return self.get_attr_val_utf8_l('cn')
|
|
|
c384d7 |
+
|
|
|
c384d7 |
def get_agmt_maxcsn(self):
|
|
|
c384d7 |
"""Get the agreement maxcsn from the database RUV entry
|
|
|
c384d7 |
:returns: CSN string if found, otherwise None is returned
|
|
|
c384d7 |
@@ -202,7 +205,7 @@ class Agreement(DSLdapObject):
|
|
|
c384d7 |
consumer.close()
|
|
|
c384d7 |
return result_msg
|
|
|
c384d7 |
|
|
|
c384d7 |
- def get_agmt_status(self, binddn=None, bindpw=None):
|
|
|
c384d7 |
+ def get_agmt_status(self, binddn=None, bindpw=None, return_json=False):
|
|
|
c384d7 |
"""Return the status message
|
|
|
c384d7 |
:param binddn: Specifies a specific bind DN to use when contacting the remote consumer
|
|
|
c384d7 |
:type binddn: str
|
|
|
c384d7 |
@@ -211,33 +214,55 @@ class Agreement(DSLdapObject):
|
|
|
c384d7 |
:returns: A status message about the replication agreement
|
|
|
c384d7 |
"""
|
|
|
c384d7 |
status = "Unknown"
|
|
|
c384d7 |
-
|
|
|
c384d7 |
+ con_maxcsn = "Unknown"
|
|
|
c384d7 |
try:
|
|
|
c384d7 |
agmt_maxcsn = self.get_agmt_maxcsn()
|
|
|
c384d7 |
+ agmt_status = json.loads(self.get_attr_val_utf8_l(AGMT_UPDATE_STATUS_JSON))
|
|
|
c384d7 |
if agmt_maxcsn is not None:
|
|
|
c384d7 |
- con_maxcsn = self.get_consumer_maxcsn(binddn=binddn, bindpw=bindpw)
|
|
|
c384d7 |
- if con_maxcsn:
|
|
|
c384d7 |
- if agmt_maxcsn == con_maxcsn:
|
|
|
c384d7 |
- status = "In Synchronization"
|
|
|
c384d7 |
- else:
|
|
|
c384d7 |
- # Not in sync - attempt to discover the cause
|
|
|
c384d7 |
- repl_msg = "Unknown"
|
|
|
c384d7 |
- if self.get_attr_val_utf8_l(AGMT_UPDATE_IN_PROGRESS) == 'true':
|
|
|
c384d7 |
- # Replication is on going - this is normal
|
|
|
c384d7 |
- repl_msg = "Replication still in progress"
|
|
|
c384d7 |
- elif "can't contact ldap" in \
|
|
|
c384d7 |
- self.get_attr_val_utf8_l(AGMT_UPDATE_STATUS):
|
|
|
c384d7 |
- # Consumer is down
|
|
|
c384d7 |
- repl_msg = "Consumer can not be contacted"
|
|
|
c384d7 |
-
|
|
|
c384d7 |
- status = ("Not in Synchronization: supplier " +
|
|
|
c384d7 |
- "(%s) consumer (%s) Reason(%s)" %
|
|
|
c384d7 |
- (agmt_maxcsn, con_maxcsn, repl_msg))
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ con_maxcsn = self.get_consumer_maxcsn(binddn=binddn, bindpw=bindpw)
|
|
|
c384d7 |
+ if con_maxcsn:
|
|
|
c384d7 |
+ if agmt_maxcsn == con_maxcsn:
|
|
|
c384d7 |
+ if return_json:
|
|
|
c384d7 |
+ return json.dumps({
|
|
|
c384d7 |
+ 'msg': "In Synchronization",
|
|
|
c384d7 |
+ 'agmt_maxcsn': agmt_maxcsn,
|
|
|
c384d7 |
+ 'con_maxcsn': con_maxcsn,
|
|
|
c384d7 |
+ 'state': agmt_status['state'],
|
|
|
c384d7 |
+ 'reason': agmt_status['message']
|
|
|
c384d7 |
+ })
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ return "In Synchronization"
|
|
|
c384d7 |
+ except:
|
|
|
c384d7 |
+ pass
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ agmt_maxcsn = "Unknown"
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # Not in sync - attempt to discover the cause
|
|
|
c384d7 |
+ repl_msg = agmt_status['message']
|
|
|
c384d7 |
+ if self.get_attr_val_utf8_l(AGMT_UPDATE_IN_PROGRESS) == 'true':
|
|
|
c384d7 |
+ # Replication is on going - this is normal
|
|
|
c384d7 |
+ repl_msg = "Replication still in progress"
|
|
|
c384d7 |
+ elif "can't contact ldap" in agmt_status['message']:
|
|
|
c384d7 |
+ # Consumer is down
|
|
|
c384d7 |
+ repl_msg = "Consumer can not be contacted"
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ if return_json:
|
|
|
c384d7 |
+ return json.dumps({
|
|
|
c384d7 |
+ 'msg': "Not in Synchronization",
|
|
|
c384d7 |
+ 'agmt_maxcsn': agmt_maxcsn,
|
|
|
c384d7 |
+ 'con_maxcsn': con_maxcsn,
|
|
|
c384d7 |
+ 'state': agmt_status['state'],
|
|
|
c384d7 |
+ 'reason': repl_msg
|
|
|
c384d7 |
+ })
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ return ("Not in Synchronization: supplier " +
|
|
|
c384d7 |
+ "(%s) consumer (%s) State (%s) Reason (%s)" %
|
|
|
c384d7 |
+ (agmt_maxcsn, con_maxcsn, agmt_status['state'], repl_msg))
|
|
|
c384d7 |
except ldap.INVALID_CREDENTIALS as e:
|
|
|
c384d7 |
raise(e)
|
|
|
c384d7 |
except ldap.LDAPError as e:
|
|
|
c384d7 |
raise ValueError(str(e))
|
|
|
c384d7 |
- return status
|
|
|
c384d7 |
|
|
|
c384d7 |
def get_lag_time(self, suffix, agmt_name, binddn=None, bindpw=None):
|
|
|
c384d7 |
"""Get the lag time between the supplier and the consumer
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
|
|
c384d7 |
index 62fd0ae94..ac2af021c 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/backend.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/backend.py
|
|
|
c384d7 |
@@ -7,6 +7,7 @@
|
|
|
c384d7 |
# --- END COPYRIGHT BLOCK ---
|
|
|
c384d7 |
|
|
|
c384d7 |
from datetime import datetime
|
|
|
c384d7 |
+import copy
|
|
|
c384d7 |
import ldap
|
|
|
c384d7 |
from lib389._constants import *
|
|
|
c384d7 |
from lib389.properties import *
|
|
|
c384d7 |
@@ -19,6 +20,8 @@ from lib389._mapped_object import DSLdapObjects, DSLdapObject
|
|
|
c384d7 |
from lib389.mappingTree import MappingTrees
|
|
|
c384d7 |
from lib389.exceptions import NoSuchEntryError, InvalidArgumentError
|
|
|
c384d7 |
from lib389.replica import Replicas
|
|
|
c384d7 |
+from lib389.cos import (CosTemplates, CosIndirectDefinitions,
|
|
|
c384d7 |
+ CosPointerDefinitions, CosClassicDefinitions)
|
|
|
c384d7 |
|
|
|
c384d7 |
# We need to be a factor to the backend monitor
|
|
|
c384d7 |
from lib389.monitor import MonitorBackend
|
|
|
c384d7 |
@@ -30,7 +33,7 @@ from lib389.encrypted_attributes import EncryptedAttr, EncryptedAttrs
|
|
|
c384d7 |
# This is for sample entry creation.
|
|
|
c384d7 |
from lib389.configurations import get_sample_entries
|
|
|
c384d7 |
|
|
|
c384d7 |
-from lib389.lint import DSBLE0001
|
|
|
c384d7 |
+from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSVIRTLE0001
|
|
|
c384d7 |
|
|
|
c384d7 |
|
|
|
c384d7 |
class BackendLegacy(object):
|
|
|
c384d7 |
@@ -410,10 +413,92 @@ class Backend(DSLdapObject):
|
|
|
c384d7 |
self._must_attributes = ['nsslapd-suffix', 'cn']
|
|
|
c384d7 |
self._create_objectclasses = ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE]
|
|
|
c384d7 |
self._protected = False
|
|
|
c384d7 |
- self._lint_functions = [self._lint_mappingtree]
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_mappingtree, self._lint_search, self._lint_virt_attrs]
|
|
|
c384d7 |
# Check if a mapping tree for this suffix exists.
|
|
|
c384d7 |
self._mts = MappingTrees(self._instance)
|
|
|
c384d7 |
|
|
|
c384d7 |
+ def _lint_virt_attrs(self):
|
|
|
c384d7 |
+ """Check if any virtual attribute are incorrectly indexed"""
|
|
|
c384d7 |
+ indexes = self.get_indexes()
|
|
|
c384d7 |
+ suffix = self.get_attr_val_utf8('nsslapd-suffix')
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # First check nsrole
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ indexes.get('nsrole')
|
|
|
c384d7 |
+ report = copy.deepcopy(DSVIRTLE0001)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('ATTR', 'nsrole')
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('ATTR', 'nsrole')
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ report['items'].append(suffix)
|
|
|
c384d7 |
+ report['items'].append('nsrole')
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ except:
|
|
|
c384d7 |
+ pass
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # Check COS next
|
|
|
c384d7 |
+ for cosDefType in [CosIndirectDefinitions, CosPointerDefinitions, CosClassicDefinitions]:
|
|
|
c384d7 |
+ defs = cosDefType(self._instance, self._dn).list()
|
|
|
c384d7 |
+ for cosDef in defs:
|
|
|
c384d7 |
+ attrs = cosDef.get_attr_val_utf8_l("cosAttribute").split()
|
|
|
c384d7 |
+ for attr in attrs:
|
|
|
c384d7 |
+ if attr in ["default", "override", "operational", "operational-default", "merge-schemes"]:
|
|
|
c384d7 |
+ # We are at the end, just break out
|
|
|
c384d7 |
+ break
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ indexes.get(attr)
|
|
|
c384d7 |
+ # If we got here there is an index (bad)
|
|
|
c384d7 |
+ report = copy.deepcopy(DSVIRTLE0001)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ report['items'].append(suffix)
|
|
|
c384d7 |
+ report['items'].append("Class Of Service (COS)")
|
|
|
c384d7 |
+ report['items'].append("cosAttribute: " + attr)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ except:
|
|
|
c384d7 |
+ # this is what we hope for
|
|
|
c384d7 |
+ pass
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_search(self):
|
|
|
c384d7 |
+ """Perform a search and make sure an entry is accessible
|
|
|
c384d7 |
+ """
|
|
|
c384d7 |
+ dn = self.get_attr_val_utf8('nsslapd-suffix')
|
|
|
c384d7 |
+ suffix = DSLdapObject(self._instance, dn=dn)
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ suffix.get_attr_val('objectclass')
|
|
|
c384d7 |
+ except ldap.NO_SUCH_OBJECT:
|
|
|
c384d7 |
+ # backend root entry not created yet
|
|
|
c384d7 |
+ DSBLE0003['items'] = [dn, ]
|
|
|
c384d7 |
+ yield DSBLE0003
|
|
|
c384d7 |
+ except ldap.LDAPError as e:
|
|
|
c384d7 |
+ # Some other error
|
|
|
c384d7 |
+ DSBLE0002['detail'] = DSBLE0002['detail'].replace('ERROR', str(e))
|
|
|
c384d7 |
+ DSBLE0002['items'] = [dn, ]
|
|
|
c384d7 |
+ yield DSBLE0002
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_mappingtree(self):
|
|
|
c384d7 |
+ """Backend lint
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ This should check for:
|
|
|
c384d7 |
+ * missing mapping tree entries for the backend
|
|
|
c384d7 |
+ * missing indices if we are local and have log access?
|
|
|
c384d7 |
+ """
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # Check for the missing mapping tree.
|
|
|
c384d7 |
+ suffix = self.get_attr_val_utf8('nsslapd-suffix')
|
|
|
c384d7 |
+ bename = self.get_attr_val_bytes('cn')
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ mt = self._mts.get(suffix)
|
|
|
c384d7 |
+ if mt.get_attr_val_bytes('nsslapd-backend') != bename and mt.get_attr_val('nsslapd-state') != ensure_bytes('backend'):
|
|
|
c384d7 |
+ raise ldap.NO_SUCH_OBJECT("We have a matching suffix, but not a backend or correct database name.")
|
|
|
c384d7 |
+ except ldap.NO_SUCH_OBJECT:
|
|
|
c384d7 |
+ result = DSBLE0001
|
|
|
c384d7 |
+ result['items'] = [bename, ]
|
|
|
c384d7 |
+ yield result
|
|
|
c384d7 |
+ return None
|
|
|
c384d7 |
+
|
|
|
c384d7 |
def create_sample_entries(self, version):
|
|
|
c384d7 |
"""Creates sample entries under nsslapd-suffix value
|
|
|
c384d7 |
|
|
|
c384d7 |
@@ -552,27 +637,6 @@ class Backend(DSLdapObject):
|
|
|
c384d7 |
# Now remove our children, this is all ldbm config
|
|
|
c384d7 |
self._instance.delete_branch_s(self._dn, ldap.SCOPE_SUBTREE)
|
|
|
c384d7 |
|
|
|
c384d7 |
- def _lint_mappingtree(self):
|
|
|
c384d7 |
- """Backend lint
|
|
|
c384d7 |
-
|
|
|
c384d7 |
- This should check for:
|
|
|
c384d7 |
- * missing mapping tree entries for the backend
|
|
|
c384d7 |
- * missing indices if we are local and have log access?
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
-
|
|
|
c384d7 |
- # Check for the missing mapping tree.
|
|
|
c384d7 |
- suffix = self.get_attr_val_utf8('nsslapd-suffix')
|
|
|
c384d7 |
- bename = self.get_attr_val_bytes('cn')
|
|
|
c384d7 |
- try:
|
|
|
c384d7 |
- mt = self._mts.get(suffix)
|
|
|
c384d7 |
- if mt.get_attr_val_bytes('nsslapd-backend') != bename and mt.get_attr_val('nsslapd-state') != ensure_bytes('backend'):
|
|
|
c384d7 |
- raise ldap.NO_SUCH_OBJECT("We have a matching suffix, but not a backend or correct database name.")
|
|
|
c384d7 |
- except ldap.NO_SUCH_OBJECT:
|
|
|
c384d7 |
- result = DSBLE0001
|
|
|
c384d7 |
- result['items'] = [bename, ]
|
|
|
c384d7 |
- return result
|
|
|
c384d7 |
- return None
|
|
|
c384d7 |
-
|
|
|
c384d7 |
def get_suffix(self):
|
|
|
c384d7 |
return self.get_attr_val_utf8_l('nsslapd-suffix')
|
|
|
c384d7 |
|
|
|
c384d7 |
@@ -753,6 +817,18 @@ class Backend(DSLdapObject):
|
|
|
c384d7 |
break
|
|
|
c384d7 |
return subsuffixes
|
|
|
c384d7 |
|
|
|
c384d7 |
+ def get_cos_indirect_defs(self):
|
|
|
c384d7 |
+ return CosIndirectDefinitions(self._instance, self._dn).list()
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def get_cos_pointer_defs(self):
|
|
|
c384d7 |
+ return CosPointerDefinitions(self._instance, self._dn).list()
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def get_cos_classic_defs(self):
|
|
|
c384d7 |
+ return CosClassicDefinitions(self._instance, self._dn).list()
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def get_cos_templates(self):
|
|
|
c384d7 |
+ return CosTemplates(self._instance, self._dn).list()
|
|
|
c384d7 |
+
|
|
|
c384d7 |
|
|
|
c384d7 |
class Backends(DSLdapObjects):
|
|
|
c384d7 |
"""DSLdapObjects that represents DN_LDBM base DN
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py
|
|
|
c384d7 |
index bbd160e8e..20b240df5 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/cli_base/dsrc.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/cli_base/dsrc.py
|
|
|
c384d7 |
@@ -41,12 +41,15 @@ def dsrc_arg_concat(args, dsrc_inst):
|
|
|
c384d7 |
'uri': args.instance,
|
|
|
c384d7 |
'basedn': args.basedn,
|
|
|
c384d7 |
'binddn': args.binddn,
|
|
|
c384d7 |
+ 'bindpw': None,
|
|
|
c384d7 |
'saslmech': None,
|
|
|
c384d7 |
'tls_cacertdir': None,
|
|
|
c384d7 |
'tls_cert': None,
|
|
|
c384d7 |
'tls_key': None,
|
|
|
c384d7 |
'tls_reqcert': ldap.OPT_X_TLS_HARD,
|
|
|
c384d7 |
'starttls': args.starttls,
|
|
|
c384d7 |
+ 'prompt': False,
|
|
|
c384d7 |
+ 'pwdfile': None,
|
|
|
c384d7 |
'args': {}
|
|
|
c384d7 |
}
|
|
|
c384d7 |
# Now gather the args
|
|
|
c384d7 |
@@ -137,7 +140,8 @@ def dsrc_to_ldap(path, instance_name, log):
|
|
|
c384d7 |
else:
|
|
|
c384d7 |
dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_HARD
|
|
|
c384d7 |
dsrc_inst['starttls'] = config.getboolean(instance_name, 'starttls', fallback=False)
|
|
|
c384d7 |
-
|
|
|
c384d7 |
+ dsrc_inst['pwdfile'] = None
|
|
|
c384d7 |
+ dsrc_inst['prompt'] = False
|
|
|
c384d7 |
# Now gather the args
|
|
|
c384d7 |
dsrc_inst['args'][SER_LDAP_URL] = dsrc_inst['uri']
|
|
|
c384d7 |
dsrc_inst['args'][SER_ROOT_DN] = dsrc_inst['binddn']
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/cli_conf/health.py b/src/lib389/lib389/cli_conf/health.py
|
|
|
c384d7 |
deleted file mode 100644
|
|
|
c384d7 |
index 040d85674..000000000
|
|
|
c384d7 |
--- a/src/lib389/lib389/cli_conf/health.py
|
|
|
c384d7 |
+++ /dev/null
|
|
|
c384d7 |
@@ -1,62 +0,0 @@
|
|
|
c384d7 |
-# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
c384d7 |
-# Copyright (C) 2016 Red Hat, Inc.
|
|
|
c384d7 |
-# All rights reserved.
|
|
|
c384d7 |
-#
|
|
|
c384d7 |
-# License: GPL (version 3 or any later version).
|
|
|
c384d7 |
-# See LICENSE for details.
|
|
|
c384d7 |
-# --- END COPYRIGHT BLOCK ---
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-from lib389.backend import Backend, Backends
|
|
|
c384d7 |
-from lib389.config import Encryption, Config
|
|
|
c384d7 |
-from lib389 import plugins
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-# These get all instances, then check them all.
|
|
|
c384d7 |
-CHECK_MANY_OBJECTS = [
|
|
|
c384d7 |
- Backends,
|
|
|
c384d7 |
-]
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-# These get single instances and check them.
|
|
|
c384d7 |
-CHECK_OBJECTS = [
|
|
|
c384d7 |
- Config,
|
|
|
c384d7 |
- Encryption,
|
|
|
c384d7 |
- plugins.ReferentialIntegrityPlugin
|
|
|
c384d7 |
-]
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-def _format_check_output(log, result):
|
|
|
c384d7 |
- log.info("==== DS Lint Error: %s ====" % result['dsle'])
|
|
|
c384d7 |
- log.info(" Severity: %s " % result['severity'])
|
|
|
c384d7 |
- log.info(" Affects:")
|
|
|
c384d7 |
- for item in result['items']:
|
|
|
c384d7 |
- log.info(" -- %s" % item)
|
|
|
c384d7 |
- log.info(" Details:")
|
|
|
c384d7 |
- log.info(result['detail'])
|
|
|
c384d7 |
- log.info(" Resolution:")
|
|
|
c384d7 |
- log.info(result['fix'])
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-def health_check_run(inst, basedn, log, args):
|
|
|
c384d7 |
- log.info("Beginning lint report, this could take a while ...")
|
|
|
c384d7 |
- report = []
|
|
|
c384d7 |
- for lo in CHECK_MANY_OBJECTS:
|
|
|
c384d7 |
- log.info("Checking %s ..." % lo.__name__)
|
|
|
c384d7 |
- lo_inst = lo(inst)
|
|
|
c384d7 |
- for clo in lo_inst.list():
|
|
|
c384d7 |
- result = clo.lint()
|
|
|
c384d7 |
- if result is not None:
|
|
|
c384d7 |
- report += result
|
|
|
c384d7 |
- for lo in CHECK_OBJECTS:
|
|
|
c384d7 |
- log.info("Checking %s ..." % lo.__name__)
|
|
|
c384d7 |
- lo_inst = lo(inst)
|
|
|
c384d7 |
- result = lo_inst.lint()
|
|
|
c384d7 |
- if result is not None:
|
|
|
c384d7 |
- report += result
|
|
|
c384d7 |
- log.info("Healthcheck complete!")
|
|
|
c384d7 |
- for item in report:
|
|
|
c384d7 |
- _format_check_output(log, item)
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-def create_parser(subparsers):
|
|
|
c384d7 |
- run_healthcheck_parser = subparsers.add_parser('healthcheck', help="Run a healthcheck report on your Directory Server instance. This is a safe, read only operation.")
|
|
|
c384d7 |
- run_healthcheck_parser.set_defaults(func=health_check_run)
|
|
|
c384d7 |
-
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/cli_ctl/health.py b/src/lib389/lib389/cli_ctl/health.py
|
|
|
c384d7 |
new file mode 100644
|
|
|
c384d7 |
index 000000000..d8f3d732b
|
|
|
c384d7 |
--- /dev/null
|
|
|
c384d7 |
+++ b/src/lib389/lib389/cli_ctl/health.py
|
|
|
c384d7 |
@@ -0,0 +1,123 @@
|
|
|
c384d7 |
+# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
c384d7 |
+# Copyright (C) 2016 Red Hat, Inc.
|
|
|
c384d7 |
+# All rights reserved.
|
|
|
c384d7 |
+#
|
|
|
c384d7 |
+# License: GPL (version 3 or any later version).
|
|
|
c384d7 |
+# See LICENSE for details.
|
|
|
c384d7 |
+# --- END COPYRIGHT BLOCK ---
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+import json
|
|
|
c384d7 |
+from getpass import getpass
|
|
|
c384d7 |
+from lib389.cli_base import connect_instance, disconnect_instance, format_error_to_dict
|
|
|
c384d7 |
+from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
|
|
|
c384d7 |
+from lib389.backend import Backend, Backends
|
|
|
c384d7 |
+from lib389.config import Encryption, Config
|
|
|
c384d7 |
+from lib389.monitor import MonitorDiskSpace
|
|
|
c384d7 |
+from lib389.replica import Replica, Changelog5
|
|
|
c384d7 |
+from lib389.nss_ssl import NssSsl
|
|
|
c384d7 |
+from lib389.dseldif import FSChecks
|
|
|
c384d7 |
+from lib389 import plugins
|
|
|
c384d7 |
+from lib389._constants import DSRC_HOME
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# These get all instances, then check them all.
|
|
|
c384d7 |
+CHECK_MANY_OBJECTS = [
|
|
|
c384d7 |
+ Backends,
|
|
|
c384d7 |
+]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# These get single instances and check them.
|
|
|
c384d7 |
+CHECK_OBJECTS = [
|
|
|
c384d7 |
+ Config,
|
|
|
c384d7 |
+ Encryption,
|
|
|
c384d7 |
+ FSChecks,
|
|
|
c384d7 |
+ plugins.ReferentialIntegrityPlugin,
|
|
|
c384d7 |
+ MonitorDiskSpace,
|
|
|
c384d7 |
+ Replica,
|
|
|
c384d7 |
+ Changelog5,
|
|
|
c384d7 |
+ NssSsl,
|
|
|
c384d7 |
+]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+def _format_check_output(log, result, idx):
|
|
|
c384d7 |
+ log.info("\n\n[{}] DS Lint Error: {}".format(idx, result['dsle']))
|
|
|
c384d7 |
+ log.info("-" * 80)
|
|
|
c384d7 |
+ log.info("Severity: %s " % result['severity'])
|
|
|
c384d7 |
+ log.info("Affects:")
|
|
|
c384d7 |
+ for item in result['items']:
|
|
|
c384d7 |
+ log.info(" -- %s" % item)
|
|
|
c384d7 |
+ log.info("\nDetails:")
|
|
|
c384d7 |
+ log.info('-----------')
|
|
|
c384d7 |
+ log.info(result['detail'])
|
|
|
c384d7 |
+ log.info("\nResolution:")
|
|
|
c384d7 |
+ log.info('-----------')
|
|
|
c384d7 |
+ log.info(result['fix'])
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+def health_check_run(inst, log, args):
|
|
|
c384d7 |
+ """Connect to the local server using LDAPI, and perform various health checks
|
|
|
c384d7 |
+ """
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # update the args for connect_instance()
|
|
|
c384d7 |
+ args.basedn = None
|
|
|
c384d7 |
+ args.binddn = None
|
|
|
c384d7 |
+ args.bindpw = None
|
|
|
c384d7 |
+ args.starttls = None
|
|
|
c384d7 |
+ args.pwdfile = None
|
|
|
c384d7 |
+ args.prompt = False
|
|
|
c384d7 |
+ dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))
|
|
|
c384d7 |
+ dsrc_inst = dsrc_arg_concat(args, dsrc_inst)
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
|
|
c384d7 |
+ except Exception as e:
|
|
|
c384d7 |
+ raise ValueError('Failed to connect to Directory Server instance: ' + str(e))
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("Beginning lint report, this could take a while ...")
|
|
|
c384d7 |
+ report = []
|
|
|
c384d7 |
+ for lo in CHECK_MANY_OBJECTS:
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("Checking %s ..." % lo.__name__)
|
|
|
c384d7 |
+ lo_inst = lo(inst)
|
|
|
c384d7 |
+ for clo in lo_inst.list():
|
|
|
c384d7 |
+ result = clo.lint()
|
|
|
c384d7 |
+ if result is not None:
|
|
|
c384d7 |
+ report += result
|
|
|
c384d7 |
+ for lo in CHECK_OBJECTS:
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("Checking %s ..." % lo.__name__)
|
|
|
c384d7 |
+ lo_inst = lo(inst)
|
|
|
c384d7 |
+ result = lo_inst.lint()
|
|
|
c384d7 |
+ if result is not None:
|
|
|
c384d7 |
+ report += result
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("Healthcheck complete.")
|
|
|
c384d7 |
+ count = len(report)
|
|
|
c384d7 |
+ if count == 0:
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("No issues found.")
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ log.info(json.dumps(report))
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ plural = ""
|
|
|
c384d7 |
+ if count > 1:
|
|
|
c384d7 |
+ plural = "s"
|
|
|
c384d7 |
+ if not args.json:
|
|
|
c384d7 |
+ log.info("{} Issue{} found! Generating report ...".format(count, plural))
|
|
|
c384d7 |
+ idx = 1
|
|
|
c384d7 |
+ for item in report:
|
|
|
c384d7 |
+ _format_check_output(log, item, idx)
|
|
|
c384d7 |
+ idx += 1
|
|
|
c384d7 |
+ log.info('\n\n===== End Of Report ({} Issue{} found) ====='.format(count, plural))
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ log.info(json.dumps(report))
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ disconnect_instance(inst)
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+def create_parser(subparsers):
|
|
|
c384d7 |
+ run_healthcheck_parser = subparsers.add_parser('healthcheck', help=
|
|
|
c384d7 |
+ "Run a healthcheck report on a local Directory Server instance. This "
|
|
|
c384d7 |
+ "is a safe and read-only operation. Do not attempt to run this on a "
|
|
|
c384d7 |
+ "remote Directory Server as this tool needs access to local resources, "
|
|
|
c384d7 |
+ "otherwise the report may be inaccurate.")
|
|
|
c384d7 |
+ run_healthcheck_parser.set_defaults(func=health_check_run)
|
|
|
c384d7 |
+
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py
|
|
|
c384d7 |
index db5359a68..f71baf2d8 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/config.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/config.py
|
|
|
c384d7 |
@@ -16,6 +16,7 @@
|
|
|
c384d7 |
DirSrv.backend.methodName()
|
|
|
c384d7 |
"""
|
|
|
c384d7 |
|
|
|
c384d7 |
+import copy
|
|
|
c384d7 |
import ldap
|
|
|
c384d7 |
from lib389._constants import *
|
|
|
c384d7 |
from lib389 import Entry
|
|
|
c384d7 |
@@ -199,17 +200,18 @@ class Config(DSLdapObject):
|
|
|
c384d7 |
def _lint_hr_timestamp(self):
|
|
|
c384d7 |
hr_timestamp = self.get_attr_val('nsslapd-logging-hr-timestamps-enabled')
|
|
|
c384d7 |
if ensure_bytes('on') != hr_timestamp:
|
|
|
c384d7 |
- return DSCLE0001
|
|
|
c384d7 |
- pass # nsslapd-logging-hr-timestamps-enabled
|
|
|
c384d7 |
+ report = copy.deepcopy(DSCLE0001)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
def _lint_passwordscheme(self):
|
|
|
c384d7 |
allowed_schemes = ['SSHA512', 'PBKDF2_SHA256']
|
|
|
c384d7 |
u_password_scheme = self.get_attr_val_utf8('passwordStorageScheme')
|
|
|
c384d7 |
u_root_scheme = self.get_attr_val_utf8('nsslapd-rootpwstoragescheme')
|
|
|
c384d7 |
if u_root_scheme not in allowed_schemes or u_password_scheme not in allowed_schemes:
|
|
|
c384d7 |
- return DSCLE0002
|
|
|
c384d7 |
- return None
|
|
|
c384d7 |
-
|
|
|
c384d7 |
+ report = copy.deepcopy(DSCLE0002)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
class Encryption(DSLdapObject):
|
|
|
c384d7 |
"""
|
|
|
c384d7 |
@@ -237,8 +239,10 @@ class Encryption(DSLdapObject):
|
|
|
c384d7 |
def _lint_check_tls_version(self):
|
|
|
c384d7 |
tls_min = self.get_attr_val('sslVersionMin')
|
|
|
c384d7 |
if tls_min < ensure_bytes('TLS1.1'):
|
|
|
c384d7 |
- return DSELE0001
|
|
|
c384d7 |
- return None
|
|
|
c384d7 |
+ report = copy.deepcopy(DSELE0001)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ yield None
|
|
|
c384d7 |
|
|
|
c384d7 |
@property
|
|
|
c384d7 |
def ciphers(self):
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
|
|
|
c384d7 |
index dfe3b91e2..4155abcdd 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/dseldif.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/dseldif.py
|
|
|
c384d7 |
@@ -1,14 +1,17 @@
|
|
|
c384d7 |
# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
c384d7 |
-# Copyright (C) 2017 Red Hat, Inc.
|
|
|
c384d7 |
+# Copyright (C) 2019 Red Hat, Inc.
|
|
|
c384d7 |
# All rights reserved.
|
|
|
c384d7 |
#
|
|
|
c384d7 |
# License: GPL (version 3 or any later version).
|
|
|
c384d7 |
# See LICENSE for details.
|
|
|
c384d7 |
# --- END COPYRIGHT BLOCK ---
|
|
|
c384d7 |
#
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+import copy
|
|
|
c384d7 |
import os
|
|
|
c384d7 |
+from stat import ST_MODE
|
|
|
c384d7 |
from lib389.paths import Paths
|
|
|
c384d7 |
-
|
|
|
c384d7 |
+from lib389.lint import DSPERMLE0001, DSPERMLE0002
|
|
|
c384d7 |
|
|
|
c384d7 |
class DSEldif(object):
|
|
|
c384d7 |
"""A class for working with dse.ldif file
|
|
|
c384d7 |
@@ -155,3 +158,39 @@ class DSEldif(object):
|
|
|
c384d7 |
self._instance.log.debug("During replace operation: {}".format(e))
|
|
|
c384d7 |
self.add(entry_dn, attr, value)
|
|
|
c384d7 |
self._update()
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+class FSChecks(object):
|
|
|
c384d7 |
+ """This is for the healthcheck feature, check commonly used system config files the
|
|
|
c384d7 |
+ server uses. This is here for lack of a better place to add this class.
|
|
|
c384d7 |
+ """
|
|
|
c384d7 |
+ def __init__(self, dirsrv=None):
|
|
|
c384d7 |
+ self.dirsrv = dirsrv
|
|
|
c384d7 |
+ self._certdb = self.dirsrv.get_cert_dir()
|
|
|
c384d7 |
+ self.ds_files = [
|
|
|
c384d7 |
+ ('/etc/resolv.conf', '644', DSPERMLE0001),
|
|
|
c384d7 |
+ (self._certdb + "/pin.txt", '600', DSPERMLE0002),
|
|
|
c384d7 |
+ (self._certdb + "/pwdfile.txt", '600', DSPERMLE0002),
|
|
|
c384d7 |
+ ]
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_file_perms]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def lint(self):
|
|
|
c384d7 |
+ results = []
|
|
|
c384d7 |
+ for fn in self._lint_functions:
|
|
|
c384d7 |
+ for result in fn():
|
|
|
c384d7 |
+ if result is not None:
|
|
|
c384d7 |
+ results.append(result)
|
|
|
c384d7 |
+ return results
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_file_perms(self):
|
|
|
c384d7 |
+ # Check file permissions are correct
|
|
|
c384d7 |
+ for ds_file in self.ds_files:
|
|
|
c384d7 |
+ perms = str(oct(os.stat(ds_file[0])[ST_MODE])[-3:])
|
|
|
c384d7 |
+ if perms != ds_file[1]:
|
|
|
c384d7 |
+ report = copy.deepcopy(ds_file[2])
|
|
|
c384d7 |
+ report['items'].append(ds_file[0])
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('FILE', ds_file[0])
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('PERMS', ds_file[1])
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('FILE', ds_file[0])
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('PERMS', ds_file[1])
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
|
|
c384d7 |
index 8c4b4dedc..515711136 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/lint.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/lint.py
|
|
|
c384d7 |
@@ -1,5 +1,5 @@
|
|
|
c384d7 |
# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
c384d7 |
-# Copyright (C) 2017 Red Hat, Inc.
|
|
|
c384d7 |
+# Copyright (C) 2019 Red Hat, Inc.
|
|
|
c384d7 |
# All rights reserved.
|
|
|
c384d7 |
#
|
|
|
c384d7 |
# License: GPL (version 3 or any later version).
|
|
|
c384d7 |
@@ -10,12 +10,12 @@
|
|
|
c384d7 |
# as well as some functions to help process them.
|
|
|
c384d7 |
|
|
|
c384d7 |
|
|
|
c384d7 |
+# Database checks
|
|
|
c384d7 |
DSBLE0001 = {
|
|
|
c384d7 |
'dsle': 'DSBLE0001',
|
|
|
c384d7 |
'severity': 'MEDIUM',
|
|
|
c384d7 |
'items' : [],
|
|
|
c384d7 |
- 'detail' : """
|
|
|
c384d7 |
-This backend may be missing the correct mapping tree references. Mapping Trees allow
|
|
|
c384d7 |
+ 'detail' : """This backend may be missing the correct mapping tree references. Mapping Trees allow
|
|
|
c384d7 |
the directory server to determine which backend an operation is routed to in the
|
|
|
c384d7 |
abscence of other information. This is extremely important for correct functioning
|
|
|
c384d7 |
of LDAP ADD for example.
|
|
|
c384d7 |
@@ -31,20 +31,35 @@ objectClass: top
|
|
|
c384d7 |
objectClass: extensibleObject
|
|
|
c384d7 |
objectClass: nsMappingTree
|
|
|
c384d7 |
|
|
|
c384d7 |
- """,
|
|
|
c384d7 |
- 'fix' : """
|
|
|
c384d7 |
-Either you need to create the mapping tree, or you need to repair the related
|
|
|
c384d7 |
+""",
|
|
|
c384d7 |
+ 'fix' : """Either you need to create the mapping tree, or you need to repair the related
|
|
|
c384d7 |
mapping tree. You will need to do this by hand by editing cn=config, or stopping
|
|
|
c384d7 |
the instance and editing dse.ldif.
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
+"""
|
|
|
c384d7 |
}
|
|
|
c384d7 |
|
|
|
c384d7 |
+DSBLE0002 = {
|
|
|
c384d7 |
+ 'dsle': 'DSBLE0002',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : [],
|
|
|
c384d7 |
+ 'detail' : """Unable to querying the backend. LDAP error (ERROR)""",
|
|
|
c384d7 |
+ 'fix' : """Check the server's error and access logs for more information."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+DSBLE0003 = {
|
|
|
c384d7 |
+ 'dsle': 'DSBLE0002',
|
|
|
c384d7 |
+ 'severity': 'LOW',
|
|
|
c384d7 |
+ 'items' : [],
|
|
|
c384d7 |
+ 'detail' : """The backend database has not been initialized yet""",
|
|
|
c384d7 |
+ 'fix' : """You need to import an LDIF file, or create the suffix entry, in order to initialize the database."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Config checks
|
|
|
c384d7 |
DSCLE0001 = {
|
|
|
c384d7 |
'dsle' : 'DSCLE0001',
|
|
|
c384d7 |
'severity' : 'LOW',
|
|
|
c384d7 |
'items': ['cn=config', ],
|
|
|
c384d7 |
- 'detail' : """
|
|
|
c384d7 |
-nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
|
|
|
c384d7 |
+ 'detail' : """nsslapd-logging-hr-timestamps-enabled changes the log format in directory server from
|
|
|
c384d7 |
|
|
|
c384d7 |
[07/Jun/2017:17:15:58 +1000]
|
|
|
c384d7 |
|
|
|
c384d7 |
@@ -54,18 +69,18 @@ to
|
|
|
c384d7 |
|
|
|
c384d7 |
This actually provides a performance improvement. Additionally, this setting will be
|
|
|
c384d7 |
removed in a future release.
|
|
|
c384d7 |
- """,
|
|
|
c384d7 |
- 'fix' : """
|
|
|
c384d7 |
-Set nsslapd-logging-hr-timestamps-enabled to on.
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
+""",
|
|
|
c384d7 |
+ 'fix' : """Set nsslapd-logging-hr-timestamps-enabled to on.
|
|
|
c384d7 |
+You can use 'dsconf' to set this attribute. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE config replace nsslapd-logging-hr-timestamps-enabled=on"""
|
|
|
c384d7 |
}
|
|
|
c384d7 |
|
|
|
c384d7 |
DSCLE0002 = {
|
|
|
c384d7 |
'dsle': 'DSCLE0002',
|
|
|
c384d7 |
'severity': 'HIGH',
|
|
|
c384d7 |
'items' : ['cn=config', ],
|
|
|
c384d7 |
- 'detail' : """
|
|
|
c384d7 |
-Password storage schemes in Directory Server define how passwords are hashed via a
|
|
|
c384d7 |
+ 'detail' : """Password storage schemes in Directory Server define how passwords are hashed via a
|
|
|
c384d7 |
one-way mathematical function for storage. Knowing the hash it is difficult to gain
|
|
|
c384d7 |
the input, but knowing the input you can easily compare the hash.
|
|
|
c384d7 |
|
|
|
c384d7 |
@@ -79,53 +94,253 @@ for "legacy" support (SSHA512).
|
|
|
c384d7 |
|
|
|
c384d7 |
Your configuration does not use these for password storage or the root password storage
|
|
|
c384d7 |
scheme.
|
|
|
c384d7 |
- """,
|
|
|
c384d7 |
- 'fix': """
|
|
|
c384d7 |
-Perform a configuration reset of the values:
|
|
|
c384d7 |
+""",
|
|
|
c384d7 |
+ 'fix': """Perform a configuration reset of the values:
|
|
|
c384d7 |
|
|
|
c384d7 |
passwordStorageScheme
|
|
|
c384d7 |
nsslapd-rootpwstoragescheme
|
|
|
c384d7 |
|
|
|
c384d7 |
IE, stop Directory Server, and in dse.ldif delete these two lines. When Directory Server
|
|
|
c384d7 |
is started, they will use the server provided defaults that are secure.
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You can also use 'dsconf' to replace these values. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE config replace passwordStorageScheme=PBKDF2_SHA256 nsslapd-rootpwstoragescheme=PBKDF2_SHA256"""
|
|
|
c384d7 |
}
|
|
|
c384d7 |
|
|
|
c384d7 |
+# Security checks
|
|
|
c384d7 |
DSELE0001 = {
|
|
|
c384d7 |
'dsle': 'DSELE0001',
|
|
|
c384d7 |
'severity': 'MEDIUM',
|
|
|
c384d7 |
'items' : ['cn=encryption,cn=config', ],
|
|
|
c384d7 |
- 'detail': """
|
|
|
c384d7 |
-This Directory Server may not be using strong TLS protocol versions. TLS1.0 is known to
|
|
|
c384d7 |
+ 'detail': """This Directory Server may not be using strong TLS protocol versions. TLS1.0 is known to
|
|
|
c384d7 |
have a number of issues with the protocol. Please see:
|
|
|
c384d7 |
|
|
|
c384d7 |
https://tools.ietf.org/html/rfc7457
|
|
|
c384d7 |
|
|
|
c384d7 |
-It is advised you set this value to the maximum possible.
|
|
|
c384d7 |
- """,
|
|
|
c384d7 |
- 'fix' : """
|
|
|
c384d7 |
-set cn=encryption,cn=config sslVersionMin to a version greater than TLS1.0
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
+It is advised you set this value to the maximum possible.""",
|
|
|
c384d7 |
+ 'fix' : """There are two options for setting the TLS minimum version allowed. You,
|
|
|
c384d7 |
+can set "sslVersionMin" in "cn=encryption,cn=config" to a version greater than "TLS1.0"
|
|
|
c384d7 |
+You can also use 'dsconf' to set this value. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE security set --tls-protocol-min=TLS1.2
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You must restart the Directory Server for this change to take effect.
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+Or, you can set the system wide crypto policy to FUTURE which will use a higher TLS
|
|
|
c384d7 |
+minimum version, but doing this affects the entire system:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # update-crypto-policies --set FUTURE"""
|
|
|
c384d7 |
}
|
|
|
c384d7 |
|
|
|
c384d7 |
+# RI plugin checks
|
|
|
c384d7 |
DSRILE0001 = {
|
|
|
c384d7 |
'dsle': 'DSRLE0001',
|
|
|
c384d7 |
'severity': 'LOW',
|
|
|
c384d7 |
'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config', ],
|
|
|
c384d7 |
- 'detail': """
|
|
|
c384d7 |
-The referential integrity plugin has an asynchronous processing mode. This is controlled by the update-delay flag.
|
|
|
c384d7 |
-
|
|
|
c384d7 |
-When this value is 0, referential integrity plugin processes these changes inside of the operation that modified the entry - ie these are synchronous.
|
|
|
c384d7 |
+ 'detail': """The referential integrity plugin has an asynchronous processing mode.
|
|
|
c384d7 |
+This is controlled by the update-delay flag. When this value is 0, referential
|
|
|
c384d7 |
+integrity plugin processes these changes inside of the operation that modified
|
|
|
c384d7 |
+the entry - ie these are synchronous.
|
|
|
c384d7 |
|
|
|
c384d7 |
However, when this is > 0, these are performed asynchronously.
|
|
|
c384d7 |
|
|
|
c384d7 |
-This leads to only having refint enabled on one master in MMR to prevent replication conflicts and loops.
|
|
|
c384d7 |
+This leads to only having referint enabled on one master in MMR to prevent replication conflicts and loops.
|
|
|
c384d7 |
Additionally, because these are performed in the background these updates may cause spurious update
|
|
|
c384d7 |
delays to your server by batching changes rather than smaller updates during sync processing.
|
|
|
c384d7 |
|
|
|
c384d7 |
-We advise that you set this value to 0, and enable refint on all masters as it provides a more predictable behaviour.
|
|
|
c384d7 |
- """,
|
|
|
c384d7 |
- 'fix' : """
|
|
|
c384d7 |
-Set referint-update-delay to 0.
|
|
|
c384d7 |
- """
|
|
|
c384d7 |
+We advise that you set this value to 0, and enable referint on all masters as it provides a more predictable behaviour.
|
|
|
c384d7 |
+""",
|
|
|
c384d7 |
+ 'fix' : """Set referint-update-delay to 0.
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You can use 'dsconf' to set this value. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE plugin referential-integrity set --update-delay 0
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You must restart the Directory Server for this change to take effect."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Note - ATTR and BACKEND are replaced by the reporting function
|
|
|
c384d7 |
+DSRILE0002 = {
|
|
|
c384d7 |
+ 'dsle': 'DSRLE0002',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['cn=referential integrity postoperation,cn=plugins,cn=config'],
|
|
|
c384d7 |
+ 'detail': """The referential integrity plugin is configured to use an attribute (ATTR)
|
|
|
c384d7 |
+that does not have an "equality" index in backend (BACKEND).
|
|
|
c384d7 |
+Failure to have the proper indexing will lead to unindexed searches which
|
|
|
c384d7 |
+cause high CPU and can significantly slow the server down.""",
|
|
|
c384d7 |
+ 'fix' : """Check the attributes set in "referint-membership-attr" to make sure they have
|
|
|
c384d7 |
+an index defined that has at least the equality "eq" index type. You will
|
|
|
c384d7 |
+need to reindex the database after adding the missing index type. Here is an
|
|
|
c384d7 |
+example using dsconf:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE backend index --attr=ATTR --reindex --index-type=eq BACKEND
|
|
|
c384d7 |
+"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Disk Space check. Note - PARTITION is replaced by the calling function
|
|
|
c384d7 |
+DSDSLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSDSLE0001',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['Server', 'cn=config'],
|
|
|
c384d7 |
+ 'detail': """The disk partition used by the server (PARTITION), either for the database, the
|
|
|
c384d7 |
+configuration files, or the logs is over 90% full. If the partition becomes
|
|
|
c384d7 |
+completely filled serious problems can occur with the database or the server's
|
|
|
c384d7 |
+stability.""",
|
|
|
c384d7 |
+ 'fix' : """Attempt to free up disk space. Also try removing old rotated logs, or disable any
|
|
|
c384d7 |
+verbose logging levels that might have been set. You might consider enabling
|
|
|
c384d7 |
+the "Disk Monitoring" feature in cn=config to help prevent a disorderly shutdown
|
|
|
c384d7 |
+of the server:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ nsslapd-disk-monitoring: on
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You can use 'dsconf' to set this value. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE config replace nsslapd-disk-monitoring=on
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+You must restart the Directory Server for this change to take effect.
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+Please see the Administration guide for more information:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ https://access.redhat.com/documentation/en-us/red_hat_directory_server/10/html/administration_guide/diskmonitoring
|
|
|
c384d7 |
+"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Replication check. Note - AGMT and SUFFIX are replaced by the reporting function
|
|
|
c384d7 |
+DSREPLLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSREPLLE0001',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Agreement'],
|
|
|
c384d7 |
+ 'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.""",
|
|
|
c384d7 |
+ 'fix' : """You may need to reinitialize this replication agreement. Please check the errors
|
|
|
c384d7 |
+log for more information. If you do need to reinitialize the agreement you can do so
|
|
|
c384d7 |
+using dsconf. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE repl-agmt init "AGMT" --suffix SUFFIX"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Note - SUFFIX and COUNT will be replaced by the calling function
|
|
|
c384d7 |
+DSREPLLE0002 = {
|
|
|
c384d7 |
+ 'dsle': 'DSREPLLE0002',
|
|
|
c384d7 |
+ 'severity': 'LOW',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Conflict Entries'],
|
|
|
c384d7 |
+ 'detail': """There were COUNT conflict entries found under the replication suffix "SUFFIX".
|
|
|
c384d7 |
+Status message: MSG""",
|
|
|
c384d7 |
+ 'fix' : """While conflict entries are expected to occur in an MMR environment, they
|
|
|
c384d7 |
+should be resolved. In regards to conflict entries there is always the original/counterpart
|
|
|
c384d7 |
+entry that has a normal DN, and then the conflict version of that entry. Technically both
|
|
|
c384d7 |
+entries are valid, you as the administrator, needs to decide which entry you want to keep.
|
|
|
c384d7 |
+First examine/compare both entries to determine which one you want to keep or remove. You
|
|
|
c384d7 |
+can use the CLI tool "dsconf" to resolve the conflict. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ List the conflict entries:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE repl-conflict list dc=example,dc=com
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ Examine conflict entry and its counterpart entry:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE repl-conflict compare <DN of conflict entry>
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ Remove conflict entry and keep only the original/counterpart entry:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE repl-conflict remove <DN of conflict entry>
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ Replace the original/counterpart entry with the conflict entry:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE repl-conflict swap <DN of conflict entry>
|
|
|
c384d7 |
+"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+DSREPLLE0003 = {
|
|
|
c384d7 |
+ 'dsle': 'DSREPLLE0003',
|
|
|
c384d7 |
+ 'severity': 'MEDIUM',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Agreement'],
|
|
|
c384d7 |
+ 'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization.
|
|
|
c384d7 |
+Status message: MSG""",
|
|
|
c384d7 |
+ 'fix' : """Replication is not in synchronization but it may recover. Continue to
|
|
|
c384d7 |
+monitor this agreement."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+DSREPLLE0004 = {
|
|
|
c384d7 |
+ 'dsle': 'DSREPLLE0004',
|
|
|
c384d7 |
+ 'severity': 'MEDIUM',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Agreement'],
|
|
|
c384d7 |
+ 'detail': """Failed to get the agreement status for agreement (AGMT) under "SUFFIX". Error (ERROR).""",
|
|
|
c384d7 |
+ 'fix' : """None"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+DSREPLLE0005 = {
|
|
|
c384d7 |
+ 'dsle': 'DSREPLLE0005',
|
|
|
c384d7 |
+ 'severity': 'MEDIUM',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Agreement'],
|
|
|
c384d7 |
+ 'detail': """The replication agreement (AGMT) under "SUFFIX" is not in synchronization,
|
|
|
c384d7 |
+because the consumer server is not reachable.""",
|
|
|
c384d7 |
+ 'fix' : """Check if the consumer is running, and also check the errors log for more information."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Replication changelog
|
|
|
c384d7 |
+DSCLLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSCLLE0001',
|
|
|
c384d7 |
+ 'severity': 'LOW',
|
|
|
c384d7 |
+ 'items' : ['Replication', 'Changelog'],
|
|
|
c384d7 |
+ 'detail': """The replication changelog does have any kind of trimming configured. This will
|
|
|
c384d7 |
+lead to the changelog size growing indefinitely.""",
|
|
|
c384d7 |
+ 'fix' : """Configure changelog trimming, preferably by setting the maximum age of a changelog
|
|
|
c384d7 |
+record. Here is an example:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE replication set-changelog --max-age 30d"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Certificate checks
|
|
|
c384d7 |
+DSCERTLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSCERTLE0001',
|
|
|
c384d7 |
+ 'severity': 'MEDIUM',
|
|
|
c384d7 |
+ 'items' : ['Expiring Certificate'],
|
|
|
c384d7 |
+ 'detail': """The certificate (CERT) will expire in less than 30 days""",
|
|
|
c384d7 |
+ 'fix' : """Renew the certificate before it expires to prevent disruptions with TLS connections."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+DSCERTLE0002 = {
|
|
|
c384d7 |
+ 'dsle': 'DSCERTLE0002',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['Expired Certificate'],
|
|
|
c384d7 |
+ 'detail': """The certificate (CERT) has expired""",
|
|
|
c384d7 |
+ 'fix' : """Renew or remove the certificate."""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# Virtual Attrs & COS. Note - ATTR and SUFFIX are replaced by the reporting function
|
|
|
c384d7 |
+DSVIRTLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSVIRTLE0001',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['Virtual Attributes'],
|
|
|
c384d7 |
+ 'detail': """You should not index virtual attributes, and as this will break searches that
|
|
|
c384d7 |
+use the attribute in a filter.""",
|
|
|
c384d7 |
+ 'fix' : """Remove the index for this attribute from the backend configuration.
|
|
|
c384d7 |
+Here is an example using 'dsconf' to remove an index:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # dsconf slapd-YOUR_INSTANCE backend index delete --attr ATTR SUFFIX"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# File permissions (resolv.conf
|
|
|
c384d7 |
+DSPERMLE0001 = {
|
|
|
c384d7 |
+ 'dsle': 'DSPERMLE0001',
|
|
|
c384d7 |
+ 'severity': 'MEDIUM',
|
|
|
c384d7 |
+ 'items' : ['File Permissions'],
|
|
|
c384d7 |
+ 'detail': """The file "FILE" does not have the expected permissions (PERMS). This
|
|
|
c384d7 |
+can cause issues with replication and chaining.""",
|
|
|
c384d7 |
+ 'fix' : """Change the file permissions:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # chmod PERMS FILE"""
|
|
|
c384d7 |
+}
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+# TLS db password/pin files
|
|
|
c384d7 |
+DSPERMLE0002 = {
|
|
|
c384d7 |
+ 'dsle': 'DSPERMLE0002',
|
|
|
c384d7 |
+ 'severity': 'HIGH',
|
|
|
c384d7 |
+ 'items' : ['File Permissions'],
|
|
|
c384d7 |
+ 'detail': """The file "FILE" does not have the expected permissions (PERMS). The
|
|
|
c384d7 |
+security database pin/password files should only be readable by Directory Server user.""",
|
|
|
c384d7 |
+ 'fix' : """Change the file permissions:
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ # chmod PERMS FILE"""
|
|
|
c384d7 |
}
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
|
|
|
c384d7 |
index 5ca967c64..290cad5e2 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/monitor.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/monitor.py
|
|
|
c384d7 |
@@ -9,6 +9,7 @@
|
|
|
c384d7 |
from lib389._constants import *
|
|
|
c384d7 |
from lib389._mapped_object import DSLdapObject
|
|
|
c384d7 |
from lib389.utils import (ds_is_older)
|
|
|
c384d7 |
+from lib389.lint import DSDSLE0001
|
|
|
c384d7 |
|
|
|
c384d7 |
|
|
|
c384d7 |
class Monitor(DSLdapObject):
|
|
|
c384d7 |
@@ -254,6 +255,19 @@ class MonitorDiskSpace(DSLdapObject):
|
|
|
c384d7 |
def __init__(self, instance, dn=None):
|
|
|
c384d7 |
super(MonitorDiskSpace, self).__init__(instance=instance, dn=dn)
|
|
|
c384d7 |
self._dn = "cn=disk space,cn=monitor"
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_disk_space]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_disk_space(self):
|
|
|
c384d7 |
+ partitions = self.get_attr_vals_utf8_l("dsDisk")
|
|
|
c384d7 |
+ for partition in partitions:
|
|
|
c384d7 |
+ parts = partition.split()
|
|
|
c384d7 |
+ percent = parts[4].split('=')[1].strip('"')
|
|
|
c384d7 |
+ if int(percent) >= 90:
|
|
|
c384d7 |
+ # this partition is over 90% full, not good
|
|
|
c384d7 |
+ report = copy.deepcopy(DSDSLE0001)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('PARTITION', parts[0].split('=')[1].strip('"'))
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
def get_disks(self):
|
|
|
c384d7 |
"""Get an information about partitions which contains a Directory Server data"""
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py
|
|
|
c384d7 |
index afe921385..2a7d1637c 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/nss_ssl.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/nss_ssl.py
|
|
|
c384d7 |
@@ -9,6 +9,7 @@
|
|
|
c384d7 |
"""Helpers for managing NSS databases in Directory Server
|
|
|
c384d7 |
"""
|
|
|
c384d7 |
|
|
|
c384d7 |
+import copy
|
|
|
c384d7 |
import os
|
|
|
c384d7 |
import re
|
|
|
c384d7 |
import socket
|
|
|
c384d7 |
@@ -17,10 +18,10 @@ import shutil
|
|
|
c384d7 |
import logging
|
|
|
c384d7 |
# from nss import nss
|
|
|
c384d7 |
import subprocess
|
|
|
c384d7 |
-from datetime import datetime, timedelta
|
|
|
c384d7 |
+from datetime import datetime, timedelta, date
|
|
|
c384d7 |
from subprocess import check_output
|
|
|
c384d7 |
from lib389.passwd import password_generate
|
|
|
c384d7 |
-
|
|
|
c384d7 |
+from lib389.lint import DSCERTLE0001, DSCERTLE0002
|
|
|
c384d7 |
from lib389.utils import ensure_str, format_cmd_list
|
|
|
c384d7 |
import uuid
|
|
|
c384d7 |
|
|
|
c384d7 |
@@ -58,6 +59,36 @@ class NssSsl(object):
|
|
|
c384d7 |
self.db_files = {"dbm_backend": ["%s/%s" % (self._certdb, f) for f in ("key3.db", "cert8.db", "secmod.db")],
|
|
|
c384d7 |
"sql_backend": ["%s/%s" % (self._certdb, f) for f in ("key4.db", "cert9.db", "pkcs11.txt")],
|
|
|
c384d7 |
"support": ["%s/%s" % (self._certdb, f) for f in ("noise.txt", PIN_TXT, PWD_TXT)]}
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_certificate_expiration,]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def lint(self):
|
|
|
c384d7 |
+ results = []
|
|
|
c384d7 |
+ for fn in self._lint_functions:
|
|
|
c384d7 |
+ for result in fn():
|
|
|
c384d7 |
+ if result is not None:
|
|
|
c384d7 |
+ results.append(result)
|
|
|
c384d7 |
+ return results
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_certificate_expiration(self):
|
|
|
c384d7 |
+ """Check all the certificates in the db if they will expire within 30 days
|
|
|
c384d7 |
+ or have already expired.
|
|
|
c384d7 |
+ """
|
|
|
c384d7 |
+ cert_list = []
|
|
|
c384d7 |
+ all_certs = self._rsa_cert_list()
|
|
|
c384d7 |
+ for cert in all_certs:
|
|
|
c384d7 |
+ cert_list.append(self.get_cert_details(cert[0]))
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ for cert in cert_list:
|
|
|
c384d7 |
+ if date.fromisoformat(cert[3].split()[0]) - date.today() < timedelta(days=0):
|
|
|
c384d7 |
+ # Expired
|
|
|
c384d7 |
+ report = copy.deepcopy(DSCERTLE0002)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('CERT', cert[0])
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ elif date.fromisoformat(cert[3].split()[0]) - date.today() < timedelta(days=30):
|
|
|
c384d7 |
+ # Expiring
|
|
|
c384d7 |
+ report = copy.deepcopy(DSCERTLE0001)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('CERT', cert[0])
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
def detect_alt_names(self, alt_names=[]):
|
|
|
c384d7 |
"""Attempt to determine appropriate subject alternate names for a host.
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
|
|
c384d7 |
index a8b8985fc..97c5d1d3b 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/plugins.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/plugins.py
|
|
|
c384d7 |
@@ -10,10 +10,9 @@ import collections
|
|
|
c384d7 |
import ldap
|
|
|
c384d7 |
import copy
|
|
|
c384d7 |
import os.path
|
|
|
c384d7 |
-
|
|
|
c384d7 |
from lib389 import tasks
|
|
|
c384d7 |
from lib389._mapped_object import DSLdapObjects, DSLdapObject
|
|
|
c384d7 |
-from lib389.lint import DSRILE0001
|
|
|
c384d7 |
+from lib389.lint import DSRILE0001, DSRILE0002
|
|
|
c384d7 |
from lib389.utils import ensure_str, ensure_list_bytes
|
|
|
c384d7 |
from lib389.schema import Schema
|
|
|
c384d7 |
from lib389._constants import DN_PLUGIN
|
|
|
c384d7 |
@@ -432,7 +431,7 @@ class ReferentialIntegrityPlugin(Plugin):
|
|
|
c384d7 |
'referint-logfile',
|
|
|
c384d7 |
'referint-membership-attr',
|
|
|
c384d7 |
])
|
|
|
c384d7 |
- self._lint_functions = [self._lint_update_delay]
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_update_delay, self._lint_attr_indexes]
|
|
|
c384d7 |
|
|
|
c384d7 |
def create(self, rdn=None, properties=None, basedn=None):
|
|
|
c384d7 |
"""Create an instance of the plugin"""
|
|
|
c384d7 |
@@ -448,7 +447,46 @@ class ReferentialIntegrityPlugin(Plugin):
|
|
|
c384d7 |
if self.status():
|
|
|
c384d7 |
delay = self.get_attr_val_int("referint-update-delay")
|
|
|
c384d7 |
if delay is not None and delay != 0:
|
|
|
c384d7 |
- return DSRILE0001
|
|
|
c384d7 |
+ report = copy.deepcopy(DSRILE0001)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_attr_indexes(self):
|
|
|
c384d7 |
+ if self.status():
|
|
|
c384d7 |
+ from lib389.backend import Backends
|
|
|
c384d7 |
+ backends = Backends(self._instance).list()
|
|
|
c384d7 |
+ for backend in backends:
|
|
|
c384d7 |
+ indexes = backend.get_indexes()
|
|
|
c384d7 |
+ suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
|
|
|
c384d7 |
+ attrs = self.get_attr_vals_utf8_l("referint-membership-attr")
|
|
|
c384d7 |
+ for attr in attrs:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSRILE0002)
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ index = indexes.get(attr)
|
|
|
c384d7 |
+ types = index.get_attr_vals_utf8_l("nsIndexType")
|
|
|
c384d7 |
+ valid = False
|
|
|
c384d7 |
+ if "eq" in types:
|
|
|
c384d7 |
+ valid = True
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ if not valid:
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('BACKEND', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('BACKEND', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ report['items'].append(suffix)
|
|
|
c384d7 |
+ report['items'].append(attr)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ except:
|
|
|
c384d7 |
+ # No index at all, bad
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('BACKEND', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('ATTR', attr)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('BACKEND', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ report['items'].append(suffix)
|
|
|
c384d7 |
+ report['items'].append(attr)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
def get_update_delay(self):
|
|
|
c384d7 |
"""Get referint-update-delay attribute"""
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/properties.py b/src/lib389/lib389/properties.py
|
|
|
c384d7 |
index d18249d20..9d7ce4161 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/properties.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/properties.py
|
|
|
c384d7 |
@@ -319,6 +319,7 @@ AGMT_UPDATE_START = 'nsds5replicaLastUpdateStart'
|
|
|
c384d7 |
AGMT_UPDATE_END = 'nsds5replicaLastUpdateEnd'
|
|
|
c384d7 |
AGMT_CHANGES_SINCE_STARTUP = 'nsds5replicaChangesSentSinceStartup' # base64
|
|
|
c384d7 |
AGMT_UPDATE_STATUS = 'nsds5replicaLastUpdateStatus'
|
|
|
c384d7 |
+AGMT_UPDATE_STATUS_JSON = 'nsds5replicaLastUpdateStatusJSON'
|
|
|
c384d7 |
AGMT_UPDATE_IN_PROGRESS = 'nsds5replicaUpdateInProgress'
|
|
|
c384d7 |
AGMT_INIT_START = 'nsds5replicaLastInitStart'
|
|
|
c384d7 |
AGMT_INIT_END = 'nsds5replicaLastInitEnd'
|
|
|
c384d7 |
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
|
|
c384d7 |
index 7145e86f9..9b84d8f7e 100644
|
|
|
c384d7 |
--- a/src/lib389/lib389/replica.py
|
|
|
c384d7 |
+++ b/src/lib389/lib389/replica.py
|
|
|
c384d7 |
@@ -15,6 +15,7 @@ import datetime
|
|
|
c384d7 |
import logging
|
|
|
c384d7 |
import uuid
|
|
|
c384d7 |
import json
|
|
|
c384d7 |
+import copy
|
|
|
c384d7 |
from operator import itemgetter
|
|
|
c384d7 |
from itertools import permutations
|
|
|
c384d7 |
from lib389._constants import *
|
|
|
c384d7 |
@@ -31,6 +32,9 @@ from lib389.idm.domain import Domain
|
|
|
c384d7 |
from lib389.idm.group import Groups
|
|
|
c384d7 |
from lib389.idm.services import ServiceAccounts
|
|
|
c384d7 |
from lib389.idm.organizationalunit import OrganizationalUnits
|
|
|
c384d7 |
+from lib389.conflicts import ConflictEntries
|
|
|
c384d7 |
+from lib389.lint import (DSREPLLE0001, DSREPLLE0002, DSREPLLE0003, DSREPLLE0004,
|
|
|
c384d7 |
+ DSREPLLE0005, DSCLLE0001)
|
|
|
c384d7 |
|
|
|
c384d7 |
|
|
|
c384d7 |
class ReplicaLegacy(object):
|
|
|
c384d7 |
@@ -1044,6 +1048,19 @@ class Changelog5(DSLdapObject):
|
|
|
c384d7 |
'extensibleobject',
|
|
|
c384d7 |
]
|
|
|
c384d7 |
self._protected = False
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_cl_trimming]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_cl_trimming(self):
|
|
|
c384d7 |
+ """Check that cl trimming is at least defined to prevent unbounded growth"""
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ if self.get_attr_val_utf8('nsslapd-changelogmaxentries') is None and \
|
|
|
c384d7 |
+ self.get_attr_val_utf8('nsslapd-changelogmaxage') is None:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSCLLE0001)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ except:
|
|
|
c384d7 |
+ # No changelog
|
|
|
c384d7 |
+ pass
|
|
|
c384d7 |
|
|
|
c384d7 |
def set_max_entries(self, value):
|
|
|
c384d7 |
"""Configure the max entries the changelog can hold.
|
|
|
c384d7 |
@@ -1102,6 +1119,59 @@ class Replica(DSLdapObject):
|
|
|
c384d7 |
self._create_objectclasses.append('extensibleobject')
|
|
|
c384d7 |
self._protected = False
|
|
|
c384d7 |
self._suffix = None
|
|
|
c384d7 |
+ self._lint_functions = [self._lint_agmts_status, self._lint_conflicts]
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_agmts_status(self):
|
|
|
c384d7 |
+ replicas = Replicas(self._instance).list()
|
|
|
c384d7 |
+ for replica in replicas:
|
|
|
c384d7 |
+ agmts = replica.get_agreements().list()
|
|
|
c384d7 |
+ suffix = replica.get_suffix()
|
|
|
c384d7 |
+ for agmt in agmts:
|
|
|
c384d7 |
+ try:
|
|
|
c384d7 |
+ status = json.loads(agmt.get_agmt_status(return_json=True))
|
|
|
c384d7 |
+ if "Not in Synchronization" in status['msg'] and not "Replication still in progress" in status['reason']:
|
|
|
c384d7 |
+ agmt_name = agmt.get_name()
|
|
|
c384d7 |
+ if status['state'] == 'red':
|
|
|
c384d7 |
+ # Serious error
|
|
|
c384d7 |
+ if "Consumer can not be contacted" in status['reason']:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSREPLLE0005)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('AGMT', agmt_name)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ else:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSREPLLE0001)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('AGMT', agmt_name)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('MSG', status['reason'])
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('AGMT', agmt_name)
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ elif status['state'] == 'amber':
|
|
|
c384d7 |
+ # Warning
|
|
|
c384d7 |
+ report = copy.deepcopy(DSREPLLE0003)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('AGMT', agmt_name)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('MSG', status['reason'])
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+ except ldap.LDAPError as e:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSREPLLE0004)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('AGMT', agmt_name)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('ERROR', str(e))
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
+
|
|
|
c384d7 |
+ def _lint_conflicts(self):
|
|
|
c384d7 |
+ replicas = Replicas(self._instance).list()
|
|
|
c384d7 |
+ for replica in replicas:
|
|
|
c384d7 |
+ conflicts = ConflictEntries(self._instance, replica.get_suffix()).list()
|
|
|
c384d7 |
+ suffix = replica.get_suffix()
|
|
|
c384d7 |
+ if len(conflicts) > 0:
|
|
|
c384d7 |
+ report = copy.deepcopy(DSREPLLE0002)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('SUFFIX', suffix)
|
|
|
c384d7 |
+ report['detail'] = report['detail'].replace('COUNT', len(conflicts))
|
|
|
c384d7 |
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
|
|
c384d7 |
+ yield report
|
|
|
c384d7 |
|
|
|
c384d7 |
def _validate(self, rdn, properties, basedn):
|
|
|
c384d7 |
(tdn, str_props) = super(Replica, self)._validate(rdn, properties, basedn)
|
|
|
c384d7 |
--
|
|
|
c384d7 |
2.21.0
|
|
|
c384d7 |
|