Skip to content

Commit

Permalink
Password hardening test fix (#6453)
Browse files Browse the repository at this point in the history
What is the motivation for this PR?
Fix the issue #6428

How did you do it?
explained in the summary.

How did you verify/test it?
Run the test with the sonic-builimage build from branch 202205
command line:
py.test /sonic-mgmt/tests/passw_hardening/test_passw_hardening.py
  • Loading branch information
davidpil2002 authored and wangxin committed Oct 17, 2022
1 parent 899337b commit 9623bd9
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 283 deletions.
47 changes: 31 additions & 16 deletions tests/passw_hardening/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import pytest
import test_passw_hardening
from tests.common.utilities import skip_release
import passw_hardening_utils

def set_default_passw_hardening_policies(duthosts, enum_rand_one_per_hwsku_hostname):
duthost = duthosts[enum_rand_one_per_hwsku_hostname]

passw_hardening_ob_dis = test_passw_hardening.PasswHardening(state='disabled',
passw_hardening_ob_dis = passw_hardening_utils.PasswHardening(state='disabled',
expiration='100',
expiration_warning='15',
history='12',
Expand All @@ -15,7 +16,21 @@ def set_default_passw_hardening_policies(duthosts, enum_rand_one_per_hwsku_hostn
digit_class="true",
special_class='true')

test_passw_hardening.config_and_review_policies(duthost, passw_hardening_ob_dis, test_passw_hardening.PAM_PASSWORD_CONF_DEFAULT_EXPECTED)
passw_hardening_utils.config_and_review_policies(duthost, passw_hardening_ob_dis, passw_hardening_utils.PAM_PASSWORD_CONF_DEFAULT_EXPECTED)

@pytest.fixture(scope="module", autouse=True)
def passw_version_required(duthosts, enum_rand_one_per_hwsku_hostname):
"""Skips this test if the SONiC image installed on DUT is older than 202111
Args:
duthost: DUT host object.
Returns:
None.
"""
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
skip_release(duthost, ["201811", "201911", "202012", "202106", "202111"])


@pytest.fixture(scope="module", autouse=True)
def passw_version_required(duthosts, enum_rand_one_per_hwsku_hostname):
Expand All @@ -32,38 +47,38 @@ def clean_passw_policies(duthosts, enum_rand_one_per_hwsku_hostname):
def clean_passw_one_policy_user(duthosts, enum_rand_one_per_hwsku_hostname):
yield
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
res_adduser_simple_0 = test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_ONE_POLICY, mode='del')
res_adduser_simple_0 = passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_ONE_POLICY, mode='del')


@pytest.fixture(scope="function")
def clean_passw_len_min(duthosts, enum_rand_one_per_hwsku_hostname):
yield
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_LEN_MIN, mode='del')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_LEN_MIN+':/d /etc/security/opasswd')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_LEN_MIN, mode='del')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_LEN_MIN+':/d /etc/security/opasswd')

@pytest.fixture(scope="function")
def clean_passw_age(duthosts, enum_rand_one_per_hwsku_hostname):
yield
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_AGE, mode='del')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_AGE+':/d /etc/security/opasswd')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_AGE, mode='del')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_AGE+':/d /etc/security/opasswd')


@pytest.fixture(scope="function")
def clean_passw_en_dis_policies(duthosts, enum_rand_one_per_hwsku_hostname):
yield
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_SIMPLE_0, mode='del')
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_SIMPLE_1, mode='del')
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_STRONG, mode='del')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_SIMPLE_0+':/d /etc/security/opasswd')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_SIMPLE_1+':/d /etc/security/opasswd')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_STRONG+':/d /etc/security/opasswd')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_SIMPLE_0, mode='del')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_SIMPLE_1, mode='del')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_STRONG, mode='del')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_SIMPLE_0+':/d /etc/security/opasswd')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_SIMPLE_1+':/d /etc/security/opasswd')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_STRONG+':/d /etc/security/opasswd')

@pytest.fixture(scope="function")
def clean_passw_history(duthosts, enum_rand_one_per_hwsku_hostname):
yield
duthost = duthosts[enum_rand_one_per_hwsku_hostname]
test_passw_hardening.config_user(duthost=duthost, username=test_passw_hardening.USERNAME_HISTORY, mode='del')
duthost.shell('sed -i /^'+test_passw_hardening.USERNAME_HISTORY+':/d /etc/security/opasswd')
passw_hardening_utils.config_user(duthost=duthost, username=passw_hardening_utils.USERNAME_HISTORY, mode='del')
duthost.shell('sed -i /^'+passw_hardening_utils.USERNAME_HISTORY+':/d /etc/security/opasswd')
112 changes: 112 additions & 0 deletions tests/passw_hardening/passw_hardening_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import logging
import os
import difflib
from tests.common.helpers.assertions import pytest_assert

CURR_DIR = os.path.dirname(os.path.abspath(__file__))

