Make the IPA server host and its services "real" IPA entries

We use kadmin.local to bootstrap the creation of the kerberos principals
for the IPA server machine: host, HTTP and ldap. This works fine and has
the side-effect of protecting the services from modification by an
admin (which would likely break the server).

Unfortunately this also means that the services can't be managed by useful
utilities such as certmonger. So we have to create them as "real" services
instead.
This commit is contained in:
Rob Crittenden 2009-12-07 23:17:00 -05:00 committed by Jason Gerard DeRose
parent 7105a0c0d6
commit 766b534da0
11 changed files with 146 additions and 24 deletions

View File

@ -8,7 +8,7 @@ aci: (targetattr != "userPassword || krbPrincipalKey || sambaLMPassword || samba
aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword")(version 3.0; acl "Self can write own password"; allow (write) userdn="ldap:///self";)
aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Admins can write passwords"; allow (add,delete,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)
aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Password change service can read/write passwords"; allow (read, write) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";)
aci: (targetattr = "userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "KDC System Account can access passwords"; allow (all) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr = "userPassword || krbPrincipalKey || krbPasswordExpiration || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "KDC System Account can access passwords"; allow (all) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr = "krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "KDC System Account can update some fields"; allow (write) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetattr = "krbPrincipalName || krbUPEnabled || krbMKey || krbTicketPolicyReference || krbPrincipalExpiration || krbPasswordExpiration || krbPwdPolicyReference || krbPrincipalType || krbPwdHistory || krbLastPwdChange || krbPrincipalAliases || krbExtraData || krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCount")(version 3.0; acl "Only the KDC System Account has access to kerberos material"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";)
aci: (targetfilter = "(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfNames)(objectClass=posixGroup))")(targetattr != "aci || userPassword || krbPrincipalKey || sambaLMPassword || sambaNTPassword || passwordHistory")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add, delete, read, write) groupdn = "ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";)

View File

@ -200,7 +200,7 @@ def install_http(config):
config.dir + "/http_pin.txt")
http = httpinstance.HTTPInstance()
http.create_instance(config.realm_name, config.host_name, config.domain_name, False, pkcs12_info, self_signed_ca=True)
http.create_instance(config.realm_name, config.host_name, config.domain_name, config.dirman_password, False, pkcs12_info, self_signed_ca=True)
# Now copy the autoconfiguration files
if ipautil.file_exists(config.dir + "/preferences.html"):
@ -347,6 +347,10 @@ def main():
CA.fix_ra_perms()
service.restart("httpd")
# The DS instance is created before the keytab, add the SSL cert we
# generated
ds.add_cert_to_service()
# Create the management framework config file
fd = open("/etc/ipa/default.conf", "w")
fd.write("[global]\n")

View File

@ -729,6 +729,10 @@ def main():
krb = krbinstance.KrbInstance(fstore)
krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password)
# The DS instance is created before the keytab, add the SSL cert we
# generated
ds.add_cert_to_service()
# Render webui assets:
ipautil.run(["/sbin/restorecon", ASSETS_DIR])
render_assets()
@ -743,10 +747,10 @@ def main():
http = httpinstance.HTTPInstance(fstore)
if options.http_pkcs12:
pkcs12_info = (options.http_pkcs12, pw_name)
http.create_instance(realm_name, host_name, domain_name, autoconfig=False, pkcs12_info=pkcs12_info)
http.create_instance(realm_name, host_name, domain_name, dm_password, autoconfig=False, pkcs12_info=pkcs12_info)
os.remove(pw_name)
else:
http.create_instance(realm_name, host_name, domain_name, autoconfig=True, self_signed_ca=not options.ca)
http.create_instance(realm_name, host_name, domain_name, dm_password, autoconfig=True, self_signed_ca=not options.ca)
ipautil.run(["/sbin/restorecon", "/var/cache/ipa/sessions"])
# Create the management framework config file

View File

