2017-06-02 11:36:29 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
|
|
|
#
|
|
|
|
|
2018-04-05 02:21:16 -05:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2018-10-24 09:08:16 -05:00
|
|
|
import sys
|
|
|
|
|
2017-06-02 11:36:29 -05:00
|
|
|
from ipalib.plugable import Registry
|
2017-06-21 11:52:57 -05:00
|
|
|
from ipaplatform import services
|
2017-06-02 11:36:29 -05:00
|
|
|
from ipaplatform.paths import paths
|
|
|
|
from ipaserver.advise.base import Advice
|
2018-01-18 14:46:36 -06:00
|
|
|
from ipaserver.install.httpinstance import OCSP_ENABLED, OCSP_DIRECTIVE
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
register = Registry()
|
|
|
|
|
|
|
|
|
2017-06-21 11:28:50 -05:00
|
|
|
class common_smart_card_auth_config(Advice):
|
|
|
|
"""
|
|
|
|
Common steps required to properly configure both server and client for
|
|
|
|
smart card auth
|
|
|
|
"""
|
|
|
|
|
|
|
|
systemwide_nssdb = paths.NSS_DB_DIR
|
2017-06-22 03:06:21 -05:00
|
|
|
smart_card_ca_certs_variable_name = "SC_CA_CERTS"
|
|
|
|
single_ca_cert_variable_name = 'ca_cert'
|
2017-06-21 11:28:50 -05:00
|
|
|
|
2017-06-21 11:52:57 -05:00
|
|
|
def check_ccache_not_empty(self):
|
|
|
|
self.log.comment('Check whether the credential cache is not empty')
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'klist',
|
|
|
|
[
|
|
|
|
"Credential cache is empty",
|
|
|
|
'Use kinit as privileged user to obtain Kerberos credentials'
|
|
|
|
])
|
|
|
|
|
2017-06-22 03:06:21 -05:00
|
|
|
def check_and_set_ca_cert_paths(self):
|
|
|
|
ca_paths_variable = self.smart_card_ca_certs_variable_name
|
|
|
|
single_ca_path_variable = self.single_ca_cert_variable_name
|
2017-06-21 11:52:57 -05:00
|
|
|
|
2017-06-22 03:06:21 -05:00
|
|
|
self.log.command("{}=$@".format(ca_paths_variable))
|
2017-06-21 11:28:50 -05:00
|
|
|
self.log.exit_on_predicate(
|
2017-06-22 03:06:21 -05:00
|
|
|
'[ -z "${}" ]'.format(ca_paths_variable),
|
|
|
|
['You need to provide one or more paths to the PEM files '
|
|
|
|
'containing CAs signing the Smart Cards']
|
2017-06-21 11:28:50 -05:00
|
|
|
)
|
2017-06-22 08:30:41 -05:00
|
|
|
with self.log.for_loop(single_ca_path_variable,
|
|
|
|
'${}'.format(ca_paths_variable)):
|
2017-06-22 06:20:05 -05:00
|
|
|
self.log.exit_on_predicate(
|
|
|
|
'[ ! -f "${}" ]'.format(single_ca_path_variable),
|
|
|
|
['Invalid CA certificate filename: ${}'.format(
|
|
|
|
single_ca_path_variable),
|
|
|
|
'Please check that the path exists and is a valid file']
|
|
|
|
)
|
2017-06-21 11:28:50 -05:00
|
|
|
|
2017-06-22 03:06:21 -05:00
|
|
|
def upload_smartcard_ca_certificates_to_systemwide_db(self):
|
2018-09-13 01:59:13 -05:00
|
|
|
# Newer version of sssd use OpenSSL and read the CA certs
|
|
|
|
# from /etc/sssd/pki/sssd_auth_ca_db.pem
|
|
|
|
self.log.command('mkdir -p /etc/sssd/pki')
|
2017-06-22 08:30:41 -05:00
|
|
|
with self.log.for_loop(
|
2017-06-22 03:06:21 -05:00
|
|
|
self.single_ca_cert_variable_name,
|
2017-06-22 08:30:41 -05:00
|
|
|
'${}'.format(self.smart_card_ca_certs_variable_name)):
|
2017-06-22 06:20:05 -05:00
|
|
|
self.log.command(
|
|
|
|
'certutil -d {} -A -i ${} -n "Smart Card CA $(uuidgen)" '
|
|
|
|
'-t CT,C,C'.format(
|
|
|
|
self.systemwide_nssdb, self.single_ca_cert_variable_name
|
2017-06-22 08:30:41 -05:00
|
|
|
)
|
2017-06-22 06:20:05 -05:00
|
|
|
)
|
2018-09-13 01:59:13 -05:00
|
|
|
self.log.command(
|
|
|
|
'cat ${} >> /etc/sssd/pki/sssd_auth_ca_db.pem'.format(
|
|
|
|
self.single_ca_cert_variable_name
|
|
|
|
)
|
|
|
|
)
|
2017-06-21 11:28:50 -05:00
|
|
|
|
2017-06-22 03:06:21 -05:00
|
|
|
def install_smart_card_signing_ca_certs(self):
|
2017-06-22 08:30:41 -05:00
|
|
|
with self.log.for_loop(
|
2017-06-22 03:06:21 -05:00
|
|
|
self.single_ca_cert_variable_name,
|
2017-06-22 08:30:41 -05:00
|
|
|
'${}'.format(self.smart_card_ca_certs_variable_name)):
|
2017-06-22 06:20:05 -05:00
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'ipa-cacert-manage install ${} -t CT,C,C'.format(
|
|
|
|
self.single_ca_cert_variable_name
|
|
|
|
),
|
|
|
|
['Failed to install external CA certificate to IPA']
|
|
|
|
)
|
2017-06-21 11:52:57 -05:00
|
|
|
|
|
|
|
def update_ipa_ca_certificate_store(self):
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'ipa-certupdate',
|
|
|
|
['Failed to update IPA CA certificate database']
|
|
|
|
)
|
|
|
|
|
2017-06-21 11:28:50 -05:00
|
|
|
|
2017-06-02 11:36:29 -05:00
|
|
|
@register()
|
2017-06-21 11:28:50 -05:00
|
|
|
class config_server_for_smart_card_auth(common_smart_card_auth_config):
|
2017-06-02 11:36:29 -05:00
|
|
|
"""
|
|
|
|
Configures smart card authentication via Kerberos (PKINIT) and for WebUI
|
|
|
|
"""
|
|
|
|
|
|
|
|
description = ("Instructions for enabling Smart Card authentication on "
|
2021-01-19 14:35:41 -06:00
|
|
|
" a single IPA server. Includes Apache configuration, "
|
2017-06-02 11:36:29 -05:00
|
|
|
"enabling PKINIT on KDC and configuring WebUI to accept "
|
|
|
|
"Smart Card auth requests. To enable the feature in the "
|
|
|
|
"whole topology you have to run the script on each master")
|
|
|
|
|
2018-04-25 11:28:34 -05:00
|
|
|
ssl_conf = paths.HTTPD_SSL_CONF
|
|
|
|
ssl_ocsp_directive = OCSP_DIRECTIVE
|
2017-06-21 11:52:57 -05:00
|
|
|
kdc_service_name = services.knownservices.krb5kdc.systemd_name
|
2018-11-16 07:54:32 -06:00
|
|
|
httpd_service_name = services.knownservices.httpd.systemd_name
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def get_info(self):
|
|
|
|
self.log.exit_on_nonroot_euid()
|
2017-06-22 03:06:21 -05:00
|
|
|
self.check_and_set_ca_cert_paths()
|
2017-06-02 11:36:29 -05:00
|
|
|
self.check_ccache_not_empty()
|
|
|
|
self.check_hostname_is_in_masters()
|
|
|
|
self.resolve_ipaca_records()
|
2018-04-25 11:28:34 -05:00
|
|
|
self.enable_ssl_ocsp()
|
2017-06-02 11:36:29 -05:00
|
|
|
self.restart_httpd()
|
|
|
|
self.record_httpd_ocsp_status()
|
|
|
|
self.check_and_enable_pkinit()
|
|
|
|
self.enable_ok_to_auth_as_delegate_on_http_principal()
|
2018-11-16 07:54:32 -06:00
|
|
|
self.allow_httpd_ifp()
|
2017-06-22 03:06:21 -05:00
|
|
|
self.upload_smartcard_ca_certificates_to_systemwide_db()
|
|
|
|
self.install_smart_card_signing_ca_certs()
|
2017-06-21 11:52:57 -05:00
|
|
|
self.update_ipa_ca_certificate_store()
|
|
|
|
self.restart_kdc()
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def check_hostname_is_in_masters(self):
|
|
|
|
self.log.comment('Check whether the host is IPA master')
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'ipa server-find $(hostname -f)',
|
|
|
|
["This script can be run on IPA master only"])
|
|
|
|
|
|
|
|
def resolve_ipaca_records(self):
|
|
|
|
ipa_domain_name = self.api.env.domain
|
|
|
|
|
|
|
|
self.log.comment('make sure bind-utils are installed so that we can '
|
|
|
|
'dig for ipa-ca records')
|
2018-11-21 03:44:55 -06:00
|
|
|
self.log.install_packages(
|
|
|
|
['bind-utils'],
|
|
|
|
['Failed to install bind-utils']
|
|
|
|
)
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
self.log.comment('make sure ipa-ca records are resolvable, '
|
|
|
|
'otherwise error out and instruct')
|
|
|
|
self.log.comment('the user to update the DNS infrastructure')
|
|
|
|
self.log.command('ipaca_records=$(dig +short '
|
|
|
|
'ipa-ca.{})'.format(ipa_domain_name))
|
|
|
|
|
|
|
|
self.log.exit_on_predicate(
|
|
|
|
'[ -z "$ipaca_records" ]',
|
|
|
|
[
|
2021-07-26 11:06:12 -05:00
|
|
|
f'Can not resolve ipa-ca records for {ipa_domain_name}',
|
2017-06-02 11:36:29 -05:00
|
|
|
'Please make sure to update your DNS infrastructure with ',
|
|
|
|
'ipa-ca record pointing to IP addresses of IPA CA masters'
|
|
|
|
])
|
|
|
|
|
2018-04-25 11:28:34 -05:00
|
|
|
def enable_ssl_ocsp(self):
|
|
|
|
self.log.comment('look for the OCSP directive in ssl.conf')
|
2017-06-02 11:36:29 -05:00
|
|
|
self.log.comment(' if it is present, switch it on')
|
|
|
|
self.log.comment(
|
|
|
|
'if it is absent, append it to the end of VirtualHost section')
|
|
|
|
predicate = self._interpolate_ocsp_directive_file_into_command(
|
|
|
|
"grep -q '{directive} ' {filename}")
|
|
|
|
|
|
|
|
self.log.commands_on_predicate(
|
|
|
|
predicate,
|
|
|
|
[
|
|
|
|
self._interpolate_ocsp_directive_file_into_command(
|
2017-06-22 08:03:45 -05:00
|
|
|
"sed -i.ipabkp -r "
|
2017-06-02 11:36:29 -05:00
|
|
|
"'s/^#*[[:space:]]*{directive}[[:space:]]+(on|off)$"
|
|
|
|
"/{directive} on/' {filename}")
|
|
|
|
],
|
|
|
|
commands_to_run_when_false=[
|
|
|
|
self._interpolate_ocsp_directive_file_into_command(
|
2018-09-24 03:49:45 -05:00
|
|
|
r"sed -i.ipabkp '/<\/VirtualHost>/i {directive} on' "
|
|
|
|
r"{filename}")
|
2017-06-02 11:36:29 -05:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
def _interpolate_ocsp_directive_file_into_command(self, fmt_line):
|
|
|
|
return self._format_command(
|
2018-04-25 11:28:34 -05:00
|
|
|
fmt_line, self.ssl_ocsp_directive, self.ssl_conf)
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def _format_command(self, fmt_line, directive, filename):
|
|
|
|
return fmt_line.format(directive=directive, filename=filename)
|
|
|
|
|
|
|
|
def restart_httpd(self):
|
|
|
|
self.log.comment('finally restart apache')
|
2018-11-16 07:54:32 -06:00
|
|
|
self.log.command(
|
|
|
|
'systemctl restart {}'.format(self.httpd_service_name)
|
|
|
|
)
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def record_httpd_ocsp_status(self):
|
|
|
|
self.log.comment('store the OCSP upgrade state')
|
|
|
|
self.log.command(
|
2018-10-24 09:08:16 -05:00
|
|
|
"{} -c 'from ipaserver.install import sysupgrade; "
|
2017-06-02 11:36:29 -05:00
|
|
|
"sysupgrade.set_upgrade_state(\"httpd\", "
|
2018-10-24 09:08:16 -05:00
|
|
|
"\"{}\", True)'".format(sys.executable, OCSP_ENABLED))
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def check_and_enable_pkinit(self):
|
|
|
|
self.log.comment('check whether PKINIT is configured on the master')
|
2017-06-22 08:08:08 -05:00
|
|
|
with self.log.if_branch(
|
|
|
|
"ipa-pkinit-manage status | grep -q 'enabled'"):
|
|
|
|
self.log.command('echo "PKINIT already enabled"')
|
|
|
|
with self.log.else_branch():
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'ipa-pkinit-manage enable',
|
|
|
|
['Failed to issue PKINIT certificates to local KDC'])
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
def enable_ok_to_auth_as_delegate_on_http_principal(self):
|
|
|
|
self.log.comment('Enable OK-AS-DELEGATE flag on the HTTP principal')
|
|
|
|
self.log.comment('This enables smart card login to WebUI')
|
|
|
|
self.log.command(
|
|
|
|
'output=$(ipa service-mod HTTP/$(hostname -f) '
|
|
|
|
'--ok-to-auth-as-delegate=True 2>&1)')
|
|
|
|
self.log.exit_on_predicate(
|
|
|
|
'[ "$?" -ne "0" -a '
|
|
|
|
'-z "$(echo $output | grep \'no modifications\')" ]',
|
|
|
|
["Failed to set OK_AS_AUTH_AS_DELEGATE flag on HTTP principal"]
|
|
|
|
)
|
|
|
|
|
2018-11-16 07:54:32 -06:00
|
|
|
def allow_httpd_ifp(self):
|
|
|
|
self.log.comment('Allow Apache to access SSSD IFP')
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'{} -c "import SSSDConfig; '
|
|
|
|
'from ipaclient.install.client import sssd_enable_ifp; '
|
|
|
|
'from ipaplatform.paths import paths; '
|
|
|
|
'c = SSSDConfig.SSSDConfig(); '
|
|
|
|
'c.import_config(); '
|
|
|
|
'sssd_enable_ifp(c, allow_httpd=True); '
|
|
|
|
'c.write(paths.SSSD_CONF)"'.format(sys.executable),
|
|
|
|
['Failed to modify SSSD config']
|
|
|
|
)
|
|
|
|
self.log.comment('Restart sssd')
|
|
|
|
self.log.command('systemctl restart sssd')
|
|
|
|
|
2017-06-21 11:52:57 -05:00
|
|
|
def restart_kdc(self):
|
|
|
|
self.log.exit_on_failed_command(
|
|
|
|
'systemctl restart {}'.format(self.kdc_service_name),
|
|
|
|
['Failed to restart KDC. Please restart the service manually.']
|
|
|
|
)
|
|
|
|
|
2017-06-02 11:36:29 -05:00
|
|
|
|
|
|
|
@register()
|
2017-06-21 11:28:50 -05:00
|
|
|
class config_client_for_smart_card_auth(common_smart_card_auth_config):
|
2017-06-02 11:36:29 -05:00
|
|
|
"""
|
2021-01-19 14:35:41 -06:00
|
|
|
Configures smart card authentication on IPA client
|
2017-06-02 11:36:29 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
description = ("Instructions for enabling Smart Card authentication on "
|
2021-01-19 14:35:41 -06:00
|
|
|
" a single IPA client. Configures Smart Card daemon, "
|
2017-06-02 11:36:29 -05:00
|
|
|
"set the system-wide trust store and configures SSSD to "
|
|
|
|
"allow smart card logins to desktop")
|
|
|
|
|
|
|
|
opensc_module_name = "OpenSC"
|
|
|
|
pkcs11_shared_lib = '/usr/lib64/opensc-pkcs11.so'
|
|
|
|
smart_card_service_file = 'pcscd.service'
|
|
|
|
smart_card_socket = 'pcscd.socket'
|
|
|
|
|
|
|
|
def get_info(self):
|
|
|
|
self.log.exit_on_nonroot_euid()
|
2017-06-22 03:06:21 -05:00
|
|
|
self.check_and_set_ca_cert_paths()
|
2017-06-21 11:52:57 -05:00
|
|
|
self.check_ccache_not_empty()
|
2017-06-02 11:36:29 -05:00
|
|
|
self.check_and_remove_pam_pkcs11()
|
|
|
|
self.install_opensc_and_dconf_packages()
|
2017-06-28 02:49:18 -05:00
|
|
|
self.install_krb5_client_dependencies()
|
2017-06-02 11:36:29 -05:00
|
|
|
self.start_enable_smartcard_daemon()
|
|
|
|
self.add_pkcs11_module_to_systemwide_db()
|
2017-06-22 03:06:21 -05:00
|
|
|
self.upload_smartcard_ca_certificates_to_systemwide_db()
|
2017-06-21 11:52:57 -05:00
|
|
|
self.update_ipa_ca_certificate_store()
|
2018-04-26 09:56:08 -05:00
|
|
|
self.run_authselect_to_configure_smart_card_auth()
|
2018-09-13 01:59:13 -05:00
|
|
|
self.configure_pam_cert_auth()
|
2017-06-02 11:36:29 -05:00
|
|
|
self.restart_sssd()
|
|
|
|
|
|
|
|
def check_and_remove_pam_pkcs11(self):
|
2018-11-21 03:44:55 -06:00
|
|
|
self.log.remove_package(
|
|
|
|
'pam_pkcs11',
|
|
|
|
['Could not remove pam_pkcs11 package']
|
2017-06-02 11:36:29 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
def install_opensc_and_dconf_packages(self):
|
|
|
|
self.log.comment(
|
|
|
|
'authconfig often complains about missing dconf, '
|
|
|
|
'install it explicitly')
|
2018-11-21 03:44:55 -06:00
|
|
|
self.log.install_packages(
|
|
|
|
[self.opensc_module_name.lower(), 'dconf'],
|
2017-06-02 11:36:29 -05:00
|
|
|
['Could not install OpenSC package']
|
|
|
|
)
|
|
|
|
|
2017-06-28 02:49:18 -05:00
|
|
|
def install_krb5_client_dependencies(self):
|
2018-11-21 03:44:55 -06:00
|
|
|
self.log.install_packages(
|
|
|
|
['krb5-pkinit-openssl'],
|
2017-06-28 02:49:18 -05:00
|
|
|
['Failed to install Kerberos client PKINIT extensions.']
|
|
|
|
)
|
|
|
|
|
2017-06-02 11:36:29 -05:00
|
|
|
def start_enable_smartcard_daemon(self):
|
|
|
|
self.log.command(
|
|
|
|
'systemctl start {service} {socket} '
|
|
|
|
'&& systemctl enable {service} {socket}'.format(
|
|
|
|
service=self.smart_card_service_file,
|
|
|
|
socket=self.smart_card_socket))
|
|
|
|
|
|
|
|
def add_pkcs11_module_to_systemwide_db(self):
|
|
|
|
module_name = self.opensc_module_name
|
|
|
|
nssdb = self.systemwide_nssdb
|
|
|
|
shared_lib = self.pkcs11_shared_lib
|
|
|
|
|
|
|
|
self.log.commands_on_predicate(
|
2021-07-23 17:17:19 -05:00
|
|
|
'modutil -dbdir {nssdb} -list | grep -q {module_name} || '
|
|
|
|
'p11-kit list-modules | grep -i {module_name} -q'.format(
|
|
|
|
nssdb=nssdb, module_name=module_name),
|
2017-06-02 11:36:29 -05:00
|
|
|
[
|
|
|
|
'echo "{} PKCS#11 module already configured"'.format(
|
|
|
|
module_name)
|
|
|
|
],
|
|
|
|
commands_to_run_when_false=[
|
|
|
|
'echo "" | modutil -dbdir {} -add "{}" -libfile {}'.format(
|
|
|
|
nssdb, module_name, shared_lib),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2018-04-26 09:56:08 -05:00
|
|
|
def run_authselect_to_configure_smart_card_auth(self):
|
2019-11-06 04:08:10 -06:00
|
|
|
# In order to be compatible with all clients, we check first
|
|
|
|
# if the client supports authselect.
|
|
|
|
# Otherwise authconfig will be used.
|
|
|
|
self.log.comment('Use either authselect or authconfig to enable '
|
|
|
|
'Smart Card authentication')
|
|
|
|
self.log.commands_on_predicate(
|
|
|
|
'[ -f {} ]'.format(paths.AUTHSELECT),
|
|
|
|
['AUTHCMD="authselect enable-feature with-smartcard"'],
|
|
|
|
['AUTHCMD="authconfig --enablesssd --enablesssdauth '
|
|
|
|
'--enablesmartcard --smartcardmodule=sssd --smartcardaction=1 '
|
|
|
|
'--updateall"']
|
|
|
|
)
|
2017-06-02 11:36:29 -05:00
|
|
|
self.log.exit_on_failed_command(
|
2019-11-06 04:08:10 -06:00
|
|
|
'$AUTHCMD',
|
2017-06-02 11:36:29 -05:00
|
|
|
[
|
|
|
|
'Failed to configure Smart Card authentication in SSSD'
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2018-09-13 01:59:13 -05:00
|
|
|
def configure_pam_cert_auth(self):
|
|
|
|
self.log.comment('Set pam_cert_auth=True in /etc/sssd/sssd.conf')
|
2019-11-06 04:08:10 -06:00
|
|
|
self.log.comment('This step is required only when authselect is used')
|
2020-05-04 10:18:03 -05:00
|
|
|
# If the advise command is run on RHEL7 or fedora but the client
|
|
|
|
# is rhel8, python3 executable may be in a different location
|
|
|
|
# Find the right python path first
|
|
|
|
self.log.command("python3 --version >/dev/null 2>&1")
|
|
|
|
self.log.commands_on_predicate(
|
|
|
|
'[ "$?" -eq 0 ]',
|
|
|
|
['PYTHON3CMD=python3'],
|
|
|
|
['PYTHON3CMD=/usr/libexec/platform-python']
|
|
|
|
)
|
2019-11-06 04:08:10 -06:00
|
|
|
self.log.commands_on_predicate(
|
|
|
|
'[ -f {} ]'.format(paths.AUTHSELECT),
|
2020-05-04 10:18:03 -05:00
|
|
|
["${PYTHON3CMD} -c 'from SSSDConfig import SSSDConfig; "
|
2019-11-06 04:08:10 -06:00
|
|
|
"c = SSSDConfig(); c.import_config(); "
|
|
|
|
"c.set(\"pam\", \"pam_cert_auth\", \"True\"); "
|
2020-05-04 10:18:03 -05:00
|
|
|
"c.write()'"])
|
2018-09-13 01:59:13 -05:00
|
|
|
|
2017-06-02 11:36:29 -05:00
|
|
|
def restart_sssd(self):
|
|
|
|
self.log.command('systemctl restart sssd.service')
|