Make hosts more like real services so we can issue certs for host principals

This patch should make joining a client to the domain and using certmonger
to get an initial certificate work.
This commit is contained in:
Rob Crittenden 2009-12-16 16:04:53 -05:00 committed by Jason Gerard DeRose
parent 585540e0a2
commit c3f9ec14d9
5 changed files with 70 additions and 16 deletions

View File

@ -43,3 +43,9 @@ changetype: modify
add: aci add: aci
aci: (targetattr=userCertificate)(version 3.0; aci "Hosts can modify service userCertificate"; allow(write) userattr = "parent[0,1].managedby#USERDN";) 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";)

View File

@ -40,6 +40,7 @@ from pyasn1.error import PyAsn1Error
import logging import logging
import traceback import traceback
from ipalib.request import ugettext as _ from ipalib.request import ugettext as _
from ipalib.request import context
def get_serial(certificate): def get_serial(certificate):
""" """
@ -154,7 +155,9 @@ class cert_request(VirtualCommand):
taskgroup (directly or indirectly via role membership). taskgroup (directly or indirectly via role membership).
""" """
bind_principal = getattr(context, 'principal')
# Can this user request certs? # Can this user request certs?
if not bind_principal.startswith('host/'):
self.check_access() self.check_access()
# FIXME: add support for subject alt name # 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 # See if the service exists and punt if it doesn't and we aren't
# going to add it # going to add it
try: 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: if 'usercertificate' in service:
# FIXME, what to do here? Do we revoke the old cert? # 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]))) 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: if not add:
raise errors.NotFound(reason="The service principal for this request doesn't exist.") raise errors.NotFound(reason="The service principal for this request doesn't exist.")
try: try:
(dn, service) = api.Command['service_add'](principal, **{}) service = api.Command['service_add'](principal, **{})
dn = service['dn']
except errors.ACIError: except errors.ACIError:
raise errors.ACIError(info='You need to be a member of the serviceadmin role to add services') 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: if subjectaltname is not None:
for name in subjectaltname: for name in subjectaltname:
try: 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: except errors.NotFound:
# We don't want to issue any certificates referencing # We don't want to issue any certificates referencing
# machines we don't know about. Nothing is stored in this # 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) result = self.Backend.ra.request_certificate(csr, **kw)
# Success? Then add it to the service entry. # Success? Then add it to the service entry.
if result.get('status') == 0: if 'certificate' in result:
if not principal.startswith('host/'):
skw = {"usercertificate": str(result.get('certificate'))} skw = {"usercertificate": str(result.get('certificate'))}
api.Command['service_mod'](principal, **skw) 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 result return dict(
result=result
)
def output_for_cli(self, textui, result, *args, **kw): def output_for_cli(self, textui, result, *args, **kw):
if isinstance(result, dict) and len(result) > 0: if isinstance(result, dict) and len(result) > 0:

View File

@ -26,10 +26,12 @@ import os
import sys import sys
from ipalib import api, errors, util 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.baseldap import *
from ipalib.plugins.service import split_principal from ipalib.plugins.service import split_principal
from ipalib.plugins.service import validate_certificate
from ipalib import _, ngettext from ipalib import _, ngettext
import base64
def validate_host(ugettext, fqdn): def validate_host(ugettext, fqdn):
@ -48,11 +50,11 @@ class host(LDAPObject):
container_dn = api.env.container_host container_dn = api.env.container_host
object_name = 'host' object_name = 'host'
object_name_plural = 'hosts' object_name_plural = 'hosts'
object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser'] object_class = ['ipaobject', 'nshost', 'ipahost', 'pkiuser', 'ipaservice']
# object_class_config = 'ipahostobjectclasses' # object_class_config = 'ipahostobjectclasses'
default_attributes = [ default_attributes = [
'fqdn', 'description', 'localityname', 'nshostlocation', 'fqdn', 'description', 'localityname', 'nshostlocation',
'nshardwareplatform', 'nsosversion' 'nshardwareplatform', 'nsosversion', 'usercertificate',
] ]
uuid_attribute = 'ipauniqueid' uuid_attribute = 'ipauniqueid'
attribute_names = { attribute_names = {
@ -107,6 +109,10 @@ class host(LDAPObject):
label='User password', label='User password',
doc='Password used in bulk enrollment', 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): def get_dn(self, *keys, **options):
@ -148,6 +154,7 @@ class host_add(LDAPCreate):
entry_attrs['objectclass'].append('krbprincipal') entry_attrs['objectclass'].append('krbprincipal')
elif 'krbprincipalaux' in entry_attrs['objectclass']: elif 'krbprincipalaux' in entry_attrs['objectclass']:
entry_attrs['objectclass'].remove('krbprincipalaux') entry_attrs['objectclass'].remove('krbprincipalaux')
entry_attrs['managedby'] = dn
return dn return dn
api.register(host_add) api.register(host_add)
@ -209,6 +216,18 @@ class host_mod(LDAPUpdate):
if 'krbprincipalaux' not in obj_classes: if 'krbprincipalaux' not in obj_classes:
obj_classes.append('krbprincipalaux') obj_classes.append('krbprincipalaux')
entry_attrs['objectclass'] = obj_classes 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 return dn
api.register(host_mod) api.register(host_mod)

View File

@ -112,7 +112,7 @@ class KrbInstance(service.Service):
# Create a host entry for this master # Create a host entry for this master
host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix)
host_entry = ipaldap.Entry(host_dn) 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('krbextradata', service_entry.getValue('krbextradata'))
host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange')) host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange'))
host_entry.setValue('krbpasswordexpiration', service_entry.getValue('krbpasswordexpiration')) 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('cn', self.fqdn)
host_entry.setValue('fqdn', self.fqdn) host_entry.setValue('fqdn', self.fqdn)
host_entry.setValue('ipauniqueid', str(uuid.uuid1())) host_entry.setValue('ipauniqueid', str(uuid.uuid1()))
host_entry.setValue('managedby', host_dn)
conn.addEntry(host_entry) conn.addEntry(host_entry)
conn.unbind() conn.unbind()

View File

@ -91,13 +91,14 @@ class join(Command):
try: try:
# First see if the host exists # First see if the host exists
kw = {'fqdn': hostname, 'all': True} 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 # If no principal name is set yet we need to try to add
# one. # one.
if 'krbprincipalname' not in attrs_list: if 'krbprincipalname' not in attrs_list:
service = "host/%s@%s" % (hostname, api.env.realm) 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? # It exists, can we write the password attributes?
allowed = ldap.can_write(dn, 'krblastpwdchange') 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) raise errors.ACIError(info="Insufficient 'write' privilege to the 'krbLastPwdChange' attribute of entry '%s'." % dn)
kw = {'fqdn': hostname, 'all': True} 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: 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) return (dn, attrs_list)