Imported Upstream version 4.0.5

This commit is contained in:
Mario Fetka
2021-07-25 07:50:50 +02:00
parent 8ff3be4216
commit 3bfaa6e020
2049 changed files with 317193 additions and 1632423 deletions

View File

@@ -17,19 +17,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import print_function
from contextlib import contextmanager
import logging
import os
from textwrap import wrap
from ipalib import api
from ipalib.plugable import Plugin, API
from ipalib.errors import ValidationError
from ipaplatform.paths import paths
from ipapython import admintool
from ipapython.ipa_log_manager import Filter
from textwrap import wrap
from ipapython.ipa_log_manager import log_mgr
"""
@@ -42,9 +35,6 @@ The class can run any arbitrary code or IPA command via api.Command['command']()
calls. It needs to override get_info() method, which returns the formatted
advice string.
Important! Do not forget to register the class to the API.
>>> @register()
>>> class sample_advice(Advice):
>>> description = 'Instructions for machine with SSSD 1.0 setup.'
@@ -75,327 +65,12 @@ As a result, you can redirect the advice's output directly to a script file.
# ipa-advise sample-advice > script.sh
# ./script.sh
Important! Do not forget to register the class to the API.
>>> api.register(sample_advice)
"""
DEFAULT_INDENTATION_INCREMENT = 2
class _IndentationTracker(object):
"""
A simple wrapper that tracks the indentation level of the generated bash
commands
"""
def __init__(self, spaces_per_indent=0):
if spaces_per_indent <= 0:
raise ValueError(
"Indentation increments cannot be zero or negative")
self.spaces_per_indent = spaces_per_indent
self._indentation_stack = []
self._total_indentation_level = 0
@property
def indentation_string(self):
"""
return a string containing number of spaces corresponding to
indentation level
"""
return " " * self._total_indentation_level
def indent(self):
"""
track a single indentation of the generated code
"""
self._indentation_stack.append(self.spaces_per_indent)
self._recompute_indentation_level()
def _recompute_indentation_level(self):
"""
Track total indentation level of the generated code
"""
self._total_indentation_level = sum(self._indentation_stack)
def dedent(self):
"""
track a single dedentation of the generated code
dedents that would result in zero or negative indentation level will be
ignored
"""
try:
self._indentation_stack.pop()
except IndexError:
# can not dedent any further
pass
self._recompute_indentation_level()
class CompoundStatement(object):
"""
Wrapper around indented blocks of Bash statements.
Override `begin_statement` and `end_statement` methods to issue
opening/closing commands using the passed in _AdviceOutput instance
"""
def __init__(self, advice_output):
self.advice_output = advice_output
def __enter__(self):
self.begin_statement()
self.advice_output.indent()
def begin_statement(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
self.advice_output.dedent()
self.end_statement()
def end_statement(self):
pass
class IfBranch(CompoundStatement):
"""
Base wrapper around `if` branch. The closing statement is empty so it
leaves trailing block that can be closed off or continued by else branches
"""
def __init__(self, advice_output, conditional):
super(IfBranch, self).__init__(advice_output)
self.conditional = conditional
def begin_statement(self):
self.advice_output.command('if {}'.format(self.conditional))
self.advice_output.command('then')
class ElseIfBranch(CompoundStatement):
"""
Wrapper for `else if <CONDITIONAL>`
"""
def __init__(self, advice_output, alternative_conditional):
super(ElseIfBranch, self).__init__(advice_output)
self.alternative_conditional = alternative_conditional
def begin_statement(self):
command = 'else if {}'.format(self.alternative_conditional)
self.advice_output.command(command)
class ElseBranch(CompoundStatement):
"""
Wrapper for final `else` block
"""
def begin_statement(self):
self.advice_output.command('else')
def end_statement(self):
self.advice_output.command('fi')
class UnbranchedIfStatement(IfBranch):
"""
Plain `if` without branches
"""
def end_statement(self):
self.advice_output.command('fi')
class ForLoop(CompoundStatement):
"""
Wrapper around the for loop
"""
def __init__(self, advice_output, loop_variable, iterable):
super(ForLoop, self).__init__(advice_output)
self.loop_variable = loop_variable
self.iterable = iterable
def begin_statement(self):
self.advice_output.command(
'for {} in {}'.format(self.loop_variable, self.iterable))
self.advice_output.command('do')
def end_statement(self):
self.advice_output.command('done')
class _AdviceOutput(object):
def __init__(self):
self.content = []
self.prefix = '# '
self.options = None
self._indentation_tracker = _IndentationTracker(
spaces_per_indent=DEFAULT_INDENTATION_INCREMENT)
def indent(self):
"""
Indent the statements by one level
"""
self._indentation_tracker.indent()
def dedent(self):
"""
Dedent the statements by one level
"""
self._indentation_tracker.dedent()
@contextmanager
def indented_block(self):
self.indent()
try:
yield
finally:
self.dedent()
def comment(self, line, wrapped=True):
if wrapped:
self.append_wrapped_and_indented_comment(line)
else:
self.append_comment(line)
def append_wrapped_and_indented_comment(self, line, character_limit=70):
"""
append wrapped and indented comment to the output
"""
for wrapped_indented_line in wrap(
self.indent_statement(line), character_limit):
self.append_comment(wrapped_indented_line)
def append_comment(self, line):
self.append_statement(self.prefix + line)
def append_statement(self, statement):
"""
Append a line to the generated content indenting it by tracked number
of spaces
"""
self.content.append(self.indent_statement(statement))
def indent_statement(self, statement):
return '{indent}{statement}'.format(
indent=self._indentation_tracker.indentation_string,
statement=statement)
def debug(self, line):
if self.options.verbose:
self.comment('DEBUG: ' + line)
def command(self, line):
self.append_statement(line)
def echo_error(self, error_message):
self.command(self._format_error(error_message))
def _format_error(self, error_message):
return 'echo "{}" >&2'.format(error_message)
def exit_on_failed_command(self, command_to_run,
error_message_lines):
self.command(command_to_run)
self.exit_on_predicate(
'[ "$?" -ne "0" ]',
error_message_lines)
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):
with self.unbranched_if(predicate):
for error_message_line in error_message_lines:
self.command(self._format_error(error_message_line))
self.command('exit 1')
@contextmanager
def unbranched_if(self, predicate):
with self._compound_statement(UnbranchedIfStatement, predicate):
yield
@contextmanager
def _compound_statement(self, statement_cls, *args):
with statement_cls(self, *args):
yield
def commands_on_predicate(self, predicate, commands_to_run_when_true,
commands_to_run_when_false=None):
if commands_to_run_when_false is not None:
if_statement = self.if_branch
else:
if_statement = self.unbranched_if
with if_statement(predicate):
for command_to_run_when_true in commands_to_run_when_true:
self.command(
command_to_run_when_true)
if commands_to_run_when_false is not None:
with self.else_branch():
for command_to_run_when_false in commands_to_run_when_false:
self.command(command_to_run_when_false)
@contextmanager
def if_branch(self, predicate):
with self._compound_statement(IfBranch, predicate):
yield
@contextmanager
def else_branch(self):
with self._compound_statement(ElseBranch):
yield
@contextmanager
def else_if_branch(self, predicate):
with self._compound_statement(ElseIfBranch, predicate):
yield
@contextmanager
def for_loop(self, loop_variable, iterable):
with self._compound_statement(ForLoop, loop_variable, iterable):
yield
class Advice(Plugin):
"""
Base class for advices, plugins for ipa-advise.
"""
options = None
require_root = False
description = ''
def __init__(self, api):
super(Advice, self).__init__(api)
self.log = _AdviceOutput()
def set_options(self, options):
self.options = options
self.log.options = options
def get_info(self):
"""
This method should be overridden by child Advices.
Returns a string with instructions.
"""
raise NotImplementedError
class AdviseAPI(API):
bases = (Advice,)
@property
def packages(self):
import ipaserver.advise.plugins
return (ipaserver.advise.plugins,)
advise_api = AdviseAPI()
class IpaAdvise(admintool.AdminTool):
"""
@@ -429,12 +104,12 @@ class IpaAdvise(admintool.AdminTool):
def print_config_list(self):
self.print_header('List of available advices')
max_keyword_len = max(
(len(advice.name) for advice in advise_api.Advice))
max_keyword_len = max((len(keyword) for keyword in api.Advice))
for advice in advise_api.Advice:
for keyword in api.Advice:
advice = getattr(api.Advice, keyword, '')
description = getattr(advice, 'description', '')
keyword = advice.name.replace('_', '-')
keyword = keyword.replace('_', '-')
# Compute the number of spaces needed for the table to be aligned
offset = max_keyword_len - len(keyword)
@@ -442,11 +117,11 @@ class IpaAdvise(admintool.AdminTool):
wrapped_description = wrap(description, 80 - len(prefix))
# Print the first line with the prefix (keyword)
print(prefix + wrapped_description[0])
print prefix + wrapped_description[0]
# Print the rest wrapped behind the colon
for line in wrapped_description[1:]:
print("{off}{line}".format(off=' ' * len(prefix), line=line))
print "{off}{line}".format(off=' ' * len(prefix), line=line)
def print_header(self, header, print_shell=False):
header_size = len(header)
@@ -454,17 +129,17 @@ class IpaAdvise(admintool.AdminTool):
prefix = ''
if print_shell:
prefix = '# '
print('#!/bin/sh')
print '#!/bin/sh'
# Do not print out empty header
if header_size > 0:
print((prefix + '-' * 70))
print(prefix + '-' * 70)
for line in wrap(header, 70):
print((prefix + line))
print((prefix + '-' * 70))
print(prefix + line)
print(prefix + '-' * 70)
def print_advice(self, keyword):
advice = getattr(advise_api.Advice, keyword, None)
advice = getattr(api.Advice, keyword, None)
# Ensure that Configuration class for given --setup option value exists
if advice is None:
@@ -492,24 +167,17 @@ class IpaAdvise(admintool.AdminTool):
advice.get_info()
api.Backend.rpcclient.disconnect()
for line in advice.log.content:
print(line)
print line
def run(self):
super(IpaAdvise, self).run()
api.bootstrap(in_server=False,
context='cli',
confdir=paths.ETC_IPA)
api.bootstrap(in_server=False, context='advise')
api.finalize()
advise_api.bootstrap(in_server=False,
context='cli',
confdir=paths.ETC_IPA)
advise_api.finalize()
if not self.options.verbose:
# Do not print connection information by default
logger_name = r'ipalib\.rpc'
root_logger = logging.getLogger()
root_logger.addFilter(Filter(logger_name, logging.WARNING))
logger_name = r'ipa\.ipalib\.plugins\.rpcclient'
log_mgr.configure(dict(logger_regexps=[(logger_name, 'warning')]))
# With no argument, print the list out and exit
if not self.args:

View File

@@ -18,13 +18,9 @@
#
from ipalib import api
from ipalib.plugable import Registry
from ipaserver.advise.base import Advice
register = Registry()
from ipalib.frontend import Advice
@register()
class config_fedora_authconfig(Advice):
"""
Provides client configuration instructions using authconfig.
@@ -40,3 +36,6 @@ class config_fedora_authconfig(Advice):
"--enablerfc2307bis --enablekrb5"
advice = template.format(server=api.env.host)
self.log.command(advice)
api.register(config_fedora_authconfig)

