mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-26 17:01:14 -06:00
32599987fd
The Service class now accepts keytab path and service name part of Kerberos principal as members. Kerberos principal is turned into a property computed from service prefix, FQDN and realm. the handling of Kerberos principals and keytabs in service installers was changed to use class members instead of copy-pasted constants. This shall aid in the future refactoring of principal/keytab handling code. https://fedorahosted.org/freeipa/ticket/6392 Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
490 lines
17 KiB
Python
490 lines
17 KiB
Python
# Authors: Ade Lee <alee@redhat.com>
|
|
#
|
|
# Copyright (C) 2014 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, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# 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, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import base64
|
|
import binascii
|
|
import ldap
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import traceback
|
|
import dbus
|
|
import pwd
|
|
|
|
from pki.client import PKIConnection
|
|
import pki.system
|
|
|
|
from ipalib import errors
|
|
|
|
from ipaplatform import services
|
|
from ipaplatform.constants import constants
|
|
from ipaplatform.paths import paths
|
|
from ipapython import certmonger
|
|
from ipapython import ipaldap
|
|
from ipapython import ipautil
|
|
from ipapython.dn import DN
|
|
from ipaserver.install import service
|
|
from ipaserver.install import installutils
|
|
from ipaserver.install import replication
|
|
from ipaserver.install.installutils import stopped_service
|
|
from ipapython.ipa_log_manager import log_mgr
|
|
|
|
HTTPD_USER = constants.HTTPD_USER
|
|
|
|
|
|
def get_security_domain():
|
|
"""
|
|
Get the security domain from the REST interface on the local Dogtag CA
|
|
This function will succeed if the local dogtag CA is up.
|
|
"""
|
|
connection = PKIConnection()
|
|
domain_client = pki.system.SecurityDomainClient(connection)
|
|
info = domain_client.get_security_domain_info()
|
|
return info
|
|
|
|
|
|
def is_installing_replica(sys_type):
|
|
"""
|
|
We expect only one of each type of Dogtag subsystem in an IPA deployment.
|
|
That means that if a subsystem of the specified type has already been
|
|
deployed - and therefore appears in the security domain - then we must be
|
|
installing a replica.
|
|
"""
|
|
info = get_security_domain()
|
|
try:
|
|
sys_list = info.systems[sys_type]
|
|
return len(sys_list.hosts) > 0
|
|
except KeyError:
|
|
return False
|
|
|
|
|
|
def export_kra_agent_pem():
|
|
"""
|
|
Export ipaCert with private key for client authentication.
|
|
"""
|
|
fd, filename = tempfile.mkstemp(dir=paths.HTTPD_ALIAS_DIR)
|
|
os.close(fd)
|
|
|
|
args = ["/usr/bin/pki",
|
|
"-d", paths.HTTPD_ALIAS_DIR,
|
|
"-C", paths.ALIAS_PWDFILE_TXT,
|
|
"client-cert-show", "ipaCert",
|
|
"--client-cert", filename]
|
|
ipautil.run(args)
|
|
|
|
pent = pwd.getpwnam(HTTPD_USER)
|
|
os.chown(filename, 0, pent.pw_gid)
|
|
os.chmod(filename, 0o440)
|
|
|
|
os.rename(filename, paths.KRA_AGENT_PEM)
|
|
|
|
|
|
class DogtagInstance(service.Service):
|
|
"""
|
|
This is the base class for a Dogtag 10+ instance, which uses a
|
|
shared tomcat instance and DS to host the relevant subsystems.
|
|
|
|
It contains functions that will be common to installations of the
|
|
CA, KRA, and eventually TKS and TPS.
|
|
"""
|
|
|
|
tracking_reqs = None
|
|
server_cert_name = None
|
|
|
|
def __init__(self, realm, subsystem, service_desc, host_name=None,
|
|
nss_db=paths.PKI_TOMCAT_ALIAS_DIR, service_prefix=None):
|
|
"""Initializer"""
|
|
|
|
super(DogtagInstance, self).__init__(
|
|
'pki-tomcatd',
|
|
service_desc=service_desc,
|
|
realm_name=realm,
|
|
service_user=constants.PKI_USER,
|
|
service_prefix=service_prefix
|
|
)
|
|
|
|
self.admin_password = None
|
|
self.fqdn = host_name
|
|
self.pkcs12_info = None
|
|
self.clone = False
|
|
|
|
self.basedn = DN(('o', 'ipa%s' % subsystem.lower()))
|
|
self.admin_user = "admin"
|
|
self.admin_dn = DN(('uid', self.admin_user),
|
|
('ou', 'people'), ('o', 'ipaca'))
|
|
self.admin_groups = None
|
|
self.agent_db = tempfile.mkdtemp(prefix="tmp-", dir=paths.VAR_LIB_IPA)
|
|
self.subsystem = subsystem
|
|
self.security_domain_name = "IPA"
|
|
# replication parameters
|
|
self.master_host = None
|
|
self.master_replication_port = None
|
|
self.subject_base = None
|
|
self.nss_db = nss_db
|
|
|
|
self.log = log_mgr.get_logger(self)
|
|
|
|
def __del__(self):
|
|
shutil.rmtree(self.agent_db, ignore_errors=True)
|
|
|
|
def is_installed(self):
|
|
"""
|
|
Determine if subsystem instance has been installed.
|
|
|
|
Returns True/False
|
|
"""
|
|
return os.path.exists(os.path.join(
|
|
paths.VAR_LIB_PKI_TOMCAT_DIR, self.subsystem.lower()))
|
|
|
|
def spawn_instance(self, cfg_file, nolog_list=None):
|
|
"""
|
|
Create and configure a new Dogtag instance using pkispawn.
|
|
Passes in a configuration file with IPA-specific
|
|
parameters.
|
|
"""
|
|
subsystem = self.subsystem
|
|
|
|
# Define the things we don't want logged
|
|
if nolog_list is None:
|
|
nolog_list = []
|
|
nolog = tuple(nolog_list) + (self.admin_password,)
|
|
|
|
args = [paths.PKISPAWN,
|
|
"-s", subsystem,
|
|
"-f", cfg_file]
|
|
|
|
with open(cfg_file) as f:
|
|
self.log.debug(
|
|
'Contents of pkispawn configuration file (%s):\n%s',
|
|
cfg_file, ipautil.nolog_replace(f.read(), nolog))
|
|
|
|
try:
|
|
ipautil.run(args, nolog=nolog)
|
|
except ipautil.CalledProcessError as e:
|
|
self.handle_setup_error(e)
|
|
|
|
def restart_instance(self):
|
|
try:
|
|
self.restart('pki-tomcat')
|
|
except Exception:
|
|
self.log.debug(traceback.format_exc())
|
|
self.log.critical(
|
|
"Failed to restart the Dogtag instance."
|
|
"See the installation log for details.")
|
|
|
|
def start_instance(self):
|
|
try:
|
|
self.start('pki-tomcat')
|
|
except Exception:
|
|
self.log.debug(traceback.format_exc())
|
|
self.log.critical(
|
|
"Failed to restart the Dogtag instance."
|
|
"See the installation log for details.")
|
|
|
|
def stop_instance(self):
|
|
try:
|
|
self.stop('pki-tomcat')
|
|
except Exception:
|
|
self.log.debug(traceback.format_exc())
|
|
self.log.critical(
|
|
"Failed to restart the Dogtag instance."
|
|
"See the installation log for details.")
|
|
|
|
def enable_client_auth_to_db(self, config):
|
|
"""
|
|
Enable client auth connection to the internal db.
|
|
Path to CS.cfg config file passed in.
|
|
"""
|
|
|
|
with stopped_service('pki-tomcatd', 'pki-tomcat'):
|
|
installutils.set_directive(
|
|
config,
|
|
'authz.instance.DirAclAuthz.ldap.ldapauth.authtype',
|
|
'SslClientAuth', quotes=False, separator='=')
|
|
installutils.set_directive(
|
|
config,
|
|
'authz.instance.DirAclAuthz.ldap.ldapauth.clientCertNickname',
|
|
'subsystemCert cert-pki-ca', quotes=False, separator='=')
|
|
installutils.set_directive(
|
|
config,
|
|
'authz.instance.DirAclAuthz.ldap.ldapconn.port', '636',
|
|
quotes=False, separator='=')
|
|
installutils.set_directive(
|
|
config,
|
|
'authz.instance.DirAclAuthz.ldap.ldapconn.secureConn',
|
|
'true', quotes=False, separator='=')
|
|
|
|
installutils.set_directive(
|
|
config,
|
|
'internaldb.ldapauth.authtype',
|
|
'SslClientAuth', quotes=False, separator='=')
|
|
|
|
installutils.set_directive(
|
|
config,
|
|
'internaldb.ldapauth.clientCertNickname',
|
|
'subsystemCert cert-pki-ca', quotes=False, separator='=')
|
|
installutils.set_directive(
|
|
config,
|
|
'internaldb.ldapconn.port', '636', quotes=False, separator='=')
|
|
installutils.set_directive(
|
|
config,
|
|
'internaldb.ldapconn.secureConn', 'true', quotes=False,
|
|
separator='=')
|
|
# Remove internaldb password as is not needed anymore
|
|
installutils.set_directive(paths.PKI_TOMCAT_PASSWORD_CONF,
|
|
'internaldb', None)
|
|
|
|
def uninstall(self):
|
|
if self.is_installed():
|
|
self.print_msg("Unconfiguring %s" % self.subsystem)
|
|
|
|
try:
|
|
ipautil.run([paths.PKIDESTROY,
|
|
"-i", 'pki-tomcat',
|
|
"-s", self.subsystem])
|
|
except ipautil.CalledProcessError as e:
|
|
self.log.critical("failed to uninstall %s instance %s",
|
|
self.subsystem, e)
|
|
|
|
def http_proxy(self):
|
|
""" Update the http proxy file """
|
|
template_filename = ipautil.SHARE_DIR + "ipa-pki-proxy.conf"
|
|
sub_dict = dict(
|
|
DOGTAG_PORT=8009,
|
|
CLONE='' if self.clone else '#',
|
|
FQDN=self.fqdn,
|
|
)
|
|
template = ipautil.template_file(template_filename, sub_dict)
|
|
with open(paths.HTTPD_IPA_PKI_PROXY_CONF, "w") as fd:
|
|
fd.write(template)
|
|
|
|
def configure_certmonger_renewal(self):
|
|
"""
|
|
Create a new CA type for certmonger that will retrieve updated
|
|
certificates from the dogtag master server.
|
|
"""
|
|
cmonger = services.knownservices.certmonger
|
|
cmonger.enable()
|
|
services.knownservices.messagebus.start()
|
|
cmonger.start()
|
|
|
|
bus = dbus.SystemBus()
|
|
obj = bus.get_object('org.fedorahosted.certmonger',
|
|
'/org/fedorahosted/certmonger')
|
|
iface = dbus.Interface(obj, 'org.fedorahosted.certmonger')
|
|
path = iface.find_ca_by_nickname('dogtag-ipa-ca-renew-agent')
|
|
if not path:
|
|
iface.add_known_ca(
|
|
'dogtag-ipa-ca-renew-agent',
|
|
paths.DOGTAG_IPA_CA_RENEW_AGENT_SUBMIT,
|
|
dbus.Array([], dbus.Signature('s')))
|
|
|
|
def __get_pin(self):
|
|
try:
|
|
return certmonger.get_pin('internal')
|
|
except IOError as e:
|
|
self.log.debug(
|
|
'Unable to determine PIN for the Dogtag instance: %s', e)
|
|
raise RuntimeError(e)
|
|
|
|
def configure_renewal(self):
|
|
""" Configure certmonger to renew system certs """
|
|
pin = self.__get_pin()
|
|
|
|
for nickname, profile in self.tracking_reqs:
|
|
try:
|
|
certmonger.dogtag_start_tracking(
|
|
ca='dogtag-ipa-ca-renew-agent',
|
|
nickname=nickname,
|
|
pin=pin,
|
|
pinfile=None,
|
|
secdir=self.nss_db,
|
|
pre_command='stop_pkicad',
|
|
post_command='renew_ca_cert "%s"' % nickname,
|
|
profile=profile)
|
|
except RuntimeError as e:
|
|
self.log.error(
|
|
"certmonger failed to start tracking certificate: %s", e)
|
|
|
|
def track_servercert(self):
|
|
"""
|
|
Specifically do not tell certmonger to restart the CA. This will be
|
|
done by the renewal script, renew_ca_cert once all the subsystem
|
|
certificates are renewed.
|
|
"""
|
|
pin = self.__get_pin()
|
|
try:
|
|
certmonger.dogtag_start_tracking(
|
|
ca='dogtag-ipa-renew-agent',
|
|
nickname=self.server_cert_name,
|
|
pin=pin,
|
|
pinfile=None,
|
|
secdir=self.nss_db,
|
|
pre_command='stop_pkicad',
|
|
post_command='renew_ca_cert "%s"' % self.server_cert_name)
|
|
except RuntimeError as e:
|
|
self.log.error(
|
|
"certmonger failed to start tracking certificate: %s" % e)
|
|
|
|
def stop_tracking_certificates(self, stop_certmonger=True):
|
|
"""Stop tracking our certificates. Called on uninstall.
|
|
"""
|
|
self.print_msg(
|
|
"Configuring certmonger to stop tracking system certificates "
|
|
"for %s" % self.subsystem)
|
|
|
|
cmonger = services.knownservices.certmonger
|
|
services.knownservices.messagebus.start()
|
|
cmonger.start()
|
|
|
|
nicknames = [nickname for nickname, _profile in self.tracking_reqs]
|
|
if self.server_cert_name is not None:
|
|
nicknames.append(self.server_cert_name)
|
|
|
|
for nickname in nicknames:
|
|
try:
|
|
certmonger.stop_tracking(
|
|
self.nss_db, nickname=nickname)
|
|
except RuntimeError as e:
|
|
self.log.error(
|
|
"certmonger failed to stop tracking certificate: %s", e)
|
|
|
|
if stop_certmonger:
|
|
cmonger.stop()
|
|
|
|
@staticmethod
|
|
def update_cert_cs_cfg(directive, cert, cs_cfg):
|
|
"""
|
|
When renewing a Dogtag subsystem certificate the configuration file
|
|
needs to get the new certificate as well.
|
|
|
|
``directive`` is the directive to update in CS.cfg
|
|
cert is a DER-encoded certificate.
|
|
cs_cfg is the path to the CS.cfg file
|
|
"""
|
|
|
|
with stopped_service('pki-tomcatd', 'pki-tomcat'):
|
|
installutils.set_directive(
|
|
cs_cfg,
|
|
directive,
|
|
base64.b64encode(cert),
|
|
quotes=False,
|
|
separator='=')
|
|
|
|
def get_admin_cert(self):
|
|
"""
|
|
Get the certificate for the admin user by checking the ldap entry
|
|
for the user. There should be only one certificate per user.
|
|
"""
|
|
self.log.debug('Trying to find the certificate for the admin user')
|
|
conn = None
|
|
|
|
try:
|
|
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm)
|
|
conn = ipaldap.LDAPClient(ldap_uri)
|
|
conn.external_bind()
|
|
|
|
entry_attrs = conn.get_entry(self.admin_dn, ['usercertificate'])
|
|
admin_cert = entry_attrs.get('usercertificate')[0]
|
|
|
|
# TODO(edewata) Add check to warn if there is more than one cert.
|
|
finally:
|
|
if conn is not None:
|
|
conn.unbind()
|
|
|
|
return base64.b64encode(admin_cert)
|
|
|
|
def handle_setup_error(self, e):
|
|
self.log.critical("Failed to configure %s instance: %s"
|
|
% (self.subsystem, e))
|
|
self.log.critical("See the installation logs and the following "
|
|
"files/directories for more information:")
|
|
self.log.critical(" %s" % paths.TOMCAT_TOPLEVEL_DIR)
|
|
|
|
raise RuntimeError("%s configuration failed." % self.subsystem)
|
|
|
|
def __add_admin_to_group(self, group):
|
|
dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca'))
|
|
entry = self.admin_conn.get_entry(dn)
|
|
members = entry.get('uniqueMember', [])
|
|
members.append(self.admin_dn)
|
|
mod = [(ldap.MOD_REPLACE, 'uniqueMember', members)]
|
|
try:
|
|
self.admin_conn.modify_s(dn, mod)
|
|
except ldap.TYPE_OR_VALUE_EXISTS:
|
|
# already there
|
|
pass
|
|
|
|
def setup_admin(self):
|
|
self.admin_user = "admin-%s" % self.fqdn
|
|
self.admin_password = binascii.hexlify(os.urandom(16))
|
|
self.admin_dn = DN(('uid', self.admin_user),
|
|
('ou', 'people'), ('o', 'ipaca'))
|
|
|
|
# remove user if left-over exists
|
|
try:
|
|
entry = self.admin_conn.delete_entry(self.admin_dn)
|
|
except errors.NotFound:
|
|
pass
|
|
|
|
# add user
|
|
entry = self.admin_conn.make_entry(
|
|
self.admin_dn,
|
|
objectclass=["top", "person", "organizationalPerson",
|
|
"inetOrgPerson", "cmsuser"],
|
|
uid=[self.admin_user],
|
|
cn=[self.admin_user],
|
|
sn=[self.admin_user],
|
|
usertype=['adminType'],
|
|
mail=['root@localhost'],
|
|
userPassword=[self.admin_password],
|
|
userstate=['1']
|
|
)
|
|
self.admin_conn.add_entry(entry)
|
|
|
|
for group in self.admin_groups:
|
|
self.__add_admin_to_group(group)
|
|
|
|
# Now wait until the other server gets replicated this data
|
|
ldap_uri = ipaldap.get_ldap_uri(self.master_host)
|
|
master_conn = ipaldap.LDAPClient(ldap_uri)
|
|
master_conn.gssapi_bind()
|
|
replication.wait_for_entry(master_conn, entry)
|
|
del master_conn
|
|
|
|
def __remove_admin_from_group(self, group):
|
|
dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca'))
|
|
mod = [(ldap.MOD_DELETE, 'uniqueMember', self.admin_dn)]
|
|
try:
|
|
self.admin_conn.modify_s(dn, mod)
|
|
except ldap.NO_SUCH_ATTRIBUTE:
|
|
# already removed
|
|
pass
|
|
|
|
def teardown_admin(self):
|
|
for group in self.admin_groups:
|
|
self.__remove_admin_from_group(group)
|
|
self.admin_conn.delete_entry(self.admin_dn)
|
|
|
|
def _use_ldaps_during_spawn(self, config, ds_cacert=paths.IPA_CA_CRT):
|
|
config.set(self.subsystem, "pki_ds_ldaps_port", "636")
|
|
config.set(self.subsystem, "pki_ds_secure_connection", "True")
|
|
config.set(self.subsystem, "pki_ds_secure_connection_ca_pem_file",
|
|
ds_cacert)
|