@ -201,6 +201,7 @@ class BindInstance(service.Service):
# Store the keytab on disk
self.fstore.backup_file("/etc/named.keytab")
installutils.create_keytab("/etc/named.keytab", dns_principal)
dns_principal = self.move_service(dns_principal)
# Make sure access is strictly reserved to the named user
pent = pwd.getpwnam(self.named_user)
@ -220,17 +221,8 @@ class BindInstance(service.Service):
logging.critical("Could not connect to the Directory Server on %s" % self.fqdn)
raise e
dns_princ_dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (dns_principal, self.realm, self.suffix)
mod = [(ldap.MOD_ADD, 'objectClass', 'ipaService')]
try:
conn.modify_s(dns_princ_dn, mod)
except Exception, e:
logging.critical("Could not modify principal's %s entry" % dns_principal)
raise e
dns_group = "cn=dnsserver,cn=rolegroups,cn=accounts,%s" % self.suffix
mod = [(ldap.MOD_ADD, 'member', dns_princ_dn)]
mod = [(ldap.MOD_ADD, 'member', dns_principal)]
try:
conn.modify_s(dns_group, mod)

View File

@ -810,7 +810,7 @@ class CAInstance(service.Service):
os.close(f)
os.chmod(self.ra_agent_pwd, stat.S_IRUSR)
stdout, stderr = self.__run_certutil(["-N"])
(stdout, stderr, returncode) = self.__run_certutil(["-N"])
def __get_ca_chain(self):
try:
@ -886,7 +886,7 @@ class CAInstance(service.Service):
# Generate our CSR. The result gets put into stdout
try:
(stdout, stderr) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", "CN=RA Subsystem Certificate,OU=pki-ipa,O=%s" % self.domain_name, "-z", noise_name, "-a"])
(stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", "CN=RA Subsystem Certificate,OU=pki-ipa,O=%s" % self.domain_name, "-z", noise_name, "-a"])
finally:
os.remove(noise_name)

View File

@ -26,6 +26,7 @@ import urllib
import xml.dom.minidom
import pwd
import fcntl
import base64
from ipapython import nsslib
from ipapython import sysrestore
@ -459,9 +460,20 @@ class CertDB(object):
(out, err) = self.request_cert(subject)
cdb.issue_server_cert(self.certreq_fname, self.certder_fname)
self.add_cert(self.certder_fname, nickname)
fd = open(self.certder_fname, "r")
dercert = fd.read()
fd.close()
os.unlink(self.certreq_fname)
os.unlink(self.certder_fname)
# On the off-chance the certificate is base64-encoded
try:
dercert = base64.b64decode(dercert)
except:
pass
return dercert
def create_signing_cert(self, nickname, hostname, other_certdb=None, subject=None):
cdb = other_certdb
if not cdb:

View File

@ -146,6 +146,7 @@ class DsInstance(service.Service):
self.host_name = None
self.pkcs12_info = None
self.ds_user = None
self.dercert = None
if realm_name:
self.suffix = util.realm_to_suffix(self.realm_name)
self.__setup_sub_dict()
@ -164,6 +165,7 @@ class DsInstance(service.Service):
self.self_signed_ca = self_signed_ca
self.uidstart = uidstart
self.gidstart = gidstart
self.principal = "ldap/%s@%s" % (self.host_name, self.realm_name)
self.__setup_sub_dict()
self.step("creating directory server user", self.__create_ds_user)
@ -203,7 +205,7 @@ class DsInstance(service.Service):
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root, DOMAIN=self.domain,
TIME=int(time.time()), UIDSTART=self.uidstart,
GIDSTART=self.gidstart)
GIDSTART=self.gidstart, HOST=self.host_name)
def __create_ds_user(self):
user_exists = True
@ -335,19 +337,20 @@ class DsInstance(service.Service):
# We only handle one server cert
nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(nickname)
else:
nickname = "Server-Cert"
cadb = certs.CertDB(httpinstance.NSS_DIR, host_name=self.host_name)
if self.self_signed_ca:
cadb.create_self_signed()
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
self.dercert = dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
dsdb.create_pin_file()
else:
# FIXME, need to set this nickname in the RA plugin
cadb.export_ca_cert('ipaCert', False)
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
self.dercert = dsdb.create_server_cert("Server-Cert", self.host_name, cadb)
dsdb.create_pin_file()
conn = ipaldap.IPAdmin("127.0.0.1")

View File

