Imported Upstream version 4.6.2
This commit is contained in:
22
ipaserver/advise/__init__.py
Normal file
22
ipaserver/advise/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Authors: Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Base subpackage for ipa-advise related code.
|
||||
"""
|
||||
520
ipaserver/advise/base.py
Normal file
520
ipaserver/advise/base.py
Normal file
@@ -0,0 +1,520 @@
|
||||
# Authors: Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# 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
|
||||
|
||||
|
||||
"""
|
||||
To add configuration instructions for a new use case, define a new class that
|
||||
inherits from Advice class.
|
||||
|
||||
You should create a plugin file for it in ipaserver/advise/plugins folder.
|
||||
|
||||
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.'
|
||||
|
||||
Description provided shows itself as a header and in the list of all advices
|
||||
currently available via ipa-advise.
|
||||
|
||||
Optionally, you can require root privileges for your plugin:
|
||||
|
||||
>>> require_root = True
|
||||
|
||||
The following method should be implemented in your plugin:
|
||||
|
||||
>>> def get_info():
|
||||
>>> self.log.debug('Entering execute() method')
|
||||
>>> self.log.comment('Providing useful advice just for you')
|
||||
>>> self.log.command('yum update sssd -y')
|
||||
|
||||
As you can see, Advice's log has 3 different levels. Debug lines are printed
|
||||
out with '# DEBUG:' prefix if --verbose had been used. Comment lines utilize
|
||||
'# ' prefix and command lines are printed raw.
|
||||
|
||||
Please note that comments are automatically wrapped after 70 characters.
|
||||
Use wrapped=False option to force the unwrapped line in the comment.
|
||||
|
||||
>>> self.log.comment("This line should not be wrapped", wrapped=False)
|
||||
|
||||
As a result, you can redirect the advice's output directly to a script file.
|
||||
|
||||
# ipa-advise sample-advice > script.sh
|
||||
# ./script.sh
|
||||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
Admin tool that given systems's configuration provides instructions how to
|
||||
configure the systems for various use cases.
|
||||
"""
|
||||
|
||||
command_name = 'ipa-advise'
|
||||
usage = "%prog ADVICE"
|
||||
description = "Provides configuration advice for various use cases. To "\
|
||||
"see the list of possible ADVICEs, run ipa-advise without "\
|
||||
"any arguments."
|
||||
|
||||
def __init__(self, options, args):
|
||||
super(IpaAdvise, self).__init__(options, args)
|
||||
|
||||
@classmethod
|
||||
def add_options(cls, parser):
|
||||
super(IpaAdvise, cls).add_options(parser)
|
||||
|
||||
def validate_options(self):
|
||||
super(IpaAdvise, self).validate_options(needs_root=False)
|
||||
|
||||
if len(self.args) > 1:
|
||||
raise self.option_parser.error("You can only provide one "
|
||||
"positional argument.")
|
||||
|
||||
def log_success(self):
|
||||
pass
|
||||
|
||||
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))
|
||||
|
||||
for advice in advise_api.Advice:
|
||||
description = getattr(advice, 'description', '')
|
||||
keyword = advice.name.replace('_', '-')
|
||||
|
||||
# Compute the number of spaces needed for the table to be aligned
|
||||
offset = max_keyword_len - len(keyword)
|
||||
prefix = " {key} {off}: ".format(key=keyword, off=' ' * offset)
|
||||
wrapped_description = wrap(description, 80 - len(prefix))
|
||||
|
||||
# Print the first line with the prefix (keyword)
|
||||
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))
|
||||
|
||||
def print_header(self, header, print_shell=False):
|
||||
header_size = len(header)
|
||||
|
||||
prefix = ''
|
||||
if print_shell:
|
||||
prefix = '# '
|
||||
print('#!/bin/sh')
|
||||
|
||||
# Do not print out empty header
|
||||
if header_size > 0:
|
||||
print((prefix + '-' * 70))
|
||||
for line in wrap(header, 70):
|
||||
print((prefix + line))
|
||||
print((prefix + '-' * 70))
|
||||
|
||||
def print_advice(self, keyword):
|
||||
advice = getattr(advise_api.Advice, keyword, None)
|
||||
|
||||
# Ensure that Configuration class for given --setup option value exists
|
||||
if advice is None:
|
||||
raise ValidationError(
|
||||
name="advice",
|
||||
error="No instructions are available for '{con}'. "
|
||||
"See the list of available configuration "
|
||||
"by invoking the ipa-advise command with no argument."
|
||||
.format(con=keyword.replace('_', '-')))
|
||||
|
||||
# Check whether root privileges are needed
|
||||
if advice.require_root and os.getegid() != 0:
|
||||
raise admintool.ScriptError(
|
||||
'Must be root to get advice for {adv}'
|
||||
.format(adv=keyword.replace('_', '-')), 1)
|
||||
|
||||
# Print out nicely formatted header
|
||||
self.print_header(advice.description, print_shell=True)
|
||||
|
||||
# Set options so that plugin can use verbose/quiet options
|
||||
advice.set_options(self.options)
|
||||
|
||||
# Print out the actual advice
|
||||
api.Backend.rpcclient.connect()
|
||||
advice.get_info()
|
||||
api.Backend.rpcclient.disconnect()
|
||||
for line in advice.log.content:
|
||||
print(line)
|
||||
|
||||
def run(self):
|
||||
super(IpaAdvise, self).run()
|
||||
|
||||
api.bootstrap(in_server=False,
|
||||
context='cli',
|
||||
confdir=paths.ETC_IPA)
|
||||
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))
|
||||
|
||||
# With no argument, print the list out and exit
|
||||
if not self.args:
|
||||
self.print_config_list()
|
||||
return
|
||||
else:
|
||||
keyword = self.args[0].replace('-', '_')
|
||||
self.print_advice(keyword)
|
||||
22
ipaserver/advise/plugins/__init__.py
Normal file
22
ipaserver/advise/plugins/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Authors: Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Provides a separate api for ipa-advise plugins.
|
||||
"""
|
||||
42
ipaserver/advise/plugins/fedora_authconfig.py
Normal file
42
ipaserver/advise/plugins/fedora_authconfig.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Authors: Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from ipalib import api
|
||||
from ipalib.plugable import Registry
|
||||
from ipaserver.advise.base import Advice
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class config_fedora_authconfig(Advice):
|
||||
"""
|
||||
Provides client configuration instructions using authconfig.
|
||||
"""
|
||||
|
||||
description = 'Authconfig instructions for configuring Fedora 18/19 '\
|
||||
'client with IPA server without use of SSSD.'
|
||||
|
||||
def get_info(self):
|
||||
self.log.debug("Hostname obtained via api.env.host")
|
||||
self.log.comment("Run the following command as a root:")
|
||||
template = "/sbin/authconfig --enableldap --ldapserver={server} "\
|
||||
"--enablerfc2307bis --enablekrb5"
|
||||
advice = template.format(server=api.env.host)
|
||||
self.log.command(advice)
|
||||
376
ipaserver/advise/plugins/legacy_clients.py
Normal file
376
ipaserver/advise/plugins/legacy_clients.py
Normal file
@@ -0,0 +1,376 @@
|
||||
# Authors: Ana Krivokapic <akrivoka@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
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()
|
||||
|
||||
|
||||
class config_base_legacy_client(Advice):
|
||||
def get_uri_and_base(self):
|
||||
uri = 'ldap://%s' % api.env.host
|
||||
base = 'cn=compat,%s' % api.env.basedn
|
||||
return uri, base
|
||||
|
||||
def check_compat_plugin(self):
|
||||
compat_is_enabled = api.Command['compat_is_enabled']()['result']
|
||||
if not compat_is_enabled:
|
||||
self.log.comment(
|
||||
'Schema Compatibility plugin has not been configured '
|
||||
'on this server. To configure it, run '
|
||||
'"ipa-adtrust-install --enable-compat"\n'
|
||||
)
|
||||
|
||||
def configure_ca_cert(self):
|
||||
self.log.comment('Please note that this script assumes '
|
||||
'/etc/openldap/cacerts as the default CA certificate '
|
||||
'location. If this value is different on your system '
|
||||
'the script needs to be modified accordingly.\n')
|
||||
|
||||
cacertdir_rehash = ('https://fedorahosted.org/authconfig/browser/'
|
||||
'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 '
|
||||
'/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(' chmod 755 ./cacertdir_rehash ;')
|
||||
self.log.command(' ./cacertdir_rehash /etc/openldap/cacerts/ ;')
|
||||
self.log.command('else')
|
||||
self.log.command(' cacertdir_rehash /etc/openldap/cacerts/ ;')
|
||||
self.log.command('fi\n')
|
||||
|
||||
def configure_and_start_sssd(self):
|
||||
uri, base = self.get_uri_and_base()
|
||||
template = os.path.join(
|
||||
paths.USR_SHARE_IPA_DIR,
|
||||
'advise',
|
||||
'legacy',
|
||||
'sssd.conf.template'
|
||||
)
|
||||
sssd_conf = template_file(template, dict(URI=uri, BASE=base))
|
||||
|
||||
self.log.comment('Configure SSSD')
|
||||
self.log.command('cat > /etc/sssd/sssd.conf << EOF \n'
|
||||
'%s\nEOF' % sssd_conf)
|
||||
self.log.command('chmod 0600 /etc/sssd/sssd.conf\n')
|
||||
|
||||
self.log.comment('Start SSSD')
|
||||
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.
|
||||
"""
|
||||
description = ('Instructions for configuring a system with an old version '
|
||||
'of SSSD (1.5-1.8) as a FreeIPA client. This set of '
|
||||
'instructions is targeted for platforms that include '
|
||||
'the authconfig utility, which are all Red Hat based '
|
||||
'platforms.')
|
||||
|
||||
def get_info(self):
|
||||
self.check_compat_plugin()
|
||||
|
||||
self.log.comment('Install required packages via yum')
|
||||
self.log.command('yum install -y sssd authconfig curl openssl\n')
|
||||
|
||||
self.configure_ca_cert()
|
||||
|
||||
self.log.comment('Use the authconfig to configure nsswitch.conf '
|
||||
'and the PAM stack')
|
||||
self.log.command('authconfig --updateall --enablesssd '
|
||||
'--enablesssdauth\n')
|
||||
|
||||
self.configure_and_start_sssd()
|
||||
|
||||
def configure_ca_cert(self):
|
||||
self.log.comment('NOTE: IPA certificate uses the SHA-256 hash '
|
||||
'function. SHA-256 was introduced in RHEL5.2. '
|
||||
'Therefore, clients older than RHEL5.2 will not be '
|
||||
'able to interoperate with IPA server 3.x.')
|
||||
super(config_redhat_sssd_before_1_9, self).configure_ca_cert()
|
||||
|
||||
|
||||
@register()
|
||||
class config_generic_linux_sssd_before_1_9(config_base_legacy_client):
|
||||
"""
|
||||
Legacy client configuration for non Red Hat based linux systems,
|
||||
using SSSD.
|
||||
"""
|
||||
description = ('Instructions for configuring a system with an old version '
|
||||
'of SSSD (1.5-1.8) as a FreeIPA client. This set of '
|
||||
'instructions is targeted for linux systems that do not '
|
||||
'include the authconfig utility.')
|
||||
|
||||
def get_info(self):
|
||||
self.check_compat_plugin()
|
||||
|
||||
with open(os.path.join(
|
||||
paths.USR_SHARE_IPA_DIR,
|
||||
'advise',
|
||||
'legacy',
|
||||
'pam.conf.sssd.template')) as fd:
|
||||
pam_conf = fd.read()
|
||||
|
||||
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.configure_ca_cert()
|
||||
|
||||
self.log.comment('Configure nsswitch.conf. Append sss to the lines '
|
||||
'beginning with passwd and group. ')
|
||||
self.log.command('grep "^passwd.*sss" /etc/nsswitch.conf')
|
||||
self.log.command('if [ $? -ne 0 ] ; then sed -i '
|
||||
'\'/^passwd/s|$| sss|\' /etc/nsswitch.conf ; fi')
|
||||
self.log.command('grep "^group.*sss" /etc/nsswitch.conf')
|
||||
self.log.command('if [ $? -ne 0 ] ; then sed -i '
|
||||
'\'/^group/s|$| sss|\' /etc/nsswitch.conf ; fi\n')
|
||||
|
||||
self.log.comment('Configure PAM. Configuring the PAM stack differs on '
|
||||
'particular distributions. The resulting PAM stack '
|
||||
'should look like this:')
|
||||
self.log.command('cat > /etc/pam.conf << EOF \n'
|
||||
'%s\nEOF\n' % pam_conf)
|
||||
|
||||
self.configure_and_start_sssd()
|
||||
|
||||
def configure_ca_cert(self):
|
||||
super(config_generic_linux_sssd_before_1_9, self).configure_ca_cert()
|
||||
|
||||
self.log.comment('Configure ldap.conf. Set the value of '
|
||||
'TLS_CACERTDIR to /etc/openldap/cacerts. Make sure '
|
||||
'that the location of ldap.conf file matches your '
|
||||
'system\'s configuration.')
|
||||
self.log.command('echo "TLS_CACERTDIR /etc/openldap/cacerts" >> '
|
||||
'/etc/ldap/ldap.conf\n')
|
||||
|
||||
|
||||
@register()
|
||||
class config_redhat_nss_pam_ldapd(config_base_legacy_client):
|
||||
"""
|
||||
Legacy client configuration for Red Hat based systems,
|
||||
using nss-pam-ldapd.
|
||||
"""
|
||||
description = ('Instructions for configuring a system with nss-pam-ldapd '
|
||||
'as a FreeIPA client. This set of instructions is targeted '
|
||||
'for platforms that include the authconfig utility, which '
|
||||
'are all Red Hat based platforms.')
|
||||
|
||||
def get_info(self):
|
||||
uri, base = self.get_uri_and_base()
|
||||
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 '
|
||||
'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 '
|
||||
'--enableldapauth --ldapserver=%s --ldapbasedn=%s\n'
|
||||
% (uri, base))
|
||||
|
||||
def configure_ca_cert(self):
|
||||
self.log.comment('NOTE: IPA certificate uses the SHA-256 hash '
|
||||
'function. SHA-256 was introduced in RHEL5.2. '
|
||||
'Therefore, clients older than RHEL5.2 will not be '
|
||||
'able to interoperate with IPA server 3.x.')
|
||||
super(config_redhat_nss_pam_ldapd, self).configure_ca_cert()
|
||||
|
||||
|
||||
@register()
|
||||
class config_generic_linux_nss_pam_ldapd(config_base_legacy_client):
|
||||
"""
|
||||
Legacy client configuration for non Red Hat based linux systems,
|
||||
using nss-pam-ldapd.
|
||||
"""
|
||||
description = ('Instructions for configuring a system with nss-pam-ldapd. '
|
||||
'This set of instructions is targeted for linux systems '
|
||||
'that do not include the authconfig utility.')
|
||||
|
||||
def get_info(self):
|
||||
uri, base = self.get_uri_and_base()
|
||||
self.check_compat_plugin()
|
||||
|
||||
with open(os.path.join(
|
||||
paths.USR_SHARE_IPA_DIR,
|
||||
'advise',
|
||||
'legacy',
|
||||
'pam.conf.nss_pam_ldapd.template')) as fd:
|
||||
pam_conf = fd.read()
|
||||
|
||||
nslcd_conf = 'uri %s\nbase %s' % (uri, base)
|
||||
|
||||
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 '
|
||||
'libpam-ldapd nslcd\n')
|
||||
|
||||
self.configure_ca_cert()
|
||||
|
||||
self.log.comment('Configure nsswitch.conf. Append ldap to the lines '
|
||||
'beginning with passwd and group. ')
|
||||
self.log.command('grep "^passwd.*ldap" /etc/nsswitch.conf')
|
||||
self.log.command('if [ $? -ne 0 ] ; then sed -i '
|
||||
'\'/^passwd/s|$| ldap|\' /etc/nsswitch.conf ; fi')
|
||||
self.log.command('grep "^group.*ldap" /etc/nsswitch.conf')
|
||||
self.log.command('if [ $? -ne 0 ] ; then sed -i '
|
||||
'\'/^group/s|$| ldap|\' /etc/nsswitch.conf ; fi\n')
|
||||
|
||||
self.log.comment('Configure PAM. Configuring the PAM stack differs on '
|
||||
'particular distributions. The resulting PAM stack '
|
||||
'should look like this:')
|
||||
self.log.command('cat > /etc/pam.conf << EOF \n'
|
||||
'%s\nEOF\n' % pam_conf)
|
||||
|
||||
self.log.comment('Configure nslcd.conf:')
|
||||
self.log.command('cat > /etc/nslcd.conf << EOF \n'
|
||||
'%s\nEOF\n' % nslcd_conf)
|
||||
|
||||
self.log.comment('Configure pam_ldap.conf:')
|
||||
self.log.command('cat > /etc/pam_ldap.conf << EOF \n'
|
||||
'%s\nEOF\n' % nslcd_conf)
|
||||
|
||||
self.log.comment('Stop nscd and restart nslcd')
|
||||
self.log.command('service nscd stop && service nslcd restart')
|
||||
|
||||
def configure_ca_cert(self):
|
||||
super(config_generic_linux_nss_pam_ldapd, self).configure_ca_cert()
|
||||
|
||||
self.log.comment('Configure ldap.conf. Set the value of '
|
||||
'TLS_CACERTDIR to /etc/openldap/cacerts. Make sure '
|
||||
'that the location of ldap.conf file matches your '
|
||||
'system\'s configuration.')
|
||||
self.log.command('echo "TLS_CACERTDIR /etc/openldap/cacerts" >> '
|
||||
'/etc/ldap/ldap.conf\n')
|
||||
|
||||
|
||||
@register()
|
||||
class config_freebsd_nss_pam_ldapd(config_base_legacy_client):
|
||||
"""
|
||||
Legacy client configuration for FreeBSD, using nss-pam-ldapd.
|
||||
"""
|
||||
description = ('Instructions for configuring a FreeBSD system with '
|
||||
'nss-pam-ldapd. ')
|
||||
|
||||
def get_info(self):
|
||||
uri, base = self.get_uri_and_base()
|
||||
cacrt = '/usr/local/etc/ipa.crt'
|
||||
|
||||
self.check_compat_plugin()
|
||||
|
||||
with open(os.path.join(
|
||||
paths.USR_SHARE_IPA_DIR,
|
||||
'advise',
|
||||
'legacy',
|
||||
'pam_conf_sshd.template')) as fd:
|
||||
pam_conf = fd.read()
|
||||
|
||||
self.log.comment('Install required packages')
|
||||
self.log.command('pkg_add -r nss-pam-ldapd curl\n')
|
||||
|
||||
self.configure_ca_cert(cacrt)
|
||||
|
||||
self.log.comment('Configure nsswitch.conf')
|
||||
self.log.command('sed -i \'\' -e \'s/^passwd:/passwd: files ldap/\' '
|
||||
'/etc/nsswitch.conf')
|
||||
self.log.command('sed -i \'\' -e \'s/^group:/group: files ldap/\' '
|
||||
'/etc/nsswitch.conf\n')
|
||||
|
||||
self.log.comment('Configure PAM stack for the sshd service')
|
||||
self.log.command('cat > /etc/pam.d/sshd << EOF \n'
|
||||
'%s\nEOF\n' % pam_conf)
|
||||
|
||||
self.log.comment('Add automated start of nslcd to /etc/rc.conf')
|
||||
self.log.command('echo \'nslcd_enable="YES"\nnslcd_debug="NO"\' >> '
|
||||
'/etc/rc.conf')
|
||||
|
||||
self.log.comment('Configure nslcd.conf:')
|
||||
self.log.command('echo "uid nslcd\n'
|
||||
'gid nslcd\n'
|
||||
'uri %s\n'
|
||||
'base %s\n'
|
||||
'scope sub\n'
|
||||
'base group cn=groups,%s\n'
|
||||
'base passwd cn=users,%s\n'
|
||||
'base shadow cn=users,%s\n'
|
||||
'ssl start_tls\n'
|
||||
'tls_cacertfile %s\n" > /usr/local/etc/nslcd.conf'
|
||||
% ((uri,) + (base,)*4 + (cacrt,)))
|
||||
|
||||
self.log.comment('Configure ldap.conf:')
|
||||
self.log.command('echo "uri %s\nbase %s\nssl start_tls\ntls_cacert %s"'
|
||||
'> /usr/local/etc/ldap.conf' % (uri, base, cacrt))
|
||||
|
||||
self.log.comment('Restart nslcd')
|
||||
self.log.command('/usr/local/etc/rc.d/nslcd restart')
|
||||
|
||||
def configure_ca_cert(self, cacrt):
|
||||
self.log.comment('Download the CA certificate of the IPA server')
|
||||
self.log.command('curl -k https://%s/ipa/config/ca.crt > '
|
||||
'%s' % (api.env.host, cacrt))
|
||||
|
||||
|
||||
@register()
|
||||
class config_redhat_nss_ldap(config_base_legacy_client):
|
||||
"""
|
||||
Legacy client configuration for Red Hat based systems,
|
||||
using nss-ldap.
|
||||
"""
|
||||
description = ('Instructions for configuring a system with nss-ldap '
|
||||
'as a FreeIPA client. This set of instructions is targeted '
|
||||
'for platforms that include the authconfig utility, which '
|
||||
'are all Red Hat based platforms.')
|
||||
|
||||
def get_info(self):
|
||||
uri, base = self.get_uri_and_base()
|
||||
self.check_compat_plugin()
|
||||
|
||||
self.log.comment('Install required packages via yum')
|
||||
self.log.command('yum install -y curl 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 '
|
||||
'--enableldapauth --ldapserver=%s --ldapbasedn=%s\n'
|
||||
% (uri, base))
|
||||
|
||||
def configure_ca_cert(self):
|
||||
self.log.comment('NOTE: IPA certificate uses the SHA-256 hash '
|
||||
'function. SHA-256 was introduced in RHEL5.2. '
|
||||
'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()
|
||||
325
ipaserver/advise/plugins/smart_card_auth.py
Normal file
325
ipaserver/advise/plugins/smart_card_auth.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#
|
||||
# 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')
|
||||
Reference in New Issue
Block a user