Import Upstream version 4.12.4
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -21,20 +21,17 @@ from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from ipalib.install import certmonger, certstore
|
||||
from ipalib.facts import is_ipa_configured
|
||||
from ipalib.install.kinit import kinit_keytab
|
||||
from ipapython import admintool, certdb, ipaldap, ipautil
|
||||
from ipaplatform import services
|
||||
from ipaplatform.paths import paths
|
||||
from ipaplatform.tasks import tasks
|
||||
from ipalib import api, errors, x509
|
||||
from ipalib.constants import IPA_CA_NICKNAME, RENEWAL_CA_NAME
|
||||
from ipalib.constants import FQDN, IPA_CA_NICKNAME, RENEWAL_CA_NAME
|
||||
from ipalib.util import check_client_configuration
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -54,12 +51,28 @@ class CertUpdate(admintool.AdminTool):
|
||||
def run(self):
|
||||
check_client_configuration()
|
||||
|
||||
api.bootstrap(context='cli_installer', confdir=paths.ETC_IPA)
|
||||
api.finalize()
|
||||
old_krb5ccname = os.environ.get('KRB5CCNAME')
|
||||
os.environ['KRB5_CLIENT_KTNAME'] = '/etc/krb5.keytab'
|
||||
os.environ['KRB5CCNAME'] = "MEMORY:"
|
||||
|
||||
api.Backend.rpcclient.connect()
|
||||
run_with_args(api)
|
||||
api.Backend.rpcclient.disconnect()
|
||||
try:
|
||||
api.bootstrap(context='cli_installer', confdir=paths.ETC_IPA)
|
||||
api.finalize()
|
||||
|
||||
api.Backend.rpcclient.connect()
|
||||
run_with_args(api)
|
||||
api.Backend.rpcclient.disconnect()
|
||||
except errors.CCacheError:
|
||||
logger.error(
|
||||
"Unable to obtain credentials for %s from /etc/krb5.keytab",
|
||||
FQDN
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
if old_krb5ccname is None:
|
||||
del os.environ['KRB5CCNAME']
|
||||
else:
|
||||
os.environ['KRB5CCNAME'] = old_krb5ccname
|
||||
|
||||
|
||||
def run_with_args(api):
|
||||
@@ -73,44 +86,39 @@ def run_with_args(api):
|
||||
server = urlsplit(api.env.jsonrpc_uri).hostname
|
||||
ldap = ipaldap.LDAPClient.from_hostname_secure(server)
|
||||
|
||||
tmpdir = tempfile.mkdtemp(prefix="tmp-")
|
||||
ccache_name = os.path.join(tmpdir, 'ccache')
|
||||
old_krb5ccname = os.environ.get('KRB5CCNAME')
|
||||
try:
|
||||
principal = str('host/%s@%s' % (api.env.host, api.env.realm))
|
||||
kinit_keytab(principal, paths.KRB5_KEYTAB, ccache_name)
|
||||
os.environ['KRB5CCNAME'] = ccache_name
|
||||
result = api.Command.ca_is_enabled(version=u'2.107')
|
||||
ca_enabled = result['result']
|
||||
except (errors.CommandError, errors.NetworkError):
|
||||
result = api.Command.env(server=True, version=u'2.0')
|
||||
ca_enabled = result['result']['enable_ra']
|
||||
|
||||
try:
|
||||
result = api.Command.ca_is_enabled(version=u'2.107')
|
||||
ca_enabled = result['result']
|
||||
except (errors.CommandError, errors.NetworkError):
|
||||
result = api.Command.env(server=True, version=u'2.0')
|
||||
ca_enabled = result['result']['enable_ra']
|
||||
ldap.gssapi_bind()
|
||||
|
||||
ldap.gssapi_bind()
|
||||
certs = certstore.get_ca_certs(
|
||||
ldap, api.env.basedn, api.env.realm, ca_enabled)
|
||||
|
||||
certs = certstore.get_ca_certs(
|
||||
ldap, api.env.basedn, api.env.realm, ca_enabled)
|
||||
if ca_enabled:
|
||||
lwcas = api.Command.ca_find()['result']
|
||||
else:
|
||||
lwcas = []
|
||||
|
||||
if ca_enabled:
|
||||
lwcas = api.Command.ca_find()['result']
|
||||
else:
|
||||
lwcas = []
|
||||
|
||||
finally:
|
||||
if old_krb5ccname is None:
|
||||
del os.environ['KRB5CCNAME']
|
||||
else:
|
||||
os.environ['KRB5CCNAME'] = old_krb5ccname
|
||||
shutil.rmtree(tmpdir)
|
||||
# update client certs before KDC and HTTPd are restarted.
|
||||
update_client(certs)
|
||||
|
||||
if is_ipa_configured():
|
||||
# look up CA servers before service restarts
|
||||
resp = api.Command.server_role_find(
|
||||
role_servrole=u'CA server',
|
||||
status='enabled',
|
||||
)
|
||||
ca_servers = [server['server_server'] for server in resp['result']]
|
||||
|
||||
update_server(certs)
|
||||
|
||||
# pylint: disable=import-error,ipa-forbidden-import
|
||||
from ipaserver.install import cainstance
|
||||
# pylint: enable=import-error,ipa-forbidden-import
|
||||
# pylint: disable=ipa-forbidden-import
|
||||
from ipaserver.install import cainstance, custodiainstance
|
||||
# pylint: enable=ipa-forbidden-import
|
||||
|
||||
# Add LWCA tracking requests. Only execute if *this server*
|
||||
# has CA installed (ca_enabled indicates CA-ful topology).
|
||||
@@ -121,7 +129,23 @@ def run_with_args(api):
|
||||
logger.exception(
|
||||
"Failed to add lightweight CA tracking requests")
|
||||
|
||||
update_client(certs)
|
||||
try:
|
||||
update_server_ra_config(
|
||||
cainstance, custodiainstance,
|
||||
api.env.enable_ra, api.env.ca_host, ca_servers,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to update RA config")
|
||||
|
||||
# update_server_ra_config possibly updated default.conf;
|
||||
# restart httpd to pick up changes.
|
||||
if services.knownservices.httpd.is_running():
|
||||
services.knownservices.httpd.restart()
|
||||
|
||||
# update_client() may have updated KDC cert bundle; restart KDC to pick
|
||||
# up changes.
|
||||
if services.knownservices.krb5kdc.is_running():
|
||||
services.knownservices.krb5kdc.restart()
|
||||
|
||||
|
||||
def update_client(certs):
|
||||
@@ -154,9 +178,6 @@ def update_server(certs):
|
||||
if services.knownservices.dirsrv.is_running():
|
||||
services.knownservices.dirsrv.restart(instance)
|
||||
|
||||
if services.knownservices.httpd.is_running():
|
||||
services.knownservices.httpd.restart()
|
||||
|
||||
criteria = {
|
||||
'cert-database': paths.PKI_TOMCAT_ALIAS_DIR,
|
||||
'cert-nickname': IPA_CA_NICKNAME,
|
||||
@@ -179,7 +200,7 @@ def update_server(certs):
|
||||
#
|
||||
logger.debug("resubmitting certmonger request '%s'", request_id)
|
||||
certmonger.resubmit_request(
|
||||
request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='')
|
||||
request_id, ca='dogtag-ipa-ca-renew-agent-reuse')
|
||||
try:
|
||||
state = certmonger.wait_for_request(request_id, timeout)
|
||||
except RuntimeError:
|
||||
@@ -199,6 +220,43 @@ def update_server(certs):
|
||||
update_file(paths.CACERT_PEM, certs)
|
||||
|
||||
|
||||
def update_server_ra_config(
|
||||
cainstance, custodiainstance,
|
||||
enable_ra, ca_host, ca_servers,
|
||||
):
|
||||
"""
|
||||
After promoting a CA-less deployment to CA-ful, or after removal
|
||||
of a CA server from the topology, it may be necessary to update
|
||||
the default.conf ca_host setting on non-CA replicas.
|
||||
|
||||
"""
|
||||
if len(ca_servers) == 0:
|
||||
return # nothing to do
|
||||
|
||||
# In case ca_host setting is not valid, select a new ca_host.
|
||||
# Just choose the first server. (Choosing a server in the same
|
||||
# location might be better, but we should only incur that
|
||||
# complexity if a need is proven).
|
||||
new_ca_host = ca_servers[0]
|
||||
|
||||
if not enable_ra:
|
||||
# RA is not enabled, but deployment is CA-ful.
|
||||
# Retrieve IPA RA credential and update ipa.conf.
|
||||
cainstance.CAInstance.configure_certmonger_renewal_helpers()
|
||||
custodia = custodiainstance.CustodiaInstance(
|
||||
host_name=api.env.host,
|
||||
realm=api.env.realm,
|
||||
custodia_peer=new_ca_host,
|
||||
)
|
||||
cainstance.import_ra_key(custodia)
|
||||
cainstance.update_ipa_conf(new_ca_host)
|
||||
|
||||
elif ca_host not in ca_servers:
|
||||
# RA is enabled but ca_host is not among the deployment's
|
||||
# CA servers. Set a valid ca_host.
|
||||
cainstance.update_ipa_conf(new_ca_host)
|
||||
|
||||
|
||||
def update_file(filename, certs, mode=0o644):
|
||||
certs = (c[0] for c in certs if c[2] is not False)
|
||||
try:
|
||||
|
||||
@@ -29,37 +29,29 @@ import shutil
|
||||
import time
|
||||
import tempfile
|
||||
import gssapi
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from xml.etree import cElementTree as etree
|
||||
except ImportError:
|
||||
from xml.etree import ElementTree as etree
|
||||
import SSSDConfig
|
||||
|
||||
# pylint: disable=import-error
|
||||
from six.moves.urllib.parse import urlsplit
|
||||
|
||||
# pylint: enable=import-error
|
||||
from optparse import OptionParser # pylint: disable=deprecated-module
|
||||
from ipapython import ipachangeconf
|
||||
from ipaclient.install import ipadiscovery
|
||||
from ipaclient import discovery
|
||||
from ipaclient.install.client import (
|
||||
CLIENT_NOT_CONFIGURED,
|
||||
CLIENT_ALREADY_CONFIGURED,
|
||||
)
|
||||
from ipalib import api, errors
|
||||
from ipalib.install import sysrestore
|
||||
from ipalib.install.kinit import kinit_keytab
|
||||
from ipalib.kinit import kinit_keytab
|
||||
from ipalib.util import check_client_configuration
|
||||
from ipapython import ipautil
|
||||
from ipapython.ipa_log_manager import standard_logging_setup
|
||||
from ipapython.dn import DN
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.tasks import tasks
|
||||
from ipaplatform import services
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.admintool import ScriptError
|
||||
from ipapython.config import IPAOptionParser
|
||||
|
||||
|
||||
logger = logging.getLogger(os.path.basename(__file__))
|
||||
@@ -67,7 +59,7 @@ logger = logging.getLogger(os.path.basename(__file__))
|
||||
|
||||
def parse_options():
|
||||
usage = "%prog [options]\n"
|
||||
parser = OptionParser(usage=usage)
|
||||
parser = IPAOptionParser(usage=usage)
|
||||
parser.add_option("--server", dest="server", help="FQDN of IPA server")
|
||||
parser.add_option(
|
||||
"--location",
|
||||
@@ -75,14 +67,6 @@ def parse_options():
|
||||
default="default",
|
||||
help="Automount location",
|
||||
)
|
||||
parser.add_option(
|
||||
"-S",
|
||||
"--no-sssd",
|
||||
dest="sssd",
|
||||
action="store_false",
|
||||
default=True,
|
||||
help="Do not configure the client to use SSSD for automount",
|
||||
)
|
||||
parser.add_option(
|
||||
"--idmap-domain",
|
||||
dest="idmapdomain",
|
||||
@@ -90,6 +74,7 @@ def parse_options():
|
||||
help="nfs domain for idmapd.conf",
|
||||
)
|
||||
parser.add_option(
|
||||
"-d",
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
@@ -148,52 +133,6 @@ def wait_for_sssd():
|
||||
)
|
||||
|
||||
|
||||
def configure_xml(fstore):
|
||||
authconf = paths.AUTOFS_LDAP_AUTH_CONF
|
||||
fstore.backup_file(authconf)
|
||||
|
||||
try:
|
||||
tree = etree.parse(authconf)
|
||||
except IOError as e:
|
||||
logger.debug('Unable to open file %s', e)
|
||||
logger.debug('Creating new from template')
|
||||
tree = etree.ElementTree(
|
||||
element=etree.Element('autofs_ldap_sasl_conf')
|
||||
)
|
||||
|
||||
element = tree.getroot()
|
||||
if element.tag != 'autofs_ldap_sasl_conf':
|
||||
raise RuntimeError('Invalid XML root in file %s' % authconf)
|
||||
|
||||
element.set('usetls', 'no')
|
||||
element.set('tlsrequired', 'no')
|
||||
element.set('authrequired', 'yes')
|
||||
element.set('authtype', 'GSSAPI')
|
||||
element.set('clientprinc', 'host/%s@%s' % (api.env.host, api.env.realm))
|
||||
|
||||
try:
|
||||
tree.write(authconf, xml_declaration=True, encoding='UTF-8')
|
||||
except IOError as e:
|
||||
print("Unable to write %s: %s" % (authconf, e))
|
||||
else:
|
||||
print("Configured %s" % authconf)
|
||||
|
||||
|
||||
def configure_nsswitch(statestore, options):
|
||||
"""
|
||||
This function was deprecated. Use ipaplatform.tasks.
|
||||
|
||||
Point automount to ldap in nsswitch.conf.
|
||||
This function is for non-SSSD setups only.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Use ipaplatform.tasks.tasks.enable_ldap_automount",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return tasks.enable_ldap_automount(statestore)
|
||||
|
||||
|
||||
def configure_autofs_sssd(fstore, statestore, autodiscover, options):
|
||||
try:
|
||||
sssdconfig = SSSDConfig.SSSDConfig()
|
||||
@@ -249,43 +188,6 @@ def configure_autofs_sssd(fstore, statestore, autodiscover, options):
|
||||
wait_for_sssd()
|
||||
|
||||
|
||||
def configure_autofs(fstore, statestore, autodiscover, server, options):
|
||||
"""
|
||||
fstore: the FileStore to back up files in
|
||||
options.server: the IPA server to use
|
||||
options.location: the Automount location to use
|
||||
"""
|
||||
if not autodiscover:
|
||||
ldap_uri = "ldap://%s" % server
|
||||
else:
|
||||
ldap_uri = "ldap:///%s" % api.env.basedn
|
||||
|
||||
search_base = str(
|
||||
DN(
|
||||
('cn', options.location),
|
||||
api.env.container_automount,
|
||||
api.env.basedn,
|
||||
)
|
||||
)
|
||||
replacevars = {
|
||||
'MAP_OBJECT_CLASS': 'automountMap',
|
||||
'ENTRY_OBJECT_CLASS': 'automount',
|
||||
'MAP_ATTRIBUTE': 'automountMapName',
|
||||
'ENTRY_ATTRIBUTE': 'automountKey',
|
||||
'VALUE_ATTRIBUTE': 'automountInformation',
|
||||
'SEARCH_BASE': search_base,
|
||||
'LDAP_URI': ldap_uri,
|
||||
}
|
||||
|
||||
ipautil.backup_config_and_replace_variables(
|
||||
fstore, paths.SYSCONFIG_AUTOFS, replacevars=replacevars
|
||||
)
|
||||
tasks.restore_context(paths.SYSCONFIG_AUTOFS)
|
||||
statestore.backup_state('autofs', 'sssd', False)
|
||||
|
||||
print("Configured %s" % paths.SYSCONFIG_AUTOFS)
|
||||
|
||||
|
||||
def configure_autofs_common(fstore, statestore, options):
|
||||
autofs = services.knownservices.autofs
|
||||
statestore.backup_state('autofs', 'enabled', autofs.is_enabled())
|
||||
@@ -312,7 +214,6 @@ def configure_autofs_common(fstore, statestore, options):
|
||||
def uninstall(fstore, statestore):
|
||||
RESTORE_FILES = [
|
||||
paths.SYSCONFIG_AUTOFS,
|
||||
paths.AUTOFS_LDAP_AUTH_CONF,
|
||||
paths.SYSCONFIG_NFS,
|
||||
paths.IDMAPD_CONF,
|
||||
]
|
||||
@@ -439,14 +340,16 @@ def configure_nfs(fstore, statestore, options):
|
||||
|
||||
|
||||
def configure_automount():
|
||||
try:
|
||||
check_client_configuration()
|
||||
except ScriptError as e:
|
||||
print(e.msg)
|
||||
sys.exit(e.rval)
|
||||
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
|
||||
if not statestore.get_state('installation', 'automount'):
|
||||
# not called from ipa-client-install
|
||||
try:
|
||||
check_client_configuration()
|
||||
except ScriptError as e:
|
||||
print(e.msg)
|
||||
sys.exit(e.rval)
|
||||
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
options, _args = parse_options()
|
||||
|
||||
@@ -481,12 +384,12 @@ def configure_automount():
|
||||
sys.exit(CLIENT_ALREADY_CONFIGURED)
|
||||
|
||||
autodiscover = False
|
||||
ds = ipadiscovery.IPADiscovery()
|
||||
ds = discovery.IPADiscovery()
|
||||
if not options.server:
|
||||
print("Searching for IPA server...")
|
||||
ret = ds.search(ca_cert_path=ca_cert_path)
|
||||
logger.debug('Executing DNS discovery')
|
||||
if ret == ipadiscovery.NO_LDAP_SERVER:
|
||||
if ret == discovery.NO_LDAP_SERVER:
|
||||
logger.debug('Autodiscovery did not find LDAP server')
|
||||
s = urlsplit(api.env.xmlrpc_uri)
|
||||
server = [s.netloc]
|
||||
@@ -506,14 +409,14 @@ def configure_automount():
|
||||
server = options.server
|
||||
logger.debug("Verifying that %s is an IPA server", server)
|
||||
ldapret = ds.ipacheckldap(server, api.env.realm, ca_cert_path)
|
||||
if ldapret[0] == ipadiscovery.NO_ACCESS_TO_LDAP:
|
||||
if ldapret[0] == discovery.NO_ACCESS_TO_LDAP:
|
||||
print("Anonymous access to the LDAP server is disabled.")
|
||||
print("Proceeding without strict verification.")
|
||||
print(
|
||||
"Note: This is not an error if anonymous access has been "
|
||||
"explicitly restricted."
|
||||
)
|
||||
elif ldapret[0] == ipadiscovery.NO_TLS_LDAP:
|
||||
elif ldapret[0] == discovery.NO_TLS_LDAP:
|
||||
logger.warning("Unencrypted access to LDAP is not supported.")
|
||||
elif ldapret[0] != 0:
|
||||
sys.exit('Unable to confirm that %s is an IPA server' % server)
|
||||
@@ -573,16 +476,8 @@ def configure_automount():
|
||||
sys.exit("Installation aborted")
|
||||
|
||||
try:
|
||||
if not options.sssd:
|
||||
tasks.enable_ldap_automount(statestore)
|
||||
configure_nfs(fstore, statestore, options)
|
||||
if options.sssd:
|
||||
configure_autofs_sssd(fstore, statestore, autodiscover, options)
|
||||
else:
|
||||
configure_xml(fstore)
|
||||
configure_autofs(
|
||||
fstore, statestore, autodiscover, server, options
|
||||
)
|
||||
configure_autofs_sssd(fstore, statestore, autodiscover, options)
|
||||
configure_autofs_common(fstore, statestore, options)
|
||||
except Exception as e:
|
||||
logger.debug('Raised exception %s', e)
|
||||
|
||||
@@ -9,7 +9,6 @@ import logging
|
||||
import os
|
||||
import gssapi
|
||||
from urllib.parse import urlsplit
|
||||
from optparse import OptionParser # pylint: disable=deprecated-module
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ipaclient import discovery
|
||||
@@ -31,6 +30,7 @@ from ipaplatform.constants import constants
|
||||
from ipaplatform import services
|
||||
from ipapython.admintool import ScriptError
|
||||
from samba import generate_random_password
|
||||
from ipapython.config import IPAOptionParser
|
||||
|
||||
logger = logging.getLogger(os.path.basename(__file__))
|
||||
logger.setLevel(logging.DEBUG)
|
||||
@@ -39,8 +39,8 @@ logger.setLevel(logging.DEBUG)
|
||||
@contextmanager
|
||||
def use_api_as_principal(principal, keytab):
|
||||
with ipautil.private_ccache() as ccache_file:
|
||||
old_principal = getattr(context, "principal", None)
|
||||
try:
|
||||
old_principal = getattr(context, "principal", None)
|
||||
name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
|
||||
store = {"ccache": ccache_file, "client_keytab": keytab}
|
||||
gssapi.Credentials(name=name, usage="initiate", store=store)
|
||||
@@ -68,7 +68,7 @@ def use_api_as_principal(principal, keytab):
|
||||
|
||||
def parse_options():
|
||||
usage = "%prog [options]\n"
|
||||
parser = OptionParser(usage=usage)
|
||||
parser = IPAOptionParser(usage=usage)
|
||||
parser.add_option(
|
||||
"--server",
|
||||
dest="server",
|
||||
@@ -102,6 +102,7 @@ def parse_options():
|
||||
help="force installation by redoing all steps",
|
||||
)
|
||||
parser.add_option(
|
||||
"-d",
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
@@ -172,8 +173,8 @@ def retrieve_domain_information(api):
|
||||
return []
|
||||
|
||||
l_domain = dict()
|
||||
for key in trust_keymap:
|
||||
l_domain[key] = result.get(trust_keymap[key], [None])[0]
|
||||
for key, val in trust_keymap.items():
|
||||
l_domain[key] = result.get(val, [None])[0]
|
||||
|
||||
# Pull down ID range and other details of our domain
|
||||
#
|
||||
@@ -445,13 +446,17 @@ def uninstall(fstore, statestore, options):
|
||||
fstore.restore_file(paths.SMB_CONF)
|
||||
|
||||
# Remove samba's persistent and temporary tdb files
|
||||
tdb_files = [
|
||||
tdb_file
|
||||
for tdb_file in os.listdir(paths.SAMBA_DIR)
|
||||
if tdb_file.endswith(".tdb")
|
||||
]
|
||||
for tdb_file in tdb_files:
|
||||
ipautil.remove_file(tdb_file)
|
||||
# in /var/lib/samba and /var/lib/samba/private
|
||||
for smbpath in (paths.SAMBA_DIR,
|
||||
os.path.join(paths.SAMBA_DIR, "private"),
|
||||
os.path.join(paths.SAMBA_DIR, "lock")):
|
||||
tdb_files = [
|
||||
os.path.join(smbpath, tdb_file)
|
||||
for tdb_file in os.listdir(smbpath)
|
||||
if tdb_file.endswith(".tdb")
|
||||
]
|
||||
for tdb_file in tdb_files:
|
||||
ipautil.remove_file(tdb_file)
|
||||
|
||||
# Remove our keys from samba's keytab
|
||||
if os.path.exists(paths.SAMBA_KEYTAB):
|
||||
|
||||
@@ -29,10 +29,12 @@ import os
|
||||
import pwd
|
||||
import logging
|
||||
import smtplib
|
||||
import ssl
|
||||
import time
|
||||
|
||||
from collections import deque
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
UTC = timezone.utc
|
||||
from email.utils import formataddr, formatdate
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
@@ -55,11 +57,15 @@ EPN_CONFIG = {
|
||||
"smtp_port": 25,
|
||||
"smtp_user": None,
|
||||
"smtp_password": None,
|
||||
"smtp_client_cert": None,
|
||||
"smtp_client_key": None,
|
||||
"smtp_client_key_pass": None,
|
||||
"smtp_timeout": 60,
|
||||
"smtp_security": "none",
|
||||
"smtp_admin": "root@localhost",
|
||||
"smtp_delay": None,
|
||||
"mail_from": None,
|
||||
"mail_from_name": "IPA-EPN",
|
||||
"notify_ttls": "28,14,7,3,1",
|
||||
"msg_charset": "utf8",
|
||||
"msg_subtype": "plain",
|
||||
@@ -81,7 +87,7 @@ def drop_privileges(new_username="daemon", new_groupname="daemon"):
|
||||
os.setuid(grp.getgrnam(new_groupname).gr_gid)
|
||||
|
||||
if os.getuid() == 0:
|
||||
raise Exception()
|
||||
raise errors.RequiresRoot("Cannot drop privileges!")
|
||||
|
||||
logger.debug(
|
||||
"Dropped privileges to user=%s, group=%s",
|
||||
@@ -205,6 +211,7 @@ class EPN(admintool.AdminTool):
|
||||
def __init__(self, options, args):
|
||||
super(EPN, self).__init__(options, args)
|
||||
self._conn = None
|
||||
self._ssl_context = None
|
||||
self._expiring_password_user_list = EPNUserList()
|
||||
self._ldap_data = []
|
||||
self._date_ranges = []
|
||||
@@ -291,12 +298,15 @@ class EPN(admintool.AdminTool):
|
||||
logger.error("IPA client is not configured on this system.")
|
||||
raise admintool.ScriptError()
|
||||
|
||||
# tasks required privileges
|
||||
self._get_krb5_ticket()
|
||||
self._read_configuration()
|
||||
self._validate_configuration()
|
||||
self._parse_configuration()
|
||||
self._get_connection()
|
||||
self._read_ipa_configuration()
|
||||
self._create_ssl_context()
|
||||
|
||||
drop_privileges()
|
||||
if self.options.mailtest:
|
||||
self._gentestdata()
|
||||
@@ -316,6 +326,7 @@ class EPN(admintool.AdminTool):
|
||||
smtp_timeout=api.env.smtp_timeout,
|
||||
smtp_username=api.env.smtp_user,
|
||||
smtp_password=api.env.smtp_password,
|
||||
ssl_context=self._ssl_context,
|
||||
x_mailer=self.command_name,
|
||||
msg_subtype=api.env.msg_subtype,
|
||||
msg_charset=api.env.msg_charset,
|
||||
@@ -327,7 +338,7 @@ class EPN(admintool.AdminTool):
|
||||
of days in the future.
|
||||
If only nbdays_end is specified, the range is 1d long.
|
||||
"""
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(tz=UTC)
|
||||
today_at_midnight = datetime.combine(now, datetime.min.time())
|
||||
range_end = today_at_midnight + timedelta(days=nbdays_end)
|
||||
if nbdays_start is not None:
|
||||
@@ -388,7 +399,7 @@ class EPN(admintool.AdminTool):
|
||||
"""
|
||||
if api.env.smtp_security.lower() not in ("none", "starttls", "ssl"):
|
||||
raise RuntimeError(
|
||||
"smtp_security must be one of: " "none, starttls or ssl"
|
||||
"smtp_security must be one of: none, starttls or ssl"
|
||||
)
|
||||
if api.env.smtp_user is not None and api.env.smtp_password is None:
|
||||
raise RuntimeError("smtp_user set and smtp_password is not")
|
||||
@@ -457,6 +468,20 @@ class EPN(admintool.AdminTool):
|
||||
|
||||
return self._conn
|
||||
|
||||
def _create_ssl_context(self):
|
||||
"""Create SSL context.
|
||||
This must be done before the dropping priviliges to allow
|
||||
read in the smtp client's certificate and private key if specified.
|
||||
"""
|
||||
if api.env.smtp_security.lower() in ("starttls", "ssl"):
|
||||
self._ssl_context = ssl.create_default_context()
|
||||
if api.env.smtp_client_cert:
|
||||
self._ssl_context.load_cert_chain(
|
||||
certfile=api.env.smtp_client_cert,
|
||||
keyfile=api.env.smtp_client_key,
|
||||
password=str(api.env.smtp_client_key_pass),
|
||||
)
|
||||
|
||||
def _fetch_data_from_ldap(self, date_range):
|
||||
"""Run a LDAP query to fetch a list of user entries whose passwords
|
||||
would expire in the near future. Store in self._ldap_data.
|
||||
@@ -542,11 +567,12 @@ class EPN(admintool.AdminTool):
|
||||
mail_body=body,
|
||||
subscribers=ast.literal_eval(entry["mail"]),
|
||||
mail_from=mail_from,
|
||||
mail_from_name=api.env.mail_from_name,
|
||||
)
|
||||
now = datetime.utcnow()
|
||||
now = datetime.now(tz=UTC)
|
||||
expdate = datetime.strptime(
|
||||
entry["krbpasswordexpiration"],
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
'%Y-%m-%d %H:%M:%S').replace(tzinfo=UTC)
|
||||
logger.debug(
|
||||
"Notified %s (%s). Password expiring in %d days at %s.",
|
||||
entry["mail"], entry["uid"], (expdate - now).days,
|
||||
@@ -558,7 +584,7 @@ class EPN(admintool.AdminTool):
|
||||
def _gentestdata(self):
|
||||
"""Generate a sample user to process through the template.
|
||||
"""
|
||||
expdate = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||
expdate = datetime.now(tz=UTC).strftime('%Y-%m-%d %H:%M:%S')
|
||||
entry = dict(
|
||||
uid=["SAUSER"],
|
||||
cn=["SAMPLE USER"],
|
||||
@@ -603,15 +629,15 @@ class MTAClient:
|
||||
smtp_timeout=60,
|
||||
smtp_username=None,
|
||||
smtp_password=None,
|
||||
ssl_context=None,
|
||||
):
|
||||
# We only support "none" (cleartext) for now.
|
||||
# Future values: "ssl", "starttls"
|
||||
self._security_protocol = security_protocol
|
||||
self._smtp_hostname = smtp_hostname
|
||||
self._smtp_port = smtp_port
|
||||
self._smtp_timeout = smtp_timeout
|
||||
self._username = smtp_username
|
||||
self._password = smtp_password
|
||||
self._ssl_context = ssl_context
|
||||
|
||||
# This should not be touched
|
||||
self._conn = None
|
||||
@@ -664,6 +690,7 @@ class MTAClient:
|
||||
host=self._smtp_hostname,
|
||||
port=self._smtp_port,
|
||||
timeout=self._smtp_timeout,
|
||||
context=self._ssl_context,
|
||||
)
|
||||
except (socketerror, smtplib.SMTPException) as e:
|
||||
msg = \
|
||||
@@ -685,20 +712,14 @@ class MTAClient:
|
||||
e,
|
||||
)
|
||||
|
||||
if (
|
||||
self._conn.has_extn("STARTTLS")
|
||||
and self._security_protocol.lower() == "starttls"
|
||||
):
|
||||
if self._security_protocol.lower() == "starttls":
|
||||
try:
|
||||
self._conn.starttls()
|
||||
self._conn.starttls(context=self._ssl_context)
|
||||
self._conn.ehlo()
|
||||
except smtplib.SMTPException as e:
|
||||
logger.error(
|
||||
raise RuntimeError(
|
||||
"IPA-EPN: Unable to create an encrypted session to "
|
||||
"%s:%s: %s",
|
||||
self._smtp_hostname,
|
||||
self._smtp_port,
|
||||
e,
|
||||
"%s:%s: %s" % (self._smtp_hostname, self._smtp_port, e)
|
||||
)
|
||||
|
||||
if self._username and self._password:
|
||||
@@ -749,6 +770,7 @@ class MailUserAgent:
|
||||
smtp_timeout=60,
|
||||
smtp_username=None,
|
||||
smtp_password=None,
|
||||
ssl_context=None,
|
||||
x_mailer=None,
|
||||
msg_subtype="plain",
|
||||
msg_charset="utf8",
|
||||
@@ -772,6 +794,7 @@ class MailUserAgent:
|
||||
smtp_timeout=smtp_timeout,
|
||||
smtp_username=smtp_username,
|
||||
smtp_password=smtp_password,
|
||||
ssl_context=ssl_context,
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
@@ -779,12 +802,13 @@ class MailUserAgent:
|
||||
|
||||
def send_message(
|
||||
self, mail_subject=None, mail_body=None, subscribers=None,
|
||||
mail_from=None
|
||||
mail_from=None, mail_from_name=None
|
||||
):
|
||||
"""Given mail_subject, mail_body, and subscribers, composes
|
||||
the message and sends it.
|
||||
"""
|
||||
if None in [mail_subject, mail_body, subscribers, mail_from]:
|
||||
if None in [mail_subject, mail_body, subscribers,
|
||||
mail_from, mail_from_name]:
|
||||
logger.error("IPA-EPN: Tried to send an empty message.")
|
||||
return False
|
||||
self._compose_message(
|
||||
@@ -792,6 +816,7 @@ class MailUserAgent:
|
||||
mail_body=mail_body,
|
||||
subscribers=subscribers,
|
||||
mail_from=mail_from,
|
||||
mail_from_name=mail_from_name,
|
||||
)
|
||||
self._mta_client.send_message(
|
||||
message_str=self._message_str, subscribers=subscribers
|
||||
@@ -799,7 +824,8 @@ class MailUserAgent:
|
||||
return True
|
||||
|
||||
def _compose_message(
|
||||
self, mail_subject, mail_body, subscribers, mail_from
|
||||
self, mail_subject, mail_body, subscribers,
|
||||
mail_from, mail_from_name
|
||||
):
|
||||
"""The composer creates a MIME multipart message.
|
||||
"""
|
||||
@@ -809,7 +835,7 @@ class MailUserAgent:
|
||||
self._subscribers = subscribers
|
||||
|
||||
self._msg = MIMEMultipart(_charset=self._charset)
|
||||
self._msg["From"] = formataddr(("IPA-EPN", mail_from))
|
||||
self._msg["From"] = formataddr((mail_from_name, mail_from))
|
||||
self._msg["To"] = ", ".join(self._subscribers)
|
||||
self._msg["Date"] = formatdate(localtime=True)
|
||||
self._msg["Subject"] = Header(self._subject, self._charset)
|
||||
|
||||
@@ -82,13 +82,16 @@ def sync_chrony():
|
||||
# Restart chronyd
|
||||
services.knownservices.chronyd.restart()
|
||||
|
||||
sync_attempt_count = 3
|
||||
# chrony attempt count to sync with configiured servers
|
||||
# each next attempt is tried after 10seconds of timeot
|
||||
# 3 attempts means: if first immidiate attempt fails
|
||||
# there is 10s delay between next attempts
|
||||
|
||||
args = [paths.CHRONYC, 'waitsync', str(sync_attempt_count), '-d']
|
||||
# chrony attempt count to sync with configured servers. Each attempt is
|
||||
# retried after $interval seconds.
|
||||
# 4 attempts with 3s interval result in a maximum delay of 9 seconds.
|
||||
sync_attempt_count = 4
|
||||
sync_attempt_interval = 3
|
||||
args = [
|
||||
paths.CHRONYC, '-d', 'waitsync',
|
||||
# max-tries, max-correction, max-skew, interval
|
||||
str(sync_attempt_count), '0', '0', str(sync_attempt_interval)
|
||||
]
|
||||
|
||||
try:
|
||||
logger.info('Attempting to sync time with chronyc.')
|
||||
|
||||
Reference in New Issue
Block a user