@ -30,6 +30,7 @@ import dsinstance
import installutils
from ipapython import sysrestore
from ipapython import ipautil
from ipalib import util
HTTPD_DIR = "/etc/httpd"
SSL_CONF = HTTPD_DIR + "/conf.d/ssl.conf"
@ -55,12 +56,16 @@ class HTTPInstance(service.Service):
else:
self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')
def create_instance(self, realm, fqdn, domain_name, autoconfig=True, pkcs12_info=None, self_signed_ca=False):
def create_instance(self, realm, fqdn, domain_name, dm_password=None, autoconfig=True, pkcs12_info=None, self_signed_ca=False):
self.fqdn = fqdn
self.realm = realm
self.domain = domain_name
self.dm_password = dm_password
self.suffix = util.realm_to_suffix(self.realm)
self.pkcs12_info = pkcs12_info
self.self_signed_ca = self_signed_ca
self.principal = "HTTP/%s@%s" % (self.fqdn, self.realm)
self.dercert = None
self.sub_dict = { "REALM" : realm, "FQDN": fqdn, "DOMAIN" : self.domain }
self.step("disabling mod_ssl in httpd", self.__disable_mod_ssl)
@ -68,11 +73,11 @@ class HTTPInstance(service.Service):
self.step("Setting mod_nss password file", self.__set_mod_nss_passwordfile)
self.step("Adding URL rewriting rules", self.__add_include)
self.step("configuring httpd", self.__configure_http)
self.step("creating a keytab for httpd", self.__create_http_keytab)
self.step("Setting up ssl", self.__setup_ssl)
if autoconfig:
self.step("Setting up browser autoconfig", self.__setup_autoconfig)
self.step("publish CA cert", self.__publish_ca_cert)
self.step("creating a keytab for httpd", self.__create_http_keytab)
self.step("configuring SELinux for httpd", self.__selinux_config)
self.step("restarting httpd", self.__start)
self.step("configuring httpd to start on boot", self.__enable)
@ -117,6 +122,8 @@ class HTTPInstance(service.Service):
http_principal = "HTTP/" + self.fqdn + "@" + self.realm
installutils.kadmin_addprinc(http_principal)
installutils.create_keytab("/etc/httpd/conf/ipa.keytab", http_principal)
self.move_service(http_principal)
self.add_cert_to_service()
pent = pwd.getpwnam("apache")
os.chown("/etc/httpd/conf/ipa.keytab", pent.pw_uid, pent.pw_gid)
@ -170,16 +177,17 @@ class HTTPInstance(service.Service):
db.create_password_conf()
# We only handle one server cert
nickname = server_certs[0][0]
self.dercert = db.get_cert_from_db(nickname)
self.__set_mod_nss_nickname(nickname)
else:
if self.self_signed_ca:
db.create_from_cacert(ca_db.cacert_fname)
db.create_password_conf()
db.create_server_cert("Server-Cert", self.fqdn, ca_db)
self.dercert = db.create_server_cert("Server-Cert", self.fqdn, ca_db)
db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
else:
db.create_server_cert("Server-Cert", self.fqdn, ca_db)
self.dercert = db.create_server_cert("Server-Cert", self.fqdn, ca_db)
db.create_signing_cert("Signing-Cert", "Object Signing Cert", ca_db)
db.create_password_conf()

View File

@ -32,6 +32,7 @@ from ipapython import sysrestore
from ipapython import ipautil
from ipalib import util
from ipalib import errors
from ipalib import uuid
from ipaserver import ipaldap
@ -91,6 +92,40 @@ class KrbInstance(service.Service):
else:
self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')
def move_service_to_host(self, principal):
"""
Used to move a host/ service principal created by kadmin.local from
cn=kerberos to reside under the host entry.
"""
conn = None
service_dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (principal, self.realm, self.suffix)
try:
conn = ipaldap.IPAdmin("127.0.0.1")
conn.simple_bind_s("cn=directory manager", self.admin_password)
except Exception, e:
logging.critical("Could not connect to the Directory Server on %s" % self.fqdn)
raise e
service_entry = conn.getEntry(service_dn, ldap.SCOPE_BASE)
conn.deleteEntry(service_dn)
# 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.setValue('krbextradata', service_entry.getValue('krbextradata'))
host_entry.setValue('krblastpwdchange', service_entry.getValue('krblastpwdchange'))
host_entry.setValue('krbpasswordexpiration', service_entry.getValue('krbpasswordexpiration'))
host_entry.setValue('krbprincipalname', service_entry.getValue('krbprincipalname'))
host_entry.setValue('krbticketflags', service_entry.getValue('krbticketflags'))
host_entry.setValue('krbprincipalkey', service_entry.getValue('krbprincipalkey'))
host_entry.setValue('serverhostname', self.fqdn.split('.',1)[0])
host_entry.setValue('cn', self.fqdn)
host_entry.setValue('fqdn', self.fqdn)
host_entry.setValue('ipauniqueid', str(uuid.uuid1()))
conn.addEntry(host_entry)
conn.unbind()
def __common_setup(self, ds_user, realm_name, host_name, domain_name, admin_password):
self.ds_user = ds_user
self.fqdn = host_name
@ -404,6 +439,7 @@ class KrbInstance(service.Service):
def __create_ds_keytab(self):
ldap_principal = "ldap/" + self.fqdn + "@" + self.realm
installutils.kadmin_addprinc(ldap_principal)
self.move_service(ldap_principal)
self.fstore.backup_file("/etc/dirsrv/ds.keytab")
installutils.create_keytab("/etc/dirsrv/ds.keytab", ldap_principal)
@ -424,6 +460,8 @@ class KrbInstance(service.Service):
os.chown("/etc/krb5.keytab", 0, 0)
os.chmod("/etc/krb5.keytab", 0600)
self.move_service_to_host(host_principal)
def __export_kadmin_changepw_keytab(self):
installutils.kadmin_modprinc("kadmin/changepw", "+requires_preauth")

