mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
parent
585540e0a2
commit
c3f9ec14d9
@ -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";)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user