From e73aec1409543e9e62abfab9dfaa2a14967f2035 Mon Sep 17 00:00:00 2001 From: Dalton Bohning Date: Wed, 8 Jan 2025 23:26:05 +0000 Subject: [PATCH 1/2] DAOS-15268 test: handle errors in add_del_user Replace security_test_base.py:add_del_user with calls to individual functions and handle errors. Test-tag: SecurityPoolACLTest SecurityPoolGroupsTest DaosContainerSecurityTest Skip-unit-tests: true Skip-fault-injection-test: true Signed-off-by: Dalton Bohning --- src/tests/ftest/security/cont_acl.py | 10 +---- src/tests/ftest/security/cont_acl.yaml | 4 +- .../ftest/util/pool_security_test_base.py | 28 +++++++----- src/tests/ftest/util/security_test_base.py | 21 +-------- src/tests/ftest/util/user_utils.py | 43 +++++++++++++++++++ utils/cq/words.dict | 2 + 6 files changed, 66 insertions(+), 42 deletions(-) diff --git a/src/tests/ftest/security/cont_acl.py b/src/tests/ftest/security/cont_acl.py index 74c4ae34124..7bb8783520c 100644 --- a/src/tests/ftest/security/cont_acl.py +++ b/src/tests/ftest/security/cont_acl.py @@ -1,12 +1,12 @@ """ (C) Copyright 2020-2024 Intel Corporation. + (C) Copyright 2025 Hewlett Packard Enterprise Development LP SPDX-License-Identifier: BSD-2-Clause-Patent """ import os import security_test_base as secTestBase -from agent_utils import include_local_host from cont_security_test_base import ContSecurityTestBase from pool_security_test_base import PoolSecurityTestBase @@ -74,10 +74,6 @@ def test_container_user_acl(self): "attribute", "/run/container_acl/*") property_name, property_value = self.params.get( "property", "/run/container_acl/*") - secTestBase.add_del_user( - include_local_host(self.hostlist_clients), "useradd", new_test_user) - secTestBase.add_del_user( - include_local_host(self.hostlist_clients), "groupadd", new_test_group) acl_file_name = self._get_acl_file_name() test_user = self.params.get( "testuser", "/run/container_acl/daos_user/*") @@ -204,7 +200,3 @@ def test_container_user_acl(self): # Restore pool permissions in case they were altered self.update_pool_acl_entry( "update", secTestBase.acl_entry("user", "OWNER", "rctd")) - secTestBase.add_del_user( - self.hostlist_clients, "userdel", new_test_user) - secTestBase.add_del_user( - self.hostlist_clients, "groupdel", new_test_group) diff --git a/src/tests/ftest/security/cont_acl.yaml b/src/tests/ftest/security/cont_acl.yaml index 799a0eddc6f..a767b32a63a 100644 --- a/src/tests/ftest/security/cont_acl.yaml +++ b/src/tests/ftest/security/cont_acl.yaml @@ -24,8 +24,8 @@ container: control_method: daos container_acl: acl_file_name: cont_test_acl1.txt - new_user: daos_ci_tester_1 - new_group: daos_ci_test_grp_1 + new_user: root + new_group: root attribute: - container_name - Container1 diff --git a/src/tests/ftest/util/pool_security_test_base.py b/src/tests/ftest/util/pool_security_test_base.py index ac7de7db866..383d315b96a 100644 --- a/src/tests/ftest/util/pool_security_test_base.py +++ b/src/tests/ftest/util/pool_security_test_base.py @@ -1,5 +1,6 @@ """ (C) Copyright 2020-2024 Intel Corporation. + (C) Copyright 2025 Hewlett Packard Enterprise Development LP SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -11,6 +12,7 @@ import agent_utils as agu import security_test_base as secTestBase from apricot import TestWithServers +from user_utils import groupadd, groupdel, useradd, userdel, usermod PERMISSIONS = ["", "r", "w", "rw"] DENY_ACCESS = "-1001" @@ -362,13 +364,14 @@ def create_pool_acl(self, num_user, num_group, current_user_acl, acl_file): for uid in range(num_user): username = user_prefix + "_tester_" + str(uid + 1) new_user = "A::" + username + "@:" + PERMISSIONS[uid % 4] - secTestBase.add_del_user(self.hostlist_clients, "useradd", username) + if not useradd(self.log, self.hostlist_clients, username, sudo=True).passed: + self.fail(f"Failed to useradd {username}") user_list.append(new_user) for gid in range(num_group): groupname = user_prefix + "_testGrp_" + str(gid + 1) new_group = "A:G:" + groupname + "@:" + PERMISSIONS[(gid + 2) % 4] - secTestBase.add_del_user(self.hostlist_clients, "groupadd", - groupname) + if not groupadd(self.log, self.hostlist_clients, groupname, sudo=True).passed: + self.fail(f"Failed to groupadd {groupname}") group_list.append(new_group) permission_list = group_list + user_list + current_user_acl random.shuffle(permission_list) @@ -386,11 +389,12 @@ def cleanup_user_group(self, num_user, num_group): user_prefix = self.params.get("user_prefix", "/run/pool_acl/*") for uid in range(num_user): username = user_prefix + "_tester_" + str(uid + 1) - secTestBase.add_del_user(self.hostlist_clients, "userdel", username) + if not userdel(self.log, self.hostlist_clients, username, sudo=True).passed: + self.log.error("Failed to userdel %s", username) for gid in range(num_group): groupname = user_prefix + "_testGrp_" + str(gid + 1) - secTestBase.add_del_user(self.hostlist_clients, "groupdel", - groupname) + if not groupdel(self.log, self.hostlist_clients, groupname, sudo=True).passed: + self.log.error("Failed to groupdel %s", groupname) def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): """Verify daos pool acl access. @@ -417,10 +421,11 @@ def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): "sg_read_write", "/run/pool_acl/primary_secondary_group_test/*") l_group = grp.getgrgid(os.getegid())[0] for group in sec_group: - secTestBase.add_del_user(self.hostlist_clients, "groupadd", group) - cmd = "usermod -G " + ",".join(sec_group) - self.log.info(" (8-1)verify_pool_acl_prim_sec_groups, cmd= %s", cmd) - secTestBase.add_del_user(self.hostlist_clients, cmd, l_group) + if not groupadd(self.log, self.hostlist_clients, group, sudo=True).passed: + self.fail(f"Failed to groupadd {group}") + self.log.info(" (8-1)verify_pool_acl_prim_sec_groups, cmd=usermod") + if not usermod(self.log, self.hostlist_clients, l_group, sec_group, sudo=True).passed: + self.fail(f"Failed to usermod {l_group}") self.log.info( " (8-2)Before update sec_group permission, pool_acl_list= %s", @@ -465,7 +470,8 @@ def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): self.verify_pool_readwrite(self.pool, "write", expect=exp_write) for group in sec_group: - secTestBase.add_del_user(self.hostlist_clients, "groupdel", group) + if not groupdel(self.log, self.hostlist_clients, group, sudo=True).passed: + self.log.error("Failed to groupdel %s", group) def pool_acl_verification(self, current_user_acl, read, write, secondary_grp_test=False): diff --git a/src/tests/ftest/util/security_test_base.py b/src/tests/ftest/util/security_test_base.py index a0b83426777..64a3ef3cd59 100644 --- a/src/tests/ftest/util/security_test_base.py +++ b/src/tests/ftest/util/security_test_base.py @@ -1,5 +1,6 @@ """ (C) Copyright 2020-2023 Intel Corporation. + (C) Copyright 2025 Hewlett Packard Enterprise Development LP SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -7,8 +8,6 @@ import os import random -from general_utils import pcmd - class DaosTestError(Exception): """DAOS API exception class.""" @@ -71,24 +70,6 @@ def get_user_type(test_user): return user_type -def add_del_user(hosts, bash_cmd, user): - """Add or delete the daos user and group on host by sudo command. - - Args: - hosts (NodeSet): hosts on which to add/delete the user. - bash_cmd (str): Linux bash command to create user or group. - user (str): user or group name to be created or cleaned. - - """ - bash_cmd = os.path.join("/usr/sbin", bash_cmd) - homedir = "" - if "usermod" not in bash_cmd and "user" in bash_cmd: - homedir = "-r" - cmd = " ".join(("sudo", bash_cmd, homedir, user)) - print(" =Clients/hosts {0}, exec cmd: {1}".format(hosts, cmd)) - pcmd(hosts, cmd, False) - - def create_acl_file(file_name, permissions): """Create a acl_file with permissions. diff --git a/src/tests/ftest/util/user_utils.py b/src/tests/ftest/util/user_utils.py index ce5a58ed4f7..4350e231607 100644 --- a/src/tests/ftest/util/user_utils.py +++ b/src/tests/ftest/util/user_utils.py @@ -1,5 +1,6 @@ """ (C) Copyright 2018-2024 Intel Corporation. + (C) Copyright 2025 Hewlett Packard Enterprise Development LP SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -113,6 +114,27 @@ def groupadd(log, hosts, group, force=False, sudo=False): return run_remote(log, hosts, command) +def groupdel(log, hosts, group, force=False, sudo=False): + """Run groupdel remotely. + + Args: + log (logger): logger for the messages produced by this method + hosts (NodeSet): hosts on which to run the command + group (str): the group to delete + force (bool, optional): whether to use the force option. Default is False + sudo (bool, optional): whether to execute commands with sudo. Default is False + + Returns: + CommandResult: groups of command results from the same hosts with the same return status + """ + command = ' '.join(filter(None, [ + 'sudo -n' if sudo else None, + 'groupdel', + '-f' if force else None, + group])) + return run_remote(log, hosts, command) + + def useradd(log, hosts, user, group=None, parent_dir=None, sudo=False): """Run useradd remotely. @@ -158,6 +180,27 @@ def userdel(log, hosts, user, sudo=False): return run_remote(log, hosts, command) +def usermod(log, hosts, login, groups, sudo=False): + """Run usermod remotely. + + Args: + log (logger): logger for the messages produced by this method + hosts (NodeSet): hosts on which to run the command + login (str): login username + groups (list): list of new groups + sudo (bool): whether to execute commands with sudo. Default is False + + Returns: + CommandResult: groups of command results from the same hosts with the same return status + """ + command = ' '.join(filter(None, [ + 'sudo -n' if sudo else None, + 'usermod', + f'-G {",".join(groups)}', + login])) + return run_remote(log, hosts, command) + + def get_group_id(log, hosts, group, sudo=False): """Get a group's id on remote nodes. diff --git a/utils/cq/words.dict b/utils/cq/words.dict index 102ca21a882..cd882cf8848 100644 --- a/utils/cq/words.dict +++ b/utils/cq/words.dict @@ -204,6 +204,7 @@ getfattr getxattr gid groupadd +groupdel groupname grp hackery @@ -498,6 +499,7 @@ uri url useradd userdel +usermod username ushort usr From 849d8d77a08d1cd7546581ae5165270ce541ef9c Mon Sep 17 00:00:00 2001 From: Dalton Bohning Date: Fri, 24 Jan 2025 15:38:20 +0000 Subject: [PATCH 2/2] use command_as_user Test-tag: SecurityPoolACLTest SecurityPoolGroupsTest DaosContainerSecurityTest dfuse_mu Skip-unit-tests: true Skip-fault-injection-test: true Signed-off-by: Dalton Bohning --- src/tests/ftest/util/launch_utils.py | 7 +-- .../ftest/util/pool_security_test_base.py | 14 ++--- src/tests/ftest/util/user_utils.py | 52 +++++++++---------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/tests/ftest/util/launch_utils.py b/src/tests/ftest/util/launch_utils.py index 0f7284c50ef..b20cfb5cfbe 100644 --- a/src/tests/ftest/util/launch_utils.py +++ b/src/tests/ftest/util/launch_utils.py @@ -1,5 +1,6 @@ """ (C) Copyright 2022-2024 Intel Corporation. + (C) Copyright 2025 Hewlett Packard Enterprise Development LP SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -688,7 +689,7 @@ def _query_create_group(logger, hosts, group, create=False): # Create the group logger.info('Creating group %s', group) - if not groupadd(logger, hosts, group, True, True).passed: + if not groupadd(logger, hosts, group, True).passed: raise LaunchException(f'Error creating group {group}') # Get the group id on each node @@ -726,11 +727,11 @@ def _query_create_user(logger, hosts, user, gid=None, create=False): # Delete and ignore errors, in case user account is inconsistent across nodes logger.info('Deleting user %s', user) - _ = userdel(logger, hosts, user, True) + _ = userdel(logger, hosts, user) logger.info('Creating user %s in group %s', user, gid) test_env = TestEnvironment() - if not useradd(logger, hosts, user, gid, test_env.user_dir, True).passed: + if not useradd(logger, hosts, user, gid, test_env.user_dir).passed: raise LaunchException(f'Error creating user {user}') def _clear_mount_points(self, logger, test, clear_mounts): diff --git a/src/tests/ftest/util/pool_security_test_base.py b/src/tests/ftest/util/pool_security_test_base.py index 383d315b96a..69dc233f987 100644 --- a/src/tests/ftest/util/pool_security_test_base.py +++ b/src/tests/ftest/util/pool_security_test_base.py @@ -364,13 +364,13 @@ def create_pool_acl(self, num_user, num_group, current_user_acl, acl_file): for uid in range(num_user): username = user_prefix + "_tester_" + str(uid + 1) new_user = "A::" + username + "@:" + PERMISSIONS[uid % 4] - if not useradd(self.log, self.hostlist_clients, username, sudo=True).passed: + if not useradd(self.log, self.hostlist_clients, username).passed: self.fail(f"Failed to useradd {username}") user_list.append(new_user) for gid in range(num_group): groupname = user_prefix + "_testGrp_" + str(gid + 1) new_group = "A:G:" + groupname + "@:" + PERMISSIONS[(gid + 2) % 4] - if not groupadd(self.log, self.hostlist_clients, groupname, sudo=True).passed: + if not groupadd(self.log, self.hostlist_clients, groupname).passed: self.fail(f"Failed to groupadd {groupname}") group_list.append(new_group) permission_list = group_list + user_list + current_user_acl @@ -389,11 +389,11 @@ def cleanup_user_group(self, num_user, num_group): user_prefix = self.params.get("user_prefix", "/run/pool_acl/*") for uid in range(num_user): username = user_prefix + "_tester_" + str(uid + 1) - if not userdel(self.log, self.hostlist_clients, username, sudo=True).passed: + if not userdel(self.log, self.hostlist_clients, username).passed: self.log.error("Failed to userdel %s", username) for gid in range(num_group): groupname = user_prefix + "_testGrp_" + str(gid + 1) - if not groupdel(self.log, self.hostlist_clients, groupname, sudo=True).passed: + if not groupdel(self.log, self.hostlist_clients, groupname).passed: self.log.error("Failed to groupdel %s", groupname) def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): @@ -421,10 +421,10 @@ def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): "sg_read_write", "/run/pool_acl/primary_secondary_group_test/*") l_group = grp.getgrgid(os.getegid())[0] for group in sec_group: - if not groupadd(self.log, self.hostlist_clients, group, sudo=True).passed: + if not groupadd(self.log, self.hostlist_clients, group).passed: self.fail(f"Failed to groupadd {group}") self.log.info(" (8-1)verify_pool_acl_prim_sec_groups, cmd=usermod") - if not usermod(self.log, self.hostlist_clients, l_group, sec_group, sudo=True).passed: + if not usermod(self.log, self.hostlist_clients, l_group, sec_group).passed: self.fail(f"Failed to usermod {l_group}") self.log.info( @@ -470,7 +470,7 @@ def verify_pool_acl_prim_sec_groups(self, pool_acl_list, acl_file): self.verify_pool_readwrite(self.pool, "write", expect=exp_write) for group in sec_group: - if not groupdel(self.log, self.hostlist_clients, group, sudo=True).passed: + if not groupdel(self.log, self.hostlist_clients, group).passed: self.log.error("Failed to groupdel %s", group) def pool_acl_verification(self, current_user_acl, read, write, diff --git a/src/tests/ftest/util/user_utils.py b/src/tests/ftest/util/user_utils.py index 4350e231607..04ccf907612 100644 --- a/src/tests/ftest/util/user_utils.py +++ b/src/tests/ftest/util/user_utils.py @@ -13,7 +13,7 @@ from ClusterShell.NodeSet import NodeSet # pylint: disable=import-error,no-name-in-module -from util.run_utils import run_remote +from util.run_utils import command_as_user, run_remote def get_primary_group(user=None): @@ -71,7 +71,7 @@ def get_chown_command(user=None, group=None, options=None, file=None): return " ".join(command) -def getent(log, hosts, database, key, sudo=False): +def getent(log, hosts, database, key, run_user=None): """Run getent remotely. Args: @@ -79,20 +79,20 @@ def getent(log, hosts, database, key, sudo=False): hosts (NodeSet): hosts on which to run the command database (str): the administrative database key (str): the key/entry to check for - sudo (bool): whether to execute commands with sudo + run_user (str, optional): user to run the command as. + Default is None, which runs as the current user Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'getent', database, key])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def groupadd(log, hosts, group, force=False, sudo=False): +def groupadd(log, hosts, group, force=False, run_user="root"): """Run groupadd remotely. Args: @@ -100,21 +100,20 @@ def groupadd(log, hosts, group, force=False, sudo=False): hosts (NodeSet): hosts on which to run the command group (str): the group to create force (bool, optional): whether to use the force option. Default is False - sudo (bool, optional): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. Default is root Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'groupadd', '-r', '-f' if force else None, group])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def groupdel(log, hosts, group, force=False, sudo=False): +def groupdel(log, hosts, group, force=False, run_user="root"): """Run groupdel remotely. Args: @@ -122,20 +121,19 @@ def groupdel(log, hosts, group, force=False, sudo=False): hosts (NodeSet): hosts on which to run the command group (str): the group to delete force (bool, optional): whether to use the force option. Default is False - sudo (bool, optional): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. Default is root Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'groupdel', '-f' if force else None, group])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def useradd(log, hosts, user, group=None, parent_dir=None, sudo=False): +def useradd(log, hosts, user, group=None, parent_dir=None, run_user="root"): """Run useradd remotely. Args: @@ -144,43 +142,41 @@ def useradd(log, hosts, user, group=None, parent_dir=None, sudo=False): user (str): user to create group (str, optional): user group. Default is None parent_dir (str, optional): parent home directory. Default is None - sudo (bool): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. Default is root Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'useradd', '-m', f'-g {group}' if group else None, f'-d {os.path.join(parent_dir, user)}' if parent_dir else None, user])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def userdel(log, hosts, user, sudo=False): +def userdel(log, hosts, user, run_user="root"): """Run userdel remotely. Args: log (logger): logger for the messages produced by this method hosts (NodeSet): hosts on which to run the command user (str): user to create - sudo (bool): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. Default is root Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'userdel', '-f', '-r', user])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def usermod(log, hosts, login, groups, sudo=False): +def usermod(log, hosts, login, groups, run_user="root"): """Run usermod remotely. Args: @@ -188,34 +184,34 @@ def usermod(log, hosts, login, groups, sudo=False): hosts (NodeSet): hosts on which to run the command login (str): login username groups (list): list of new groups - sudo (bool): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. Default is root Returns: CommandResult: groups of command results from the same hosts with the same return status """ command = ' '.join(filter(None, [ - 'sudo -n' if sudo else None, 'usermod', f'-G {",".join(groups)}', login])) - return run_remote(log, hosts, command) + return run_remote(log, hosts, command_as_user(command, run_user)) -def get_group_id(log, hosts, group, sudo=False): +def get_group_id(log, hosts, group, run_user=None): """Get a group's id on remote nodes. Args: log (logger): logger for the messages produced by this method hosts (NodeSet): hosts on which to run the command group (str): group to get id of - sudo (bool): whether to execute commands with sudo. Default is False + run_user (str, optional): user to run the command as. + Default is None, which runs as the current user Returns: dict: gid:NodeSet mapping for each gid, where gid is None if non-existent """ gids = defaultdict(NodeSet) - result = getent(log, hosts, 'group', group, sudo) + result = getent(log, hosts, 'group', group, run_user) for data in result.output: if data.returncode == 0: gid = re.findall(r'.*:.*:(.*):.*', '\n'.join(data.stdout))[0]