# Sample/Expected files
PAM_PASSWORD_CONF_DEFAULT_EXPECTED = CURR_DIR + '/sample/passw_hardening_default/common-password'
PAM_PASSWORD_CONF_EXPECTED = CURR_DIR + '/sample/passw_hardening_enable/common-password'
PAM_PASSWORD_CONF_HISTORY_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_history/common-password'
PAM_PASSWORD_CONF_REJECT_USER_PASSW_MATCH_EXPECTED = CURR_DIR + '/sample/passw_hardening_reject_user_passw_match/common-password'
PAM_PASSWORD_CONF_DIGITS_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_digits/common-password'
PAM_PASSWORD_CONF_LOWER_LETTER_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_lower_letter/common-password'
PAM_PASSWORD_CONF_UPPER_LETTER_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_upper_letter/common-password'
PAM_PASSWORD_CONF_SPECIAL_LETTER_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_special_letter/common-password'
PAM_PASSWORD_CONF_LEN_MIN_ONLY_EXPECTED = CURR_DIR + '/sample/passw_hardening_min_len/common-password'
PAM_PASSWORD_CONF_OUTPUT = CURR_DIR + '/output/login.def'
PAM_PASSWORD_CONF = "/etc/pam.d/common-password"

# users
USERNAME_STRONG = 'user_strong_test'
USERNAME_SIMPLE_0 = 'user_simple_0_test'
USERNAME_SIMPLE_1 = 'user_simple_1_test'
USERNAME_ONE_POLICY = 'user_one_policy_test'
USERNAME_AGE = 'user_test'
USERNAME_HISTORY = 'user_history_test'
USERNAME_LEN_MIN = 'user_test'


class PasswHardening:
def __init__(self, state='disabled', expiration='100', expiration_warning='15', history='12',
len_min='8', reject_user_passw_match='true', lower_class='true',
upper_class='true', digit_class='true', special_class='true'):
self.policies = {
"state": state,
"expiration": expiration,
"expiration-warning": expiration_warning,
"history-cnt": history,
"len-min": len_min,
"reject-user-passw-match": reject_user_passw_match,
"lower-class": lower_class,
"upper-class": upper_class,
"digits-class": digit_class,
"special-class": special_class
}


def config_user(duthost, username, mode='add'):
""" Function add or rm users using useradd/userdel tool. """

username = username.strip()
command = "user{} {}".format(mode, username)
user_cmd = duthost.shell(command, module_ignore_errors=True)
return user_cmd


def configure_passw_policies(duthost, passw_hardening_ob):
for key, value in passw_hardening_ob.policies.items():
logging.debug("configuration to be set: key={}, value={}".format(key, value))
# cmd_config = 'sudo config passw-hardening policies ' + key + ' ' + value

cmd_config = 'sudo config passw-hardening policies {} {}'.format(key, value)

duthost.command(cmd_config)
return True


def compare_passw_policies_in_linux(duthost, pam_file_expected=PAM_PASSWORD_CONF_EXPECTED):
"""Compare DUT common-password with the expected one."""

command_password_stdout = ''
read_command_password = 'cat {}'.format(PAM_PASSWORD_CONF)

logging.debug('DUT command = {}'.format(read_command_password))
read_command_password_cmd = duthost.command(read_command_password)
command_password_stdout = read_command_password_cmd["stdout_lines"]
command_password_stdout = [line.encode('utf-8') for line in command_password_stdout]

common_password_expected = []
with open(pam_file_expected, 'r') as expected_common_password_file:
for line in expected_common_password_file:
line = line.strip()
line = line.strip('\n')
line = line.strip('\t')
common_password_expected.append(line)

common_password_diff = [li for li in difflib.ndiff(command_password_stdout, common_password_expected) if
li[0] != ' ']
pytest_assert(len(common_password_diff) == 0, common_password_diff)


def config_and_review_policies(duthost, passw_hardening_ob, pam_file_expected):
"""
1. Config passw hardening policies
2. Show passw hardening policies
3. Compare passw hardening polices from show cli to the expected (configured)
4. Verify polices in PAM files was set according the configured
"""
FIRST_LINE = 0
configure_passw_policies(duthost, passw_hardening_ob)

curr_show_policies = duthost.show_and_parse('show passw-hardening policies')[FIRST_LINE]
exp_show_policies = dict((k.replace('-', ' '), v) for k, v in passw_hardening_ob.policies.items())

# ~~ test passw policies in show CLI ~~
cli_passw_policies_cmp = cmp(exp_show_policies, curr_show_policies)
pytest_assert(cli_passw_policies_cmp == 0, "Fail: exp_show_policies='{}',not equal to curr_show_policies='{}'"
.format(exp_show_policies, curr_show_policies))

# ~~ test passw policies in PAM files ~~
compare_passw_policies_in_linux(duthost, pam_file_expected)
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root

password required pam_pwhistory.so remember=0 use_authtok enforce_for_root
password required pam_pwhistory.so remember=12 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

# here are the per-package modules (the "Primary" block)

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root
password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root

password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=0 lcredit=-1 dcredit=0 ocredit=0 enforce_for_root

password required pam_pwhistory.so remember=0 use_authtok enforce_for_root
password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=0 lcredit=0 dcredit=-1 ocredit=0 enforce_for_root

password required pam_pwhistory.so remember=1 use_authtok enforce_for_root
password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=0 lcredit=0 dcredit=0 ocredit=0 reject_username enforce_for_root

password required pam_pwhistory.so remember=0 use_authtok enforce_for_root
password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=0 lcredit=0 dcredit=0 ocredit=-1 enforce_for_root

password required pam_pwhistory.so remember=0 use_authtok enforce_for_root
password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=1 ucredit=-1 lcredit=0 dcredit=0 ocredit=0 enforce_for_root

password required pam_pwhistory.so remember=0 use_authtok enforce_for_root
password required pam_pwhistory.so remember=10 use_authtok enforce_for_root

password [success=1 default=ignore] pam_unix.so obscure yescrypt
# here's the fallback if no module succeeds
Expand Down
Loading

0 comments on commit 9623bd9

Please sign in to comment.