diff options
-rw-r--r-- | install/share/default-aci.ldif | 6 | ||||
-rw-r--r-- | ipalib/plugins/cert.py | 43 | ||||
-rw-r--r-- | ipalib/plugins/host.py | 25 | ||||
-rw-r--r-- | ipaserver/install/krbinstance.py | 3 | ||||
-rw-r--r-- | ipaserver/plugins/join.py | 11 |
5 files changed, 71 insertions, 17 deletions
diff --git a/install/share/default-aci.ldif b/install/share/default-aci.ldif index 784377e9f..9c058ae58 100644 --- a/install/share/default-aci.ldif +++ b/install/share/default-aci.ldif @@ -43,3 +43,9 @@ changetype: modify add: aci aci: (targetattr=userCertificate)(version 3.0; aci "Hosts can modify service userCertificate"; allow(write) userattr = "parent[0,1].managedby#USERDN";) +# Allow hosts to update their own certificate in host/ +dn: cn=computers,cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr="userCertificate")(version 3.0; aci "Hosts can modify service userCertificate"; allow(write) userdn = "ldap:///self";) + diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index 5540e6ecf..21d0ebcdb 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -40,6 +40,7 @@ from pyasn1.error import PyAsn1Error import logging import traceback from ipalib.request import ugettext as _ +from ipalib.request import context def get_serial(certificate): """ @@ -154,8 +155,10 @@ class cert_request(VirtualCommand): taskgroup (directly or indirectly via role membership). """ + bind_principal = getattr(context, 'principal') # Can this user request certs? - self.check_access() + if not bind_principal.startswith('host/'): + self.check_access() # FIXME: add support for subject alt name @@ -170,7 +173,17 @@ class cert_request(VirtualCommand): # See if the service exists and punt if it doesn't and we aren't # going to add it try: - (dn, service) = api.Command['service_show'](principal, all=True, raw=True) + if not principal.startswith('host/'): + service = api.Command['service_show'](principal, all=True, raw=True) + dn = service['dn'] + else: + realm = principal.find('@') + if realm == -1: + realm = len(principal) + hostname = principal[5:realm] + + service = api.Command['host_show'](hostname, all=True, raw=True)['result'] + dn = service['dn'] if 'usercertificate' in service: # FIXME, what to do here? Do we revoke the old cert? raise errors.CertificateOperationError(error=_('entry already has a certificate, serial number %s') % get_serial(base64.b64encode(service['usercertificate'][0]))) @@ -178,7 +191,8 @@ class cert_request(VirtualCommand): if not add: raise errors.NotFound(reason="The service principal for this request doesn't exist.") try: - (dn, service) = api.Command['service_add'](principal, **{}) + service = api.Command['service_add'](principal, **{}) + dn = service['dn'] except errors.ACIError: raise errors.ACIError(info='You need to be a member of the serviceadmin role to add services') @@ -191,7 +205,8 @@ class cert_request(VirtualCommand): if subjectaltname is not None: for name in subjectaltname: try: - (hostdn, hostentry) = api.Command['host_show'](name, all=True, raw=True) + hostentry = api.Command['host_show'](name, all=True, raw=True)['result'] + hostdn = hostentry['dn'] except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this @@ -206,11 +221,21 @@ class cert_request(VirtualCommand): result = self.Backend.ra.request_certificate(csr, **kw) # Success? Then add it to the service entry. - if result.get('status') == 0: - skw = {"usercertificate": str(result.get('certificate'))} - api.Command['service_mod'](principal, **skw) - - return result + if 'certificate' in result: + if not principal.startswith('host/'): + 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] + skw = {"usercertificate": str(result.get('certificate'))} + api.Command['host_mod'](hostname, **skw) + + return dict( + result=result + ) def output_for_cli(self, textui, result, *args, **kw): if isinstance(result, dict) and len(result) > 0: diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index dd19362bd..3d59be7cf 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -26,10 +26,12 @@ import os import sys from ipalib import api, errors, util -from ipalib import Str, Flag +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 import _, ngettext +import base64 def validate_host(ugettext, fqdn): @@ -48,11 +50,11 @@ class host(LDAPObject): container_dn = api.env.container_host object_name = 'host' object_name_plural = 'hosts' - object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser'] + object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser', 'ipaservice'] # object_class_config = 'ipahostobjectclasses' default_attributes = [ 'fqdn', 'description', 'localityname', 'nshostlocation', - 'nshardwareplatform', 'nsosversion' + 'nshardwareplatform', 'nsosversion', 'usercertificate', ] uuid_attribute = 'ipauniqueid' attribute_names = { @@ -107,6 +109,10 @@ class host(LDAPObject): label='User password', doc='Password used in bulk enrollment', ), + Bytes('usercertificate?', validate_certificate, + cli_name='certificate', + doc='base-64 encoded server certificate', + ), ) def get_dn(self, *keys, **options): @@ -148,6 +154,7 @@ class host_add(LDAPCreate): entry_attrs['objectclass'].append('krbprincipal') elif 'krbprincipalaux' in entry_attrs['objectclass']: entry_attrs['objectclass'].remove('krbprincipalaux') + entry_attrs['managedby'] = dn return dn api.register(host_add) @@ -209,6 +216,18 @@ class host_mod(LDAPUpdate): if 'krbprincipalaux' not in obj_classes: obj_classes.append('krbprincipalaux') entry_attrs['objectclass'] = obj_classes + cert = entry_attrs.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']) + ) + raise errors.GenericError(format=fmt) + # FIXME: should be in normalizer; see service_add + entry_attrs['usercertificate'] = base64.b64decode(cert) + return dn api.register(host_mod) diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index c4a20b545..71aeeb207 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -112,7 +112,7 @@ class KrbInstance(service.Service): # Create a host entry for this master host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) host_entry = ipaldap.Entry(host_dn) - host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux']) + host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux']) host_entry.setValue('krbextradata', service_entry.getValue('krbextradata')) host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange')) host_entry.setValue('krbpasswordexpiration', service_entry.getValue('krbpasswordexpiration')) @@ -123,6 +123,7 @@ class KrbInstance(service.Service): host_entry.setValue('cn', self.fqdn) host_entry.setValue('fqdn', self.fqdn) host_entry.setValue('ipauniqueid', str(uuid.uuid1())) + host_entry.setValue('managedby', host_dn) conn.addEntry(host_entry) conn.unbind() diff --git a/ipaserver/plugins/join.py b/ipaserver/plugins/join.py index 34f4c58cb..fe9f88dd9 100644 --- a/ipaserver/plugins/join.py +++ b/ipaserver/plugins/join.py @@ -91,13 +91,14 @@ class join(Command): try: # First see if the host exists kw = {'fqdn': hostname, 'all': True} - (dn, attrs_list) = api.Command['host_show'](**kw) + attrs_list = api.Command['host_show'](**kw)['result'] + dn = attrs_list['dn'] # If no principal name is set yet we need to try to add # one. if 'krbprincipalname' not in attrs_list: service = "host/%s@%s" % (hostname, api.env.realm) - (d, a) = api.Command['host_mod'](hostname, krbprincipalname=service) + api.Command['host_mod'](hostname, krbprincipalname=service) # It exists, can we write the password attributes? allowed = ldap.can_write(dn, 'krblastpwdchange') @@ -105,9 +106,11 @@ class join(Command): raise errors.ACIError(info="Insufficient 'write' privilege to the 'krbLastPwdChange' attribute of entry '%s'." % dn) kw = {'fqdn': hostname, 'all': True} - (dn, attrs_list) = api.Command['host_show'](**kw) + attrs_list = api.Command['host_show'](**kw)['result'] + dn = attrs_list['dn'] except errors.NotFound: - (dn, attrs_list) = api.Command['host_add'](hostname) + attrs_list = api.Command['host_add'](hostname)['result'] + dn = attrs_list['dn'] return (dn, attrs_list) |