View File

@@ -19,12 +19,8 @@
import os
from ipalib import api
from ipalib.plugable import Registry
from ipaplatform.paths import paths
from ipaserver.advise.base import Advice
from ipapython.ipautil import template_file
register = Registry()
from ipalib.frontend import Advice
from ipapython.ipautil import template_file, SHARE_DIR
class config_base_legacy_client(Advice):
@@ -52,13 +48,13 @@ class config_base_legacy_client(Advice):
'cacertdir_rehash?format=txt')
self.log.comment('Download the CA certificate of the IPA server')
self.log.command('mkdir -p -m 755 /etc/openldap/cacerts')
self.log.command('curl http://%s/ipa/config/ca.crt -o '
self.log.command('wget http://%s/ipa/config/ca.crt -O '
'/etc/openldap/cacerts/ipa.crt\n' % api.env.host)
self.log.comment('Generate hashes for the openldap library')
self.log.command('command -v cacertdir_rehash')
self.log.command('if [ $? -ne 0 ] ; then')
self.log.command(' curl "%s" -o cacertdir_rehash ;' % cacertdir_rehash)
self.log.command(' wget "%s" -O cacertdir_rehash ;' % cacertdir_rehash)
self.log.command(' chmod 755 ./cacertdir_rehash ;')
self.log.command(' ./cacertdir_rehash /etc/openldap/cacerts/ ;')
self.log.command('else')
@@ -68,7 +64,7 @@ class config_base_legacy_client(Advice):
def configure_and_start_sssd(self):
uri, base = self.get_uri_and_base()
template = os.path.join(
paths.USR_SHARE_IPA_DIR,
SHARE_DIR,
'advise',
'legacy',
'sssd.conf.template'
@@ -84,7 +80,6 @@ class config_base_legacy_client(Advice):
self.log.command('service sssd start')
@register()
class config_redhat_sssd_before_1_9(config_base_legacy_client):
"""
Legacy client configuration for Red Hat based systems, using SSSD.
@@ -99,7 +94,7 @@ class config_redhat_sssd_before_1_9(config_base_legacy_client):
self.check_compat_plugin()
self.log.comment('Install required packages via yum')
self.log.command('yum install -y sssd authconfig curl openssl\n')
self.log.command('yum install -y sssd authconfig wget openssl\n')
self.configure_ca_cert()
@@ -118,7 +113,9 @@ class config_redhat_sssd_before_1_9(config_base_legacy_client):
super(config_redhat_sssd_before_1_9, self).configure_ca_cert()
@register()
api.register(config_redhat_sssd_before_1_9)
class config_generic_linux_sssd_before_1_9(config_base_legacy_client):
"""
Legacy client configuration for non Red Hat based linux systems,
@@ -133,7 +130,7 @@ class config_generic_linux_sssd_before_1_9(config_base_legacy_client):
self.check_compat_plugin()
with open(os.path.join(
paths.USR_SHARE_IPA_DIR,
SHARE_DIR,
'advise',
'legacy',
'pam.conf.sssd.template')) as fd:
@@ -141,7 +138,7 @@ class config_generic_linux_sssd_before_1_9(config_base_legacy_client):
self.log.comment('Install required packages using your system\'s '
'package manager. E.g:')
self.log.command('apt-get -y install sssd curl openssl\n')
self.log.command('apt-get -y install sssd wget openssl\n')
self.configure_ca_cert()
@@ -173,7 +170,9 @@ class config_generic_linux_sssd_before_1_9(config_base_legacy_client):
'/etc/ldap/ldap.conf\n')
@register()
api.register(config_generic_linux_sssd_before_1_9)
class config_redhat_nss_pam_ldapd(config_base_legacy_client):
"""
Legacy client configuration for Red Hat based systems,
@@ -189,14 +188,14 @@ class config_redhat_nss_pam_ldapd(config_base_legacy_client):
self.check_compat_plugin()
self.log.comment('Install required packages via yum')
self.log.command('yum install -y curl openssl nss-pam-ldapd pam_ldap '
self.log.command('yum install -y wget openssl nss-pam-ldapd pam_ldap '
'authconfig\n')
self.configure_ca_cert()
self.log.comment('Use the authconfig to configure nsswitch.conf '
'and the PAM stack')
self.log.command('authconfig --updateall --enableldap --enableldaptls '
self.log.command('authconfig --updateall --enableldap '
'--enableldapauth --ldapserver=%s --ldapbasedn=%s\n'
% (uri, base))
@@ -208,7 +207,9 @@ class config_redhat_nss_pam_ldapd(config_base_legacy_client):
super(config_redhat_nss_pam_ldapd, self).configure_ca_cert()
@register()
api.register(config_redhat_nss_pam_ldapd)
class config_generic_linux_nss_pam_ldapd(config_base_legacy_client):
"""
Legacy client configuration for non Red Hat based linux systems,
@@ -223,7 +224,7 @@ class config_generic_linux_nss_pam_ldapd(config_base_legacy_client):
self.check_compat_plugin()
with open(os.path.join(
paths.USR_SHARE_IPA_DIR,
SHARE_DIR,
'advise',
'legacy',
'pam.conf.nss_pam_ldapd.template')) as fd:
@@ -233,7 +234,7 @@ class config_generic_linux_nss_pam_ldapd(config_base_legacy_client):
self.log.comment('Install required packages using your system\'s '
'package manager. E.g:')
self.log.command('apt-get -y install curl openssl libnss-ldapd '
self.log.command('apt-get -y install wget openssl libnss-ldapd '
'libpam-ldapd nslcd\n')
self.configure_ca_cert()
@@ -275,7 +276,9 @@ class config_generic_linux_nss_pam_ldapd(config_base_legacy_client):
'/etc/ldap/ldap.conf\n')
@register()
api.register(config_generic_linux_nss_pam_ldapd)
class config_freebsd_nss_pam_ldapd(config_base_legacy_client):
"""
Legacy client configuration for FreeBSD, using nss-pam-ldapd.
@@ -290,7 +293,7 @@ class config_freebsd_nss_pam_ldapd(config_base_legacy_client):
self.check_compat_plugin()
with open(os.path.join(
paths.USR_SHARE_IPA_DIR,
SHARE_DIR,
'advise',
'legacy',
'pam_conf_sshd.template')) as fd:
@@ -340,8 +343,9 @@ class config_freebsd_nss_pam_ldapd(config_base_legacy_client):
self.log.command('curl -k https://%s/ipa/config/ca.crt > '
'%s' % (api.env.host, cacrt))
api.register(config_freebsd_nss_pam_ldapd)
@register()
class config_redhat_nss_ldap(config_base_legacy_client):
"""
Legacy client configuration for Red Hat based systems,
@@ -357,14 +361,14 @@ class config_redhat_nss_ldap(config_base_legacy_client):
self.check_compat_plugin()
self.log.comment('Install required packages via yum')
self.log.command('yum install -y curl openssl nss_ldap '
self.log.command('yum install -y wget openssl nss_ldap '
'authconfig\n')
self.configure_ca_cert()
self.log.comment('Use the authconfig to configure nsswitch.conf '
'and the PAM stack')
self.log.command('authconfig --updateall --enableldap --enableldaptls '
self.log.command('authconfig --updateall --enableldap '
'--enableldapauth --ldapserver=%s --ldapbasedn=%s\n'
% (uri, base))
@@ -374,3 +378,5 @@ class config_redhat_nss_ldap(config_base_legacy_client):
'Therefore, clients older than RHEL5.2 will not be '
'able to interoperate with IPA server 3.x.')
super(config_redhat_nss_ldap, self).configure_ca_cert()
api.register(config_redhat_nss_ldap)

View File

@@ -1,325 +0,0 @@
#
# Copyright (C) 2017 FreeIPA Contributors see COPYING for license
#
import os
from ipalib.plugable import Registry
from ipaplatform import services
from ipaplatform.paths import paths
from ipaserver.advise.base import Advice
from ipaserver.install.httpinstance import NSS_OCSP_ENABLED
register = Registry()
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
smart_card_ca_certs_variable_name = "SC_CA_CERTS"
single_ca_cert_variable_name = 'ca_cert'
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_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
self.log.command("{}=$@".format(ca_paths_variable))
self.log.exit_on_predicate(
'[ -z "${}" ]'.format(ca_paths_variable),
['You need to provide one or more paths to the PEM files '
'containing CAs signing the Smart Cards']
)
with self.log.for_loop(single_ca_path_variable,
'${}'.format(ca_paths_variable)):
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']
)
def upload_smartcard_ca_certificates_to_systemwide_db(self):
with self.log.for_loop(
self.single_ca_cert_variable_name,
'${}'.format(self.smart_card_ca_certs_variable_name)):
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
)
)
def install_smart_card_signing_ca_certs(self):
with self.log.for_loop(
self.single_ca_cert_variable_name,
'${}'.format(self.smart_card_ca_certs_variable_name)):
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']
)
def update_ipa_ca_certificate_store(self):
self.log.exit_on_failed_command(
'ipa-certupdate',
['Failed to update IPA CA certificate database']
)
@register()
class config_server_for_smart_card_auth(common_smart_card_auth_config):
"""
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'
kdc_service_name = services.knownservices.krb5kdc.systemd_name
def get_info(self):
self.log.exit_on_nonroot_euid()
self.check_and_set_ca_cert_paths()
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.upload_smartcard_ca_certificates_to_systemwide_db()
self.install_smart_card_signing_ca_certs()
self.update_ipa_ca_certificate_store()
self.restart_kdc()
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.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(
"sed -i.ipabkp -r "
"'s/^#*[[:space:]]*{directive}[[:space:]]+(on|off)$"
"/{directive} on/' {filename}")
],
commands_to_run_when_false=[
self._interpolate_ocsp_directive_file_into_command(
"sed -i.ipabkp '/<\/VirtualHost>/i {directive} on' "
"{filename}")
]
)
def _interpolate_ocsp_directive_file_into_command(self, fmt_line):
return self._format_command(
fmt_line, self.nss_ocsp_directive, self.nss_conf)
def _format_command(self, fmt_line, directive, filename):
return fmt_line.format(directive=directive, filename=filename)
def mark_httpd_cert_as_trusted(self):
httpd_nss_database_pwd_file = os.path.join(
paths.HTTPD_ALIAS_DIR, 'pwdfile.txt')
self.log.comment(
'mark the HTTP certificate as trusted peer to avoid '
'chicken-egg startup issue')
self.log.command(
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 "{}" -f {} -t "Pu,u,u"'.format(
paths.HTTPD_ALIAS_DIR,
httpd_nss_database_pwd_file),
['Can not set trust flags on HTTP certificate'])
def _interpolate_nssnickname_directive_file_into_command(self, fmt_line):
return 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')
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'])
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 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.']
)
@register()
class config_client_for_smart_card_auth(common_smart_card_auth_config):
"""
Configures smart card authentication on FreeIPA client
"""
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")
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()
self.check_and_set_ca_cert_paths()
self.check_ccache_not_empty()
self.check_and_remove_pam_pkcs11()
self.install_opensc_and_dconf_packages()
self.install_krb5_client_dependencies()
self.start_enable_smartcard_daemon()
self.add_pkcs11_module_to_systemwide_db()
self.upload_smartcard_ca_certificates_to_systemwide_db()
self.update_ipa_ca_certificate_store()
self.run_authconfig_to_configure_smart_card_auth()
self.restart_sssd()
def check_and_remove_pam_pkcs11(self):
self.log.command('rpm -qi pam_pkcs11 > /dev/null')
self.log.commands_on_predicate(
'[ "$?" -eq "0" ]',
[
'yum remove -y pam_pkcs11'
]
)
def install_opensc_and_dconf_packages(self):
self.log.comment(
'authconfig often complains about missing dconf, '
'install it explicitly')
self.log.exit_on_failed_command(
'yum install -y {} dconf'.format(self.opensc_module_name.lower()),
['Could not install OpenSC package']
)
def install_krb5_client_dependencies(self):
self.log.exit_on_failed_command(
'yum install -y krb5-pkinit-openssl',
['Failed to install Kerberos client PKINIT extensions.']
)
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(
'modutil -dbdir {} -list | grep -q {}'.format(
nssdb, module_name),
[
'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),
]
)
def run_authconfig_to_configure_smart_card_auth(self):
self.log.exit_on_failed_command(
'authconfig --enablesmartcard --smartcardmodule=sssd --updateall',
[
'Failed to configure Smart Card authentication in SSSD'
]
)
def restart_sssd(self):
self.log.command('systemctl restart sssd.service')