mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Test installation with (fake) userspace FIPS
Based on userspace FIPS mode by Ondrej Moris. Userspace FIPS mode fakes a Kernel in FIPS enforcing mode. User space programs behave like the Kernel was booted in FIPS enforcing mode. Kernel space code still runs in standard mode. Fixes: https://pagure.io/freeipa/issue/8118 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
@@ -159,6 +159,18 @@ jobs:
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-latest/test_fips:
|
||||
requires: [fedora-latest/build]
|
||||
priority: 50
|
||||
job:
|
||||
class: RunPytest
|
||||
args:
|
||||
build_url: '{fedora-latest/build_url}'
|
||||
test_suite: test_integration/test_fips.py
|
||||
template: *ci-master-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-latest/test_forced_client_enrolment:
|
||||
requires: [fedora-latest/build]
|
||||
priority: 50
|
||||
|
||||
@@ -159,6 +159,18 @@ jobs:
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-previous/test_fips:
|
||||
requires: [fedora-previous/build]
|
||||
priority: 50
|
||||
job:
|
||||
class: RunPytest
|
||||
args:
|
||||
build_url: '{fedora-previous/build_url}'
|
||||
test_suite: test_integration/test_fips.py
|
||||
template: *ci-master-previous
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-previous/test_forced_client_enrolment:
|
||||
requires: [fedora-previous/build]
|
||||
priority: 50
|
||||
|
||||
@@ -159,6 +159,18 @@ jobs:
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-rawhide/test_fips:
|
||||
requires: [fedora-rawhide/build]
|
||||
priority: 50
|
||||
job:
|
||||
class: RunPytest
|
||||
args:
|
||||
build_url: '{fedora-rawhide/build_url}'
|
||||
test_suite: test_integration/test_fips.py
|
||||
template: *ci-master-frawhide
|
||||
timeout: 3600
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-rawhide/test_forced_client_enrolment:
|
||||
requires: [fedora-rawhide/build]
|
||||
priority: 50
|
||||
|
||||
@@ -43,6 +43,7 @@ class Config(pytest_multihost.config.Config):
|
||||
'dns_forwarder',
|
||||
'domain_level',
|
||||
'log_journal_since',
|
||||
'fips_mode',
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -67,6 +68,7 @@ class Config(pytest_multihost.config.Config):
|
||||
self.log_journal_since = kwargs.get('log_journal_since') or '-1h'
|
||||
if self.domain_level is None:
|
||||
self.domain_level = MAX_DOMAIN_LEVEL
|
||||
self.fips_mode = kwargs.get('fips_mode', False)
|
||||
|
||||
def get_domain_class(self):
|
||||
return Domain
|
||||
|
||||
@@ -63,6 +63,8 @@ _setting_infos = (
|
||||
_SettingInfo('domain_level', 'DOMAINLVL', MAX_DOMAIN_LEVEL),
|
||||
|
||||
_SettingInfo('log_journal_since', 'LOG_JOURNAL_SINCE', '-1h'),
|
||||
# userspace FIPS mode
|
||||
_SettingInfo('fips_mode', 'IPA_FIPS_MODE', False),
|
||||
)
|
||||
|
||||
|
||||
|
||||
67
ipatests/pytest_ipa/integration/fips.py
Normal file
67
ipatests/pytest_ipa/integration/fips.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#
|
||||
# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
"""FIPS testing helpers
|
||||
|
||||
Based on userspace FIPS mode by Ondrej Moris.
|
||||
|
||||
Userspace FIPS mode fakes a Kernel in FIPS enforcing mode. User space
|
||||
programs behave like the Kernel was booted in FIPS enforcing mode. Kernel
|
||||
space code still runs in standard mode.
|
||||
"""
|
||||
import os
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
FIPS_OVERLAY_DIR = "/var/tmp/userspace-fips"
|
||||
FIPS_OVERLAY = os.path.join(FIPS_OVERLAY_DIR, "fips_enabled")
|
||||
SYSTEM_FIPS = "/etc/system-fips"
|
||||
|
||||
|
||||
def is_fips_enabled(host):
|
||||
"""Check if host has """
|
||||
result = host.run_command(
|
||||
["cat", paths.PROC_FIPS_ENABLED], raiseonerr=False
|
||||
)
|
||||
if result.returncode == 1:
|
||||
# FIPS mode not available
|
||||
return None
|
||||
elif result.returncode == 0:
|
||||
return result.stdout_text.strip() == "1"
|
||||
else:
|
||||
raise RuntimeError(result.stderr_text)
|
||||
|
||||
|
||||
def enable_userspace_fips(host):
|
||||
# create /etc/system-fips
|
||||
host.put_file_contents(SYSTEM_FIPS, "# userspace fips\n")
|
||||
# fake Kernel FIPS mode with bind mount
|
||||
host.run_command(["mkdir", "-p", FIPS_OVERLAY_DIR])
|
||||
host.put_file_contents(FIPS_OVERLAY, "1\n")
|
||||
host.run_command(
|
||||
["mount", "--bind", FIPS_OVERLAY, paths.PROC_FIPS_ENABLED]
|
||||
)
|
||||
# set crypto policy to FIPS mode
|
||||
host.run_command(["update-crypto-policies", "--show"])
|
||||
host.run_command(["update-crypto-policies", "--set", "FIPS"])
|
||||
# sanity check
|
||||
assert is_fips_enabled(host)
|
||||
result = host.run_command(
|
||||
["openssl", "md5", "/dev/null"], raiseonerr=False
|
||||
)
|
||||
assert result.returncode == 1
|
||||
assert "EVP_DigestInit_ex:disabled for FIPS" in result.stderr_text
|
||||
|
||||
|
||||
def disable_userspace_fips(host):
|
||||
host.run_command(["rm", "-f", SYSTEM_FIPS])
|
||||
host.run_command(["update-crypto-policies", "--set", "DEFAULT"])
|
||||
result = host.run_command(
|
||||
["umount", paths.PROC_FIPS_ENABLED], raiseonerr=False
|
||||
)
|
||||
host.run_command(["rm", "-rf", FIPS_OVERLAY_DIR])
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr_text)
|
||||
|
||||
# sanity check
|
||||
assert not is_fips_enabled(host)
|
||||
host.run_command(["openssl", "md5", "/dev/null"])
|
||||
@@ -27,6 +27,10 @@ import pytest_multihost.host
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython import ipaldap
|
||||
|
||||
from .fips import (
|
||||
is_fips_enabled, enable_userspace_fips, disable_userspace_fips
|
||||
)
|
||||
|
||||
|
||||
class LDAPClientWithoutCertCheck(ipaldap.LDAPClient):
|
||||
"""Adds an option to disable certificate check for TLS connection
|
||||
@@ -58,6 +62,61 @@ class LDAPClientWithoutCertCheck(ipaldap.LDAPClient):
|
||||
class Host(pytest_multihost.host.Host):
|
||||
"""Representation of a remote IPA host"""
|
||||
|
||||
def __init__(self, domain, hostname, role, ip=None,
|
||||
external_hostname=None, username=None, password=None,
|
||||
test_dir=None, host_type=None):
|
||||
super().__init__(
|
||||
domain, hostname, role, ip=ip,
|
||||
external_hostname=external_hostname, username=username,
|
||||
password=password, test_dir=test_dir, host_type=host_type
|
||||
)
|
||||
self._fips_mode = None
|
||||
self._userspace_fips = False
|
||||
|
||||
@property
|
||||
def is_fips_mode(self):
|
||||
"""Check and cache if a system is in FIPS mode
|
||||
"""
|
||||
if self._fips_mode is None:
|
||||
self._fips_mode = is_fips_enabled(self)
|
||||
return self._fips_mode
|
||||
|
||||
@property
|
||||
def is_userspace_fips(self):
|
||||
"""Check if host uses fake userspace FIPS
|
||||
"""
|
||||
return self._userspace_fips
|
||||
|
||||
def enable_userspace_fips(self):
|
||||
"""Enable fake userspace FIPS mode
|
||||
|
||||
The call has no effect if the system is already in FIPS mode.
|
||||
|
||||
:return: True if system was modified, else None
|
||||
"""
|
||||
if not self.is_fips_mode:
|
||||
enable_userspace_fips(self)
|
||||
self._fips_mode = True
|
||||
self._userspace_fips = True
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def disable_userspace_fips(self):
|
||||
"""Disable fake userspace FIPS mode
|
||||
|
||||
The call has no effect if userspace FIPS mode is not enabled.
|
||||
|
||||
:return: True if system was modified, else None
|
||||
"""
|
||||
if self.is_userspace_fips:
|
||||
disable_userspace_fips(self)
|
||||
self._userspace_fips = False
|
||||
self._fips_mode = False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _make_host(domain, hostname, role, ip, external_hostname):
|
||||
# We need to determine the type of the host, this depends on the domain
|
||||
|
||||
@@ -37,6 +37,7 @@ class IntegrationTest:
|
||||
required_extra_roles = []
|
||||
topology = None
|
||||
domain_level = None
|
||||
fips_mode = None
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
@@ -60,12 +61,30 @@ class IntegrationTest:
|
||||
def get_domains(cls):
|
||||
return [cls.domain] + cls.ad_domains
|
||||
|
||||
@classmethod
|
||||
def enable_fips_mode(cls):
|
||||
for host in cls.get_all_hosts():
|
||||
if not host.is_fips_mode:
|
||||
host.enable_userspace_fips()
|
||||
|
||||
@classmethod
|
||||
def disable_fips_mode(cls):
|
||||
for host in cls.get_all_hosts():
|
||||
if host.is_userspace_fips:
|
||||
host.disable_userspace_fips()
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
if cls.domain_level is not None:
|
||||
domain_level = cls.domain_level
|
||||
else:
|
||||
domain_level = cls.master.config.domain_level
|
||||
|
||||
if cls.master.config.fips_mode:
|
||||
cls.fips_mode = True
|
||||
if cls.fips_mode:
|
||||
cls.enable_fips_mode()
|
||||
|
||||
if cls.topology is None:
|
||||
return
|
||||
else:
|
||||
@@ -83,3 +102,5 @@ class IntegrationTest:
|
||||
tasks.uninstall_master(replica)
|
||||
for client in cls.clients:
|
||||
tasks.uninstall_client(client)
|
||||
if cls.fips_mode:
|
||||
cls.disable_fips_mode()
|
||||
|
||||
@@ -98,6 +98,16 @@ def dnszone_add_dnssec(host, test_zone):
|
||||
return host.run_command(args)
|
||||
|
||||
|
||||
def dnssec_install_master(host):
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--forwarder", host.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
return host.run_command(args)
|
||||
|
||||
|
||||
class TestInstallDNSSECLast(IntegrationTest):
|
||||
"""Simple DNSSEC test
|
||||
|
||||
@@ -114,13 +124,7 @@ class TestInstallDNSSECLast(IntegrationTest):
|
||||
|
||||
def test_install_dnssec_master(self):
|
||||
"""Both master and replica have DNS installed"""
|
||||
args = [
|
||||
"ipa-dns-install",
|
||||
"--dnssec-master",
|
||||
"--forwarder", self.master.config.dns_forwarder,
|
||||
"-U",
|
||||
]
|
||||
self.master.run_command(args)
|
||||
dnssec_install_master(self.master)
|
||||
|
||||
def test_if_zone_is_signed_master(self):
|
||||
# add zone with enabled DNSSEC signing on master
|
||||
|
||||
127
ipatests/test_integration/test_fips.py
Normal file
127
ipatests/test_integration/test_fips.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#
|
||||
# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
"""Smoke tests for FreeIPA installation in (fake) userspace FIPS mode
|
||||
"""
|
||||
from ipapython.dn import DN
|
||||
from ipapython.ipautil import ipa_generate_password, realm_to_suffix
|
||||
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.pytest_ipa.integration import fips
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
|
||||
from .test_dnssec import (
|
||||
test_zone,
|
||||
dnssec_install_master,
|
||||
dnszone_add_dnssec,
|
||||
wait_until_record_is_signed,
|
||||
)
|
||||
|
||||
|
||||
class TestInstallFIPS(IntegrationTest):
|
||||
num_replicas = 1
|
||||
num_clients = 1
|
||||
fips_mode = True
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
super(TestInstallFIPS, cls).install(mh)
|
||||
# sanity check
|
||||
for host in cls.get_all_hosts():
|
||||
assert host.is_fips_mode
|
||||
assert fips.is_fips_enabled(host)
|
||||
# patch named-pkcs11 crypto policy
|
||||
# see RHBZ#1772111
|
||||
for host in [cls.master] + cls.replicas:
|
||||
host.run_command(
|
||||
[
|
||||
"sed",
|
||||
"-i",
|
||||
"-E",
|
||||
"s/RSAMD5;//g",
|
||||
"/etc/crypto-policies/back-ends/bind.config",
|
||||
]
|
||||
)
|
||||
# master with CA, KRA, DNS+DNSSEC
|
||||
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
|
||||
# replica with CA, KRA, DNS
|
||||
tasks.install_replica(
|
||||
cls.master,
|
||||
cls.replicas[0],
|
||||
setup_dns=True,
|
||||
setup_ca=True,
|
||||
setup_kra=True,
|
||||
)
|
||||
tasks.install_clients([cls.master] + cls.replicas, cls.clients)
|
||||
|
||||
def test_basic(self):
|
||||
client = self.clients[0]
|
||||
tasks.kinit_admin(client)
|
||||
client.run_command(["ipa", "ping"])
|
||||
|
||||
def test_dnssec(self):
|
||||
dnssec_install_master(self.master)
|
||||
# DNSSEC zone
|
||||
dnszone_add_dnssec(self.master, test_zone)
|
||||
assert wait_until_record_is_signed(
|
||||
self.master.ip, test_zone, timeout=100
|
||||
), ("Zone %s is not signed (master)" % test_zone)
|
||||
|
||||
# test replica
|
||||
assert wait_until_record_is_signed(
|
||||
self.replicas[0].ip, test_zone, timeout=200
|
||||
), ("DNS zone %s is not signed (replica)" % test_zone)
|
||||
|
||||
def test_vault_basic(self):
|
||||
vault_name = "testvault"
|
||||
vault_password = ipa_generate_password()
|
||||
vault_data = "SSBsb3ZlIENJIHRlc3RzCg=="
|
||||
# create vault
|
||||
self.master.run_command(
|
||||
[
|
||||
"ipa",
|
||||
"vault-add",
|
||||
vault_name,
|
||||
"--password",
|
||||
vault_password,
|
||||
"--type",
|
||||
"symmetric",
|
||||
]
|
||||
)
|
||||
|
||||
# archive secret
|
||||
self.master.run_command(
|
||||
[
|
||||
"ipa",
|
||||
"vault-archive",
|
||||
vault_name,
|
||||
"--password",
|
||||
vault_password,
|
||||
"--data",
|
||||
vault_data,
|
||||
]
|
||||
)
|
||||
self.master.run_command(
|
||||
[
|
||||
"ipa",
|
||||
"vault-retrieve",
|
||||
vault_name,
|
||||
"--password",
|
||||
vault_password,
|
||||
]
|
||||
)
|
||||
|
||||
def test_krb_enctypes(self):
|
||||
realm = self.master.domain.realm
|
||||
suffix = realm_to_suffix(realm)
|
||||
dn = DN(("cn", realm), ("cn", "kerberos")) + suffix
|
||||
args = ["krbSupportedEncSaltTypes", "krbDefaultEncSaltTypes"]
|
||||
for host in [self.master] + self.replicas:
|
||||
result = tasks.ldapsearch_dm(host, str(dn), args, scope="base")
|
||||
assert "camellia" not in result.stdout_text
|
||||
assert "aes256-cts" in result.stdout_text
|
||||
assert "aes128-cts" in result.stdout_text
|
||||
# test that update does not add camellia
|
||||
self.master.run_command(["ipa-server-upgrade"])
|
||||
result = tasks.ldapsearch_dm(self.master, str(dn), args, scope="base")
|
||||
assert "camellia" not in result.stdout_text
|
||||
@@ -43,6 +43,7 @@ DEFAULT_OUTPUT_DICT = {
|
||||
"admin_password": "Secret123",
|
||||
"domain_level": MAX_DOMAIN_LEVEL,
|
||||
"log_journal_since": "-1h",
|
||||
"fips_mode": False,
|
||||
}
|
||||
|
||||
DEFAULT_OUTPUT_ENV = {
|
||||
@@ -62,6 +63,7 @@ DEFAULT_OUTPUT_ENV = {
|
||||
"IPADEBUG": "",
|
||||
"DOMAINLVL": str(MAX_DOMAIN_LEVEL),
|
||||
"LOG_JOURNAL_SINCE": "-1h",
|
||||
"IPA_FIPS_MODE": "",
|
||||
}
|
||||
|
||||
DEFAULT_INPUT_ENV = {
|
||||
|
||||
@@ -194,6 +194,7 @@ ipa_class_members = {
|
||||
{'ad_admin_name': dir(str)},
|
||||
{'ad_admin_password': dir(str)},
|
||||
{'domain_level': dir(str)},
|
||||
{'fips_mode': dir(bool)},
|
||||
]},
|
||||
{'domain': [
|
||||
{'realm': dir(str)},
|
||||
|
||||
Reference in New Issue
Block a user