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

Reply via email to