View File

@ -28,7 +28,7 @@ import sys
from ipaserver.install import installutils
from ipaserver import ipaldap
from ipapython import entity, ipautil
from ipalib import util
from ipalib import util, uuid
from ipalib import errors
import ldap
import logging
@ -124,6 +124,7 @@ class LDAPUpdate:
def _template_str(self, s):
try:
self.sub_dict["UUID"] = str(uuid.uuid1())
return ipautil.template_str(s, self.sub_dict)
except KeyError, e:
raise BadSyntax("Unknown template keyword %s" % e)

View File

@ -22,6 +22,10 @@ import os
import tempfile
from ipapython import sysrestore
from ipapython import ipautil
from ipalib import uuid, errors
import ldap
from ipaserver import ipaldap
import base64
def stop(service_name, instance_name=""):
@ -98,6 +102,7 @@ class Service:
path = ipautil.SHARE_DIR + ldif
if sub_dict is not None:
sub_dict['UUID'] = str(uuid.uuid1())
txt = ipautil.template_file(path, sub_dict)
fd = ipautil.write_tmp_file(txt)
path = fd.name
@ -120,6 +125,61 @@ class Service:
if fd is not None:
fd.close()
def move_service(self, principal):
"""
Used to move a principal entry created by kadmin.local from
cn=kerberos to cn=services
"""
dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (principal, self.realm, self.suffix)
try:
conn = ipaldap.IPAdmin("127.0.0.1")
conn.simple_bind_s("cn=directory manager", self.dm_password)
except Exception, e:
logging.critical("Could not connect to the Directory Server on %s: %s" % (self.fqdn, str(e)))
raise e
try:
entry = conn.getEntry(dn, ldap.SCOPE_BASE)
except errors.NotFound:
# There is no service in the wrong location, nothing to do.
# This can happen when installing a replica
conn.unbind()
return
newdn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix)
conn.deleteEntry(dn)
entry.dn = newdn
classes = entry.getValues("objectclass")
classes = classes + ["ipaobject", "ipaservice", "pkiuser"]
entry.setValues("objectclass", list(set(classes)))
entry.setValue("ipauniqueid", str(uuid.uuid1()))
conn.addEntry(entry)
conn.unbind()
return newdn
def add_cert_to_service(self):
"""
Add a certificate to a service
This should be passed in DER format but we'll be nice and convert
a base64-encoded cert if needed.
"""
try:
self.dercert = base64.b64decode(self.dercert)
except Exception:
pass
dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (self.principal, self.suffix)
try:
conn = ipaldap.IPAdmin("127.0.0.1")
conn.simple_bind_s("cn=directory manager", self.dm_password)
except Exception, e:
logging.critical("Could not connect to the Directory Server on %s: %s" % (self.fqdn, str(e)))
raise e
mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)]
try:
conn.modify_s(dn, mod)
except Exception, e:
logging.critical("Could not add certificate to service %s entry: %s" % (self.principal, str(e)))
conn.unbind()
def set_output(self, fd):
self.output_fd = fd