Commit 34ce0c87 authored by Robin Schneider's avatar Robin Schneider

Rework how the config is handled and make `diff_command` configurable

parent b52c5fc5
......@@ -28,11 +28,11 @@ Each host has it’s own section. Supported options inside sections:
The command can make use of the following tokens, which are expanded at
runtime:
* ``{originalhost}``, hostname as it was specified on the command-line.
* ``{host}``, target hostname, after any substitution by ssh.
* ``{ssh_port}``, SSH port.
* ``{hostname}``, hostname without domain.
* ``{domain}``, domain of the host.
* ``%(originalhost)s``, hostname as it was specified on the command-line.
* ``%(host)s``, target hostname, after any substitution by ssh.
* ``%(ssh_port)s``, SSH port.
* ``%(hostname)s``, hostname without domain.
* ``%(domain)s``, domain of the host.
``start_command_shell``
Boolean determining if :ref:`config_start_command <fdeunlock__ref_config_start_command>`
......
......@@ -62,7 +62,22 @@ SSH based checkers
[DEFAULT]
additional_checksum_commands =
uname -a
dd if=/dev/vda bs=512 count=1 | sha512sum -
dmesg | egrep '(DMI:|Command line:|Booting paravirtualized kernel on bare hardware)' | sed 's/^\[\s*[[:digit:].]\+\]\s*//;'
cat /sys/devices/virtual/dmi/id/board_serial /sys/devices/virtual/dmi/id/product_uuid
ls /dev/disk/by-id/
grep '^MemTotal:' /proc/meminfo
dd if=/dev/sda bs=512 count=1 | sha512sum -
The ``diff_command`` configuration option can be used to set a different text
diffing program than the default `diff`.
Comparison is run on your local machine so note that the diffing program is
exposed to untrusted input.
Example:
.. code-block:: ini
[DEFAULT]
diff_command = git diff --color-words --no-index
Proper remote attestation (Trusted Computing) should be implemented.
Feel free to add supported for this to FDEunlock :-)
......
......@@ -106,24 +106,29 @@ class LinkLayerAddressChecker(NetworkBasedChecker):
' '.join(ip_command)))
neigh_status = proc.stdout.read().decode('utf-8').strip().split()
link_layer_ind = neigh_status.index('lladdr')
if link_layer_ind == -1:
raise Exception("lladdr in `ip` command output not found.")
untrusted_link_layer_address = neigh_status[link_layer_ind + 1]
if not re.match(r"^[0-9a-f]{2}([-:])[0-9a-f]{2}(\1[0-9a-f]{2}){4}$", untrusted_link_layer_address.lower()):
raise Exception("MAC address not valid: {}.".format(untrusted_link_layer_address))
LOG.debug("Link layer address: {}".format(untrusted_link_layer_address))
host = self._unlocker._original_host
link_layer_addresses = self._unlocker._properties.get(host, 'link_layer_addresses', fallback='')
try:
link_layer_ind = neigh_status.index('lladdr')
except ValueError:
link_layer_ind = -1
untrusted_link_layer_address = 'Unable to read link layer address'
LOG.debug(untrusted_link_layer_address)
else:
untrusted_link_layer_address = neigh_status[link_layer_ind + 1]
if not re.match(r"^[0-9a-f]{2}([-:])[0-9a-f]{2}(\1[0-9a-f]{2}){4}$", untrusted_link_layer_address.lower()):
raise Exception("MAC address not valid: {}.".format(untrusted_link_layer_address))
LOG.debug("Link layer address: {}".format(untrusted_link_layer_address))
original_host = self._unlocker._original_host
link_layer_addresses = self._unlocker._properties.get(original_host, 'link_layer_addresses', fallback='')
link_layer_addresses = [a.strip() for a in link_layer_addresses.split('\n') if a]
if len(link_layer_addresses) == 0:
LOG.info("No link layer addresses found to compare to. Trusting the current once (TOFU).")
self._unlocker._properties.set(host, 'link_layer_addresses', untrusted_link_layer_address)
self._unlocker._properties.set(original_host, 'link_layer_addresses', untrusted_link_layer_address)
return True
elif untrusted_link_layer_address in link_layer_addresses:
LOG.info("Link layer address matches the trusted once.")
LOG.info("Link layer address matches the trusted once: {}".format(
untrusted_link_layer_address))
return True
self._link_layer_addresses = link_layer_addresses
......@@ -141,9 +146,9 @@ class LinkLayerAddressChecker(NetworkBasedChecker):
))
answer = input("Choose one of 'save', 'ignore' (for current run) or anything else to exit: ")
if answer == 'save':
host = self._unlocker._original_host
original_host = self._unlocker._original_host
self._unlocker._properties.set(
host,
original_host,
'link_layer_addresses',
self._untrusted_link_layer_address,
)
......@@ -168,13 +173,13 @@ class ChecksumChecker(SshBasedChecker):
os.makedirs(self._bin_dir, exist_ok=True) # pylint: disable=unexpected-keyword-arg
os.makedirs(self._checksum_dir, exist_ok=True) # pylint: disable=unexpected-keyword-arg
host = self._unlocker._original_host
original_host = self._unlocker._original_host
self._checksum_file = os.path.join(
self._checksum_dir,
'{}_initramfs.list'.format(host))
'{}_initramfs.list'.format(original_host))
self._untrusted_checksum_file = os.path.join(
self._checksum_dir,
'{}_untrusted_initramfs.list'.format(host))
'{}_untrusted_initramfs.list'.format(original_host))
def check(self, shell=None, **kwargs):
......@@ -188,8 +193,8 @@ class ChecksumChecker(SshBasedChecker):
shell.copy_to_remote(self.checksum_prog, '/root/hashdeep')
shell.run_command('chmod 500 /root/hashdeep')
host = self._unlocker._original_host
additional_checksum_commands = self._unlocker._cfg.get(host, 'additional_checksum_commands', fallback='')
original_host = self._unlocker._original_host
additional_checksum_commands = self._unlocker._cfg.get(original_host, 'additional_checksum_commands')
additional_checksum_commands = [a.strip() for a in additional_checksum_commands.split('\n') if a]
with open(self._untrusted_checksum_file, 'w') as untrusted_checksum_fh:
......@@ -206,8 +211,8 @@ class ChecksumChecker(SshBasedChecker):
' -not -path "/run/*"'
' -print0'
# 'find /root -type f -print0'
' | sort -z'
' | xargs -0 /root/hashdeep -r -c md5,sha1,sha256 | grep -v "^[#%]"'
# ' | sort -z'
' | xargs -0 /root/hashdeep -r -c md5,sha1,sha256 | grep -v "^[#%]" | sort -t "," -k4'
)
shell.prompt()
for cmd in additional_checksum_commands:
......@@ -237,7 +242,9 @@ class ChecksumChecker(SshBasedChecker):
def update(self):
LOG.info("Deviation to trusted checksums:")
subprocess.call(['diff', self._checksum_file, self._untrusted_checksum_file])
original_host = self._unlocker._original_host
diff_command = self._unlocker._cfg.get(original_host, 'diff_command').split(' ')
subprocess.call(diff_command + [self._checksum_file, self._untrusted_checksum_file])
answer = input("Choose one of 'save', 'ignore' (for current run) or anything else to exit: ")
if LOG.isEnabledFor(logging.DEBUG):
hexdump(answer.encode('utf-8'))
......@@ -277,12 +284,12 @@ class AuthenticatedLatencyChecker(SshBasedChecker):
untrusted_latency = end - start
LOG.info("Latency to execute a command over SSH and get the response back: {:.4f} s".format(untrusted_latency))
host = self._unlocker._original_host
latency = self._unlocker._properties.getfloat(host, 'authenticated_latency', fallback=-1.0)
deviation = self._unlocker._cfg.get(host, 'authenticated_latency_deviation', fallback=0.01)
original_host = self._unlocker._original_host
latency = self._unlocker._properties.getfloat(original_host, 'authenticated_latency', fallback=-1.0)
deviation = self._unlocker._cfg.getfloat(original_host, 'authenticated_latency_deviation')
if latency == -1.0:
LOG.info("No latency found to compare to. Trusting the current one (TOFU).")
self._unlocker._properties.set(host, 'authenticated_latency', str(untrusted_latency))
self._unlocker._properties.set(original_host, 'authenticated_latency', str(untrusted_latency))
return True
elif latency-deviation <= untrusted_latency and untrusted_latency <= latency+deviation:
LOG.info("Latency is within the boundaries.")
......@@ -301,9 +308,9 @@ class AuthenticatedLatencyChecker(SshBasedChecker):
))
answer = input("Choose one of 'save', 'ignore' (for current run) or anything else to exit: ")
if answer == 'save':
host = self._unlocker._original_host
original_host = self._unlocker._original_host
self._unlocker._properties.set(
host,
original_host,
'authenticated_latency',
str(self._untrusted_latency),
)
......
......@@ -44,22 +44,31 @@ class FdeUnlock(object):
self._original_host = host
if host not in self._properties:
self._properties.add_section(host)
if not self._cfg.has_section(self._original_host):
self._cfg.add_section(self._original_host)
if not self._properties.has_section(self._original_host):
self._properties.add_section(self._original_host)
ssh_host_cfg = self._ssh_cfg.lookup(host)
ssh_host_cfg = self._ssh_cfg.lookup(self._original_host)
self._address_family = self._cfg.get(
host, 'address_family',
self._original_host, 'address_family',
fallback=ssh_host_cfg.get('addressfamily', 'any'))
start_command = self._cfg.get(
host, 'start_command', fallback=None)
start_command_shell = self._cfg.getboolean(
host, 'start_command_shell', fallback=False)
port = ssh_host_cfg.get('port', 22)
host = ssh_host_cfg.get('hostname', host)
host = ssh_host_cfg.get('hostname', self._original_host)
LOG.debug("SSH options: host: {}, port: {}".format(
host, port,
self._original_host, port,
))
start_command = self._cfg.get(
self._original_host, 'start_command',
vars={
'originalhost': self._original_host,
'host': host,
'ssh_port': port,
'hostname': self._original_host.split('.')[0],
'domain': '.'.join(self._original_host.split('.')[1:]),
},
)
while True:
if self._is_reachable(host):
......@@ -103,17 +112,10 @@ class FdeUnlock(object):
if start_command is None:
LOG.info("Host offline. Waiting …")
else:
command = start_command.format(
originalhost=self._original_host,
host=host,
ssh_port=port,
hostname=self._original_host.split('.')[0],
domain='.'.join(self._original_host.split('.')[1:]),
)
LOG.info("Host offline. Attempting to start using: {}".format(command))
LOG.info("Host offline. Attempting to start using: {}".format(start_command))
returncode = subprocess.call(
command.split(' '),
shell=start_command_shell
start_command.split(' '),
shell=self._cfg.getboolean(self._original_host, 'start_command_shell'),
)
LOG.info("Start command returned with: {}".format(returncode))
start_command = None
......@@ -193,7 +195,7 @@ class FdeUnlock(object):
checker = checker_class(self)
violation_message = (
"Check {} report an unrecoverable violation causing"
"{} report an unrecoverable violation causing"
" FDEunlock to stop at this point.".format(checker_class.__name__))
if not checker.check(shell=shell):
if not checker.update():
......
......@@ -54,7 +54,13 @@ def read_config():
cfg_files.append(os.path.join(config_dir, 'config.cfg'))
for cfg_file in cfg_files:
ensure_permissions(cfg_file, 0o0600)
cfg = configparser.ConfigParser()
cfg = configparser.ConfigParser(defaults={
'start_command': None,
'start_command_shell': 'False',
'additional_checksum_commands': '',
'diff_command': 'diff',
'authenticated_latency_deviation': '0.01',
})
cfg.read(cfg_files)
LOG.debug("Read configuration files: {}".format(cfg_files))
return cfg
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment