|
|
ac7d03 |
From f16d9533c7917a8a57a9148dee61df3b12a5e767 Mon Sep 17 00:00:00 2001
|
|
|
ac7d03 |
From: Martin Babinsky <mbabinsk@redhat.com>
|
|
|
ac7d03 |
Date: Fri, 2 Jun 2017 18:36:29 +0200
|
|
|
ac7d03 |
Subject: [PATCH] Prepare advise plugin for smart card auth configuration
|
|
|
ac7d03 |
|
|
|
ac7d03 |
The plugin contains recipes for configuring Smart Card authentication
|
|
|
ac7d03 |
on FreeIPA server and enrolled client.
|
|
|
ac7d03 |
|
|
|
ac7d03 |
https://www.freeipa.org/page/V4/Smartcard_authentication_ipa-advise_recipes
|
|
|
ac7d03 |
https://pagure.io/freeipa/issue/6982
|
|
|
ac7d03 |
|
|
|
ac7d03 |
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
|
|
|
ac7d03 |
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
|
|
ac7d03 |
---
|
|
|
ac7d03 |
ipaserver/advise/plugins/smart_card_auth.py | 266 ++++++++++++++++++++++++++++
|
|
|
ac7d03 |
1 file changed, 266 insertions(+)
|
|
|
ac7d03 |
create mode 100644 ipaserver/advise/plugins/smart_card_auth.py
|
|
|
ac7d03 |
|
|
|
ac7d03 |
diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py
|
|
|
ac7d03 |
new file mode 100644
|
|
|
ac7d03 |
index 0000000000000000000000000000000000000000..5859e350939fdba0a8b258de5285dd10c7b3bc23
|
|
|
ac7d03 |
--- /dev/null
|
|
|
ac7d03 |
+++ b/ipaserver/advise/plugins/smart_card_auth.py
|
|
|
ac7d03 |
@@ -0,0 +1,266 @@
|
|
|
ac7d03 |
+#
|
|
|
ac7d03 |
+# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
|
|
|
ac7d03 |
+#
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+from ipalib.plugable import Registry
|
|
|
ac7d03 |
+from ipaplatform.paths import paths
|
|
|
ac7d03 |
+from ipaserver.advise.base import Advice
|
|
|
ac7d03 |
+from ipaserver.install.httpinstance import NSS_OCSP_ENABLED
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+register = Registry()
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+@register()
|
|
|
ac7d03 |
+class config_server_for_smart_card_auth(Advice):
|
|
|
ac7d03 |
+ """
|
|
|
ac7d03 |
+ Configures smart card authentication via Kerberos (PKINIT) and for WebUI
|
|
|
ac7d03 |
+ """
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ description = ("Instructions for enabling Smart Card authentication on "
|
|
|
ac7d03 |
+ " a single FreeIPA server. Includes Apache configuration, "
|
|
|
ac7d03 |
+ "enabling PKINIT on KDC and configuring WebUI to accept "
|
|
|
ac7d03 |
+ "Smart Card auth requests. To enable the feature in the "
|
|
|
ac7d03 |
+ "whole topology you have to run the script on each master")
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ nss_conf = paths.HTTPD_NSS_CONF
|
|
|
ac7d03 |
+ nss_ocsp_directive = 'NSSOCSP'
|
|
|
ac7d03 |
+ nss_nickname_directive = 'NSSNickname'
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def get_info(self):
|
|
|
ac7d03 |
+ self.log.exit_on_nonroot_euid()
|
|
|
ac7d03 |
+ self.check_ccache_not_empty()
|
|
|
ac7d03 |
+ self.check_hostname_is_in_masters()
|
|
|
ac7d03 |
+ self.resolve_ipaca_records()
|
|
|
ac7d03 |
+ self.enable_nss_ocsp()
|
|
|
ac7d03 |
+ self.mark_httpd_cert_as_trusted()
|
|
|
ac7d03 |
+ self.restart_httpd()
|
|
|
ac7d03 |
+ self.record_httpd_ocsp_status()
|
|
|
ac7d03 |
+ self.check_and_enable_pkinit()
|
|
|
ac7d03 |
+ self.enable_ok_to_auth_as_delegate_on_http_principal()
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def check_ccache_not_empty(self):
|
|
|
ac7d03 |
+ self.log.comment('Check whether the credential cache is not empty')
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'klist',
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ "Credential cache is empty",
|
|
|
ac7d03 |
+ 'Use kinit as privileged user to obtain Kerberos credentials'
|
|
|
ac7d03 |
+ ])
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def check_hostname_is_in_masters(self):
|
|
|
ac7d03 |
+ self.log.comment('Check whether the host is IPA master')
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'ipa server-find $(hostname -f)',
|
|
|
ac7d03 |
+ ["This script can be run on IPA master only"])
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def resolve_ipaca_records(self):
|
|
|
ac7d03 |
+ ipa_domain_name = self.api.env.domain
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.comment('make sure bind-utils are installed so that we can '
|
|
|
ac7d03 |
+ 'dig for ipa-ca records')
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'yum install -y bind-utils',
|
|
|
ac7d03 |
+ ['Failed to install bind-utils'])
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.comment('make sure ipa-ca records are resolvable, '
|
|
|
ac7d03 |
+ 'otherwise error out and instruct')
|
|
|
ac7d03 |
+ self.log.comment('the user to update the DNS infrastructure')
|
|
|
ac7d03 |
+ self.log.command('ipaca_records=$(dig +short '
|
|
|
ac7d03 |
+ 'ipa-ca.{})'.format(ipa_domain_name))
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.exit_on_predicate(
|
|
|
ac7d03 |
+ '[ -z "$ipaca_records" ]',
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ 'Can not resolve ipa-ca records for ${domain_name}',
|
|
|
ac7d03 |
+ 'Please make sure to update your DNS infrastructure with ',
|
|
|
ac7d03 |
+ 'ipa-ca record pointing to IP addresses of IPA CA masters'
|
|
|
ac7d03 |
+ ])
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def enable_nss_ocsp(self):
|
|
|
ac7d03 |
+ self.log.comment('look for the OCSP directive in nss.conf')
|
|
|
ac7d03 |
+ self.log.comment(' if it is present, switch it on')
|
|
|
ac7d03 |
+ self.log.comment(
|
|
|
ac7d03 |
+ 'if it is absent, append it to the end of VirtualHost section')
|
|
|
ac7d03 |
+ predicate = self._interpolate_ocsp_directive_file_into_command(
|
|
|
ac7d03 |
+ "grep -q '{directive} ' {filename}")
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.commands_on_predicate(
|
|
|
ac7d03 |
+ predicate,
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ self._interpolate_ocsp_directive_file_into_command(
|
|
|
ac7d03 |
+ " sed -i.ipabkp -r "
|
|
|
ac7d03 |
+ "'s/^#*[[:space:]]*{directive}[[:space:]]+(on|off)$"
|
|
|
ac7d03 |
+ "/{directive} on/' {filename}")
|
|
|
ac7d03 |
+ ],
|
|
|
ac7d03 |
+ commands_to_run_when_false=[
|
|
|
ac7d03 |
+ self._interpolate_ocsp_directive_file_into_command(
|
|
|
ac7d03 |
+ " sed -i.ipabkp '/<\/VirtualHost>/i {directive} on' "
|
|
|
ac7d03 |
+ "{filename}")
|
|
|
ac7d03 |
+ ]
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def _interpolate_ocsp_directive_file_into_command(self, fmt_line):
|
|
|
ac7d03 |
+ return self._format_command(
|
|
|
ac7d03 |
+ fmt_line, self.nss_ocsp_directive, self.nss_conf)
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def _format_command(self, fmt_line, directive, filename):
|
|
|
ac7d03 |
+ return fmt_line.format(directive=directive, filename=filename)
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def mark_httpd_cert_as_trusted(self):
|
|
|
ac7d03 |
+ self.log.comment(
|
|
|
ac7d03 |
+ 'mark the HTTP certificate as trusted peer to avoid '
|
|
|
ac7d03 |
+ 'chicken-egg startup issue')
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ self._interpolate_nssnickname_directive_file_into_command(
|
|
|
ac7d03 |
+ "http_cert_nick=$(grep '{directive}' {filename} |"
|
|
|
ac7d03 |
+ " cut -f 2 -d ' ')"))
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'certutil -M -n $http_cert_nick -d "{}" -t "Pu,u,u"'.format(
|
|
|
ac7d03 |
+ paths.HTTPD_ALIAS_DIR),
|
|
|
ac7d03 |
+ ['Can not set trust flags on HTTP certificate'])
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def _interpolate_nssnickname_directive_file_into_command(self, fmt_line):
|
|
|
ac7d03 |
+ return self._format_command(
|
|
|
ac7d03 |
+ fmt_line, self.nss_nickname_directive, self.nss_conf)
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def restart_httpd(self):
|
|
|
ac7d03 |
+ self.log.comment('finally restart apache')
|
|
|
ac7d03 |
+ self.log.command('systemctl restart httpd')
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def record_httpd_ocsp_status(self):
|
|
|
ac7d03 |
+ self.log.comment('store the OCSP upgrade state')
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ "python -c 'from ipaserver.install import sysupgrade; "
|
|
|
ac7d03 |
+ "sysupgrade.set_upgrade_state(\"httpd\", "
|
|
|
ac7d03 |
+ "\"{}\", True)'".format(NSS_OCSP_ENABLED))
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def check_and_enable_pkinit(self):
|
|
|
ac7d03 |
+ self.log.comment('check whether PKINIT is configured on the master')
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ "if ipa-pkinit-manage status | grep -q 'enabled'")
|
|
|
ac7d03 |
+ self.log.command('then')
|
|
|
ac7d03 |
+ self.log.command(' echo "PKINIT already enabled"')
|
|
|
ac7d03 |
+ self.log.command('else')
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'ipa-pkinit-manage enable',
|
|
|
ac7d03 |
+ ['Failed to issue PKINIT certificates to local KDC'],
|
|
|
ac7d03 |
+ indent_spaces=2)
|
|
|
ac7d03 |
+ self.log.command('fi')
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def enable_ok_to_auth_as_delegate_on_http_principal(self):
|
|
|
ac7d03 |
+ self.log.comment('Enable OK-AS-DELEGATE flag on the HTTP principal')
|
|
|
ac7d03 |
+ self.log.comment('This enables smart card login to WebUI')
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ 'output=$(ipa service-mod HTTP/$(hostname -f) '
|
|
|
ac7d03 |
+ '--ok-to-auth-as-delegate=True 2>&1)')
|
|
|
ac7d03 |
+ self.log.exit_on_predicate(
|
|
|
ac7d03 |
+ '[ "$?" -ne "0" -a '
|
|
|
ac7d03 |
+ '-z "$(echo $output | grep \'no modifications\')" ]',
|
|
|
ac7d03 |
+ ["Failed to set OK_AS_AUTH_AS_DELEGATE flag on HTTP principal"]
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+@register()
|
|
|
ac7d03 |
+class config_client_for_smart_card_auth(Advice):
|
|
|
ac7d03 |
+ """
|
|
|
ac7d03 |
+ Configures smart card authentication on FreeIPA client
|
|
|
ac7d03 |
+ """
|
|
|
ac7d03 |
+ smart_card_ca_cert_variable_name = "SC_CA_CERT"
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ description = ("Instructions for enabling Smart Card authentication on "
|
|
|
ac7d03 |
+ " a single FreeIPA client. Configures Smart Card daemon, "
|
|
|
ac7d03 |
+ "set the system-wide trust store and configures SSSD to "
|
|
|
ac7d03 |
+ "allow smart card logins to desktop")
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ opensc_module_name = "OpenSC"
|
|
|
ac7d03 |
+ pkcs11_shared_lib = '/usr/lib64/opensc-pkcs11.so'
|
|
|
ac7d03 |
+ smart_card_service_file = 'pcscd.service'
|
|
|
ac7d03 |
+ smart_card_socket = 'pcscd.socket'
|
|
|
ac7d03 |
+ systemwide_nssdb = paths.NSS_DB_DIR
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def get_info(self):
|
|
|
ac7d03 |
+ self.log.exit_on_nonroot_euid()
|
|
|
ac7d03 |
+ self.check_and_set_ca_cert_path()
|
|
|
ac7d03 |
+ self.check_and_remove_pam_pkcs11()
|
|
|
ac7d03 |
+ self.install_opensc_and_dconf_packages()
|
|
|
ac7d03 |
+ self.start_enable_smartcard_daemon()
|
|
|
ac7d03 |
+ self.add_pkcs11_module_to_systemwide_db()
|
|
|
ac7d03 |
+ self.upload_smartcard_ca_certificate_to_systemwide_db()
|
|
|
ac7d03 |
+ self.run_authconfig_to_configure_smart_card_auth()
|
|
|
ac7d03 |
+ self.restart_sssd()
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def check_and_set_ca_cert_path(self):
|
|
|
ac7d03 |
+ ca_path_variable = self.smart_card_ca_cert_variable_name
|
|
|
ac7d03 |
+ self.log.command("{}=$1".format(ca_path_variable))
|
|
|
ac7d03 |
+ self.log.exit_on_predicate(
|
|
|
ac7d03 |
+ '[ -z "${}" ]'.format(ca_path_variable),
|
|
|
ac7d03 |
+ ['You need to provide the path to the PEM file containing CA '
|
|
|
ac7d03 |
+ 'signing the Smart Cards']
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+ self.log.exit_on_predicate(
|
|
|
ac7d03 |
+ '[ ! -f "${}" ]'.format(ca_path_variable),
|
|
|
ac7d03 |
+ ['Invalid CA certificate filename: ${}'.format(ca_path_variable),
|
|
|
ac7d03 |
+ 'Please check that the path exists and is a valid file']
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def check_and_remove_pam_pkcs11(self):
|
|
|
ac7d03 |
+ self.log.command('rpm -qi pam_pkcs11 > /dev/null')
|
|
|
ac7d03 |
+ self.log.commands_on_predicate(
|
|
|
ac7d03 |
+ '[ "$?" -eq "0" ]',
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ 'yum remove -y pam_pkcs11'
|
|
|
ac7d03 |
+ ]
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def install_opensc_and_dconf_packages(self):
|
|
|
ac7d03 |
+ self.log.comment(
|
|
|
ac7d03 |
+ 'authconfig often complains about missing dconf, '
|
|
|
ac7d03 |
+ 'install it explicitly')
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'yum install -y {} dconf'.format(self.opensc_module_name.lower()),
|
|
|
ac7d03 |
+ ['Could not install OpenSC package']
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def start_enable_smartcard_daemon(self):
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ 'systemctl start {service} {socket} '
|
|
|
ac7d03 |
+ '&& systemctl enable {service} {socket}'.format(
|
|
|
ac7d03 |
+ service=self.smart_card_service_file,
|
|
|
ac7d03 |
+ socket=self.smart_card_socket))
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def add_pkcs11_module_to_systemwide_db(self):
|
|
|
ac7d03 |
+ module_name = self.opensc_module_name
|
|
|
ac7d03 |
+ nssdb = self.systemwide_nssdb
|
|
|
ac7d03 |
+ shared_lib = self.pkcs11_shared_lib
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ self.log.commands_on_predicate(
|
|
|
ac7d03 |
+ 'modutil -dbdir {} -list | grep -q {}'.format(
|
|
|
ac7d03 |
+ nssdb, module_name),
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ 'echo "{} PKCS#11 module already configured"'.format(
|
|
|
ac7d03 |
+ module_name)
|
|
|
ac7d03 |
+ ],
|
|
|
ac7d03 |
+ commands_to_run_when_false=[
|
|
|
ac7d03 |
+ 'echo "" | modutil -dbdir {} -add "{}" -libfile {}'.format(
|
|
|
ac7d03 |
+ nssdb, module_name, shared_lib),
|
|
|
ac7d03 |
+ ]
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def upload_smartcard_ca_certificate_to_systemwide_db(self):
|
|
|
ac7d03 |
+ self.log.command(
|
|
|
ac7d03 |
+ 'certutil -d {} -A -i ${} -n "Smart Card CA" -t CT,C,C'.format(
|
|
|
ac7d03 |
+ self.systemwide_nssdb, self.smart_card_ca_cert_variable_name
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def run_authconfig_to_configure_smart_card_auth(self):
|
|
|
ac7d03 |
+ self.log.exit_on_failed_command(
|
|
|
ac7d03 |
+ 'authconfig --enablesmartcard --smartcardmodule=sssd --updateall',
|
|
|
ac7d03 |
+ [
|
|
|
ac7d03 |
+ 'Failed to configure Smart Card authentication in SSSD'
|
|
|
ac7d03 |
+ ]
|
|
|
ac7d03 |
+ )
|
|
|
ac7d03 |
+
|
|
|
ac7d03 |
+ def restart_sssd(self):
|
|
|
ac7d03 |
+ self.log.command('systemctl restart sssd.service')
|
|
|
ac7d03 |
--
|
|
|
ac7d03 |
2.9.4
|
|
|
ac7d03 |
|