From cf85ab8f93ad1972aaa730bed139aa059e13646d Mon Sep 17 00:00:00 2001 From: William Brown Date: Mon, 30 May 2016 13:22:39 +1000 Subject: [PATCH] Ticket 48820 - Move Encryption and RSA to the new object types Fix Description: Make an Encyrption and RSA types on the new objects. To make this work, rejig some of the create code to move creation to the single item. https://fedorahosted.org/389/ticket/48820 Author: wibrown Review by: ??? --- lib389/__init__.py | 9 ++-- lib389/_mapped_object.py | 110 +++++++++++++++++++++++++++++------------------ lib389/backend.py | 33 +++++++------- lib389/config.py | 47 ++++++++++++++++++++ 4 files changed, 134 insertions(+), 65 deletions(-) diff --git a/lib389/__init__.py b/lib389/__init__.py index 0468d59..c897cee 100644 --- a/lib389/__init__.py +++ b/lib389/__init__.py @@ -339,8 +339,8 @@ class DirSrv(SimpleLDAPObject): from lib389.index import Index from lib389.aci import Aci from lib389.monitor import Monitor - if MAJOR < 3: - from lib389.nss_ssl import NssSsl + from lib389.nss_ssl import NssSsl + from lib389.config import RSA from lib389.dirsrv_log import DirsrvAccessLog, DirsrvErrorLog from lib389.ldclt import Ldclt @@ -358,8 +358,9 @@ class DirSrv(SimpleLDAPObject): self.aci = Aci(self) self.monitor = Monitor(self) # Do we have a certdb path? - if MAJOR < 3: - self.nss_ssl = NssSsl(self) + #if MAJOR < 3: + self.nss_ssl = NssSsl(self) + self.rsa = RSA(self) self.ds_access_log = DirsrvAccessLog(self) self.ds_error_log = DirsrvErrorLog(self) self.ldclt = Ldclt(self) diff --git a/lib389/_mapped_object.py b/lib389/_mapped_object.py index 2845b84..011baa1 100644 --- a/lib389/_mapped_object.py +++ b/lib389/_mapped_object.py @@ -63,24 +63,29 @@ class DSLogging(object): class DSLdapObject(DSLogging): + # TODO: Automatically create objects when they are requested to have properties added def __init__(self, instance, dn=None, batch=False): """ """ self._instance = instance super(DSLdapObject, self).__init__(self._instance.verbose) # This allows some factor objects to be overriden - self._dn = '' + self._dn = None if dn is not None: self._dn = dn self._batch = batch - self._naming_attr = None self._protected = True + # Used in creation + self._create_objectclasses = [] + self._rdn_attribute = None + self._must_attributes = None + self._basedn = "" def __unicode__(self): val = self._dn - if self._naming_attr: - val = self.get(self._naming_attr) + if self._rdn_attribute: + val = self.get(self._rdn_attribute) return ensure_str(val) def __str__(self): @@ -98,6 +103,7 @@ class DSLdapObject(DSLogging): def get(self, key): """Get an attribute under dn""" self._log.debug("%s get(%r)" % (self._dn, key)) + # We might need to add a state check for NONE dn. if self._instance.state != DIRSRV_STATE_ONLINE: ValueError("Invalid state. Cannot get properties on instance that is not ONLINE") # In the future, I plan to add a mode where if local == true, we can use @@ -123,6 +129,51 @@ class DSLdapObject(DSLogging): if not self._protected: pass + def _validate(self, tdn, properties): + """ + Used to validate a create request. + This way, it can be over-ridden without affecting + the create types + + It also checks that all the values in _must_attribute exist + in some form in the dictionary + + It has the useful trick of returning the dn, so subtypes + can use extra properties to create the dn's here for this. + """ + if properties is None: + raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') + if type(properties) != dict: + raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") + + # I think this needs to be made case insensitive + # How will this work with the dictionary? + for attr in self._must_attributes: + if properties.get(attr, None) is None: + raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr) + + # We may need to map over the data in the properties dict to satisfy python-ldap + # + # Do we need to do extra dn validation here? + return (tdn, properties) + + def create(self, dn, properties=None): + assert(len(self._create_objectclasses) > 0) + self._log.debug('Creating %s : %s' % (dn, properties)) + # Make sure these aren't none. + # Create the dn based on the various properties. + (dn, valid_props) = self._validate(dn, properties) + # Check if the entry exists or not? .add_s is going to error anyway ... + self._log.debug('Validated %s : %s' % (dn, properties)) + + e = Entry(dn) + e.update({'objectclass' : self._create_objectclasses}) + e.update(valid_props) + # We rely on exceptions here to indicate failure to the parent. + self._instance.add_s(e) + # If it worked, we need to fix our instance dn + self._dn = dn + # A challenge of this, is how do we manage indexes? They have two naming attribunes.... @@ -132,14 +183,11 @@ class DSLdapObjects(DSLogging): self._instance = instance super(DSLdapObjects, self).__init__(self._instance.verbose) self._objectclasses = [] - self._create_objectclasses = [] self._filterattrs = [] self._list_attrlist = ['dn'] self._basedn = "" self._batch = batch self._scope = ldap.SCOPE_SUBTREE - self._rdn_attribute = None - self._must_attributes = None def list(self): # Filter based on the objectclasses and the basedn @@ -181,17 +229,10 @@ class DSLdapObjects(DSLogging): raise ldap.UNWILLING_TO_PERFORM("Too many objects matched selection criteria %s" % selector) return self._childobject(instance=self._instance, dn=results[0].dn, batch=self._batch) + def _validate(self, rdn, properties): """ - Used to validate a create request. - This way, it can be over-ridden without affecting - the create types - - It also checks that all the values in _must_attribute exist - in some form in the dictionary - - It has the useful trick of returning the dn, so subtypes - can use extra properties to create the dn's here for this. + Validate the factor part of the creation """ if properties is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') @@ -211,32 +252,15 @@ class DSLdapObjects(DSLogging): if type(rdn) != str: raise ldap.UNWILLING_TO_PERFORM("rdn %s must be a utf8 string (str)", rdn) - for attr in self._must_attributes: - if properties.get(attr, None) is None: - raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr) - - # We may need to map over the data in the properties dict to satisfy python-ldap - # to do str -> bytes - # - # Do we need to fix anything here in the rdn_attribute? - dn = '%s=%s,%s' % (self._rdn_attribute, rdn, self._basedn) - # Do we need to do extra dn validation here? - return (dn, rdn, properties) - def create(self, rdn=None, properties=None): - assert(len(self._create_objectclasses) > 0) - self._log.debug('Creating %s : %s' % (rdn, properties)) - # Make sure these aren't none. - # Create the dn based on the various properties. - (dn, rdn, valid_props) = self._validate(rdn, properties) - # Check if the entry exists or not? .add_s is going to error anyway ... - self._log.debug('Validated %s : %s' % (dn, properties)) - - e = Entry(dn) - e.update({'objectclass' : self._create_objectclasses}) - e.update(valid_props) - self._instance.add_s(e) - - # Now return the created instance. - return self._childobject(instance=self._instance, dn=dn, batch=self._batch) + # Create the object + # Should we inject the rdn to properties? + co = self._childobject(instance=self._instance, batch=self._batch) + # Make the rdn naming attr avaliable + self._rdn_attribute = co._rdn_attribute + (rdn, properties) = self._validate(rdn, properties) + # Do we need to fix anything here in the rdn_attribute? + dn = '%s=%s,%s' % (co._rdn_attribute, rdn, self._basedn) + # Now actually commit the creation req + return co.create(dn, properties) diff --git a/lib389/backend.py b/lib389/backend.py index aba4d40..9ed2a04 100644 --- a/lib389/backend.py +++ b/lib389/backend.py @@ -380,24 +380,12 @@ class BackendLegacy(object): class Backend(DSLdapObject): def __init__(self, instance, dn=None, batch=False): super(Backend, self).__init__(instance, dn, batch) - self._naming_attr = 'cn' + self._rdn_attribute = 'cn' + self._must_attributes = ['nsslapd-suffix', 'cn'] def create_sample_entries(self): self._log.debug('Creating sample entries ....') -# This only does ldbm backends. Chaining backends are a special case -# of this, so they can be subclassed off. -class Backends(DSLdapObjects): - def __init__(self, instance, batch=False): - super(Backends, self).__init__(instance=instance, batch=False) - self._objectclasses = [BACKEND_OBJECTCLASS_VALUE] - self._create_objectclasses = self._objectclasses + ['top', 'extensibleObject' ] - self._filterattrs = ['cn', 'nsslapd-suffix', 'nsslapd-directory'] - self._basedn = DN_LDBM - self._childobject = Backend - self._rdn_attribute = 'cn' - self._must_attributes = ['nsslapd-suffix', 'cn'] - def _validate(self, rdn, properties): # We always need to call the super validate first. This way we can # guarantee that properties is a dictionary. @@ -420,12 +408,21 @@ class Backends(DSLdapObjects): return (dn, rdn, nprops) def create(self, rdn=None, properties=None): - # properties for a backend might contain a key called BACKEND_SAMPLE_ENTRIES - # We need to pop this value out, and pass it to our new instance. sample_entries = properties.pop(BACKEND_SAMPLE_ENTRIES, False) - be_inst = super(Backends, self).create(rdn, properties) + super(Backend, self).create(rdn, properties) if sample_entries is True: be_inst.create_sample_entries() - return be_inst + +# This only does ldbm backends. Chaining backends are a special case +# of this, so they can be subclassed off. +class Backends(DSLdapObjects): + def __init__(self, instance, batch=False): + super(Backends, self).__init__(instance=instance, batch=False) + self._objectclasses = [BACKEND_OBJECTCLASS_VALUE] + self._create_objectclasses = self._objectclasses + ['top', 'extensibleObject' ] + self._filterattrs = ['cn', 'nsslapd-suffix', 'nsslapd-directory'] + self._basedn = DN_LDBM + self._childobject = Backend + diff --git a/lib389/config.py b/lib389/config.py index d4c00a7..d35efc9 100644 --- a/lib389/config.py +++ b/lib389/config.py @@ -106,6 +106,7 @@ class Config(DSLdapObject): 'nsSSLPersonalitySSL': 'Server-Cert' } """ + self._log.debug("config.enable_ssl is deprecated! Use RSA, Encryption instead!") self._log.debug("configuring SSL with secargs:%r" % secargs) secargs = secargs or {} @@ -151,3 +152,49 @@ class Config(DSLdapObject): fields = 'nsslapd-security nsslapd-ssl-check-hostname'.split() return self.conn.getEntry(DN_CONFIG, attrlist=fields) + + +class Encryption(DSLdapObject): + """ + Manage "cn=encryption,cn=config" tree, including: + - ssl ciphers + - ssl / tls levels + """ + def __init__(self, conn, batch=False): + """@param conn - a DirSrv instance """ + super(Encryption, self).__init__(instance=conn, batch=batch) + self._dn = 'cn=encryption,%s' % DN_CONFIG + # Once created, don't allow it's removal + self._protected = True + + +class RSA(DSLdapObject): + """ + Manage the "cn=RSA,cn=encryption,cn=config" object + - Set the certificate name + - Database path + - ssl token name + """ + def __init__(self, conn, batch=False): + """@param conn - a DirSrv instance """ + super(RSA, self).__init__(instance=conn, batch=batch) + self._dn = 'cn=RSA,cn=encryption,%s' % DN_CONFIG + self._create_objectclasses = ['top', 'nsEncryptionModule'] + self._rdn_attribute = 'cn' + self._must_attributes = ['cn'] + # Once we create it, don't remove it + self._protected = True + + def _validate(self, tdn, properties): + (dn, valid_props) = super(RSA, self)._validate(tdn, properties) + # Ensure that dn matches self._dn + assert(self._dn == dn) + return (dn, valid_props) + + def create(self, dn=None, properties={'cn': 'RSA'}): + # Is this the best way to for the dn? + if dn is not None: + self._log.debug("dn on cn=Rsa create request is not None. This is a mistake.") + super(RSA, self).create(dn=self._dn, properties=properties) + + -- 2.5.5