URL: https://github.com/freeipa/freeipa/pull/854 Author: martbab Title: #854: RFC: server-side smart card auth advise plugin Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/854/head:pr854 git checkout pr854
From 70298a7285cb84d28172a059dfe23917c074e4c2 Mon Sep 17 00:00:00 2001 From: Martin Babinsky <mbabi...@redhat.com> Date: Mon, 5 Jun 2017 16:59:25 +0200 Subject: [PATCH 1/3] Extend the advice printing code by some useful abstractions The advise printing code was augmented by methods that simplify generating bash snippets that report errors or failed commands. https://pagure.io/freeipa/issue/6982 --- ipaserver/advise/base.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/ipaserver/advise/base.py b/ipaserver/advise/base.py index 40dabd0426..7b23adc115 100644 --- a/ipaserver/advise/base.py +++ b/ipaserver/advise/base.py @@ -94,8 +94,57 @@ def debug(self, line): if self.options.verbose: self.comment('DEBUG: ' + line) - def command(self, line): - self.content.append(line) + def command(self, line, indent_spaces=0): + self.content.append( + '{}{}'.format(self._format_indent(indent_spaces), line)) + + def _format_indent(self, num_spaces): + return ' ' * num_spaces + + def echo_error(self, error_message, indent_spaces=0): + self.command( + self._format_error(error_message, indent_spaces=indent_spaces)) + + def _format_error(self, error_message, indent_spaces=0): + return '{}echo "{}" >&2'.format( + self._format_indent(indent_spaces), error_message) + + def exit_on_failed_command(self, command_to_run, + error_message_lines, indent_spaces=0): + self.command(command_to_run, indent_spaces=indent_spaces) + self.exit_on_predicate( + '"$?" -ne "0"', error_message_lines, indent_spaces=indent_spaces) + + def exit_on_nonroot_euid(self): + self.exit_on_predicate( + '"$(id -u)" -ne "0"', + ["This script has to be run as root user"] + ) + + def exit_on_predicate(self, predicate, error_message_lines, + indent_spaces=0): + commands_to_run = [ + self._format_error(error_message_line, indent_spaces=indent_spaces) + for error_message_line in error_message_lines] + + commands_to_run.append('exit 1') + self.commands_on_predicate( + predicate, + commands_to_run, + indent_spaces=indent_spaces) + + def commands_on_predicate(self, predicate, commands_to_run, + indent_spaces=0): + if_command = 'if [ {} ]'.format(predicate) + self.command(if_command, indent_spaces=indent_spaces) + self.command('then', indent_spaces=indent_spaces) + + indented_block_spaces = indent_spaces + 2 + + for command_to_run in commands_to_run: + self.command(command_to_run, indent_spaces=indented_block_spaces) + + self.command('fi', indent_spaces=indent_spaces) class Advice(Plugin): From 6de3a19dd2fe43909b5b38bd4688da3eed339e4e Mon Sep 17 00:00:00 2001 From: Martin Babinsky <mbabi...@redhat.com> Date: Fri, 2 Jun 2017 18:36:29 +0200 Subject: [PATCH 2/3] Prepare an advise plugin for server-side smart card auth configuration The plugin will contain topics for configuring Smart Card authentication on FreeIPA server. https://pagure.io/freeipa/issue/6982 --- ipaserver/advise/plugins/smart_card_auth.py | 166 ++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 ipaserver/advise/plugins/smart_card_auth.py diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py new file mode 100644 index 0000000000..55fe996d7e --- /dev/null +++ b/ipaserver/advise/plugins/smart_card_auth.py @@ -0,0 +1,166 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +from ipalib.plugable import Registry +from ipaplatform.paths import paths +from ipaserver.advise.base import Advice +from ipaserver.install.httpinstance import NSS_OCSP_ENABLED + +register = Registry() + + +@register() +class config_server_for_smart_card_auth(Advice): + """ + Configures smart card authentication via Kerberos (PKINIT) and for WebUI + """ + + description = ("Instructions for enabling Smart Card authentication on " + " a single FreeIPA server. Includes Apache configuration, " + "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") + + nss_conf = paths.HTTPD_NSS_CONF + nss_ocsp_directive = 'NSSOCSP' + nss_nickname_directive = 'NSSNickname' + + def get_info(self): + self.log.exit_on_nonroot_euid() + self.check_ccache_not_empty() + self.check_hostname_is_in_masters() + self.resolve_ipaca_records() + self.enable_nss_ocsp() + self.mark_httpd_cert_as_trusted() + self.restart_httpd() + self.record_httpd_ocsp_status() + self.check_and_enable_pkinit() + self.enable_ok_to_auth_as_delegate_on_http_principal() + self.enable_pam_auth_in_sssd() + self.restart_sssd() + + 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' + ]) + + 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') + self.log.exit_on_failed_command( + 'yum install -y bind-utils', + ['Failed to install bind-utils']) + + 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"', + [ + 'Can not resolve ipa-ca records for ${domain_name}', + 'Please make sure to update your DNS infrastructure with ', + 'ipa-ca record pointing to IP addresses of IPA CA masters' + ]) + + def enable_nss_ocsp(self): + self.log.comment('look for the OCSP directive in nss.conf') + self._interpolate_ocsp_directive_file_into_command( + "if grep -q '{directive} ' {filename}") + self.log.command('then') + self.log.comment(' present, switch it on') + self._interpolate_ocsp_directive_file_into_command( + " sed -i.ipabkp -r " + "'s/^#*[[:space:]]*{directive}[[:space:]]+(on|off)$" + "/{directive} on/' {filename}") + self.log.command("else") + self.log.comment( + ' absent, append it to the end of VirtualHost section') + self._interpolate_ocsp_directive_file_into_command( + " sed -i.ipabkp '/<\/VirtualHost>/i {directive} on' " + "{filename}") + self.log.command('fi') + + def _interpolate_ocsp_directive_file_into_command(self, fmt_line): + self._format_command(fmt_line, self.nss_ocsp_directive, self.nss_conf) + + def _format_command(self, fmt_line, directive, filename): + self.log.command( + fmt_line.format(directive=directive, + filename=filename)) + + def mark_httpd_cert_as_trusted(self): + self.log.comment( + 'mark the HTTP certificate as trusted peer to avoid ' + 'chicken-egg startup issue') + self._interpolate_nssnickname_directive_file_into_command( + "http_cert_nick=`grep '{directive}' {filename} | cut -f 2 -d ' '`") + + self.log.exit_on_failed_command( + 'certutil -M -n $http_cert_nick -d "{}" -t "Pu,u,u"'.format( + paths.HTTPD_ALIAS_DIR), + ['Can not set trust flags on HTTP certificate']) + + def _interpolate_nssnickname_directive_file_into_command(self, fmt_line): + self._format_command( + fmt_line, self.nss_nickname_directive, self.nss_conf) + + def restart_httpd(self): + self.log.comment('finally restart apache') + self.log.command('systemctl restart httpd') + + def record_httpd_ocsp_status(self): + self.log.comment('store the OCSP upgrade state') + self.log.command( + "python -c 'from ipaserver.install import sysupgrade; " + "sysupgrade.set_upgrade_state(\"httpd\", " + "\"{}\", True)'".format(NSS_OCSP_ENABLED)) + + def check_and_enable_pkinit(self): + self.log.comment('check whether PKINIT is configured on the master') + self.log.command( + "if ipa-pkinit-manage status | grep -q 'enabled'") + self.log.command('then') + self.log.command(' echo "PKINIT already enabled"') + self.log.command('else') + self.log.exit_on_failed_command( + 'ipa-pkinit-manage enable', + ['Failed to issue PKINIT certificates to local KDC']) + self.log.command('fi') + + 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"] + ) + + def enable_pam_auth_in_sssd(self): + self.log.command('python << EOF') + self.log.command('from SSSDConfig import SSSDConfig') + self.log.command('c = SSSDConfig()') + self.log.command('c.import_config()') + self.log.command('c.set("pam", "pam_cert_auth", "True")') + self.log.command('EOF') + + def restart_sssd(self): + self.log.command('systemctl restart sssd.service') From 102d54b02176e83a63f07642f5190e3b0ce9dadb Mon Sep 17 00:00:00 2001 From: Martin Babinsky <mbabi...@redhat.com> Date: Fri, 9 Jun 2017 10:08:04 +0200 Subject: [PATCH 3/3] Provide an advise for client-side configuration of smart card auth This recipe will configure the Smart Card login to desktop on an FreeIPA-enrolled client. https://pagure.io/freeipa/issue/6982 --- ipaserver/advise/plugins/smart_card_auth.py | 101 ++++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/ipaserver/advise/plugins/smart_card_auth.py b/ipaserver/advise/plugins/smart_card_auth.py index 55fe996d7e..8007543e24 100644 --- a/ipaserver/advise/plugins/smart_card_auth.py +++ b/ipaserver/advise/plugins/smart_card_auth.py @@ -10,8 +10,30 @@ register = Registry() +class common_smart_card_auth_advise(Advice): + """ + A common advise steps for both server and client smart card auth + """ + + def enable_pam_auth_in_sssd(self): + self.log.command('python << EOF') + self.log.command('from SSSDConfig import SSSDConfig') + self.log.command('c = SSSDConfig()') + self.log.command('c.import_config()') + self.log.command('c.set("pam", "pam_cert_auth", "True")') + self.log.command('c.write()') + self.log.command('EOF') + + def restart_sssd(self): + self.log.command('systemctl restart sssd.service') + + def get_info(self): + self.enable_pam_auth_in_sssd() + self.restart_sssd() + + @register() -class config_server_for_smart_card_auth(Advice): +class config_server_for_smart_card_auth(common_smart_card_auth_advise): """ Configures smart card authentication via Kerberos (PKINIT) and for WebUI """ @@ -37,8 +59,7 @@ def get_info(self): self.record_httpd_ocsp_status() self.check_and_enable_pkinit() self.enable_ok_to_auth_as_delegate_on_http_principal() - self.enable_pam_auth_in_sssd() - self.restart_sssd() + super(config_server_for_smart_card_auth, self).get_info() def check_ccache_not_empty(self): self.log.comment('Check whether the credential cache is not empty') @@ -154,13 +175,69 @@ def enable_ok_to_auth_as_delegate_on_http_principal(self): ["Failed to set OK_AS_AUTH_AS_DELEGATE flag on HTTP principal"] ) - def enable_pam_auth_in_sssd(self): - self.log.command('python << EOF') - self.log.command('from SSSDConfig import SSSDConfig') - self.log.command('c = SSSDConfig()') - self.log.command('c.import_config()') - self.log.command('c.set("pam", "pam_cert_auth", "True")') - self.log.command('EOF') - def restart_sssd(self): - self.log.command('systemctl restart sssd.service') +@register() +class config_client_for_smart_card_auth(common_smart_card_auth_advise): + """ + Configures smart card authentication on FreeIPA client + """ + pkcs11_shared_lib = '/usr/lib64/opensc-pkcs11.so' + systemwide_nssdb = paths.NSS_DB_DIR + smart_card_service_file = 'pcscd.service' + smart_card_socket = 'pcscd.socket' + smart_card_ca_cert_variable_name = "SC_CA_CERT" + + description = ("Instructions for enabling Smart Card authentication on " + " a single FreeIPA client. Configures Smart Card daemon, " + "set the system-wide trust store and configures SSSD to " + "allow smart card logins to desktop") + + def get_info(self): + self.log.exit_on_nonroot_euid() + self.check_and_set_ca_cert_path() + self.install_opensc_package() + self.start_enable_smartcard_daemon() + self.add_pkcs11_module_to_systemwide_db() + self.upload_smartcard_ca_certificate_to_systemwide_db() + + super(config_client_for_smart_card_auth, self).get_info() + + def check_and_set_ca_cert_path(self): + ca_path_variable = self.smart_card_ca_cert_variable_name + self.log.command("{}=$1".format(ca_path_variable)) + self.log.exit_on_predicate( + '-z "${}"'.format(ca_path_variable), + ['You need to provide the path to the PEM file containing CA ' + 'signing the Smart Cards'] + ) + self.log.exit_on_predicate( + '! -f "${}"'.format(ca_path_variable), + ['Invalid CA certificate filename: ${}'.format(ca_path_variable), + 'Please check that the path exists and is a valid file'] + ) + + def install_opensc_package(self): + self.log.exit_on_failed_command( + 'yum install -y opensc', + ['Could not install OpenSC package'] + ) + + 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): + self.log.exit_on_failed_command( + 'echo "" | modutil -dbdir {} -add "OpenSC" -libfile {}'.format( + self.systemwide_nssdb, self.pkcs11_shared_lib), + ['Failed to add PKCS#11 module to systemwide NSS DB']) + + def upload_smartcard_ca_certificate_to_systemwide_db(self): + self.log.command( + 'certutil -d {} -A -i ${} -n "Smart Card CA" -t CT,C,C'.format( + self.systemwide_nssdb, self.smart_card_ca_cert_variable_name + ) + )
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org