diff options
author | Adam Young <ayoung@redhat.com> | 2010-07-15 12:21:02 -0400 |
---|---|---|
committer | Adam Young <ayoung@redhat.com> | 2010-07-15 12:21:02 -0400 |
commit | c62f8e9fdef1214cced53eb4e1267d0c1cdc1325 (patch) | |
tree | 5eabd770aa13b700b3de6eae0528c16ed5c47604 | |
parent | 28a5ef6616a36590c3acec16a233dc587c782a8b (diff) | |
parent | 57a9001f8d12a5caec01c628089a4624499b76bc (diff) | |
download | freeipa-c62f8e9fdef1214cced53eb4e1267d0c1cdc1325.tar.gz freeipa-c62f8e9fdef1214cced53eb4e1267d0c1cdc1325.tar.xz freeipa-c62f8e9fdef1214cced53eb4e1267d0c1cdc1325.zip |
Merged wsgi, based on pavels changes.
-rw-r--r-- | install/share/nis.uldif | 12 | ||||
-rwxr-xr-x | install/tools/ipa-compat-manage | 60 | ||||
-rwxr-xr-x | install/tools/ipa-nis-manage | 94 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/frontend.py | 2 | ||||
-rw-r--r-- | ipalib/plugable.py | 5 | ||||
-rw-r--r-- | ipalib/plugins/baseldap.py | 16 | ||||
-rw-r--r-- | ipalib/plugins/cert.py | 165 | ||||
-rw-r--r-- | ipalib/plugins/group.py | 1 | ||||
-rw-r--r-- | ipalib/plugins/host.py | 56 | ||||
-rw-r--r-- | ipalib/plugins/migration.py | 5 | ||||
-rw-r--r-- | ipalib/plugins/netgroup.py | 72 | ||||
-rw-r--r-- | ipalib/plugins/service.py | 109 | ||||
-rw-r--r-- | ipalib/plugins/user.py | 1 | ||||
-rw-r--r-- | ipalib/x509.py | 289 | ||||
-rw-r--r-- | ipapython/nsslib.py | 1 | ||||
-rw-r--r-- | ipaserver/plugins/ldap2.py | 46 | ||||
-rw-r--r-- | ipaserver/plugins/selfsign.py | 7 | ||||
-rwxr-xr-x | lite-server.py | 1 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_cert.py | 34 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_host_plugin.py | 3 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_netgroup_plugin.py | 146 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_service_plugin.py | 1 | ||||
-rw-r--r-- | tests/test_xmlrpc/xmlrpc_test.py | 2 |
24 files changed, 634 insertions, 495 deletions
diff --git a/install/share/nis.uldif b/install/share/nis.uldif index 86f521b61..5860d23af 100644 --- a/install/share/nis.uldif +++ b/install/share/nis.uldif @@ -65,12 +65,22 @@ default:nis-map: netid.byname default:nis-base: cn=users, cn=accounts, $SUFFIX default:nis-secure: no +# Note that the escapes in this entry can be quite confusing. The trick +# is that each level of nesting requires (2^n) - 1 escapes. So the +# first level is \", the second is \\\", the third is \\\\\\\", etc. +# (1, 3, 7, 15, more than that and you'll go insane) + +# Note that this configuration mirrors the Schema Compat configuration for +# triples. dn: nis-domain=$DOMAIN+nis-map=netgroup, cn=NIS Server, cn=plugins, cn=config default:objectclass: top default:objectclass: extensibleObject default:nis-domain: $DOMAIN default:nis-map: netgroup -default:nis-base: cn=ng, cn=compat, $SUFFIX +default:nis-base: cn=ng, cn=alt, $SUFFIX +default:nis-filter: (objectClass=ipanisNetgroup) +default:nis-key-format: %{cn} +default:nis-value-format: %merge(" ","%{memberNisNetgroup}","(%link(\"%collect(\\\"%{externalHost}\\\",\\\"%deref(\\\\\\\"memberHost\\\\\\\",\\\\\\\"fqdn\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"member\\\\\\\",\\\\\\\"fqdn\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"memberHost\\\\\\\",\\\\\\\"member\\\\\\\",\\\\\\\"fqdn\\\\\\\")\\\")\",\"-\",\",\",\"%collect(\\\"%deref(\\\\\\\"memberUser\\\\\\\",\\\\\\\"uid\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"member\\\\\\\",\\\\\\\"uid\\\\\\\")\\\",\\\"%deref_r(\\\\\\\"memberUser\\\\\\\",\\\\\\\"member\\\\\\\",\\\\\\\"uid\\\\\\\")\\\")\",\"-\"),%{nisDomainName:-})") default:nis-secure: no dn: cn=ng,cn=Schema Compatibility,cn=plugins,cn=config diff --git a/install/tools/ipa-compat-manage b/install/tools/ipa-compat-manage index b22ce77f9..3128ed718 100755 --- a/install/tools/ipa-compat-manage +++ b/install/tools/ipa-compat-manage @@ -22,18 +22,12 @@ import sys try: from optparse import OptionParser - from ipapython import entity, ipautil, config + from ipapython import ipautil, config from ipaserver.install import installutils - from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR + from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax from ipaserver.plugins.ldap2 import ldap2 - from ipalib import errors + from ipalib import api, errors import logging - import re - import krbV - import platform - import shlex - import time - import random except ImportError: print >> sys.stderr, """\ There was a problem importing one of the required Python modules. The @@ -43,6 +37,8 @@ error was: """ % sys.exc_value sys.exit(1) +netgroup_compat_dn = "cn=ng,cn=Schema Compatibility,cn=plugins,cn=config" + def parse_options(): usage = "%prog [options] <enable|disable>\n" usage += "%prog [options]\n" @@ -71,7 +67,7 @@ def get_dirman_password(): def main(): retval = 0 loglevel = logging.ERROR - files=['/usr/share/ipa/schema_compat.uldif'] + files = ['/usr/share/ipa/schema_compat.uldif'] options, args = parse_options() if options.debug: @@ -94,6 +90,9 @@ def main(): else: dirman_password = get_dirman_password() + api.bootstrap(context='cli', debug=options.debug) + api.finalize() + conn = None try: ldapuri = 'ldap://%s' % installutils.get_fqdn() @@ -102,42 +101,57 @@ def main(): conn.connect( bind_dn='cn=directory manager', bind_pw=dirman_password ) - except errors.LDAPError, e: + except errors.LDAPError, lde: print "An error occurred while connecting to the server." - print e + print lde return 1 if args[0] == "enable": try: - conn.get_entry('cn=Schema Compatibility,cn=plugins,cn=config') + conn.get_entry('cn=Schema Compatibility,cn=plugins,cn=config', normalize=False) print "Plugin already Enabled" retval = 2 except errors.NotFound: print "Enabling plugin" - except errors.LDAPError, e: + except errors.LDAPError, lde: print "An error occurred while talking to the server." - print e + print lde retval = 1 if retval == 0: ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}) - retval = ld.update(files) - if retval == 0: + rv = ld.update(files) + if rv: print "This setting will not take effect until you restart Directory Server." + else: + print "Updating Directory Server failed." + retval = 1 elif args[0] == "disable": - # Make a quick hack foir now, directly delete the entries by name, + # We can't disable schema compat if the NIS plugin is enabled + try: + conn.get_entry(netgroup_compat_dn, normalize=False) + print "The NIS plugin is configured, cannot disable compatibility." + print "Run 'ipa-nis-manage disable' first." + return 2 + except errors.NotFound: + pass + # Make a quick hack for now, directly delete the entries by name, # In future we should add delete capabilites to LDAPUpdate try: - conn.delete_entry('cn=groups,cn=Schema Compatibility,cn=plugins,cn=config') - conn.delete_entry('cn=users,cn=Schema Compatibility,cn=plugins,cn=config') - conn.delete_entry('cn=Schema Compatibility,cn=plugins,cn=config') + conn.delete_entry('cn=groups,cn=Schema Compatibility,cn=plugins,cn=config', normalize=False) + conn.delete_entry('cn=users,cn=Schema Compatibility,cn=plugins,cn=config', normalize=False) + conn.delete_entry('cn=Schema Compatibility,cn=plugins,cn=config', normalize=False) except errors.NotFound: print "Plugin is already disabled" retval = 2 - except errors.LDAPError, e: + except errors.DatabaseError, dbe: + print "An error occurred while talking to the server." + print lde + retval = 1 + except errors.LDAPError, lde: print "An error occurred while talking to the server." - print e + print lde retval = 1 else: diff --git a/install/tools/ipa-nis-manage b/install/tools/ipa-nis-manage index 22cfd432e..706b0e630 100755 --- a/install/tools/ipa-nis-manage +++ b/install/tools/ipa-nis-manage @@ -22,11 +22,11 @@ import sys try: from optparse import OptionParser - from ipapython import entity, ipautil, config + from ipapython import ipautil, config from ipaserver.install import installutils - from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR + from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax from ipaserver.plugins.ldap2 import ldap2 - from ipalib import errors + from ipalib import api, errors import logging except ImportError: print >> sys.stderr, """\ @@ -38,6 +38,7 @@ error was: sys.exit(1) nis_config_dn = "cn=NIS Server, cn=plugins, cn=config" +compat_dn = "cn=Schema Compatibility,cn=plugins,cn=config" def parse_options(): usage = "%prog [options] <enable|disable>\n" @@ -64,10 +65,14 @@ def get_dirman_password(): return password -def get_nis_config(conn): +def get_entry(dn, conn): + """ + Return the entry for the given DN. If the entry is not found return + None. + """ entry = None try: - (dn, entry) = conn.get_entry(nis_config_dn) + (dn, entry) = conn.get_entry(dn, normalize=False) except errors.NotFound: pass return entry @@ -75,7 +80,7 @@ def get_nis_config(conn): def main(): retval = 0 loglevel = logging.ERROR - files=['/usr/share/ipa/nis.uldif'] + files = ['/usr/share/ipa/nis.uldif'] servicemsg = "" options, args = parse_options() @@ -99,6 +104,9 @@ def main(): else: dirman_password = get_dirman_password() + api.bootstrap(context='cli', debug=options.debug) + api.finalize() + conn = None try: ldapuri = 'ldap://%s' % installutils.get_fqdn() @@ -107,62 +115,86 @@ def main(): conn.connect( bind_dn='cn=directory manager', bind_pw=dirman_password ) - except errors.LDAPError, e: + except errors.LDAPError, lde: print "An error occurred while connecting to the server." - print e + print lde return 1 if args[0] == "enable": + compat = get_entry(compat_dn, conn) + if compat is None: + print "The compat plugin needs to be enabled: ipa-compat-manage enable" + return 1 entry = None try: - entry = get_nis_config(conn) - except errors.LDAPError, e: + entry = get_entry(nis_config_dn, conn) + except errors.LDAPError, lde: print "An error occurred while talking to the server." - print e + print lde retval = 1 # Enable either the portmap or rpcbind service try: ipautil.run(["/sbin/chkconfig", "portmap", "on"]) servicemsg = "portmap" - except ipautil.CalledProcessError, e: - if e.returncode == 1: + except ipautil.CalledProcessError, cpe: + if cpe.returncode == 1: try: ipautil.run(["/sbin/chkconfig", "rpcbind", "on"]) servicemsg = "rpcbind" - except ipautil.CalledProcessError, e: + except ipautil.CalledProcessError, cpe: print "Unable to enable either portmap or rpcbind" retval = 3 - if entry is None: + # The cn=config entry for the plugin may already exist but it + # could be turned off, handle both cases. + if (entry is None or + entry.get('nsslapd-pluginenabled', [''])[0].lower() == 'off'): + # Already configured, just enable the plugin print "Enabling plugin" - - if entry is None: - # Load the plugin configuration - ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}) - retval = ld.update(files) + ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}) + if ld.update(files) != True: + retval = 1 + mod = {'nsslapd-pluginenabled': 'on'} + try: + conn.update_entry(nis_config_dn, mod, normalize=False) + except errors.EmptyModlist: + # plugin is already enabled, silently continue + pass else: - if entry.get('nsslapd-pluginenabled', '').lower() == 'off': - # Already configured, just enable the plugin - print "Enabling plugin" - mod = {'nsslapd-pluginenabled': 'on'} - conn.update_entry(nis_config_dn, mod) - else: - print "Plugin already Enabled" - retval = 2 + print "Plugin already Enabled" + retval = 2 elif args[0] == "disable": try: mod = {'nsslapd-pluginenabled': 'off'} - conn.update_entry(nis_config_dn, mod) + conn.update_entry(nis_config_dn, mod, normalize=False) except errors.NotFound: print "Plugin is already disabled" retval = 2 - except errors.LDAPError, e: + except errors.EmptyModlist: + print "Plugin is already disabled" + retval = 2 + except errors.LDAPError, lde: print "An error occurred while talking to the server." - print e + print lde retval = 1 + # delete the netgroups compat area. + try: + conn.delete_entry('cn=ng,cn=Schema Compatibility,cn=plugins,cn=config', normalize=False) + except errors.NotFound: + pass + except errors.DatabaseError, dbe: + print "An error occurred while talking to the server." + print lde + retval = 1 + except errors.LDAPError, lde: + print "An error occurred while talking to the server." + print lde + retval = 1 + + else: retval = 1 diff --git a/ipalib/constants.py b/ipalib/constants.py index 4008b0721..6b0ab5746 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -121,6 +121,7 @@ DEFAULT_CONFIG = ( # Debugging: ('verbose', 0), ('debug', False), + ('startup_traceback', False), ('mode', 'production'), # CA plugin: diff --git a/ipalib/frontend.py b/ipalib/frontend.py index c270981f6..d320f02e0 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -1165,6 +1165,8 @@ class Method(Attribute, Command): if 'no_output' in param.flags: continue yield param + for param in self._get_param_iterable('output_params', verb='has'): + yield param class Property(Attribute): diff --git a/ipalib/plugable.py b/ipalib/plugable.py index 397004eb1..fd5f31a76 100644 --- a/ipalib/plugable.py +++ b/ipalib/plugable.py @@ -536,8 +536,9 @@ class API(DictProxy): 'skipping plugin module %s: %s', fullname, e.reason ) except StandardError, e: - import traceback - self.log.error('could not load plugin module %r\n%s', pyfile, traceback.format_exc()) + if self.env.startup_traceback: + import traceback + self.log.error('could not load plugin module %r\n%s', pyfile, traceback.format_exc()) raise e def finalize(self): diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 92b42d4ee..d492ebde9 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -70,6 +70,8 @@ class LDAPObject(Object): object_name_plural = 'entries' object_class = [] object_class_config = None + search_attributes = [] + search_attributes_config = None default_attributes = [] hidden_attributes = ['objectclass', 'aci'] uuid_attribute = '' @@ -834,11 +836,23 @@ class LDAPSearch(CallbackInterface, crud.Search): set(self.obj.default_attributes + search_kw.keys()) ) + if self.obj.search_attributes: + search_attrs = self.obj.search_attributes + else: + search_attrs = self.obj.default_attributes + if self.obj.search_attributes_config: + config = ldap.get_ipa_config()[1] + config_attrs = config.get( + self.obj.search_attributes_config, []) + if len(config_attrs) == 1 and ( + isinstance(config_attrs[0], basestring)): + search_attrs = config_attrs[0].split(',') + search_kw['objectclass'] = self.obj.object_class attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL) search_kw = {} - for a in self.obj.default_attributes: + for a in search_attrs: search_kw[a] = term term_filter = ldap.make_filter(search_kw, exact=False) diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index 17e4c46b0..1de4ac64e 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -44,7 +44,7 @@ EXAMPLES: ipa cert-request --add --principal=HTTP/lion.example.com example.csr Retrieve an existing certificate: - ipa cert-request 1032 + ipa cert-show 1032 Revoke a certificate (see RFC 5280 for reason details): ipa cert-revoke --revocation-reason=6 1032 @@ -75,53 +75,8 @@ import traceback from ipalib.text import _ from ipalib.request import context from ipalib.output import Output - -def get_serial(certificate): - """ - Given a certificate, return the serial number in that cert - as a Python long object. - - In theory there should be only one cert per object so even if we get - passed in a list/tuple only return the first one. - """ - if type(certificate) in (list, tuple): - certificate = certificate[0] - try: - certificate = base64.b64decode(certificate) - except Exception, e: - pass - try: - - serial = x509.get_serial_number(certificate, x509.DER) - except PyAsn1Error: - raise errors.CertificateOperationError(error=_('Unable to decode certificate in entry')) - - return serial - -def get_subject(certificate): - """ - Given a certificate, return the subject - - In theory there should be only one cert per object so even if we get - passed in a list/tuple only return the first one. - """ - if type(certificate) in (list, tuple): - certificate = certificate[0] - try: - certificate = base64.b64decode(certificate) - except Exception, e: - pass - try: - sub = list(x509.get_subject_components(certificate, type=x509.DER)) - sub.reverse() - except PyAsn1Error: - raise errors.CertificateOperationError(error=_('Unable to decode certificate in entry')) - - subject = "" - for s in sub: - subject = subject + "%s=%s," % (s[0], s[1]) - - return subject[:-1] +from ipalib.plugins.service import validate_principal +import nss.nss as nss def get_csr_hostname(csr): """ @@ -192,6 +147,20 @@ def normalize_csr(csr): return csr +def get_host_from_principal(principal): + """ + Given a principal with or without a realm return the + host portion. + """ + validate_principal(None, principal) + realm = principal.find('@') + slash = principal.find('/') + if realm == -1: + realm = len(principal) + hostname = principal[slash+1:realm] + + return hostname + class cert_request(VirtualCommand): """ Submit a certificate signing request. @@ -219,6 +188,9 @@ class cert_request(VirtualCommand): default=False, autofill=True ), + ) + + has_output_params = ( Str('certificate?', label=_('Certificate'), flags=['no_create', 'no_update', 'no_search'], @@ -227,6 +199,26 @@ class cert_request(VirtualCommand): label=_('Subject'), flags=['no_create', 'no_update', 'no_search'], ), + Str('issuer?', + label=_('Issuer'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('valid_not_before?', + label=_('Not Before'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('valid_not_after?', + label=_('Not After'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('md5_fingerprint?', + label=_('Fingerprint (MD5)'), + flags=['no_create', 'no_update', 'no_search'], + ), + Str('sha1_fingerprint?', + label=_('Fingerprint (SHA1)'), + flags=['no_create', 'no_update', 'no_search'], + ), Str('serial_number?', label=_('Serial number'), flags=['no_create', 'no_update', 'no_search'], @@ -281,11 +273,7 @@ class cert_request(VirtualCommand): service = api.Command['service_show'](principal, all=True, raw=True)['result'] dn = service['dn'] else: - realm = principal.find('@') - if realm == -1: - realm = len(principal) - hostname = principal[5:realm] - + hostname = get_host_from_principal(principal) service = api.Command['host_show'](hostname, all=True, raw=True)['result'] dn = service['dn'] except errors.NotFound, e: @@ -319,12 +307,12 @@ class cert_request(VirtualCommand): raise errors.ACIError(info="Insufficient privilege to create a certificate with subject alt name '%s'." % name) if 'usercertificate' in service: - serial = get_serial(base64.b64encode(service['usercertificate'][0])) + serial = x509.get_serial_number(service['usercertificate'][0], datatype=x509.DER) # revoke the certificate and remove it from the service # entry before proceeding. First we retrieve the certificate to # see if it is already revoked, if not then we revoke it. try: - result = api.Command['cert_get'](unicode(serial))['result'] + result = api.Command['cert_show'](unicode(serial))['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](unicode(serial), revocation_reason=4) @@ -334,10 +322,20 @@ class cert_request(VirtualCommand): except errors.NotImplementedError: # some CA's might not implement get pass - api.Command['service_mod'](principal, usercertificate=None) + if not principal.startswith('host/'): + api.Command['service_mod'](principal, usercertificate=None) + else: + hostname = get_host_from_principal(principal) + api.Command['host_mod'](hostname, usercertificate=None) # Request the certificate result = self.Backend.ra.request_certificate(csr, **kw) + cert = x509.load_certificate(result['certificate']) + result['issuer'] = unicode(cert.issuer) + result['valid_not_before'] = unicode(cert.valid_not_before_str) + result['valid_not_after'] = unicode(cert.valid_not_after_str) + result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) + result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) # Success? Then add it to the service entry. if 'certificate' in result: @@ -345,10 +343,7 @@ class cert_request(VirtualCommand): skw = {"usercertificate": str(result.get('certificate'))} api.Command['service_mod'](principal, **skw) else: - realm = principal.find('@') - if realm == -1: - realm = len(principal) - hostname = principal[5:realm] + hostname = get_host_from_principal(principal) skw = {"usercertificate": str(result.get('certificate'))} api.Command['host_mod'](hostname, **skw) @@ -370,10 +365,9 @@ class cert_status(VirtualCommand): flags=['no_create', 'no_update', 'no_search'], ), ) - takes_options = ( - Str('cert_request_status?', + has_output_params = ( + Str('cert_request_status', label=_('Request status'), - flags=['no_create', 'no_update', 'no_search'], ), ) operation = "certificate status" @@ -393,25 +387,37 @@ _serial_number = Str('serial_number', doc=_('Serial number in decimal or if prefixed with 0x in hexadecimal'), ) -class cert_get(VirtualCommand): +class cert_show(VirtualCommand): """ Retrieve an existing certificate. """ takes_args = _serial_number - takes_options = ( - Str('certificate?', + has_output_params = ( + Str('certificate', label=_('Certificate'), - flags=['no_create', 'no_update', 'no_search'], ), - Str('subject?', + Str('subject', label=_('Subject'), - flags=['no_create', 'no_update', 'no_search'], + ), + Str('issuer', + label=_('Issuer'), + ), + Str('valid_not_before', + label=_('Not Before'), + ), + Str('valid_not_after', + label=_('Not After'), + ), + Str('md5_fingerprint', + label=_('Fingerprint (MD5)'), + ), + Str('sha1_fingerprint', + label=_('Fingerprint (SHA1)'), ), Str('revocation_reason?', label=_('Revocation reason'), - flags=['no_create', 'no_update', 'no_search'], ), ) @@ -420,10 +426,16 @@ class cert_get(VirtualCommand): def execute(self, serial_number): self.check_access() result=self.Backend.ra.get_certificate(serial_number) - result['subject'] = get_subject(result['certificate']) + cert = x509.load_certificate(result['certificate']) + result['subject'] = unicode(cert.subject) + result['issuer'] = unicode(cert.issuer) + result['valid_not_before'] = unicode(cert.valid_not_before_str) + result['valid_not_after'] = unicode(cert.valid_not_after_str) + result['md5_fingerprint'] = unicode(nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) + result['sha1_fingerprint'] = unicode(nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) return dict(result=result) -api.register(cert_get) +api.register(cert_show) class cert_revoke(VirtualCommand): @@ -433,10 +445,9 @@ class cert_revoke(VirtualCommand): takes_args = _serial_number - takes_options = ( - Flag('revoked?', + has_output_params = ( + Flag('revoked', label=_('Revoked'), - flags=['no_create', 'no_update', 'no_search'], ), ) operation = "revoke certificate" @@ -468,14 +479,12 @@ class cert_remove_hold(VirtualCommand): takes_args = _serial_number - takes_options = ( + has_output_params = ( Flag('unrevoked?', label=_('Unrevoked'), - flags=['no_create', 'no_update', 'no_search'], ), Str('error_string?', label=_('Error'), - flags=['no_create', 'no_update', 'no_search'], ), ) operation = "certificate remove hold" diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py index 9da4fe569..2558c38ab 100644 --- a/ipalib/plugins/group.py +++ b/ipalib/plugins/group.py @@ -75,6 +75,7 @@ class group(LDAPObject): object_name_plural = 'groups' object_class = ['ipausergroup'] object_class_config = 'ipagroupobjectclasses' + search_attributes_config = 'ipagroupsearchfields' default_attributes = [ 'cn', 'description', 'gidnumber', 'member', 'memberof' ] diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index 82ef16457..b42cbbcb7 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -57,6 +57,9 @@ EXAMPLES: Update information about a host ipa host-mod --os='Fedora 12' test.example.com + + Disable the host kerberos key + ipa host-disable test.example.com """ import platform @@ -68,8 +71,8 @@ from ipalib import Str, Flag, Bytes from ipalib.plugins.baseldap import * from ipalib.plugins.service import split_principal from ipalib.plugins.service import validate_certificate -from ipalib.plugins.service import get_serial from ipalib import _, ngettext +from ipalib import x509 import base64 @@ -91,9 +94,14 @@ class host(LDAPObject): object_name_plural = 'hosts' object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser', 'ipaservice'] # object_class_config = 'ipahostobjectclasses' + search_attributes = [ + 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname', + 'nshardwareplatform', 'nsosversion', + ] default_attributes = [ 'fqdn', 'description', 'l', 'nshostlocation', 'krbprincipalname', 'nshardwareplatform', 'nsosversion', 'usercertificate', 'memberof', + 'krblastpwdchange', ] uuid_attribute = 'ipauniqueid' attribute_members = { @@ -283,10 +291,10 @@ class host_mod(LDAPUpdate): if 'usercertificate' in entry_attrs_old: # FIXME: what to do here? do we revoke the old cert? fmt = 'entry already has a certificate, serial number: %s' % ( - get_serial(entry_attrs_old['usercertificate']) + x509.get_serial_number(entry_attrs_old['usercertificate'][0], x509.DER) ) raise errors.GenericError(format=fmt) - # FIXME: should be in normalizer; see service_add + # FIXME: decoding should be in normalizer; see service_add entry_attrs['usercertificate'] = base64.b64decode(cert) return dn @@ -316,5 +324,47 @@ class host_show(LDAPRetrieve): """ Display host. """ + has_output_params = ( + Flag('has_keytab', + label=_('Keytab'), + ) + ) + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + if 'krblastpwdchange' in entry_attrs: + entry_attrs['has_keytab'] = True + if not options.get('all', False): + del entry_attrs['krblastpwdchange'] + else: + entry_attrs['has_keytab'] = False + + return dn api.register(host_show) + + +class host_disable(LDAPQuery): + """ + Disable the kerberos key of this host. + """ + has_output = output.standard_value + msg_summary = _('Removed kerberos key from "%(value)s"') + + def execute(self, *keys, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(*keys, **options) + (dn, entry_attrs) = ldap.get_entry(dn, ['krblastpwdchange']) + + if 'krblastpwdchange' not in entry_attrs: + error_msg = _('Host principal has no kerberos key') + raise errors.NotFound(reason=error_msg) + + ldap.remove_principal_key(dn) + + return dict( + result=True, + value=keys[0], + ) + +api.register(host_disable) diff --git a/ipalib/plugins/migration.py b/ipalib/plugins/migration.py index 55a21572c..a2773efb8 100644 --- a/ipalib/plugins/migration.py +++ b/ipalib/plugins/migration.py @@ -31,7 +31,10 @@ from ipalib import api, errors, output, uuid from ipalib import Command, List, Password, Str from ipalib.cli import to_cli if api.env.in_server and api.env.context in ['lite', 'server']: - from ipaserver.plugins.ldap2 import ldap2 + try: + from ipaserver.plugins.ldap2 import ldap2 + except StandardError, e: + raise e from ipalib import _ from ipalib.text import Gettext # FIXME: remove once the other Gettext FIXME is removed diff --git a/ipalib/plugins/netgroup.py b/ipalib/plugins/netgroup.py index ad97c7226..755c5f4da 100644 --- a/ipalib/plugins/netgroup.py +++ b/ipalib/plugins/netgroup.py @@ -46,6 +46,23 @@ from ipalib.plugins.baseldap import * from ipalib import _, ngettext +output_params = ( + Str('memberuser_user?', + label='Member User', + ), + Str('memberuser_group?', + label='Member Group', + ), + Str('memberhost_host?', + label=_('Member Host'), + ), + Str('memberhost_hostgroup?', + label='Member Hostgroup', + ), + Str('externalhost?', + label=_('External host'), + ), + ) class netgroup(LDAPObject): """ Netgroup object. @@ -55,13 +72,14 @@ class netgroup(LDAPObject): object_name_plural = 'netgroups' object_class = ['ipaobject', 'ipaassociation', 'ipanisnetgroup'] default_attributes = [ - 'cn', 'description', 'member', 'memberof', 'externalhost', - 'nisdomainname', + 'cn', 'description', 'memberof', 'externalhost', + 'nisdomainname', 'memberuser', 'memberhost', ] uuid_attribute = 'ipauniqueid' attribute_members = { - 'member': ['user', 'group', 'host', 'hostgroup', 'netgroup'], 'memberof': ['netgroup'], + 'memberuser': ['user', 'group'], + 'memberhost': ['host', 'hostgroup'], } label = _('Net Groups') @@ -88,26 +106,6 @@ class netgroup(LDAPObject): doc=_('IPA unique ID'), flags=['no_create', 'no_update'], ), - Str('member_user?', - label='Member User', - flags=['no_create', 'no_update', 'no_search'], - ), - Str('member_group?', - label='Member Group', - flags=['no_create', 'no_update', 'no_search'], - ), - Str('member_host?', - label=_('Member host'), - flags=['no_create', 'no_update', 'no_search'], - ), - Str('member_hostgroup?', - label='Member Hostgroup', - flags=['no_create', 'no_update', 'no_search'], - ), - Str('externalhost?', - label=_('External host'), - flags=['no_create', 'no_update', 'no_search'], - ), ) def get_dn(self, *keys, **kwargs): @@ -135,6 +133,7 @@ class netgroup_add(LDAPCreate): """ Create new netgroup. """ + has_output_params = output_params def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): if not dn.startswith('cn='): msg = 'netgroup with name "%s" already exists' % keys[-1] @@ -160,6 +159,7 @@ class netgroup_mod(LDAPUpdate): """ Modify netgroup. """ + has_output_params = output_params api.register(netgroup_mod) @@ -168,6 +168,7 @@ class netgroup_find(LDAPSearch): """ Search the groups. """ + has_output_params = output_params api.register(netgroup_find) @@ -176,6 +177,7 @@ class netgroup_show(LDAPRetrieve): """ Display netgroup. """ + has_output_params = output_params api.register(netgroup_show) @@ -184,14 +186,18 @@ class netgroup_add_member(LDAPAddMember): """ Add members to netgroup. """ + has_output_params = output_params + member_attributes = ['memberuser', 'memberhost'] def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): - if 'member' in failed and 'host' in failed['member']: + completed_external = 0 + # Sift through the host failures. We assume that these are all + # hosts that aren't stored in IPA, aka external hosts. + if 'memberhost' in failed and 'host' in failed['memberhost']: (dn, entry_attrs_) = ldap.get_entry(dn, ['externalhost']) - members = entry_attrs.get('member', []) + members = entry_attrs.get('memberhost', []) external_hosts = entry_attrs_.get('externalhost', []) failed_hosts = [] - completed_external = 0 - for host in failed['member']['host']: + for host in failed['memberhost']['host']: host = host.lower() host_dn = self.api.Object['host'].get_dn(host) if host not in external_hosts and host_dn not in members: @@ -204,7 +210,7 @@ class netgroup_add_member(LDAPAddMember): ldap.update_entry(dn, {'externalhost': external_hosts}) except errors.EmptyModlist: pass - failed['member']['host'] = failed_hosts + failed['memberhost']['host'] = failed_hosts entry_attrs['externalhost'] = external_hosts return (completed + completed_external, dn) @@ -216,13 +222,17 @@ class netgroup_remove_member(LDAPRemoveMember): """ Remove members from netgroup. """ + has_output_params = output_params + member_attributes = ['memberuser', 'memberhost'] def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): - if 'member' in failed and 'host' in failed['member']: + # Run through the host failures and gracefully remove any defined as + # as an externalhost. + if 'memberhost' in failed and 'host' in failed['memberhost']: (dn, entry_attrs) = ldap.get_entry(dn, ['externalhost']) external_hosts = entry_attrs.get('externalhost', []) failed_hosts = [] completed_external = 0 - for host in failed['member']['host']: + for host in failed['memberhost']['host']: host = host.lower() if host in external_hosts: external_hosts.remove(host) @@ -234,7 +244,7 @@ class netgroup_remove_member(LDAPRemoveMember): ldap.update_entry(dn, {'externalhost': external_hosts}) except errors.EmptyModlist: pass - failed['member']['host'] = failed_hosts + failed['memberhost']['host'] = failed_hosts entry_attrs['externalhost'] = external_hosts return (completed + completed_external, dn) diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 3484e29a4..37de3df42 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -53,6 +53,10 @@ EXAMPLES: Find all HTTP services: ipa service-find HTTP + + Disable a service kerberos key: + ipa service-disable HTTP/web.example.com + """ import base64 @@ -60,26 +64,9 @@ from ipalib import api, errors from ipalib import Str, Flag, Bytes from ipalib.plugins.baseldap import * from ipalib import x509 -from pyasn1.error import PyAsn1Error from ipalib import _, ngettext -def get_serial(certificate): - """ - Given a certificate, return the serial number in that - cert as a Python long object. - """ - if type(certificate) in (list, tuple): - certificate = certificate[0] - - try: - serial = x509.get_serial_number(certificate, type=x509.DER) - except PyAsn1Error, e: - raise errors.GenericError( - format='Unable to decode certificate in entry: %s' % e - ) - return serial - def split_principal(principal): service = hostname = realm = None @@ -140,7 +127,8 @@ class service(LDAPObject): 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject', 'ipaservice', 'pkiuser' ] - default_attributes = ['krbprincipalname', 'usercertificate', 'managedby'] + search_attributes = ['krbprincipalname', 'managedby'] + default_attributes = ['krbprincipalname', 'usercertificate', 'managedby', 'krblastpwdchange'] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], @@ -156,11 +144,6 @@ class service(LDAPObject): primary_key=True, normalizer=lambda value: normalize_principal(value), ), - Bytes('usercertificate?', validate_certificate, - cli_name='certificate', - label=_('Certificate'), - doc=_('Base-64 encoded server certificate'), - ), ) api.register(service) @@ -176,6 +159,11 @@ class service_add(LDAPCreate): Flag('force', doc=_('force principal name even if not in DNS'), ), + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded server certificate'), + ), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): (service, hostname, realm) = split_principal(keys[-1]) @@ -189,6 +177,7 @@ class service_add(LDAPCreate): cert = entry_attrs.get('usercertificate') if cert: + cert = cert[0] # FIXME: should be in a normalizer: need to fix normalizers # to work on non-unicode data entry_attrs['usercertificate'] = base64.b64decode(cert) @@ -224,9 +213,10 @@ class service_del(LDAPDelete): (dn, entry_attrs) = ldap.get_entry(dn, ['usercertificate']) cert = entry_attrs.get('usercertificate') if cert: - serial = unicode(get_serial(cert)) + cert = cert[0] + serial = unicode(x509.get_serial_number(cert, x509.DER)) try: - result = api.Command['cert_get'](unicode(serial))['result'] + result = api.Command['cert_show'](unicode(serial))['result'] if 'revocation_reason' not in result: try: api.Command['cert_revoke'](unicode(serial), revocation_reason=4) @@ -245,15 +235,24 @@ class service_mod(LDAPUpdate): """ Modify service. """ + takes_options = LDAPUpdate.takes_options + ( + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded server certificate'), + ), + ) + member_attributes = ['managedby'] + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): - cert = entry_attrs.get('usercertificate') + cert = options.get('usercertificate') if cert: (dn, entry_attrs_old) = ldap.get_entry(dn, ['usercertificate']) if 'usercertificate' in entry_attrs_old: # FIXME: what to do here? do we revoke the old cert? fmt = 'entry already has a certificate, serial number: %s' % ( - get_serial(entry_attrs_old['usercertificate']) + x509.get_serial_number(entry_attrs_old['usercertificate'][0], x509.DER) ) raise errors.GenericError(format=fmt) # FIXME: should be in normalizer; see service_add @@ -268,6 +267,13 @@ class service_find(LDAPSearch): Search for services. """ member_attributes = ['managedby'] + takes_options = LDAPSearch.takes_options + ( + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded server certificate'), + ), + ) def pre_callback(self, ldap, filter, attrs_list, base_dn, *args, **options): # lisp style! custom_filter = '(&(objectclass=ipaService)' \ @@ -289,6 +295,28 @@ class service_show(LDAPRetrieve): Display service. """ member_attributes = ['managedby'] + takes_options = LDAPRetrieve.takes_options + ( + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + label=_('Certificate'), + doc=_('Base-64 encoded server certificate'), + ), + ) + has_output_params = ( + Flag('has_keytab', + label=_('Keytab'), + ) + ) + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + if 'krblastpwdchange' in entry_attrs: + entry_attrs['has_keytab'] = True + if not options.get('all', False): + del entry_attrs['krblastpwdchange'] + else: + entry_attrs['has_keytab'] = False + + return dn api.register(service_show) @@ -308,3 +336,30 @@ class service_remove_host(LDAPRemoveMember): member_attributes = ['managedby'] api.register(service_remove_host) + + +class service_disable(LDAPQuery): + """ + Disable the kerberos key of this service. + """ + has_output = output.standard_value + msg_summary = _('Removed kerberos key from "%(value)s"') + + def execute(self, *keys, **options): + ldap = self.obj.backend + + dn = self.obj.get_dn(*keys, **options) + (dn, entry_attrs) = ldap.get_entry(dn, ['krblastpwdchange']) + + if 'krblastpwdchange' not in entry_attrs: + error_msg = _('Service principal has no kerberos key') + raise errors.NotFound(reason=error_msg) + + ldap.remove_principal_key(dn) + + return dict( + result=True, + value=keys[0], + ) + +api.register(service_disable) diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index 610d85a95..de5ff2d27 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -58,6 +58,7 @@ class user(LDAPObject): object_name_plural = 'users' object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' + search_attributes_config = 'ipausersearchfields' default_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'ou', 'telephonenumber', 'title', 'memberof', diff --git a/ipalib/x509.py b/ipalib/x509.py index 3c38a354e..4637e9f27 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -1,199 +1,32 @@ -""" -Imported from pyasn1 project: - -Copyright (c) 2005-2009 Ilya Etingof <ilya@glas.net>, all rights reserved. - -THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY SITUATION -ENDANGERING HUMAN LIFE OR PROPERTY. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * The name of the authors may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - -""" -Enhancements released under IPA GPLv2 only license -""" - -# Read ASN.1/PEM X.509 certificates on stdin, parse each into plain text, -# then build substrate from it -import sys, string, base64 -from pyasn1.type import tag,namedtype,namedval,univ,constraint,char,useful -from pyasn1.codec.der import decoder, encoder -from pyasn1 import error - -# Would be autogenerated from ASN.1 source by a ASN.1 parser -# X.509 spec (rfc2459) +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import sys +import base64 +import nss.nss as nss +from ipapython import ipautil +from ipalib import api PEM = 0 DER = 1 -# Common OIDs found in a subject -oidtable = { "2.5.4.3": "CN", - "2.5.4.6": "C", - "2.5.4.7": "L", - "2.5.4.8": "ST", - "2.5.4.10": "O", - "2.5.4.11": "OU", - "1.2.840.113549.1.9.1": "E", - "0.9.2342.19200300.100.1.25": "DC", - } - -MAX = 64 # XXX ? - -class DirectoryString(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), - namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX - ) - -class AttributeValue(DirectoryString): pass - -class AttributeType(univ.ObjectIdentifier): pass - -class AttributeTypeAndValue(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('type', AttributeType()), - namedtype.NamedType('value', AttributeValue()) - ) - -class RelativeDistinguishedName(univ.SetOf): - componentType = AttributeTypeAndValue() - -class RDNSequence(univ.SequenceOf): - componentType = RelativeDistinguishedName() - -class Name(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('', RDNSequence()) - ) - - def get_components(self): - components = self.getComponentByPosition(0) - complist = [] - for idx in range(len(components)): - attrandvalue = components[idx].getComponentByPosition(0) - oid = attrandvalue.getComponentByPosition(0) - # FIXME, should handle any string type - value = attrandvalue.getComponentByPosition(1).getComponentByType(char.PrintableString.tagSet) - if value is None: - value = attrandvalue.getComponentByPosition(1).getComponentByType(char.UTF8String.tagSet) - if value is None: - value = attrandvalue.getComponentByPosition(1).getComponentByType(char.IA5String.tagSet) - vout = value.prettyOut(value).decode('utf-8') - oidout = oid.prettyOut(oid).decode('utf-8') - c = ((oidtable.get(oidout, oidout), vout)) - complist.append(c) - - return tuple(complist) - -class AlgorithmIdentifier(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('algorithm', univ.ObjectIdentifier()), - namedtype.OptionalNamedType('parameters', univ.Null()) - # XXX syntax screwed? -# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier()) - ) - -class Extension(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('extnID', univ.ObjectIdentifier()), - namedtype.DefaultedNamedType('critical', univ.Boolean('False')), - namedtype.NamedType('extnValue', univ.OctetString()) - ) - -class Extensions(univ.SequenceOf): - componentType = Extension() - sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) - -class SubjectPublicKeyInfo(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('algorithm', AlgorithmIdentifier()), - namedtype.NamedType('subjectPublicKey', univ.BitString()) - ) - -class UniqueIdentifier(univ.BitString): pass - -class Time(univ.Choice): - componentType = namedtype.NamedTypes( - namedtype.NamedType('utcTime', useful.UTCTime()), - namedtype.NamedType('generalTime', useful.GeneralizedTime()) - ) - -class Validity(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('notBefore', Time()), - namedtype.NamedType('notAfter', Time()) - ) - -class CertificateSerialNumber(univ.Integer): pass - -class Version(univ.Integer): - namedValues = namedval.NamedValues( - ('v1', 0), ('v2', 1), ('v3', 2) - ) - -class TBSCertificate(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))), - namedtype.NamedType('serialNumber', CertificateSerialNumber()), - namedtype.NamedType('signature', AlgorithmIdentifier()), - namedtype.NamedType('issuer', Name()), - namedtype.NamedType('validity', Validity()), - namedtype.NamedType('subject', Name()), - namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), - namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), - namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), - namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) - ) - -class Certificate(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('tbsCertificate', TBSCertificate()), - namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), - namedtype.NamedType('signatureValue', univ.BitString()) - ) - - def get_version(self): - info = self.getComponentByName('tbsCertificate') - version = info.getComponentByName('version') - return version._value - - def get_subject(self): - info = self.getComponentByName('tbsCertificate') - return info.getComponentByName('subject') - - def get_serial_number(self): - 'return the serial number as a Python long object' - info = self.getComponentByName('tbsCertificate') - return long(info.getComponentByName('serialNumber')) - -# end of ASN.1 data structures - def strip_header(pem): """ Remove the header and footer from a certificate. @@ -205,69 +38,53 @@ def strip_header(pem): return pem - -def load_certificate(data, type=PEM): +def load_certificate(data, datatype=PEM, dbdir=None): """ Given a base64-encoded certificate, with or without the header/footer, return a request object. + + Returns a nss.Certificate type """ - if (type == PEM): + if type(data) in (tuple, list): + data = data[0] + + if (datatype == PEM): data = strip_header(data) data = base64.b64decode(data) - return decoder.decode(data, asn1Spec=Certificate())[0] + if dbdir is None: + if api.env.in_tree: + dbdir = api.env.dot_ipa + os.sep + 'alias' + else: + dbdir = "/etc/httpd/alias" + + nss.nss_init(dbdir) + return nss.Certificate(buffer(data)) -def get_subject_components(certificate, type=PEM): +def get_subject(certificate, datatype=PEM): """ Load an X509.3 certificate and get the subject. - - Return a tuple of a certificate subject. - (('CN', u'www.example.com'), ('O', u'IPA')) """ - x509cert = load_certificate(certificate, type) - return x509cert.get_subject().get_components() + cert = load_certificate(certificate, datatype) + return cert.subject -def get_serial_number(certificate, type=PEM): +def get_serial_number(certificate, datatype=PEM): """ - Return the serial number of a certificate as a Python long object. + Return the decimal value of the serial number. """ - x509cert = load_certificate(certificate, type) - return x509cert.get_serial_number() + cert = load_certificate(certificate, datatype) + return cert.serial_number if __name__ == '__main__': - certType = Certificate() - - # Read PEM certs from stdin and print them out in plain text - - stSpam, stHam, stDump = 0, 1, 2 - state = stSpam - certCnt = 0 - for certLine in sys.stdin.readlines(): - certLine = string.strip(certLine) - if state == stSpam: - if state == stSpam: - if certLine == '-----BEGIN CERTIFICATE-----': - certLines = [] - state = stHam - continue - if state == stHam: - if certLine == '-----END CERTIFICATE-----': - state = stDump - else: - certLines.append(certLine) - if state == stDump: - substrate = '' - for certLine in certLines: - substrate = substrate + base64.b64decode(certLine) + nss.nss_init_nodb() - cert = decoder.decode(substrate, asn1Spec=certType)[0] - print cert.prettyPrint() + # Read PEM certs from stdin and print out its components - assert encoder.encode(cert) == substrate, 'cert recode fails' + certlines = sys.stdin.readlines() + cert = ''.join(certlines) - certCnt = certCnt + 1 - state = stSpam + cert = load_certificate(cert) - print '*** %s PEM cert(s) de/serialized' % certCnt + print cert diff --git a/ipapython/nsslib.py b/ipapython/nsslib.py index 1710a7d74..02bff00a8 100644 --- a/ipapython/nsslib.py +++ b/ipapython/nsslib.py @@ -188,7 +188,6 @@ class NSPRConnection(httplib.HTTPConnection): httplib.HTTPConnection.__init__(self, host, port, strict) logging.debug('%s init %s', self.__class__.__name__, host) - nss.nss_init_nodb() self.sock = io.Socket() def connect(self): diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 987203caa..3c536e241 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -124,17 +124,20 @@ def global_init(url): try: if api.env.context == 'server': - # Create a new credentials cache for this Apache process - tmpdir = tempfile.mkdtemp(prefix = "tmp-") - ccache_file = 'FILE:%s/ccache' % tmpdir - krbcontext = krbV.default_context() - principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) - keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) - principal = krbV.Principal(name=principal, context=krbcontext) - os.environ['KRB5CCNAME'] = ccache_file - ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) - ccache.init(principal) - ccache.init_creds_keytab(keytab=keytab, principal=principal) + try: + # Create a new credentials cache for this Apache process + tmpdir = tempfile.mkdtemp(prefix = "tmp-") + ccache_file = 'FILE:%s/ccache' % tmpdir + krbcontext = krbV.default_context() + principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) + keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) + principal = krbV.Principal(name=principal, context=krbcontext) + os.environ['KRB5CCNAME'] = ccache_file + ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) + ccache.init(principal) + ccache.init_creds_keytab(keytab=keytab, principal=principal) + except krbV.Krb5Error, e: + raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e))) conn = _ldap.initialize(url) conn.sasl_interactive_bind_s('', SASL_AUTH) @@ -155,8 +158,9 @@ def global_init(url): except _ldap.SERVER_DOWN: return (None, upg) except _ldap.LDAPError, e: - # TODO: raise a more appropriate exception - _handle_errors(e, **{}) + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + raise StandardError('Unable to retrieve LDAP schema: %s: %s' % (desc, info)) except IndexError: # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' # TODO: DS uses 'cn=schema', support for other server? @@ -821,6 +825,22 @@ class ldap2(CrudBackend, Encoder): """Mark entry inactive.""" self.set_entry_active(dn, False) + def remove_principal_key(self, dn): + """Remove a kerberos principal key.""" + + dn = self.normalize_dn(dn) + + # We need to do this directly using the LDAP library because we + # don't have read access to krbprincipalkey so we need to delete + # it in the blind. + mod = [(_ldap.MOD_REPLACE, 'krbprincipalkey', None), + (_ldap.MOD_REPLACE, 'krblastpwdchange', None)] + + try: + self.conn.modify_s(dn, mod) + except _ldap.LDAPError, e: + self._handle_errors(e, **{}) + # CrudBackend methods def _get_normalized_entry_for_crud(self, dn, attrs_list=None): diff --git a/ipaserver/plugins/selfsign.py b/ipaserver/plugins/selfsign.py index 67b8efef9..39d1c539f 100644 --- a/ipaserver/plugins/selfsign.py +++ b/ipaserver/plugins/selfsign.py @@ -208,12 +208,7 @@ class ra(rabase.rabase): try: # Grab the subject, reverse it, combine it and return it - sub = list(x509.get_subject_components(cert)) - sub.reverse() - subject = "" - for s in sub: - subject = subject + "%s=%s," % (s[0], s[1]) - subject = subject[:-1] + subject = x509.get_subject(cert) serial = x509.get_serial_number(cert) except error.PyAsn1Error, e: diff --git a/lite-server.py b/lite-server.py index ba7cfe3d3..22ff720f9 100755 --- a/lite-server.py +++ b/lite-server.py @@ -72,6 +72,7 @@ if __name__ == '__main__': ) api.env.in_server = True + api.env.startup_traceback = True (options, args) = api.bootstrap_with_global_options(parser, context='lite') api.env._merge( lite_port=options.port, diff --git a/tests/test_xmlrpc/test_cert.py b/tests/test_xmlrpc/test_cert.py index fd3db5939..a99848152 100644 --- a/tests/test_xmlrpc/test_cert.py +++ b/tests/test_xmlrpc/test_cert.py @@ -29,6 +29,10 @@ from ipalib import errors import tempfile from ipapython import ipautil import nose +import base64 + +# So we can save the cert from issuance and compare it later +cert = None # Test setup # @@ -110,14 +114,40 @@ class test_cert(XMLRPC_test): Test the `xmlrpc.cert_request` method with --add. """ # Our host should exist from previous test + global cert csr = unicode(self.generateCSR(self.subject)) res = api.Command['cert_request'](csr, principal=self.service_princ, add=True)['result'] assert res['subject'] == self.subject + # save the cert for the service_show/find tests + cert = res['certificate'] + + def test_3_service_show(self): + """ + Verify that service-show has the right certificate. + """ + global cert + + res = api.Command['service_show'](self.service_princ)['result'] + assert base64.b64encode(res['usercertificate'][0]) == cert + def test_4_service_find(self): + """ + Verify that service-find has the right certificate. + """ + global cert + + # Assume there is only one service + res = api.Command['service_find'](self.service_princ)['result'] + assert base64.b64encode(res[0]['usercertificate'][0]) == cert - def test_3_cleanup(self): + def test_5_cleanup(self): + """ + Clean up cert test data + """ # Now clean things up api.Command['host_del'](self.host_fqdn) - assert(True) + # Verify that the service is gone + res = api.Command['service_find'](self.service_princ) + assert res['count'] == 0 diff --git a/tests/test_xmlrpc/test_host_plugin.py b/tests/test_xmlrpc/test_host_plugin.py index 36e920b09..7ae068c36 100644 --- a/tests/test_xmlrpc/test_host_plugin.py +++ b/tests/test_xmlrpc/test_host_plugin.py @@ -112,6 +112,7 @@ class test_host(Declarative): description=[u'Test host 1'], l=[u'Undisclosed location 1'], krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False ), ), ), @@ -138,6 +139,7 @@ class test_host(Declarative): objectclass=objectclasses.host, managedby=[dn1], ipauniqueid=[fuzzy_uuid], + has_keytab=False ), ), ), @@ -220,6 +222,7 @@ class test_host(Declarative): description=[u'Updated host 1'], l=[u'Undisclosed location 1'], krbprincipalname=[u'host/%s@%s' % (fqdn1, api.env.realm)], + has_keytab=False ), ), ), diff --git a/tests/test_xmlrpc/test_netgroup_plugin.py b/tests/test_xmlrpc/test_netgroup_plugin.py index 3e98f2654..3363883ae 100644 --- a/tests/test_xmlrpc/test_netgroup_plugin.py +++ b/tests/test_xmlrpc/test_netgroup_plugin.py @@ -22,10 +22,18 @@ Test the `ipalib/plugins/netgroup.py` module. """ import sys +import nose +import krbV from xmlrpc_test import XMLRPC_test, assert_attr_equal, assert_is_member from ipalib import api from ipalib import errors +from ipaserver.plugins.ldap2 import ldap2 +# Global so we can save the value between tests +netgroup_dn = None + +# See if our LDAP server is up and we can talk to it over GSSAPI +ccache = krbV.default_context().default_ccache().name class test_netgroup(XMLRPC_test): """ @@ -40,7 +48,7 @@ class test_netgroup(XMLRPC_test): host_localityname = u'Undisclosed location' host_kw = {'fqdn': host_fqdn, 'description': host_description, 'localityname': host_localityname, 'raw': True} - hg_cn = u'ng1' + hg_cn = u'hg1' hg_description = u'Netgroup' hg_kw = {'cn': hg_cn, 'description': hg_description, 'raw': True} @@ -50,6 +58,13 @@ class test_netgroup(XMLRPC_test): user_home = u'/home/%s' % user_uid user_kw = {'givenname': user_givenname,'sn': user_sn,'uid': user_uid,'homedirectory': user_home, 'raw': True} + # user2 is a member of testgroup + user2_uid = u'pexample' + user2_givenname = u'Pete' + user2_sn = u'Example' + user2_home = u'/home/%s' % user2_uid + user2_kw = {'givenname': user2_givenname,'sn': user2_sn,'uid': user2_uid,'homedirectory': user2_home, 'raw': True} + group_cn = u'testgroup' group_description = u'This is a test' group_kw = {'description': group_description,'cn': group_cn} @@ -81,11 +96,22 @@ class test_netgroup(XMLRPC_test): assert_attr_equal(entry, 'givenname', self.user_givenname) assert_attr_equal(entry, 'uid', self.user_uid) + # Add our second user + entry = api.Command['user_add'](**self.user2_kw)['result'] + assert_attr_equal(entry, 'givenname', self.user2_givenname) + assert_attr_equal(entry, 'uid', self.user2_uid) + # Add a group entry = api.Command['group_add'](**self.group_kw)['result'] assert_attr_equal(entry, 'description', self.group_description) assert_attr_equal(entry, 'cn', self.group_cn) + # Add a user to the group + kw = {'raw': True} + kw['user'] = self.user2_uid + res = api.Command['group_add_member'](self.group_cn, **kw) + assert res['completed'] == 1 + def test_3_netgroup_add_member(self): """ Test the `xmlrpc.netgroup_add_member` method. @@ -93,25 +119,25 @@ class test_netgroup(XMLRPC_test): kw = {'raw': True} kw['host'] = self.host_fqdn entry = api.Command['netgroup_add_member'](self.ng_cn, **kw)['result'] - assert_is_member(entry, 'fqdn=%s' % self.host_fqdn) + assert_is_member(entry, 'fqdn=%s' % self.host_fqdn, 'memberhost') kw = {'raw': True} kw['hostgroup'] = self.hg_cn ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 1 - assert_is_member(ret['result'], 'cn=%s' % self.hg_cn) + assert_is_member(ret['result'], 'cn=%s' % self.hg_cn, 'memberhost') kw = {'raw': True} kw['user'] = self.user_uid ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 1 - assert_is_member(ret['result'], 'uid=%s' % self.user_uid) + assert_is_member(ret['result'], 'uid=%s' % self.user_uid, 'memberuser') kw = {'raw': True} kw['group'] = self.group_cn ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 1 - assert_is_member(ret['result'], 'cn=%s' % self.group_cn) + assert_is_member(ret['result'], 'cn=%s' % self.group_cn, 'memberuser') def test_4_netgroup_add_member(self): """ @@ -122,36 +148,36 @@ class test_netgroup(XMLRPC_test): ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'host' in failed['member'] - assert self.host_fqdn in failed['member']['host'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert self.host_fqdn in failed['memberhost']['host'] kw = {'raw': True} kw['hostgroup'] = self.hg_cn ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'hostgroup' in failed['member'] - assert self.hg_cn in failed['member']['hostgroup'] + assert 'memberhost' in failed + assert 'hostgroup' in failed['memberhost'] + assert self.hg_cn in failed['memberhost']['hostgroup'] kw = {'raw': True} kw['user'] = self.user_uid ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'user' in failed['member'] - assert self.user_uid in failed['member']['user'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert self.user_uid in failed['memberuser']['user'] kw = {'raw': True} kw['group'] = self.group_cn ret = api.Command['netgroup_add_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'group' in failed['member'] - assert self.group_cn in failed['member']['group'] + assert 'memberuser' in failed + assert 'group' in failed['memberuser'] + assert self.group_cn in failed['memberuser']['group'] def test_5_netgroup_add_member(self): """ @@ -166,28 +192,69 @@ class test_netgroup(XMLRPC_test): def test_6_netgroup_show(self): """ - Test the `xmlrpc.netgroup_show` method. + Test the `xmlrpc.netgroup_show` method with --all. """ entry = api.Command['netgroup_show'](self.ng_cn, all=True, raw=True)['result'] assert_attr_equal(entry, 'description', self.ng_description) assert_attr_equal(entry, 'cn', self.ng_cn) - assert_is_member(entry, 'fqdn=%s' % self.host_fqdn) - assert_is_member(entry, 'cn=%s' % self.hg_cn) - assert_is_member(entry, 'uid=%s' % self.user_uid) - assert_is_member(entry, 'cn=%s' % self.group_cn) + assert_is_member(entry, 'fqdn=%s' % self.host_fqdn, 'memberhost') + assert_is_member(entry, 'cn=%s' % self.hg_cn, 'memberhost') + assert_is_member(entry, 'uid=%s' % self.user_uid, 'memberuser') + assert_is_member(entry, 'cn=%s' % self.group_cn, 'memberuser') assert_attr_equal(entry, 'objectclass', 'ipaobject') + assert_attr_equal(entry, 'objectclass', 'ipanisnetgroup') + assert_attr_equal(entry, 'objectclass', 'ipaassociation') + + def test_6a_netgroup_show(self): + """ + Test the `xmlrpc.netgroup_show` method. + """ + global netgroup_dn + entry = api.Command['netgroup_show'](self.ng_cn, all=False, raw=True)['result'] + assert_attr_equal(entry, 'description', self.ng_description) + assert_attr_equal(entry, 'cn', self.ng_cn) + assert_is_member(entry, 'fqdn=%s' % self.host_fqdn, 'memberhost') + assert_is_member(entry, 'cn=%s' % self.hg_cn, 'memberhost') + assert_is_member(entry, 'uid=%s' % self.user_uid, 'memberuser') + assert_is_member(entry, 'cn=%s' % self.group_cn, 'memberuser') + netgroup_dn = entry['dn'] + + def test_6b_netgroup_show(self): + """ + Confirm the underlying triples + """ + # Do an LDAP query to the compat area and verify that the entry + # is correct + conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri, base_dn=api.env.basedn) + conn.connect(ccache=ccache) + try: + entries = conn.find_entries('cn=%s' % self.ng_cn, + base_dn='cn=ng,cn=compat,%s' % api.env.basedn) + except errors.NotFound: + raise nose.SkipTest('compat and nis are not enabled, skipping test') + finally: + conn.disconnect() + triples = entries[0][0][1]['nisnetgrouptriple'] + + # This may not prove to be reliable since order is not guaranteed + # and even which user gets into which triple can be random. + assert '(nosuchhost,jexample,example.com)' in triples + assert '(ipatesthost.%s,pexample,example.com)' % api.env.domain in triples def test_7_netgroup_find(self): """ - Test the `xmlrpc.hostgroup_find` method. + Test the `xmlrpc.netgroup_find` method. """ - entries = api.Command.netgroup_find(self.ng_cn, raw=True)['result'] + result = api.Command.netgroup_find(self.ng_cn, raw=True) + entries = result['result'] + + assert(result['count'] == 1) assert_attr_equal(entries[0], 'description', self.ng_description) assert_attr_equal(entries[0], 'cn', self.ng_cn) def test_8_netgroup_mod(self): """ - Test the `xmlrpc.hostgroup_mod` method. + Test the `xmlrpc.netgroup_mod` method. """ newdesc = u'Updated host group' modkw = {'cn': self.ng_cn, 'description': newdesc, 'raw': True} @@ -201,7 +268,7 @@ class test_netgroup(XMLRPC_test): def test_9_netgroup_remove_member(self): """ - Test the `xmlrpc.hostgroup_remove_member` method. + Test the `xmlrpc.netgroup_remove_member` method. """ kw = {'raw': True} kw['host'] = self.host_fqdn @@ -232,18 +299,18 @@ class test_netgroup(XMLRPC_test): ret = api.Command['netgroup_remove_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'host' in failed['member'] - assert self.host_fqdn in failed['member']['host'] + assert 'memberhost' in failed + assert 'host' in failed['memberhost'] + assert self.host_fqdn in failed['memberhost']['host'] kw = {'raw': True} kw['hostgroup'] = self.hg_cn ret = api.Command['netgroup_remove_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'hostgroup' in failed['member'] - assert self.hg_cn in failed['member']['hostgroup'] + assert 'memberhost' in failed + assert 'hostgroup' in failed['memberhost'] + assert self.hg_cn in failed['memberhost']['hostgroup'] kw = {'raw': True} kw['user'] = self.user_uid @@ -251,18 +318,18 @@ class test_netgroup(XMLRPC_test): ret = api.Command['netgroup_remove_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'user' in failed['member'] - assert self.user_uid in failed['member']['user'] + assert 'memberuser' in failed + assert 'user' in failed['memberuser'] + assert self.user_uid in failed['memberuser']['user'] kw = {'raw': True} kw['group'] = self.group_cn ret = api.Command['netgroup_remove_member'](self.ng_cn, **kw) assert ret['completed'] == 0 failed = ret['failed'] - assert 'member' in failed - assert 'group' in failed['member'] - assert self.group_cn in failed['member']['group'] + assert 'memberuser' in failed + assert 'group' in failed['memberuser'] + assert self.group_cn in failed['memberuser']['group'] def test_b_netgroup_del(self): """ @@ -304,8 +371,9 @@ class test_netgroup(XMLRPC_test): else: assert False - # Remove the user + # Remove the users assert api.Command['user_del'](self.user_uid)['result'] is True + assert api.Command['user_del'](self.user2_uid)['result'] is True # Verify that it is gone try: diff --git a/tests/test_xmlrpc/test_service_plugin.py b/tests/test_xmlrpc/test_service_plugin.py index 432a86b0e..299c64fc1 100644 --- a/tests/test_xmlrpc/test_service_plugin.py +++ b/tests/test_xmlrpc/test_service_plugin.py @@ -93,6 +93,7 @@ class test_service(XMLRPC_test): """ entry = api.Command['service_show'](self.principal)['result'] assert_attr_equal(entry, 'krbprincipalname', self.principal) + assert(entry['has_keytab'] == False) def test_6_service_find(self): """ diff --git a/tests/test_xmlrpc/xmlrpc_test.py b/tests/test_xmlrpc/xmlrpc_test.py index 61fca50aa..1966edf93 100644 --- a/tests/test_xmlrpc/xmlrpc_test.py +++ b/tests/test_xmlrpc/xmlrpc_test.py @@ -46,6 +46,8 @@ try: res = api.Command['user_show'](u'notfound') except errors.NetworkError: server_available = False +except IOError: + server_available = False except errors.NotFound: server_available = True |