diff --git a/cloudinit/config/cc_ssh_authkey_fingerprints.py b/cloudinit/config/cc_ssh_authkey_fingerprints.py index c2d3b0d1c55..db5c1454904 100755 --- a/cloudinit/config/cc_ssh_authkey_fingerprints.py +++ b/cloudinit/config/cc_ssh_authkey_fingerprints.py @@ -123,6 +123,11 @@ def handle(name, cfg, cloud, log, _args): (users, _groups) = ug_util.normalize_users_groups(cfg, cloud.distro) for (user_name, _cfg) in users.items(): if _cfg.get("no_create_home") or _cfg.get("system"): + log.debug( + "Skipping printing of ssh fingerprints for user '%s' because " + "no home directory is created", + user_name, + ) continue (key_fn, key_entries) = ssh_util.extract_authorized_keys(user_name) diff --git a/cloudinit/config/cc_users_groups.py b/cloudinit/config/cc_users_groups.py index d3c945e6ca2..96e63242c63 100644 --- a/cloudinit/config/cc_users_groups.py +++ b/cloudinit/config/cc_users_groups.py @@ -152,14 +152,29 @@ LOG = logging.getLogger(__name__) +# NO_HOME and NEED_HOME are mutually exclusive options +NO_HOME = ("no_create_home", "system") +NEED_HOME = ("ssh_authorized_keys", "ssh_import_id", "ssh_redirect_user") + def handle(name, cfg, cloud, _log, _args): (users, groups) = ug_util.normalize_users_groups(cfg, cloud.distro) (default_user, _user_config) = ug_util.extract_default(users) cloud_keys = cloud.get_public_ssh_keys() or [] + for (name, members) in groups.items(): cloud.distro.create_group(name, members) + for (user, config) in users.items(): + + no_home = [key for key in NO_HOME if config.get(key)] + need_home = [key for key in NEED_HOME if config.get(key)] + if no_home and need_home: + raise ValueError( + f"Not creating user {user}. Key(s) {', '.join(need_home)}" + f" cannot be provided with {', '.join(no_home)}" + ) + ssh_redirect_user = config.pop("ssh_redirect_user", False) if ssh_redirect_user: if "ssh_authorized_keys" in config or "ssh_import_id" in config: @@ -186,6 +201,7 @@ def handle(name, cfg, cloud, _log, _args): else: config["ssh_redirect_user"] = default_user config["cloud_public_ssh_keys"] = cloud_keys + cloud.distro.create_user(user, **config) diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt index 987d9508231..a66b0d75b5f 100644 --- a/doc/examples/cloud-config-user-groups.txt +++ b/doc/examples/cloud-config-user-groups.txt @@ -87,6 +87,8 @@ users: # no_log_init: When set to true, do not initialize lastlog and faillog database. # ssh_import_id: Optional. Import SSH ids # ssh_authorized_keys: Optional. [list] Add keys to user's authorized keys file +# An error will be raised if no_create_home or system is +# also set. # ssh_redirect_user: Optional. [bool] Set true to block ssh logins for cloud # ssh public keys and emit a message redirecting logins to # use instead. This option only disables cloud diff --git a/tests/unittests/config/test_cc_users_groups.py b/tests/unittests/config/test_cc_users_groups.py index 9210c1f6bd6..af8bdc3027b 100644 --- a/tests/unittests/config/test_cc_users_groups.py +++ b/tests/unittests/config/test_cc_users_groups.py @@ -192,6 +192,27 @@ def test_users_with_ssh_redirect_user_default_str(self, m_user, m_group): ) m_group.assert_not_called() + def test_users_without_home_cannot_import_ssh_keys(self, m_user, m_group): + cfg = { + "users": [ + "default", + { + "name": "me2", + "ssh_import_id": ["snowflake"], + "no_create_home": True, + }, + ] + } + cloud = self.tmp_cloud(distro="ubuntu", sys_cfg={}, metadata={}) + with self.assertRaises(ValueError) as context_manager: + cc_users_groups.handle("modulename", cfg, cloud, None, None) + m_group.assert_not_called() + self.assertEqual( + "Not creating user me2. Key(s) ssh_import_id cannot be provided" + " with no_create_home", + str(context_manager.exception), + ) + def test_users_with_ssh_redirect_user_non_default(self, m_user, m_group): """Warn when ssh_redirect_user is not 'default'.""" cfg = {