Imported Debian patch 4.8.10-2

This commit is contained in:
Timo Aaltonen
2020-11-23 20:48:56 +02:00
committed by Mario Fetka
parent 8bc559c5a1
commit 358acdd85f
917 changed files with 1185414 additions and 1069733 deletions

View File

@@ -46,8 +46,39 @@ DBUS_CM_REQUEST_IF = 'org.fedorahosted.certmonger.request'
DBUS_CM_CA_IF = 'org.fedorahosted.certmonger.ca'
DBUS_PROPERTY_IF = 'org.freedesktop.DBus.Properties'
# These properties, if encountered in search criteria, result in a
# subset test instead of equality test.
ARRAY_PROPERTIES = ['template-hostname']
class _cm_dbus_object(object):
"""
Certmonger helper routines.
Search criteria
---------------
Functions that look up requests take a ``dict`` of search criteria.
In general, the key is a name of a property in the request property
interface. But there are some special cases with different
behaviour:
``nickname``
a.k.a. "request ID". If given, only the specified request is
retrieved (if it exists), and it is still tested against other
criteria.
``ca-name``
Test equality against the nickname of the CA (a.k.a. request
helper) object for the request.
``template-hostname``
Must be an iterable of DNS names. Tests that the given values
are a subset of the values defined on the Certmonger request.
"""
class _cm_dbus_object:
"""
Auxiliary class for convenient DBus object handling.
"""
@@ -156,9 +187,12 @@ class _certmonger(_cm_dbus_object):
DBUS_CM_IF)
def _get_requests(criteria=dict()):
def _get_requests(criteria):
"""
Get all requests that matches the provided criteria.
:param criteria: dict of criteria; see module doc for details
"""
if not isinstance(criteria, dict):
raise TypeError('"criteria" must be dict.')
@@ -179,13 +213,23 @@ def _get_requests(criteria=dict()):
for criterion in criteria:
if criterion == 'ca-name':
ca_path = request.obj_if.get_ca()
if ca_path is None:
raise RuntimeError("certmonger CA '%s' is not defined" %
criteria.get('ca-name'))
ca = _cm_dbus_object(cm.bus, cm, ca_path, DBUS_CM_CA_IF,
DBUS_CM_IF)
value = ca.obj_if.get_nickname()
if criteria[criterion] != ca.obj_if.get_nickname():
break
elif criterion in ARRAY_PROPERTIES:
# perform subset test
expect = set(criteria[criterion])
got = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
if not expect.issubset(got):
break
else:
value = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
if value != criteria[criterion]:
break
if criteria[criterion] != value:
break
else:
requests.append(request)
@@ -194,11 +238,11 @@ def _get_requests(criteria=dict()):
def _get_request(criteria):
"""
Find request that matches criteria.
If 'nickname' is specified other criteria are ignored because 'nickname'
uniquely identify single request.
When multiple or none request matches specified criteria RuntimeError is
raised.
Find request that matches criteria. Return ``None`` if no match.
Raise ``RuntimeError`` if there is more than one matching request.
:param criteria: dict of criteria; see module doc for details
"""
requests = _get_requests(criteria)
if len(requests) == 0:
@@ -236,11 +280,11 @@ def get_request_id(criteria):
If you don't know the certmonger request_id then try to find it by looking
through all the requests.
criteria is a tuple of key/value to search for. The more specific
the better. An error is raised if multiple request_ids are returned for
the same criteria.
Return ``None`` if no match. Raise ``RuntimeError`` if there is
more than one matching request.
:param criteria: dict of criteria; see module doc for details
None is returned if none of the criteria match.
"""
try:
request = _get_request(criteria)
@@ -306,7 +350,7 @@ def request_and_wait_for_cert(
certpath, subject, principal, nickname=None, passwd_fname=None,
dns=None, ca='IPA', profile=None,
pre_command=None, post_command=None, storage='NSSDB', perms=None,
resubmit_timeout=0):
resubmit_timeout=0, stop_tracking_on_error=False):
"""Request certificate, wait and possibly resubmit failing requests
Submit a cert request to certmonger and wait until the request has
@@ -326,7 +370,11 @@ def request_and_wait_for_cert(
deadline = time.time() + resubmit_timeout
while True: # until success, timeout, or error
state = wait_for_request(req_id, api.env.replication_wait_timeout)
try:
state = wait_for_request(req_id, api.env.http_timeout)
except RuntimeError as e:
logger.debug("wait_for_request raised %s", e)
state = 'TIMEOUT'
ca_error = get_request_value(req_id, 'ca-error')
if state == 'MONITORING' and ca_error is None:
# we got a winner, exiting
@@ -336,22 +384,28 @@ def request_and_wait_for_cert(
logger.debug(
"Cert request %s failed: %s (%s)", req_id, state, ca_error
)
if state not in {'CA_REJECTED', 'CA_UNREACHABLE'}:
if state in {'CA_REJECTED', 'CA_UNREACHABLE'}:
# probably unrecoverable error
logger.debug("Giving up on cert request %s", req_id)
break
elif not resubmit_timeout:
if not resubmit_timeout:
# no resubmit
break
elif time.time() > deadline:
logger.debug("Request %s reached resubmit dead line", req_id)
if time.time() > deadline:
logger.debug("Request %s reached resubmit deadline", req_id)
break
if state == 'TIMEOUT':
logger.debug("%s not in final state, continue waiting", req_id)
time.sleep(10)
else:
# sleep and resubmit
logger.debug("Sleep and resubmit cert request %s", req_id)
time.sleep(10)
resubmit_request(req_id)
if stop_tracking_on_error:
stop_tracking(request_id=req_id)
raise RuntimeError(
"Certificate issuance failed ({}: {})".format(state, ca_error)
)
@@ -427,7 +481,8 @@ def request_cert(
def start_tracking(
certpath, ca='IPA', nickname=None, pin=None, pinfile=None,
pre_command=None, post_command=None, profile=None, storage="NSSDB"):
pre_command=None, post_command=None, profile=None, storage="NSSDB",
token_name=None, dns=None):
"""
Tell certmonger to track the given certificate in either a file or an NSS
database. The certificate access can be protected by a password_file.
@@ -460,6 +515,10 @@ def start_tracking(
NSS or OpenSSL backend to track the certificate in ``certpath``
:param profile:
Which certificate profile should be used.
:param token_name:
Hardware token name for HSM support
:param dns:
List of DNS names
:returns: certificate tracking nickname.
"""
if storage == 'FILE':
@@ -500,6 +559,12 @@ def start_tracking(
params['cert-postsave-command'] = post_command
if profile:
params['ca-profile'] = profile
if token_name not in {None, "internal"}:
# only pass token names for external tokens (e.g. HSM)
params['key-token'] = token_name
params['cert-token'] = token_name
if dns is not None and len(dns) > 0:
params['DNS'] = dns
result = cm.obj_if.add_request(params)
try:
@@ -590,6 +655,18 @@ def resubmit_request(
update['template-is-ca'] = True
update['template-ca-path-length'] = -1 # no path length
# TODO: certmonger assumes some hard-coded defaults like RSA 2048.
# Try to fetch values from current cert rather.
for key, convert in [('key-size', int), ('key-type', str)]:
try:
value = request.prop_if.Get(DBUS_CM_REQUEST_IF, key)
except dbus.DBusException:
continue
else:
if value:
# convert dbus.Int64() to int, dbus.String() to str
update[key] = convert(value)
if len(update) > 0:
request.obj_if.modify(update)
request.obj_if.resubmit()
@@ -663,7 +740,7 @@ def modify_ca_helper(ca_name, helper):
return old_helper
def get_pin(token):
def get_pin(token="internal"):
"""
Dogtag stores its NSS pin in a file formatted as token:PIN.

View File

@@ -0,0 +1,155 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""DNS forwarder and systemd-resolve1 helpers
"""
import ipaddress
import logging
import os
import socket
import dbus
from ipaplatform.paths import paths
from ipapython.dnsutil import get_ipa_resolver
logger = logging.getLogger(__name__)
_SYSTEMD_RESOLV_CONF = {
"/run/systemd/resolve/stub-resolv.conf",
"/run/systemd/resolve/resolv.conf",
"/lib/systemd/resolv.conf",
"/usr/lib/systemd/resolv.conf",
}
_DBUS_RESOLVE1_NAME = "org.freedesktop.resolve1"
_DBUS_RESOLVE1_PATH = "/org/freedesktop/resolve1"
_DBUS_RESOLVE1_MANAGER_IF = "org.freedesktop.resolve1.Manager"
_DBUS_PROPERTY_IF = "org.freedesktop.DBus.Properties"
# netlink interface index for resolve1 global settings and loopback
IFINDEX_GLOBAL = 0
IFINDEX_LOOPBACK = 1
def detect_resolve1_resolv_conf():
"""Detect if /etc/resolv.conf is managed by systemd-resolved
See man(5) NetworkManager.conf
"""
try:
dest = os.readlink(paths.RESOLV_CONF)
except OSError:
# not a link
return False
# convert path relative to /etc/resolv.conf to abs path
dest = os.path.normpath(
os.path.join(os.path.dirname(paths.RESOLV_CONF), dest)
)
return dest in _SYSTEMD_RESOLV_CONF
def get_resolve1_nameservers(*, with_ifindex=False):
"""Get list of DNS nameservers from systemd-resolved
:return: list of tuples (ifindex, ipaddress_obj)
"""
bus = dbus.SystemBus()
try:
resolve1 = bus.get_object(_DBUS_RESOLVE1_NAME, _DBUS_RESOLVE1_PATH)
prop_if = dbus.Interface(resolve1, _DBUS_PROPERTY_IF)
dns_prop = prop_if.Get(_DBUS_RESOLVE1_MANAGER_IF, "DNSEx")
finally:
bus.close()
results = []
for ifindex, af, dns_arr, port, sniname in dns_prop:
if port not in {0, 53} or sniname:
# non-default port, non-standard port, or SNI name configuration
# for DNS over TLS, e.g. 1.2.3.4:9953#example.com
continue
# convert packed format to IPAddress object (like inet_ntop)
if af == socket.AF_INET:
dnsip = ipaddress.IPv4Address(bytes(dns_arr))
elif af == socket.AF_INET6:
dnsip = ipaddress.IPv6Address(bytes(dns_arr))
else:
# neither IPv4 nor IPv6
continue
if with_ifindex:
# netlink interface index, see socket.if_nameindex()
ifindex = int(ifindex)
results.append((ifindex, dnsip))
else:
results.append(dnsip)
return results
def get_dnspython_nameservers(*, with_ifindex=False):
"""Get list of DNS nameservers from dnspython
On Linux dnspython parses /etc/resolv.conf for us
:return: list of tuples (ifindex, ipaddress_obj)
"""
results = []
for nameserver in get_ipa_resolver().nameservers:
nameserver = ipaddress.ip_address(nameserver)
if with_ifindex:
results.append((IFINDEX_GLOBAL, nameserver))
else:
results.append(nameserver)
return results
def get_nameservers():
"""Get list of unique, non-loopback DNS nameservers
:return: list of strings
"""
if detect_resolve1_resolv_conf():
logger.debug(
"systemd-resolved detected, fetching nameservers from D-Bus"
)
nameservers = get_resolve1_nameservers(with_ifindex=True)
else:
logger.debug(
"systemd-resolved not detected, parsing %s", paths.RESOLV_CONF
)
nameservers = get_dnspython_nameservers(with_ifindex=True)
logger.debug("Detected nameservers: %r", nameservers)
result = []
seen = set()
for ifindex, ip in nameservers:
# unique entries
if ip in seen:
continue
seen.add(ip)
# skip loopback
if ifindex == IFINDEX_LOOPBACK or ip.is_loopback:
continue
result.append(str(ip))
logger.debug("Use nameservers %r", result)
return result
if __name__ == "__main__":
from pprint import pprint
print("systemd-resolved detected:", detect_resolve1_resolv_conf())
print("Interfaces:", socket.if_nameindex())
print("dnspython nameservers:")
pprint(get_dnspython_nameservers(with_ifindex=True))
print("resolve1 nameservers:")
try:
pprint(get_resolve1_nameservers(with_ifindex=True))
except Exception as e:
print(e)
print("nameservers:", get_nameservers())

View File

@@ -1,457 +1,21 @@
# Authors: Mark McLoughlin <markmc@redhat.com>
#
# Copyright (C) 2007 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/>.
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
#
# This module provides a very simple API which allows
# ipa-xxx-install --uninstall to restore certain
# parts of the system configuration to the way it was
# before ipa-server-install was first run
"""
Facade for ipalib.sysrestore for backwards compatibility
"""
from __future__ import absolute_import
from ipalib import sysrestore as real_sysrestore
import logging
import os
import os.path
import shutil
import random
from hashlib import sha256
class FileStore(real_sysrestore.FileStore):
def __init__(self, path=real_sysrestore.SYSRESTORE_PATH,
index_file=real_sysrestore.SYSRESTORE_INDEXFILE):
super(FileStore, self).__init__(path, index_file)
import six
# pylint: disable=import-error
if six.PY3:
# The SafeConfigParser class has been renamed to ConfigParser in Py3
from configparser import ConfigParser as SafeConfigParser
else:
from ConfigParser import SafeConfigParser
# pylint: enable=import-error
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
if six.PY3:
unicode = str
logger = logging.getLogger(__name__)
SYSRESTORE_PATH = paths.TMP
SYSRESTORE_INDEXFILE = "sysrestore.index"
SYSRESTORE_STATEFILE = "sysrestore.state"
class FileStore(object):
"""Class for handling backup and restore of files"""
def __init__(self, path = SYSRESTORE_PATH, index_file = SYSRESTORE_INDEXFILE):
"""Create a _StoreFiles object, that uses @path as the
base directory.
The file @path/sysrestore.index is used to store information
about the original location of the saved files.
"""
self._path = path
self._index = os.path.join(self._path, index_file)
self.random = random.Random()
self.files = {}
self._load()
def _load(self):
"""Load the file list from the index file. @files will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading Index file from '%s'", self._index)
self.files = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._index)
for section in p.sections():
if section == "files":
for (key, value) in p.items(section):
self.files[key] = value
def save(self):
"""Save the file list to @_index. If @files is an empty
dict, then @_index should be removed.
"""
logger.debug("Saving Index File to '%s'", self._index)
if len(self.files) == 0:
logger.debug(" -> no files, removing file")
if os.path.exists(self._index):
os.remove(self._index)
return
p = SafeConfigParser()
p.optionxform = str
p.add_section('files')
for (key, value) in self.files.items():
p.set('files', key, str(value))
with open(self._index, "w") as f:
p.write(f)
def backup_file(self, path):
"""Create a copy of the file at @path - as long as an exact copy
does not already exist - which will be restored to its
original location by restore_files().
"""
logger.debug("Backing up system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if not os.path.isfile(path):
logger.debug(" -> Not backing up - '%s' doesn't exist", path)
return
_reldir, backupfile = os.path.split(path)
with open(path, 'rb') as f:
cont_hash = sha256(f.read()).hexdigest()
filename = "{hexhash}-{bcppath}".format(
hexhash=cont_hash, bcppath=backupfile)
backup_path = os.path.join(self._path, filename)
if os.path.exists(backup_path):
logger.debug(" -> Not backing up - already have a copy of '%s'",
path)
return
shutil.copy2(path, backup_path)
stat = os.stat(path)
template = '{stat.st_mode},{stat.st_uid},{stat.st_gid},{path}'
self.files[filename] = template.format(stat=stat, path=path)
self.save()
def has_file(self, path):
"""Checks whether file at @path was added to the file store
Returns #True if the file exists in the file store, #False otherwise
"""
result = False
for _key, value in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
result = True
break
return result
def restore_file(self, path, new_path = None):
"""Restore the copy of a file at @path to its original
location and delete the copy.
Takes optional parameter @new_path which specifies the
location where the file is to be restored.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if new_path is None:
logger.debug("Restoring system configuration file '%s'",
path)
else:
logger.debug("Restoring system configuration file '%s' to '%s'",
path, new_path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
if new_path is not None and not os.path.isabs(new_path):
raise ValueError("Absolute new path required")
mode = None
uid = None
gid = None
filename = None
for (key, value) in self.files.items():
(mode,uid,gid,filepath) = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
if new_path is not None:
path = new_path
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
del self.files[filename]
self.save()
return True
def restore_all_files(self):
"""Restore the files in the inbdex to their original
location and delete the copy.
Returns #True if the file was restored, #False if there
was no backup file to restore
"""
if len(self.files) == 0:
return False
for (filename, value) in self.files.items():
(mode,uid,gid,path) = value.split(',', 3)
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
continue
shutil.copy(backup_path, path) # SELinux needs copy
os.remove(backup_path)
os.chown(path, int(uid), int(gid))
os.chmod(path, int(mode))
tasks.restore_context(path)
# force file to be deleted
self.files = {}
self.save()
return True
def has_files(self):
"""Return True or False if there are any files in the index
Can be used to determine if a program is configured.
"""
return len(self.files) > 0
def untrack_file(self, path):
"""Remove file at path @path from list of backed up files.
Does not remove any files from the filesystem.
Returns #True if the file was untracked, #False if there
was no backup file to restore
"""
logger.debug("Untracking system configuration file '%s'", path)
if not os.path.isabs(path):
raise ValueError("Absolute path required")
filename = None
for (key, value) in self.files.items():
_mode, _uid, _gid, filepath = value.split(',', 3)
if (filepath == path):
filename = key
break
if not filename:
raise ValueError("No such file name in the index")
backup_path = os.path.join(self._path, filename)
if not os.path.exists(backup_path):
logger.debug(" -> Not restoring - '%s' doesn't exist",
backup_path)
return False
try:
os.unlink(backup_path)
except Exception as e:
logger.error('Error removing %s: %s', backup_path, str(e))
del self.files[filename]
self.save()
return True
class StateFile(object):
"""A metadata file for recording system state which can
be backed up and later restored.
StateFile gets reloaded every time to prevent loss of information
recorded by child processes. But we do not solve concurrency
because there is no need for it right now.
The format is something like:
[httpd]
running=True
enabled=False
"""
def __init__(self, path = SYSRESTORE_PATH, state_file = SYSRESTORE_STATEFILE):
"""Create a StateFile object, loading from @path.
The dictionary @modules, a member of the returned object,
is where the state can be modified. @modules is indexed
using a module name to return another dictionary containing
key/value pairs with the saved state of that module.
The keys in these latter dictionaries are arbitrary strings
and the values may either be strings or booleans.
"""
self._path = os.path.join(path, state_file)
self.modules = {}
self._load()
def _load(self):
"""Load the modules from the file @_path. @modules will
be an empty dictionary if the file doesn't exist.
"""
logger.debug("Loading StateFile from '%s'", self._path)
self.modules = {}
p = SafeConfigParser()
p.optionxform = str
p.read(self._path)
for module in p.sections():
self.modules[module] = {}
for (key, value) in p.items(module):
if value == str(True):
value = True
elif value == str(False):
value = False
self.modules[module][key] = value
def save(self):
"""Save the modules to @_path. If @modules is an empty
dict, then @_path should be removed.
"""
logger.debug("Saving StateFile to '%s'", self._path)
for module in list(self.modules):
if len(self.modules[module]) == 0:
del self.modules[module]
if len(self.modules) == 0:
logger.debug(" -> no modules, removing file")
if os.path.exists(self._path):
os.remove(self._path)
return
p = SafeConfigParser()
p.optionxform = str
for module in self.modules:
p.add_section(module)
for (key, value) in self.modules[module].items():
p.set(module, key, str(value))
with open(self._path, "w") as f:
p.write(f)
def backup_state(self, module, key, value):
"""Backup an item of system state from @module, identified
by the string @key and with the value @value. @value may be
a string or boolean.
"""
if not isinstance(value, (str, bool, unicode)):
raise ValueError("Only strings, booleans or unicode strings are supported")
self._load()
if module not in self.modules:
self.modules[module] = {}
if key not in self.modules:
self.modules[module][key] = value
self.save()
def get_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
self._load()
if module not in self.modules:
return None
return self.modules[module].get(key, None)
def delete_state(self, module, key):
"""Delete system state from @module, identified by the string
@key.
If the item doesn't exist, no change is done.
"""
self._load()
try:
del self.modules[module][key]
except KeyError:
pass
else:
self.save()
def restore_state(self, module, key):
"""Return the value of an item of system state from @module,
identified by the string @key, and remove it from the backed
up system state.
If the item doesn't exist, #None will be returned, otherwise
the original string or boolean value is returned.
"""
value = self.get_state(module, key)
if value is not None:
self.delete_state(module, key)
return value
def has_state(self, module):
"""Return True or False if there is any state stored for @module.
Can be used to determine if a service is configured.
"""
return module in self.modules
class StateFile(real_sysrestore.StateFile):
def __init__(self, path=real_sysrestore.SYSRESTORE_PATH,
state_file=real_sysrestore.SYSRESTORE_STATEFILE):
super(StateFile, self).__init__(path, state_file)