summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAni Sinha <anisinha@redhat.com>2023-05-02 20:35:45 +0530
committerGitHub <noreply@github.com>2023-05-02 17:05:45 +0200
commitc53f04aeb2acf9526a2ebf3d3320f149ac46caa6 (patch)
tree392c5036abfa0c92a0410e305fa521dd7a8922bf
parent76fe7ddb590f05a650f22fb15a7764320f58a42e (diff)
downloadcloud-init-git-c53f04aeb2acf9526a2ebf3d3320f149ac46caa6.tar.gz
Do not generate dsa and ed25519 key types when crypto FIPS mode is enabled (#2142)
DSA and ED25519 key types are not supported when FIPS is enabled in crypto. Check if FIPS has been enabled on the system and if so, do not generate those key types. Presently the check is only available on Linux systems. LP: 2017761 RHBZ: 2187164 Signed-off-by: Ani Sinha <anisinha@redhat.com>
-rw-r--r--cloudinit/config/cc_ssh.py21
-rw-r--r--cloudinit/util.py12
-rw-r--r--tests/unittests/config/test_cc_ssh.py36
-rw-r--r--tests/unittests/test_util.py25
4 files changed, 85 insertions, 9 deletions
diff --git a/cloudinit/config/cc_ssh.py b/cloudinit/config/cc_ssh.py
index 7c9ae36b..d7b9e704 100644
--- a/cloudinit/config/cc_ssh.py
+++ b/cloudinit/config/cc_ssh.py
@@ -173,6 +173,8 @@ __doc__ = get_meta_doc(meta)
LOG = logging.getLogger(__name__)
GENERATE_KEY_NAMES = ["rsa", "dsa", "ecdsa", "ed25519"]
+FIPS_UNSUPPORTED_KEY_NAMES = ["dsa", "ed25519"]
+
pattern_unsupported_config_keys = re.compile(
"^(ecdsa-sk|ed25519-sk)_(private|public|certificate)$"
)
@@ -258,9 +260,26 @@ def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
genkeys = util.get_cfg_option_list(
cfg, "ssh_genkeytypes", GENERATE_KEY_NAMES
)
+ # remove keys that are not supported in fips mode if its enabled
+ key_names = (
+ genkeys
+ if not util.fips_enabled()
+ else [
+ names
+ for names in genkeys
+ if names not in FIPS_UNSUPPORTED_KEY_NAMES
+ ]
+ )
+ skipped_keys = set(genkeys).difference(key_names)
+ if skipped_keys:
+ LOG.debug(
+ "skipping keys that are not supported in fips mode: %s",
+ ",".join(skipped_keys),
+ )
+
lang_c = os.environ.copy()
lang_c["LANG"] = "C"
- for keytype in genkeys:
+ for keytype in key_names:
keyfile = KEY_FILE_TPL % (keytype)
if os.path.exists(keyfile):
continue
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 2eb79d33..b0d2ddb0 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1578,6 +1578,18 @@ def get_cmdline():
return _get_cmdline()
+def fips_enabled() -> bool:
+ fips_proc = "/proc/sys/crypto/fips_enabled"
+ try:
+ contents = load_file(fips_proc).strip()
+ return contents == "1"
+ except (IOError, OSError):
+ # for BSD systems and Linux systems where the proc entry is not
+ # available, we assume FIPS is disabled to retain the old behavior
+ # for now.
+ return False
+
+
def pipe_in_out(in_fh, out_fh, chunk_size=1024, chunk_cb=None):
bytes_piped = 0
while True:
diff --git a/tests/unittests/config/test_cc_ssh.py b/tests/unittests/config/test_cc_ssh.py
index 524fb81d..bbcbf385 100644
--- a/tests/unittests/config/test_cc_ssh.py
+++ b/tests/unittests/config/test_cc_ssh.py
@@ -101,11 +101,16 @@ class TestHandleSsh:
expected_calls = [mock.call(set(keys), user)] + expected_calls
assert expected_calls == m_setup_keys.call_args_list
+ @pytest.mark.parametrize("fips_enabled", (True, False))
@mock.patch(MODPATH + "glob.glob")
@mock.patch(MODPATH + "ug_util.normalize_users_groups")
@mock.patch(MODPATH + "os.path.exists")
- def test_handle_no_cfg(self, m_path_exists, m_nug, m_glob, m_setup_keys):
+ @mock.patch(MODPATH + "util.fips_enabled")
+ def test_handle_no_cfg(
+ self, m_fips, m_path_exists, m_nug, m_glob, m_setup_keys, fips_enabled
+ ):
"""Test handle with no config ignores generating existing keyfiles."""
+ m_fips.return_value = fips_enabled
cfg = {}
keys = ["key1"]
m_glob.return_value = [] # Return no matching keys to prevent removal
@@ -118,12 +123,22 @@ class TestHandleSsh:
options = ssh_util.DISABLE_USER_OPTS.replace("$USER", "NONE")
options = options.replace("$DISABLE_USER", "root")
m_glob.assert_called_once_with("/etc/ssh/ssh_host_*key*")
- assert [
- mock.call("/etc/ssh/ssh_host_rsa_key"),
- mock.call("/etc/ssh/ssh_host_dsa_key"),
- mock.call("/etc/ssh/ssh_host_ecdsa_key"),
- mock.call("/etc/ssh/ssh_host_ed25519_key"),
- ] in m_path_exists.call_args_list
+ m_fips.assert_called_once()
+
+ if not m_fips():
+ expected_calls = [
+ mock.call("/etc/ssh/ssh_host_rsa_key"),
+ mock.call("/etc/ssh/ssh_host_dsa_key"),
+ mock.call("/etc/ssh/ssh_host_ecdsa_key"),
+ mock.call("/etc/ssh/ssh_host_ed25519_key"),
+ ]
+ else:
+ # Enabled fips doesn't generate dsa or ed25519
+ expected_calls = [
+ mock.call("/etc/ssh/ssh_host_rsa_key"),
+ mock.call("/etc/ssh/ssh_host_ecdsa_key"),
+ ]
+ assert expected_calls in m_path_exists.call_args_list
assert [
mock.call(set(keys), "root", options=options)
] == m_setup_keys.call_args_list
@@ -131,8 +146,9 @@ class TestHandleSsh:
@mock.patch(MODPATH + "glob.glob")
@mock.patch(MODPATH + "ug_util.normalize_users_groups")
@mock.patch(MODPATH + "os.path.exists")
+ @mock.patch(MODPATH + "util.fips_enabled", return_value=False)
def test_dont_allow_public_ssh_keys(
- self, m_path_exists, m_nug, m_glob, m_setup_keys
+ self, m_fips, m_path_exists, m_nug, m_glob, m_setup_keys
):
"""Test allow_public_ssh_keys=False ignores ssh public keys from
platform.
@@ -176,8 +192,10 @@ class TestHandleSsh:
@mock.patch(MODPATH + "glob.glob")
@mock.patch(MODPATH + "ug_util.normalize_users_groups")
@mock.patch(MODPATH + "os.path.exists")
+ @mock.patch(MODPATH + "util.fips_enabled", return_value=False)
def test_handle_default_root(
self,
+ m_fips,
m_path_exists,
m_nug,
m_glob,
@@ -241,8 +259,10 @@ class TestHandleSsh:
@mock.patch(MODPATH + "glob.glob")
@mock.patch(MODPATH + "ug_util.normalize_users_groups")
@mock.patch(MODPATH + "os.path.exists")
+ @mock.patch(MODPATH + "util.fips_enabled", return_value=False)
def test_handle_publish_hostkeys(
self,
+ m_fips,
m_path_exists,
m_nug,
m_glob,
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index c23f6399..256eb291 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -1947,6 +1947,31 @@ class TestGetCmdline(helpers.TestCase):
self.assertEqual("abcd 123", ret)
+class TestFipsEnabled:
+ @pytest.mark.parametrize(
+ "fips_enabled_content,expected",
+ (
+ pytest.param(None, False, id="false_when_no_fips_enabled_file"),
+ pytest.param("0\n", False, id="false_when_fips_disabled"),
+ pytest.param("1\n", True, id="true_when_fips_enabled"),
+ pytest.param("1", True, id="true_when_fips_enabled_no_newline"),
+ ),
+ )
+ @mock.patch(M_PATH + "load_file")
+ def test_fips_enabled_based_on_proc_crypto(
+ self, load_file, fips_enabled_content, expected, tmpdir
+ ):
+ def fake_load_file(path):
+ assert path == "/proc/sys/crypto/fips_enabled"
+ if fips_enabled_content is None:
+ raise IOError("No file exists Bob")
+ return fips_enabled_content
+
+ load_file.side_effect = fake_load_file
+
+ assert expected is util.fips_enabled()
+
+
class TestLoadYaml(helpers.CiTestCase):
mydefault = "7b03a8ebace993d806255121073fed52"
with_logs = True