From 2a59875b85d7055f483a580f804308dc4eff9a05 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Tue, 1 Mar 2022 15:38:44 +0200 Subject: [PATCH 01/18] Password Hardening Feature --- files/build_templates/init_cfg.json.j2 | 16 +- .../build_templates/sonic_debian_extension.j2 | 4 + rules/cracklib.deb | 9 + rules/cracklib.mk | 10 + slave.mk | 1 + .../templates/common-password.j2 | 43 ++ src/sonic-host-services/scripts/hostcfgd | 249 ++++++- .../tests/hostcfgd/hostcfgd_passwh_test.py | 191 ++++++ .../tests/hostcfgd/hostcfgd_radius_test.py | 7 +- .../tests/hostcfgd/hostcfgd_tacacs_test.py | 6 +- .../common-password | 36 + .../login.defs | 340 ++++++++++ .../login.defs.old | 340 ++++++++++ .../common-password | 39 ++ .../login.defs | 340 ++++++++++ .../login.defs.old | 340 ++++++++++ .../common-password | 40 ++ .../login.defs | 340 ++++++++++ .../login.defs.old | 340 ++++++++++ .../tests/hostcfgd/test_passwh_vectors.py | 621 ++++++++++++++++++ .../yang-models/sonic-passwh.yang | 85 +++ 21 files changed, 3381 insertions(+), 16 deletions(-) create mode 100644 rules/cracklib.deb create mode 100644 rules/cracklib.mk create mode 100644 src/sonic-host-services-data/templates/common-password.j2 create mode 100755 src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old create mode 100644 src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py create mode 100755 src/sonic-yang-models/yang-models/sonic-passwh.yang diff --git a/files/build_templates/init_cfg.json.j2 b/files/build_templates/init_cfg.json.j2 index e1320214e02c..24fc4bd64733 100644 --- a/files/build_templates/init_cfg.json.j2 +++ b/files/build_templates/init_cfg.json.j2 @@ -83,6 +83,20 @@ "state" : "disabled", {% endif %} "rate_limit_interval" : "600" }{%if not loop.last %},{% endif -%} -{% endfor %} +{% endfor %} + }, + "PASSW_HARDENING": { + "POLICIES":{ + "state": "disabled", + "expiration": "180", + "expiration_warning": "15", + "history_ctr": "10", + "len_min": "8", + "reject_user_passw_match": "True", + "lower_class": "True", + "upper_class": "True", + "digit_class": "True", + "special_class": "True" + } } } diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 3efb5ae0690b..e372f936ba85 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -267,6 +267,10 @@ fi sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/sonic-device-data_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f +# Install cracklib (and its dependencies via 'apt-get -y install -f') +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-cracklib_*.deb || \ + sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f + # Install pam-tacplus and nss-tacplus sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libtac2_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f diff --git a/rules/cracklib.deb b/rules/cracklib.deb new file mode 100644 index 000000000000..6065ddd81754 --- /dev/null +++ b/rules/cracklib.deb @@ -0,0 +1,9 @@ +SPATH := $($(LIBPAM_CRACKLIB)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/cracklib.mk rules/cracklib.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(SPATH)) + +$(SOCAT)_CACHE_MODE := GIT_CONTENT_SHA +$(SOCAT)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(SOCAT)_DEP_FILES := $(DEP_FILES) + diff --git a/rules/cracklib.mk b/rules/cracklib.mk new file mode 100644 index 000000000000..0e1f24d15cbb --- /dev/null +++ b/rules/cracklib.mk @@ -0,0 +1,10 @@ +# CRACKLIB packages + +PAM_CRACKLIB_VERSION = 1.4.0-9+deb11u1 +export PAM_CRACKLIB_VERSION + +LIBPAM_CRACKLIB = libpam-cracklib_$(PAM_CRACKLIB_VERSION)_$(CONFIGURED_ARCH).deb + +$(LIBPAM_CRACKLIB)_URL = "http://http.us.debian.org/debian/pool/main/p/pam/$(LIBPAM_CRACKLIB)" + +SONIC_ONLINE_DEBS += $(LIBPAM_CRACKLIB) diff --git a/slave.mk b/slave.mk index 1b879350b734..fbb5d7bd0f04 100644 --- a/slave.mk +++ b/slave.mk @@ -965,6 +965,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(INITRAMFS_TOOLS) \ $(LINUX_KERNEL) \ $(SONIC_DEVICE_DATA) \ + $(LIBPAM_CRACKLIB) \ $(IFUPDOWN2) \ $(KDUMP_TOOLS) \ $(NTP) \ diff --git a/src/sonic-host-services-data/templates/common-password.j2 b/src/sonic-host-services-data/templates/common-password.j2 new file mode 100644 index 000000000000..2f52679d4bbb --- /dev/null +++ b/src/sonic-host-services-data/templates/common-password.j2 @@ -0,0 +1,43 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + +{% if passw_policies %} +{% if passw_policies['state'] == 'enabled' %} +password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen={{passw_policies['len_min']}} {% if passw_policies['upper_class'] %} ucredit=-1 {% endif %} {% if passw_policies['lower_class'] %} lcredit=-1 {% endif %} {% if passw_policies['digit_class'] %} dcredit=-1 {% endif %} {% if passw_policies['special_class'] %} ocredit=-1 {% endif %} {% if passw_policies['reject_user_passw_match'] %} reject_username {% endif %} enforce_for_root + +password required pam_pwhistory.so remember={{passw_policies['history_ctr']}} use_authtok +{% endif %} +{% endif %} + +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 9b39fb5eb807..9138abc4a82c 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -8,8 +8,9 @@ import sys import subprocess import syslog import signal - +import re import jinja2 +from datetime import datetime from sonic_py_common import device_info from swsscommon.swsscommon import SubscriberStateTable, DBConnector, Select from swsscommon.swsscommon import ConfigDBConnector, TableConsumable @@ -17,6 +18,8 @@ from swsscommon.swsscommon import ConfigDBConnector, TableConsumable # FILE PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" PAM_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/common-auth-sonic.j2" +PAM_PASSWORD_CONF = "/etc/pam.d/common-password" +PAM_PASSWORD_CONF_TEMPLATE = "/usr/share/sonic/templates/common-password.j2" NSS_TACPLUS_CONF = "/etc/tacplus_nss.conf" NSS_TACPLUS_CONF_TEMPLATE = "/usr/share/sonic/templates/tacplus_nss.conf.j2" NSS_RADIUS_CONF = "/etc/radius_nss.conf" @@ -25,6 +28,16 @@ PAM_RADIUS_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/pam_radius_auth.conf NSS_CONF = "/etc/nsswitch.conf" ETC_PAMD_SSHD = "/etc/pam.d/sshd" ETC_PAMD_LOGIN = "/etc/pam.d/login" +ETC_LOGIN_DEF = "/etc/login.defs" + +# Linux login.def default values (password hardening disable) +LINUX_DEFAULT_PASS_MAX_DAYS = 99999 +LINUX_DEFAULT_PASS_WARN_AGE = 7 + +ACCOUNT_NAME = 0 # index of account name +AGE_DICT = { 'MAX_DAYS': {'REGEX_DAYS': r'^PASS_MAX_DAYS[ \t]*(?P\d*)', 'DAYS': 'max_days', 'CHAGE_FLAG': '-M '}, + 'WARN_DAYS': {'REGEX_DAYS': r'^PASS_WARN_AGE[ \t]*(?P\d*)', 'DAYS': 'warn_days', 'CHAGE_FLAG': '-W '} + } # TACACS+ TACPLUS_SERVER_PASSKEY_DEFAULT = "" @@ -81,8 +94,10 @@ def run_cmd(cmd, log_err=True, raise_exception=False): def is_true(val): if val == 'True' or val == 'true': return True - else: + elif val == 'False' or val == 'false': return False + syslog.syslog(syslog.LOG_ERR, "Failed to get bool value, instead val= {}".format(val)) + return False def is_vlan_sub_interface(ifname): @@ -458,6 +473,9 @@ class AaaCfg(object): self.tacplus_global = {} self.tacplus_servers = {} + self.passw_policies_default = {} + self.passw_policies = {} + self.radius_global_default = { 'priority': 0, 'auth_port': RADIUS_SERVER_AUTH_PORT_DEFAULT, @@ -478,9 +496,12 @@ class AaaCfg(object): self.hostname = "" # Load conf from ConfigDb - def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf): + # passw_policies_conf + def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf, policies_conf): for row in aaa_conf: self.aaa_update(row, aaa_conf[row], modify_conf=False) + for row in policies_conf: + self.passw_policies_update(row, policies_conf[row], modify_conf=False) for row in tac_global_conf: self.tacacs_global_update(row, tac_global_conf[row], modify_conf=False) for row in tacplus_conf: @@ -490,7 +511,6 @@ class AaaCfg(object): self.radius_global_update(row, rad_global_conf[row], modify_conf=False) for row in radius_conf: self.radius_server_update(row, radius_conf[row], modify_conf=False) - self.modify_conf_file() def aaa_update(self, key, data, modify_conf=True): @@ -507,6 +527,31 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() + def passw_policies_update(self, key, data, modify_conf=True): + syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - key: {}".format(key)) + syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - data: {}".format(data)) + + if data == {}: + self.passw_policies = {} + else: + # TODO: maybe create an input validation function for this val, and add val to all the inputs. + if 'reject_user_passw_match' in data: + data['reject_user_passw_match'] = is_true(data['reject_user_passw_match']) + if 'lower_class' in data: + data['lower_class'] = is_true(data['lower_class']) + if 'upper_class' in data: + data['upper_class'] = is_true(data['upper_class']) + if 'digit_class' in data: + data['digit_class'] = is_true(data['digit_class']) + if 'special_class' in data: + data['special_class'] = is_true(data['special_class']) + + if key == 'POLICIES': + self.passw_policies = data + + if modify_conf: + self.modify_conf_file() + def pick_src_intf_ipaddrs(self, keys, src_intf): new_ipv4_addr = "" new_ipv6_addr = "" @@ -530,6 +575,172 @@ class AaaCfg(object): return(new_ipv4_addr, new_ipv6_addr) + def set_passw_hardening_policies(self, passw_policies): + # Password Hardening flow + # When feature is enabled, the passw_policies from CONFIG_DB will be set in the pam files /etc/pam.d/common-password and /etc/login.def. + # When the feature is disabled or not compiled, the files above will be generate with the linux default (without secured passw_policies). + + syslog.syslog(syslog.LOG_DEBUG, "modify_conf_file: passw_policies - {}".format(passw_policies)) + + template_passwh_file = os.path.abspath(PAM_PASSWORD_CONF_TEMPLATE) + env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) + env.filters['sub'] = sub + template_passwh = env.get_template(template_passwh_file) + + # Render common-password file with passw hardening policies if any. Other render without them. + pam_passwh_conf = template_passwh.render(debug=self.debug, passw_policies=passw_policies) + + # Use rename(), which is atomic (on the same fs) to avoid empty file + with open(PAM_PASSWORD_CONF + ".tmp", 'w') as f: + f.write(pam_passwh_conf) + os.chmod(PAM_PASSWORD_CONF + ".tmp", 0o644) + os.rename(PAM_PASSWORD_CONF + ".tmp", PAM_PASSWORD_CONF) + + # Age policy + # When feature disabled or age policy disabled, expiry days policy should be as linux default, other, accoriding CONFIG_DB. + curr_expiration = LINUX_DEFAULT_PASS_MAX_DAYS + curr_expiration_warning = LINUX_DEFAULT_PASS_WARN_AGE + + if passw_policies: + if passw_policies['state'] == 'enabled': + if 'expiration' in passw_policies: + if int(self.passw_policies['expiration']) != 0: # value '0' meaning age policy is disabled + # the logic is to modify the expiration time according the last updated modificatiion + # + curr_expiration = int(passw_policies['expiration']) + + if 'expiration_warning' in passw_policies: + if int(self.passw_policies['expiration_warning']) != 0: # value '0' meaning age policy is disabled + curr_expiration_warning = int(passw_policies['expiration_warning']) + + if passw_policies: + if self.is_passwd_aging_expire_update(curr_expiration, 'MAX_DAYS'): + # Set aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration, 'MAX_DAYS') + + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_MAX_DAYS/c\PASS_MAX_DAYS " +str(curr_expiration)+"\'"]) + + if self.is_passwd_aging_expire_update(curr_expiration_warning, 'WARN_DAYS'): + # Aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration_warning, 'WARN_DAYS') + + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_WARN_AGE/c\PASS_WARN_AGE " +str(curr_expiration_warning)+"\'"]) + + def passwd_aging_expire_modify(self, curr_expiration, age_type): + normal_accounts = self.get_normal_accounts() + if not normal_accounts: + syslog.syslog(syslog.LOG_ERR,"failed, no normal users found in /etc/passwd") + return + chage_flag = AGE_DICT[age_type]['CHAGE_FLAG'] + for normal_account in normal_accounts: + + new_curr_expiration = self.get_user_expire_time(normal_account, curr_expiration) + + try: + chage_p_m = subprocess.Popen(('chage', chage_flag + str(new_curr_expiration), normal_account), stdout=subprocess.PIPE) + return_code_chage_p_m = chage_p_m.poll() + if return_code_chage_p_m != 0: + syslog.syslog(syslog.LOG_ERR, "failed: return code - {}".format(return_code_chage_p_m)) + + except subprocess.CalledProcessError as e: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(e.cmd, e.returncode, e.output)) + + def get_user_expire_time(self, normal_account, curr_expiration): + last_passw_change = '' + new_curr_expiration = curr_expiration + REGEX_LAST_PASSW_CHANGE = r'^Last password change[ \t]*:[ \t]*(?P.*)' + + try: + chage = subprocess.Popen(('chage', '-l', '-i', normal_account), stdout=subprocess.PIPE) + chage_data = chage.stdout.readlines() + + for line in chage_data: + m1 = re.match(REGEX_LAST_PASSW_CHANGE, line.decode('utf-8')) + if m1: + last_passw_change = m1.group("last_passw_change") + break + + # update the expire passw time according the last passw change + if not last_passw_change: + return curr_expiration + + last_passw_change_obj = datetime.strptime(last_passw_change, '%Y-%m-%d') + + today = datetime.now() + last_passw_change_diff = today - last_passw_change_obj + if (curr_expiration - last_passw_change_diff.days) > 0: + new_curr_expiration = curr_expiration - last_passw_change_diff.days + else: + new_curr_expiration = 0 + + except subprocess.CalledProcessError as err: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) + + return new_curr_expiration + + def is_passwd_aging_expire_update(self, curr_expiration, age_type): + """ Function verify that the current age expiry policy values are equal from the old one + Return update_age_status 'True' value meaning that was a modification from the last time, and vice versa. + """ + update_age_status = False + regex_days = AGE_DICT[age_type]['REGEX_DAYS'] + days_type = AGE_DICT[age_type]['DAYS'] + + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(regex_days, line) + if m1: + days_num = int(m1.group(days_type)) + break + + if curr_expiration != days_num: + update_age_status = True + + return update_age_status + + def get_normal_accounts(self): + # Get user list + try: + getent_out = subprocess.check_output(['getent', 'passwd']).decode('utf-8').split('\n') + except subprocess.CalledProcessError as err: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) + + # Get range of normal users + REGEX_UID_MAX = r'^UID_MAX[ \t]*(?P\d*)' + REGEX_UID_MIN = r'^UID_MIN[ \t]*(?P\d*)' + uid_max = None + uid_min = None + + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(REGEX_UID_MAX, line) + m2 = re.match(REGEX_UID_MIN, line) + if m1: + uid_max = int(m1.group("uid_max")) + if m2: + uid_min = int(m2.group("uid_min")) + + if not uid_max or not uid_min: + syslog.syslog(syslog.LOG_ERR,"failed, no UID_MAX/UID_MIN founded in login.def file") + return False + + # Get normal user list + normal_accounts = [] + for account in getent_out[0:-1]: # last item is always empty + account_spl = account.split(':') + account_number = int(account_spl[2]) + if account_number >= uid_min and account_number <= uid_max: + normal_accounts.append(account_spl[ACCOUNT_NAME]) + + normal_accounts.append('root') # root is also a candidate to be age modify. + return normal_accounts + def tacacs_global_update(self, key, data, modify_conf=True): if key == 'global': self.tacplus_global = data @@ -663,6 +874,12 @@ class AaaCfg(object): cmd = "sed -e {0} {1} > {1}.new; mv -f {1} {1}.old; mv -f {1}.new {1}".format(' -e '.join(operations), filename) os.system(cmd) + def modify_single_file_inplace(self, filename, operations=None): + if operations: + cmd = "sed -i {0} {1}".format(' -i '.join(operations), filename) + syslog.syslog(syslog.LOG_DEBUG, "modify_single_file_inplace: cmd - {}".format(cmd)) + os.system(cmd) + def modify_conf_file(self): authentication = self.authentication_default.copy() authentication.update(self.authentication) @@ -670,6 +887,8 @@ class AaaCfg(object): authorization.update(self.authorization) accounting = self.accounting_default.copy() accounting.update(self.accounting) + passw_policies = self.passw_policies_default.copy() + passw_policies.update(self.passw_policies) tacplus_global = self.tacplus_global_default.copy() tacplus_global.update(self.tacplus_global) if 'src_ip' in tacplus_global: @@ -728,6 +947,10 @@ class AaaCfg(object): env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) env.filters['sub'] = sub template = env.get_template(template_file) + + # set new Password Hardening policies. + self.set_passw_hardening_policies(passw_policies) + if 'radius' in authentication['login']: pam_conf = template.render(debug=self.debug, trace=self.trace, auth=authentication, servers=radsrvs_conf) else: @@ -914,14 +1137,14 @@ class NtpCfg(object): new_src = data.get('src_intf', '') new_src_set = set(new_src.split(";")) new_vrf = data.get('vrf', '') - + # Update the Local Cache self.ntp_global = data # check if ntp server configured, if not, do nothing if not self.ntp_servers: syslog.syslog(syslog.LOG_INFO, "No ntp server when global config change, do nothing") - return + return if orig_src_set != new_src_set: syslog.syslog(syslog.LOG_INFO, "ntp global update for source intf old {} new {}, restarting ntp-config" @@ -987,15 +1210,16 @@ class HostConfigDaemon: # Initialize AAACfg self.hostname_cache="" self.aaacfg = AaaCfg() - + def load(self): aaa = self.config_db.get_table('AAA') + passwh = self.config_db.get_table('PASSW_HARDENING') tacacs_global = self.config_db.get_table('TACPLUS') tacacs_server = self.config_db.get_table('TACPLUS_SERVER') radius_global = self.config_db.get_table('RADIUS') radius_server = self.config_db.get_table('RADIUS_SERVER') - self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) + self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server, passwh) try: dev_meta = self.config_db.get_table('DEVICE_METADATA') @@ -1004,7 +1228,7 @@ class HostConfigDaemon: self.hostname_cache = dev_meta['localhost']['hostname'] except Exception as e: pass - + # Update AAA with the hostname self.aaacfg.hostname_update(self.hostname_cache) @@ -1019,6 +1243,10 @@ class HostConfigDaemon: self.aaacfg.aaa_update(key, data) syslog.syslog(syslog.LOG_INFO, 'AAA Update: key: {}, op: {}, data: {}'.format(key, op, data)) + def passwh_handler(self, key, op, data): + self.aaacfg.passw_policies_update(key, data) + syslog.syslog(syslog.LOG_INFO, 'PASSW_HARDENING Update: key: {}, op: {}, data: {}'.format(key, op, data)) + def tacacs_server_handler(self, key, op, data): self.aaacfg.tacacs_server_update(key, data) log_data = copy.deepcopy(data) @@ -1115,6 +1343,7 @@ class HostConfigDaemon: self.subscribe('FEATURE', lambda table, key, op, data: self.feature_handler.handle(key, op, data), HOSTCFGD_MAX_PRI-1) # Handle AAA, TACACS and RADIUS related tables self.subscribe('AAA', lambda table, key, op, data: self.aaa_handler(key, op, data), HOSTCFGD_MAX_PRI-2) + self.subscribe('PASSW_HARDENING', lambda table, key, op, data: self.passwh_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.subscribe('TACPLUS', lambda table, key, op, data: self.tacacs_global_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.subscribe('TACPLUS_SERVER', lambda table, key, op, data: self.tacacs_server_handler(key, op, data), HOSTCFGD_MAX_PRI-2) self.subscribe('RADIUS', lambda table, key, op, data: self.radius_global_handler(key, op, data), HOSTCFGD_MAX_PRI-2) @@ -1130,7 +1359,7 @@ class HostConfigDaemon: self.subscribe('VLAN_SUB_INTERFACE', lambda table, key, op, data: self.vlan_sub_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.subscribe('PORTCHANNEL_INTERFACE', lambda table, key, op, data: self.portchannel_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) self.subscribe('INTERFACE', lambda table, key, op, data: self.phy_intf_handler(key, op, data), HOSTCFGD_MAX_PRI-5) - + syslog.syslog(syslog.LOG_INFO, "Waiting for systemctl to finish initialization") self.wait_till_system_init_done() diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py new file mode 100755 index 000000000000..efb35bf076ac --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -0,0 +1,191 @@ +import importlib.machinery +import importlib.util +import filecmp +import shutil +import os +import sys +import subprocess +import re +from swsscommon import swsscommon + +from parameterized import parameterized +from unittest import TestCase, mock +from tests.hostcfgd.test_passwh_vectors import HOSTCFGD_TEST_PASSWH_VECTOR +from tests.common.mock_configdb import MockConfigDb, MockSubscriberStateTable +from tests.common.mock_configdb import MockSelect, MockDBConnector + +test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +src_path = os.path.dirname(modules_path) +templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +output_path = os.path.join(test_path, "hostcfgd/output") +sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") +sys.path.insert(0, modules_path) + +# Load the file under test +hostcfgd_path = os.path.join(scripts_path, 'hostcfgd') +loader = importlib.machinery.SourceFileLoader('hostcfgd', hostcfgd_path) +spec = importlib.util.spec_from_loader(loader.name, loader) +hostcfgd = importlib.util.module_from_spec(spec) +loader.exec_module(hostcfgd) +sys.modules['hostcfgd'] = hostcfgd + +# Mock swsscommon classes +hostcfgd.ConfigDBConnector = MockConfigDb +hostcfgd.SubscriberStateTable = MockSubscriberStateTable +hostcfgd.Select = MockSelect +hostcfgd.DBConnector = MockDBConnector + +AGE_DICT = { 'MAX_DAYS': {'REGEX_DAYS': r'^PASS_MAX_DAYS[ \t]*(?P\d*)', 'DAYS': 'max_days', 'CHAGE_FLAG': '-M '}, + 'WARN_DAYS': {'REGEX_DAYS': r'^PASS_WARN_AGE[ \t]*(?P\d*)', 'DAYS': 'warn_days', 'CHAGE_FLAG': '-W '} + } + +class TestHostcfgdPASSWH(TestCase): + """ + Test hostcfd daemon - PASSWH + """ + def run_diff(self, file1, file2): + try: + diff_out = subprocess.check_output('diff -ur {} {} || true'.format(file1, file2), shell=True) + return diff_out + except subprocess.CalledProcessError as err: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) + return -1 + + def get_passw_days(self, login_file, age_type): + days_num = -1 + + regex_days = AGE_DICT[age_type]['REGEX_DAYS'] + days_type = AGE_DICT[age_type]['DAYS'] + + with open(login_file, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(regex_days, line) + if m1: + days_num = int(m1.group(days_type)) + break + return days_num + + """ + Check different config + """ + def check_config(self, test_name, test_data, config_name): + t_path = templates_path + op_path = output_path + "/" + test_name + "_" + config_name + sop_path = sample_output_path + "/" + test_name + "_" + config_name + + hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" + hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" + hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" + hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" + hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" + hostcfgd.PAM_PASSWORD_CONF = op_path + "/common-password" + hostcfgd.ETC_LOGIN_DEF = op_path + "/login.defs" + hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" + hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" + hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" + hostcfgd.NSS_CONF = op_path + "/nsswitch.conf" + hostcfgd.ETC_PAMD_SSHD = op_path + "/sshd" + hostcfgd.ETC_PAMD_LOGIN = op_path + "/login" + hostcfgd.RADIUS_PAM_AUTH_CONF_DIR = op_path + "/" + + shutil.rmtree(op_path, ignore_errors=True) + os.mkdir(op_path) + + + shutil.copyfile(sop_path + "/login.defs.old", op_path + "/login.defs") + MockConfigDb.set_config_db(test_data[config_name]) + host_config_daemon = hostcfgd.HostConfigDaemon() + + aaa = host_config_daemon.config_db.get_table('AAA') + + try: + passwh_table = host_config_daemon.config_db.get_table('PASSWH') + except: + # TODO: raise an error? + passwh_table = [] + + host_config_daemon.aaacfg.load(aaa,[],[],[],[],passwh_table) + + # dcmp = filecmp.dircmp(sop_path, op_path) + # print(hostcfgd.PAM_PASSWORD_CONF) + # /sonic/src/sonic-host-services/tests/hostcfgd/output/PASSWORD_HARDENING_default_values/common-password + diff_output = "" + # name = 'common-password' + + files_to_compare = ['common-password'] + + # check output files exists + for name in files_to_compare: + if not os.path.isfile(sop_path + "/" + name): + raise ValueError('filename: %s not exit' % (sop_path + "/" + name)) + if not os.path.isfile(op_path + "/" + name): + raise ValueError('filename: %s not exit' % (op_path + "/" + name)) + + # deep comparison + match, mismatch, errors = filecmp.cmpfiles(sop_path, op_path, files_to_compare, shallow=False) + + if not match: + for name in files_to_compare: + diff_output += self.run_diff( sop_path + "/" + name,\ + op_path + "/" + name).decode('utf-8') + + self.assertTrue(len(diff_output) == 0, diff_output) + + # compare age data in login.def file. + out_passw_age_days = self.get_passw_days(op_path + "/login.defs", 'MAX_DAYS') + sout_passw_age_days = self.get_passw_days(sop_path + "/login.defs", 'MAX_DAYS') + out_passw_age_warn_days = self.get_passw_days(op_path + "/login.defs", 'WARN_DAYS') + sout_passw_age_warn_days = self.get_passw_days(sop_path + "/login.defs", 'WARN_DAYS') + + self.assertEqual(out_passw_age_days, sout_passw_age_days) + self.assertEqual(out_passw_age_warn_days, sout_passw_age_warn_days) + + @parameterized.expand(HOSTCFGD_TEST_PASSWH_VECTOR) + def test_hostcfgd_passwh(self, test_name, test_data): + """ + Test PASSWH hostcfd daemon initialization + + Args: + test_name(str): test name + test_data(dict): test data which contains initial Config Db tables, and expected results + + Returns: + None + """ + + self.check_config(test_name, test_data, "default_values") + + @parameterized.expand(HOSTCFGD_TEST_PASSWH_VECTOR) + def test_hostcfgd_passwh_enable(self, test_name, test_data): + """ + Test PASSWH hostcfd daemon initialization + + Args: + test_name(str): test name + test_data(dict): test data which contains initial Config Db tables, and expected results + + Returns: + None + """ + + self.check_config(test_name, test_data, "enable_feature") + + + @parameterized.expand(HOSTCFGD_TEST_PASSWH_VECTOR) + def test_hostcfgd_passwh_classes(self, test_name, test_data): + """ + Test PASSWH hostcfd daemon initialization + + Args: + test_name(str): test name + test_data(dict): test data which contains initial Config Db tables, and expected results + + Returns: + None + """ + + self.check_config(test_name, test_data, "enable_digit_class") \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py index 4e3d18648100..2b6200397e6f 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py @@ -56,15 +56,16 @@ def test_hostcfgd_radius(self, test_name, test_data): Returns: None """ - t_path = templates_path op_path = output_path + "/" + test_name sop_path = sample_output_path + "/" + test_name + hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" + hostcfgd.PAM_PASSWORD_CONF = op_path + "/common-password" hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" @@ -73,6 +74,7 @@ def test_hostcfgd_radius(self, test_name, test_data): hostcfgd.ETC_PAMD_LOGIN = op_path + "/login" hostcfgd.RADIUS_PAM_AUTH_CONF_DIR = op_path + "/" + # import pdb; pdb.set_trace() shutil.rmtree( op_path, ignore_errors=True) os.mkdir( op_path) @@ -93,8 +95,7 @@ def test_hostcfgd_radius(self, test_name, test_data): host_config_daemon.config_db.get_table('RADIUS_SERVER') except: radius_server = [] - - host_config_daemon.aaacfg.load(aaa,[],[],radius_global,radius_server) + host_config_daemon.aaacfg.load(aaa,[],[],radius_global,radius_server, []) dcmp = filecmp.dircmp(sop_path, op_path) diff_output = "" for name in dcmp.diff_files: diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py index 3cc3504d606b..ca563089ff86 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py @@ -44,17 +44,19 @@ def run_diff(self, file1, file2): return subprocess.check_output('diff -uR {} {} || true'.format(file1, file2), shell=True) """ - Check different config + Check different config """ def check_config(self, test_name, test_data, config_name): t_path = templates_path op_path = output_path + "/" + test_name + "_" + config_name sop_path = sample_output_path + "/" + test_name + "_" + config_name + hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" + hostcfgd.PAM_PASSWORD_CONF = op_path + "/common-password" hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" @@ -84,7 +86,7 @@ def check_config(self, test_name, test_data, config_name): except: tacacs_server = [] - host_config_daemon.aaacfg.load(aaa,tacacs_global,tacacs_server,[],[]) + host_config_daemon.aaacfg.load(aaa,tacacs_global,tacacs_server,[],[],[]) dcmp = filecmp.dircmp(sop_path, op_path) diff_output = "" for name in dcmp.diff_files: diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password new file mode 100644 index 000000000000..11efc43374a3 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password @@ -0,0 +1,36 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + + +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password new file mode 100644 index 000000000000..0648437f708a --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password @@ -0,0 +1,39 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + +password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 dcredit=-1 enforce_for_root + +password required pam_pwhistory.so remember=0 use_authtok + +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password new file mode 100644 index 000000000000..7427655f8306 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password @@ -0,0 +1,40 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + +password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root + +password required pam_pwhistory.so remember=10 use_authtok + +TODO: fallback? +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs new file mode 100644 index 000000000000..1c8b360a14aa --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 180 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 15 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + diff --git a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py new file mode 100644 index 000000000000..8a0c91a3620f --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py @@ -0,0 +1,621 @@ +from unittest.mock import call + +""" + hostcfgd test password hardening vector +""" +HOSTCFGD_TEST_PASSWH_VECTOR = [ + [ + "PASSWORD_HARDENING", + { + "default_values":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "disabled", + "expiration": "180", + "expiration_warning": "15", + "history_ctr": "10", + "len_min": "8", + "reject_user_passw_match": "True", + "lower_class": "True", + "upper_class": "True", + "digit_class": "True", + "special_class": "True" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "default_values":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "disabled", + "expiration": "180", + "expiration_warning": "15", + "history_ctr": "10", + "len_min": "8", + "reject_user_passw_match": "True", + "lower_class": "True", + "upper_class": "True", + "digit_class": "True", + "special_class": "True" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "enable_feature":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "180", + "expiration_warning": "15", + "history_ctr": "10", + "len_min": "8", + "reject_user_passw_match": "True", + "lower_class": "True", + "upper_class": "True", + "digit_class": "True", + "special_class": "True" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "enable_digit_class":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "0", + "expiration_warning": "0", + "history_ctr": "0", + "len_min": "8", + "reject_user_passw_match": "False", + "lower_class": "False", + "upper_class": "False", + "digit_class": "True", + "special_class": "False" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "enable_lower_class":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "0", + "expiration_warning": "0", + "history_ctr": "0", + "len_min": "8", + "reject_user_passw_match": "False", + "lower_class": "True", + "upper_class": "False", + "digit_class": "False", + "special_class": "False" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "enable_upper_class":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "0", + "expiration_warning": "0", + "history_ctr": "0", + "len_min": "8", + "reject_user_passw_match": "False", + "lower_class": "False", + "upper_class": "True", + "digit_class": "False", + "special_class": "False" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "enable_special_class":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "0", + "expiration_warning": "0", + "history_ctr": "0", + "len_min": "8", + "reject_user_passw_match": "False", + "lower_class": "False", + "upper_class": "False", + "digit_class": "False", + "special_class": "True" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }, + "TODO: X":{ + "PASSW_HARDENING": { + "POLICIES":{ + "state": "enabled", + "expiration": "180", + "expiration_warning": "15", + "history_ctr": "10", + "len_min": "8", + "reject_user_passw_match": "True", + "lower_class": "True", + "upper_class": "True", + "digit_class": "True", + "special_class": "True" + } + }, + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local" + }, + "authorization": { + "login": "local" + }, + "accounting": { + "login": "local" + } + }, + "TACPLUS": { + "global": { + "auth_type": "chap", + "timeout": 5, + "passkey": "dellsonic", + "src_intf": "Ethernet0" + } + }, + "TACPLUS_SERVER": { + "192.168.1.1" : { + "priority": 5, + "tcp_port": 50, + "timeout": 10, + "auth_type": "chap", + "passkey": "dellsonic", + "vrf": "default" + }, + "192.168.1.2" : { + "priority": 2, + "tcp_port": 51, + "timeout": 15, + "auth_type": "pap", + "passkey": "dellsonic1", + "vrf": "mgmt" + } + } + }# TODO X + + } + ] +] diff --git a/src/sonic-yang-models/yang-models/sonic-passwh.yang b/src/sonic-yang-models/yang-models/sonic-passwh.yang new file mode 100755 index 000000000000..c1f62d95892c --- /dev/null +++ b/src/sonic-yang-models/yang-models/sonic-passwh.yang @@ -0,0 +1,85 @@ +module sonic-passwh { + yang-version 1.1; + namespace "http://github.com/Azure/sonic-passwh"; + prefix passwh; + + description "PASSWORD HARDENING YANG Module for SONiC OS"; + + revision 2021-10-12 { + description "First Revision"; + } + + container sonic-passwh { + + typedef feature_state { + type enumeration { + enum enabled; + enum disabled; + } + } + + container PASSWH { + description "PASSWORD HARDENING part of config_db.json"; + container POLICIES { + leaf state { + description "state of the feature"; + type feature_state; + default "disabled"; + } + leaf expiration { + description "expiration time (days unit)"; + default 180; + type uint32 { + range 1..365; + } + } + leaf expiration_warning { + description "expiration warning time (days unit)"; + default 15; + type uint8 { + range 1..30; + } + } + leaf history_cnt { + description "num of old password that the system will recorded"; + default 10; + type uint8 { + range 1..100; + } + } + leaf len_min { + description "password min length"; + default 8; + type uint8 { + range 1..32; + } + } + leaf reject_user_passw_match{ + description "username password match"; + default true; + type boolean; + } + leaf lower_class{ + description "password lower chars policy"; + default true; + type boolean; + } + leaf upper_class{ + description "password upper chars policy"; + default true; + type boolean; + } + leaf digits_class{ + description "password digits chars policy"; + default true; + type boolean; + } + leaf special_class{ + description "password special chars policy"; + default true; + type boolean; + } + }/*container policies */ + } /* container PASSWH */ + }/* container sonic-passwh */ +}/* end of module sonic-passwh */ \ No newline at end of file From 440307d6b3f1818c17c16facb5891fecd0c31cc3 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Wed, 2 Mar 2022 12:16:44 +0200 Subject: [PATCH 02/18] fix password hardening comments from the pull request --- src/sonic-host-services/scripts/hostcfgd | 1 - .../tests/hostcfgd/test_passwh_vectors.py | 153 ------------------ .../yang-models/sonic-passwh.yang | 118 +++++++------- 3 files changed, 54 insertions(+), 218 deletions(-) diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 9138abc4a82c..0c370b83de3a 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -496,7 +496,6 @@ class AaaCfg(object): self.hostname = "" # Load conf from ConfigDb - # passw_policies_conf def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf, policies_conf): for row in aaa_conf: self.aaa_update(row, aaa_conf[row], modify_conf=False) diff --git a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py index 8a0c91a3620f..a18f705e068b 100644 --- a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py +++ b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py @@ -82,82 +82,6 @@ "vrf": "mgmt" } } - }, - "default_values":{ - "PASSW_HARDENING": { - "POLICIES":{ - "state": "disabled", - "expiration": "180", - "expiration_warning": "15", - "history_ctr": "10", - "len_min": "8", - "reject_user_passw_match": "True", - "lower_class": "True", - "upper_class": "True", - "digit_class": "True", - "special_class": "True" - } - }, - "DEVICE_METADATA": { - "localhost": { - "hostname": "radius", - } - }, - "FEATURE": { - "dhcp_relay": { - "auto_restart": "enabled", - "has_global_scope": "True", - "has_per_asic_scope": "False", - "has_timer": "False", - "high_mem_alert": "disabled", - "set_owner": "kube", - "state": "enabled" - }, - }, - "KDUMP": { - "config": { - "enabled": "false", - "num_dumps": "3", - "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } - } }, "enable_feature":{ "PASSW_HARDENING": { @@ -539,83 +463,6 @@ } } }, - "TODO: X":{ - "PASSW_HARDENING": { - "POLICIES":{ - "state": "enabled", - "expiration": "180", - "expiration_warning": "15", - "history_ctr": "10", - "len_min": "8", - "reject_user_passw_match": "True", - "lower_class": "True", - "upper_class": "True", - "digit_class": "True", - "special_class": "True" - } - }, - "DEVICE_METADATA": { - "localhost": { - "hostname": "radius", - } - }, - "FEATURE": { - "dhcp_relay": { - "auto_restart": "enabled", - "has_global_scope": "True", - "has_per_asic_scope": "False", - "has_timer": "False", - "high_mem_alert": "disabled", - "set_owner": "kube", - "state": "enabled" - }, - }, - "KDUMP": { - "config": { - "enabled": "false", - "num_dumps": "3", - "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" - } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } - } - }# TODO X - } ] ] diff --git a/src/sonic-yang-models/yang-models/sonic-passwh.yang b/src/sonic-yang-models/yang-models/sonic-passwh.yang index c1f62d95892c..4cf9b1ac7316 100755 --- a/src/sonic-yang-models/yang-models/sonic-passwh.yang +++ b/src/sonic-yang-models/yang-models/sonic-passwh.yang @@ -1,7 +1,7 @@ module sonic-passwh { yang-version 1.1; namespace "http://github.com/Azure/sonic-passwh"; - prefix passwh; + prefix password; description "PASSWORD HARDENING YANG Module for SONiC OS"; @@ -18,68 +18,58 @@ module sonic-passwh { } } - container PASSWH { - description "PASSWORD HARDENING part of config_db.json"; - container POLICIES { - leaf state { - description "state of the feature"; - type feature_state; - default "disabled"; - } - leaf expiration { - description "expiration time (days unit)"; - default 180; - type uint32 { - range 1..365; - } - } - leaf expiration_warning { - description "expiration warning time (days unit)"; - default 15; - type uint8 { - range 1..30; - } - } - leaf history_cnt { - description "num of old password that the system will recorded"; - default 10; - type uint8 { - range 1..100; - } - } - leaf len_min { - description "password min length"; - default 8; - type uint8 { - range 1..32; - } - } - leaf reject_user_passw_match{ - description "username password match"; - default true; - type boolean; - } - leaf lower_class{ - description "password lower chars policy"; - default true; - type boolean; - } - leaf upper_class{ - description "password upper chars policy"; - default true; - type boolean; - } - leaf digits_class{ - description "password digits chars policy"; - default true; - type boolean; - } - leaf special_class{ - description "password special chars policy"; - default true; - type boolean; - } - }/*container policies */ - } /* container PASSWH */ + container PASSWORD { + description "PASSWORD HARDENING part of config_db.json"; + container POLICIES { + leaf state { + description "state of the feature"; + type feature_state; + } + leaf expiration { + description "expiration time (days unit)"; + type uint32 { + range 1..365; + } + } + leaf expiration_warning { + description "expiration warning time (days unit)"; + type uint8 { + range 1..30; + } + } + leaf history_cnt { + description "num of old password that the system will recorded"; + type uint8 { + range 1..100; + } + } + leaf len_min { + description "password min length"; + type uint8 { + range 1..32; + } + } + leaf reject_user_passw_match{ + description "username password match"; + type boolean; + } + leaf lower_class{ + description "password lower chars policy"; + type boolean; + } + leaf upper_class{ + description "password upper chars policy"; + type boolean; + } + leaf digits_class{ + description "password digits chars policy"; + type boolean; + } + leaf special_class{ + description "password special chars policy"; + type boolean; + } + }/*container policies */ + } /* container PASSWH */ }/* container sonic-passwh */ }/* end of module sonic-passwh */ \ No newline at end of file From 7a7fc90321ac57c4083c5f61d47c35f2d4564eb7 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Tue, 8 Mar 2022 12:30:04 +0200 Subject: [PATCH 03/18] small fix about passw hardening P.R --- src/sonic-host-services/scripts/hostcfgd | 3 +- .../tests/hostcfgd/hostcfgd_passwh_test.py | 20 +- .../login.defs.old | 0 .../login.defs.old | 340 ------------------ .../common-password | 1 - .../login.defs.old | 340 ------------------ .../tests/hostcfgd/test_passwh_vectors.py | 224 +----------- .../yang-models/sonic-passwh.yang | 75 ---- 8 files changed, 9 insertions(+), 994 deletions(-) rename src/sonic-host-services/tests/hostcfgd/sample_output/{PASSWORD_HARDENING_default_values => PASSWORD_HARDENING}/login.defs.old (100%) delete mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old delete mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old delete mode 100755 src/sonic-yang-models/yang-models/sonic-passwh.yang diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 0c370b83de3a..fc441786c173 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -577,8 +577,7 @@ class AaaCfg(object): def set_passw_hardening_policies(self, passw_policies): # Password Hardening flow # When feature is enabled, the passw_policies from CONFIG_DB will be set in the pam files /etc/pam.d/common-password and /etc/login.def. - # When the feature is disabled or not compiled, the files above will be generate with the linux default (without secured passw_policies). - + # When the feature is disabled, the files above will be generate with the linux default (without secured passw_policies). syslog.syslog(syslog.LOG_DEBUG, "modify_conf_file: passw_policies - {}".format(passw_policies)) template_passwh_file = os.path.abspath(PAM_PASSWORD_CONF_TEMPLATE) diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index efb35bf076ac..e1b92401abeb 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -76,6 +76,7 @@ def check_config(self, test_name, test_data, config_name): t_path = templates_path op_path = output_path + "/" + test_name + "_" + config_name sop_path = sample_output_path + "/" + test_name + "_" + config_name + sop_path_common = sample_output_path + "/" + test_name hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" @@ -95,27 +96,20 @@ def check_config(self, test_name, test_data, config_name): shutil.rmtree(op_path, ignore_errors=True) os.mkdir(op_path) - - shutil.copyfile(sop_path + "/login.defs.old", op_path + "/login.defs") + shutil.copyfile(sop_path_common + "/login.defs.old", op_path + "/login.defs") MockConfigDb.set_config_db(test_data[config_name]) host_config_daemon = hostcfgd.HostConfigDaemon() - aaa = host_config_daemon.config_db.get_table('AAA') - try: - passwh_table = host_config_daemon.config_db.get_table('PASSWH') - except: - # TODO: raise an error? + passwh_table = host_config_daemon.config_db.get_table('PASSW_HARDENING') + except Exception as e: + syslog.syslog(syslog.LOG_ERR, "failed: get_table 'PASSW_HARDENING', exception={}".format(e)) passwh_table = [] - host_config_daemon.aaacfg.load(aaa,[],[],[],[],passwh_table) + host_config_daemon.aaacfg.load([],[],[],[],[],passwh_table) - # dcmp = filecmp.dircmp(sop_path, op_path) - # print(hostcfgd.PAM_PASSWORD_CONF) - # /sonic/src/sonic-host-services/tests/hostcfgd/output/PASSWORD_HARDENING_default_values/common-password - diff_output = "" - # name = 'common-password' + diff_output = "" files_to_compare = ['common-password'] # check output files exists diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING/login.defs.old similarity index 100% rename from src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/login.defs.old rename to src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING/login.defs.old diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old deleted file mode 100644 index db8baa4d2bf8..000000000000 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs.old +++ /dev/null @@ -1,340 +0,0 @@ -# -# /etc/login.defs - Configuration control definitions for the login package. -# -# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. -# If unspecified, some arbitrary (and possibly incorrect) value will -# be assumed. All other items are optional - if not specified then -# the described action or option will be inhibited. -# -# Comment lines (lines beginning with "#") and blank lines are ignored. -# -# Modified for Linux. --marekm - -# REQUIRED for useradd/userdel/usermod -# Directory where mailboxes reside, _or_ name of file, relative to the -# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, -# MAIL_DIR takes precedence. -# -# Essentially: -# - MAIL_DIR defines the location of users mail spool files -# (for mbox use) by appending the username to MAIL_DIR as defined -# below. -# - MAIL_FILE defines the location of the users mail spool files as the -# fully-qualified filename obtained by prepending the user home -# directory before $MAIL_FILE -# -# NOTE: This is no more used for setting up users MAIL environment variable -# which is, starting from shadow 4.0.12-1 in Debian, entirely the -# job of the pam_mail PAM modules -# See default PAM configuration files provided for -# login, su, etc. -# -# This is a temporary situation: setting these variables will soon -# move to /etc/default/useradd and the variables will then be -# no more supported -MAIL_DIR /var/mail -#MAIL_FILE .mail - -# -# Enable logging and display of /var/log/faillog login failure info. -# This option conflicts with the pam_tally PAM module. -# -FAILLOG_ENAB yes - -# -# Enable display of unknown usernames when login failures are recorded. -# -# WARNING: Unknown usernames may become world readable. -# See #290803 and #298773 for details about how this could become a security -# concern -LOG_UNKFAIL_ENAB no - -# -# Enable logging of successful logins -# -LOG_OK_LOGINS no - -# -# Enable "syslog" logging of su activity - in addition to sulog file logging. -# SYSLOG_SG_ENAB does the same for newgrp and sg. -# -SYSLOG_SU_ENAB yes -SYSLOG_SG_ENAB yes - -# -# If defined, all su activity is logged to this file. -# -#SULOG_FILE /var/log/sulog - -# -# If defined, file which maps tty line to TERM environment parameter. -# Each line of the file is in a format something like "vt100 tty01". -# -#TTYTYPE_FILE /etc/ttytype - -# -# If defined, login failures will be logged here in a utmp format -# last, when invoked as lastb, will read /var/log/btmp, so... -# -FTMP_FILE /var/log/btmp - -# -# If defined, the command name to display when running "su -". For -# example, if this is defined as "su" then a "ps" will display the -# command is "-su". If not defined, then "ps" would display the -# name of the shell actually being run, e.g. something like "-sh". -# -SU_NAME su - -# -# If defined, file which inhibits all the usual chatter during the login -# sequence. If a full pathname, then hushed mode will be enabled if the -# user's name or shell are found in the file. If not a full pathname, then -# hushed mode will be enabled if the file exists in the user's home directory. -# -HUSHLOGIN_FILE .hushlogin -#HUSHLOGIN_FILE /etc/hushlogins - -# -# *REQUIRED* The default PATH settings, for superuser and normal users. -# -# (they are minimal, add the rest in the shell startup files) -ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games - -# -# Terminal permissions -# -# TTYGROUP Login tty will be assigned this group ownership. -# TTYPERM Login tty will be set to this permission. -# -# If you have a "write" program which is "setgid" to a special group -# which owns the terminals, define TTYGROUP to the group number and -# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign -# TTYPERM to either 622 or 600. -# -# In Debian /usr/bin/bsd-write or similar programs are setgid tty -# However, the default and recommended value for TTYPERM is still 0600 -# to not allow anyone to write to anyone else console or terminal - -# Users can still allow other people to write them by issuing -# the "mesg y" command. - -TTYGROUP tty -TTYPERM 0600 - -# -# Login configuration initializations: -# -# ERASECHAR Terminal ERASE character ('\010' = backspace). -# KILLCHAR Terminal KILL character ('\025' = CTRL/U). -# UMASK Default "umask" value. -# -# The ERASECHAR and KILLCHAR are used only on System V machines. -# -# UMASK is the default umask value for pam_umask and is used by -# useradd and newusers to set the mode of the new home directories. -# 022 is the "historical" value in Debian for UMASK -# 027, or even 077, could be considered better for privacy -# There is no One True Answer here : each sysadmin must make up his/her -# mind. -# -# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value -# for private user groups, i. e. the uid is the same as gid, and username is -# the same as the primary group name: for these, the user permissions will be -# used as group permissions, e. g. 022 will become 002. -# -# Prefix these values with "0" to get octal, "0x" to get hexadecimal. -# -ERASECHAR 0177 -KILLCHAR 025 -UMASK 022 - -# -# Password aging controls: -# -# PASS_MAX_DAYS Maximum number of days a password may be used. -# PASS_MIN_DAYS Minimum number of days allowed between password changes. -# PASS_WARN_AGE Number of days warning given before a password expires. -# -PASS_MAX_DAYS 99999 -PASS_MIN_DAYS 0 -PASS_WARN_AGE 7 - -# -# Min/max values for automatic uid selection in useradd -# -UID_MIN 1000 -UID_MAX 60000 -# System accounts -#SYS_UID_MIN 100 -#SYS_UID_MAX 999 - -# -# Min/max values for automatic gid selection in groupadd -# -GID_MIN 1000 -GID_MAX 60000 -# System accounts -#SYS_GID_MIN 100 -#SYS_GID_MAX 999 - -# -# Max number of login retries if password is bad. This will most likely be -# overriden by PAM, since the default pam_unix module has it's own built -# in of 3 retries. However, this is a safe fallback in case you are using -# an authentication module that does not enforce PAM_MAXTRIES. -# -LOGIN_RETRIES 5 - -# -# Max time in seconds for login -# -LOGIN_TIMEOUT 60 - -# -# Which fields may be changed by regular users using chfn - use -# any combination of letters "frwh" (full name, room number, work -# phone, home phone). If not defined, no changes are allowed. -# For backward compatibility, "yes" = "rwh" and "no" = "frwh". -# -CHFN_RESTRICT rwh - -# -# Should login be allowed if we can't cd to the home directory? -# Default in no. -# -DEFAULT_HOME yes - -# -# If defined, this command is run when removing a user. -# It should remove any at/cron/print jobs etc. owned by -# the user to be removed (passed as the first argument). -# -#USERDEL_CMD /usr/sbin/userdel_local - -# -# If set to yes, userdel will remove the user's group if it contains no -# more members, and useradd will create by default a group with the name -# of the user. -# -# Other former uses of this variable such as setting the umask when -# user==primary group are not used in PAM environments, such as Debian -# -USERGROUPS_ENAB yes - -# -# Instead of the real user shell, the program specified by this parameter -# will be launched, although its visible name (argv[0]) will be the shell's. -# The program may do whatever it wants (logging, additional authentification, -# banner, ...) before running the actual shell. -# -# FAKE_SHELL /bin/fakeshell - -# -# If defined, either full pathname of a file containing device names or -# a ":" delimited list of device names. Root logins will be allowed only -# upon these devices. -# -# This variable is used by login and su. -# -#CONSOLE /etc/consoles -#CONSOLE console:tty01:tty02:tty03:tty04 - -# -# List of groups to add to the user's supplementary group set -# when logging in on the console (as determined by the CONSOLE -# setting). Default is none. -# -# Use with caution - it is possible for users to gain permanent -# access to these groups, even when not logged in on the console. -# How to do it is left as an exercise for the reader... -# -# This variable is used by login and su. -# -#CONSOLE_GROUPS floppy:audio:cdrom - -# -# If set to "yes", new passwords will be encrypted using the MD5-based -# algorithm compatible with the one used by recent releases of FreeBSD. -# It supports passwords of unlimited length and longer salt strings. -# Set to "no" if you need to copy encrypted passwords to other systems -# which don't understand the new algorithm. Default is "no". -# -# This variable is deprecated. You should use ENCRYPT_METHOD. -# -#MD5_CRYPT_ENAB no - -# -# If set to MD5 , MD5-based algorithm will be used for encrypting password -# If set to SHA256, SHA256-based algorithm will be used for encrypting password -# If set to SHA512, SHA512-based algorithm will be used for encrypting password -# If set to DES, DES-based algorithm will be used for encrypting password (default) -# Overrides the MD5_CRYPT_ENAB option -# -# Note: It is recommended to use a value consistent with -# the PAM modules configuration. -# -ENCRYPT_METHOD SHA512 - -# -# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. -# -# Define the number of SHA rounds. -# With a lot of rounds, it is more difficult to brute forcing the password. -# But note also that it more CPU resources will be needed to authenticate -# users. -# -# If not specified, the libc will choose the default number of rounds (5000). -# The values must be inside the 1000-999999999 range. -# If only one of the MIN or MAX values is set, then this value will be used. -# If MIN > MAX, the highest value will be used. -# -# SHA_CRYPT_MIN_ROUNDS 5000 -# SHA_CRYPT_MAX_ROUNDS 5000 - -################# OBSOLETED BY PAM ############## -# # -# These options are now handled by PAM. Please # -# edit the appropriate file in /etc/pam.d/ to # -# enable the equivelants of them. -# -############### - -#MOTD_FILE -#DIALUPS_CHECK_ENAB -#LASTLOG_ENAB -#MAIL_CHECK_ENAB -#OBSCURE_CHECKS_ENAB -#PORTTIME_CHECKS_ENAB -#SU_WHEEL_ONLY -#CRACKLIB_DICTPATH -#PASS_CHANGE_TRIES -#PASS_ALWAYS_WARN -#ENVIRON_FILE -#NOLOGINS_FILE -#ISSUE_FILE -#PASS_MIN_LEN -#PASS_MAX_LEN -#ULIMIT -#ENV_HZ -#CHFN_AUTH -#CHSH_AUTH -#FAIL_DELAY - -################# OBSOLETED ####################### -# # -# These options are no more handled by shadow. # -# # -# Shadow utilities will display a warning if they # -# still appear. # -# # -################################################### - -# CLOSE_SESSIONS -# LOGIN_STRING -# NO_PASSWORD_CONSOLE -# QMAIL_DIR - - - diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password index 7427655f8306..099e46336f22 100644 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password @@ -28,7 +28,6 @@ password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredi password required pam_pwhistory.so remember=10 use_authtok -TODO: fallback? password [success=1 default=ignore] pam_unix.so obscure yescrypt # here's the fallback if no module succeeds password requisite pam_deny.so diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old deleted file mode 100644 index db8baa4d2bf8..000000000000 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/login.defs.old +++ /dev/null @@ -1,340 +0,0 @@ -# -# /etc/login.defs - Configuration control definitions for the login package. -# -# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. -# If unspecified, some arbitrary (and possibly incorrect) value will -# be assumed. All other items are optional - if not specified then -# the described action or option will be inhibited. -# -# Comment lines (lines beginning with "#") and blank lines are ignored. -# -# Modified for Linux. --marekm - -# REQUIRED for useradd/userdel/usermod -# Directory where mailboxes reside, _or_ name of file, relative to the -# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, -# MAIL_DIR takes precedence. -# -# Essentially: -# - MAIL_DIR defines the location of users mail spool files -# (for mbox use) by appending the username to MAIL_DIR as defined -# below. -# - MAIL_FILE defines the location of the users mail spool files as the -# fully-qualified filename obtained by prepending the user home -# directory before $MAIL_FILE -# -# NOTE: This is no more used for setting up users MAIL environment variable -# which is, starting from shadow 4.0.12-1 in Debian, entirely the -# job of the pam_mail PAM modules -# See default PAM configuration files provided for -# login, su, etc. -# -# This is a temporary situation: setting these variables will soon -# move to /etc/default/useradd and the variables will then be -# no more supported -MAIL_DIR /var/mail -#MAIL_FILE .mail - -# -# Enable logging and display of /var/log/faillog login failure info. -# This option conflicts with the pam_tally PAM module. -# -FAILLOG_ENAB yes - -# -# Enable display of unknown usernames when login failures are recorded. -# -# WARNING: Unknown usernames may become world readable. -# See #290803 and #298773 for details about how this could become a security -# concern -LOG_UNKFAIL_ENAB no - -# -# Enable logging of successful logins -# -LOG_OK_LOGINS no - -# -# Enable "syslog" logging of su activity - in addition to sulog file logging. -# SYSLOG_SG_ENAB does the same for newgrp and sg. -# -SYSLOG_SU_ENAB yes -SYSLOG_SG_ENAB yes - -# -# If defined, all su activity is logged to this file. -# -#SULOG_FILE /var/log/sulog - -# -# If defined, file which maps tty line to TERM environment parameter. -# Each line of the file is in a format something like "vt100 tty01". -# -#TTYTYPE_FILE /etc/ttytype - -# -# If defined, login failures will be logged here in a utmp format -# last, when invoked as lastb, will read /var/log/btmp, so... -# -FTMP_FILE /var/log/btmp - -# -# If defined, the command name to display when running "su -". For -# example, if this is defined as "su" then a "ps" will display the -# command is "-su". If not defined, then "ps" would display the -# name of the shell actually being run, e.g. something like "-sh". -# -SU_NAME su - -# -# If defined, file which inhibits all the usual chatter during the login -# sequence. If a full pathname, then hushed mode will be enabled if the -# user's name or shell are found in the file. If not a full pathname, then -# hushed mode will be enabled if the file exists in the user's home directory. -# -HUSHLOGIN_FILE .hushlogin -#HUSHLOGIN_FILE /etc/hushlogins - -# -# *REQUIRED* The default PATH settings, for superuser and normal users. -# -# (they are minimal, add the rest in the shell startup files) -ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games - -# -# Terminal permissions -# -# TTYGROUP Login tty will be assigned this group ownership. -# TTYPERM Login tty will be set to this permission. -# -# If you have a "write" program which is "setgid" to a special group -# which owns the terminals, define TTYGROUP to the group number and -# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign -# TTYPERM to either 622 or 600. -# -# In Debian /usr/bin/bsd-write or similar programs are setgid tty -# However, the default and recommended value for TTYPERM is still 0600 -# to not allow anyone to write to anyone else console or terminal - -# Users can still allow other people to write them by issuing -# the "mesg y" command. - -TTYGROUP tty -TTYPERM 0600 - -# -# Login configuration initializations: -# -# ERASECHAR Terminal ERASE character ('\010' = backspace). -# KILLCHAR Terminal KILL character ('\025' = CTRL/U). -# UMASK Default "umask" value. -# -# The ERASECHAR and KILLCHAR are used only on System V machines. -# -# UMASK is the default umask value for pam_umask and is used by -# useradd and newusers to set the mode of the new home directories. -# 022 is the "historical" value in Debian for UMASK -# 027, or even 077, could be considered better for privacy -# There is no One True Answer here : each sysadmin must make up his/her -# mind. -# -# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value -# for private user groups, i. e. the uid is the same as gid, and username is -# the same as the primary group name: for these, the user permissions will be -# used as group permissions, e. g. 022 will become 002. -# -# Prefix these values with "0" to get octal, "0x" to get hexadecimal. -# -ERASECHAR 0177 -KILLCHAR 025 -UMASK 022 - -# -# Password aging controls: -# -# PASS_MAX_DAYS Maximum number of days a password may be used. -# PASS_MIN_DAYS Minimum number of days allowed between password changes. -# PASS_WARN_AGE Number of days warning given before a password expires. -# -PASS_MAX_DAYS 99999 -PASS_MIN_DAYS 0 -PASS_WARN_AGE 7 - -# -# Min/max values for automatic uid selection in useradd -# -UID_MIN 1000 -UID_MAX 60000 -# System accounts -#SYS_UID_MIN 100 -#SYS_UID_MAX 999 - -# -# Min/max values for automatic gid selection in groupadd -# -GID_MIN 1000 -GID_MAX 60000 -# System accounts -#SYS_GID_MIN 100 -#SYS_GID_MAX 999 - -# -# Max number of login retries if password is bad. This will most likely be -# overriden by PAM, since the default pam_unix module has it's own built -# in of 3 retries. However, this is a safe fallback in case you are using -# an authentication module that does not enforce PAM_MAXTRIES. -# -LOGIN_RETRIES 5 - -# -# Max time in seconds for login -# -LOGIN_TIMEOUT 60 - -# -# Which fields may be changed by regular users using chfn - use -# any combination of letters "frwh" (full name, room number, work -# phone, home phone). If not defined, no changes are allowed. -# For backward compatibility, "yes" = "rwh" and "no" = "frwh". -# -CHFN_RESTRICT rwh - -# -# Should login be allowed if we can't cd to the home directory? -# Default in no. -# -DEFAULT_HOME yes - -# -# If defined, this command is run when removing a user. -# It should remove any at/cron/print jobs etc. owned by -# the user to be removed (passed as the first argument). -# -#USERDEL_CMD /usr/sbin/userdel_local - -# -# If set to yes, userdel will remove the user's group if it contains no -# more members, and useradd will create by default a group with the name -# of the user. -# -# Other former uses of this variable such as setting the umask when -# user==primary group are not used in PAM environments, such as Debian -# -USERGROUPS_ENAB yes - -# -# Instead of the real user shell, the program specified by this parameter -# will be launched, although its visible name (argv[0]) will be the shell's. -# The program may do whatever it wants (logging, additional authentification, -# banner, ...) before running the actual shell. -# -# FAKE_SHELL /bin/fakeshell - -# -# If defined, either full pathname of a file containing device names or -# a ":" delimited list of device names. Root logins will be allowed only -# upon these devices. -# -# This variable is used by login and su. -# -#CONSOLE /etc/consoles -#CONSOLE console:tty01:tty02:tty03:tty04 - -# -# List of groups to add to the user's supplementary group set -# when logging in on the console (as determined by the CONSOLE -# setting). Default is none. -# -# Use with caution - it is possible for users to gain permanent -# access to these groups, even when not logged in on the console. -# How to do it is left as an exercise for the reader... -# -# This variable is used by login and su. -# -#CONSOLE_GROUPS floppy:audio:cdrom - -# -# If set to "yes", new passwords will be encrypted using the MD5-based -# algorithm compatible with the one used by recent releases of FreeBSD. -# It supports passwords of unlimited length and longer salt strings. -# Set to "no" if you need to copy encrypted passwords to other systems -# which don't understand the new algorithm. Default is "no". -# -# This variable is deprecated. You should use ENCRYPT_METHOD. -# -#MD5_CRYPT_ENAB no - -# -# If set to MD5 , MD5-based algorithm will be used for encrypting password -# If set to SHA256, SHA256-based algorithm will be used for encrypting password -# If set to SHA512, SHA512-based algorithm will be used for encrypting password -# If set to DES, DES-based algorithm will be used for encrypting password (default) -# Overrides the MD5_CRYPT_ENAB option -# -# Note: It is recommended to use a value consistent with -# the PAM modules configuration. -# -ENCRYPT_METHOD SHA512 - -# -# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. -# -# Define the number of SHA rounds. -# With a lot of rounds, it is more difficult to brute forcing the password. -# But note also that it more CPU resources will be needed to authenticate -# users. -# -# If not specified, the libc will choose the default number of rounds (5000). -# The values must be inside the 1000-999999999 range. -# If only one of the MIN or MAX values is set, then this value will be used. -# If MIN > MAX, the highest value will be used. -# -# SHA_CRYPT_MIN_ROUNDS 5000 -# SHA_CRYPT_MAX_ROUNDS 5000 - -################# OBSOLETED BY PAM ############## -# # -# These options are now handled by PAM. Please # -# edit the appropriate file in /etc/pam.d/ to # -# enable the equivelants of them. -# -############### - -#MOTD_FILE -#DIALUPS_CHECK_ENAB -#LASTLOG_ENAB -#MAIL_CHECK_ENAB -#OBSCURE_CHECKS_ENAB -#PORTTIME_CHECKS_ENAB -#SU_WHEEL_ONLY -#CRACKLIB_DICTPATH -#PASS_CHANGE_TRIES -#PASS_ALWAYS_WARN -#ENVIRON_FILE -#NOLOGINS_FILE -#ISSUE_FILE -#PASS_MIN_LEN -#PASS_MAX_LEN -#ULIMIT -#ENV_HZ -#CHFN_AUTH -#CHSH_AUTH -#FAIL_DELAY - -################# OBSOLETED ####################### -# # -# These options are no more handled by shadow. # -# # -# Shadow utilities will display a warning if they # -# still appear. # -# # -################################################### - -# CLOSE_SESSIONS -# LOGIN_STRING -# NO_PASSWORD_CONSOLE -# QMAIL_DIR - - - diff --git a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py index a18f705e068b..c95183b6ddfc 100644 --- a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py +++ b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py @@ -44,43 +44,6 @@ "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } } }, "enable_feature":{ @@ -120,43 +83,6 @@ "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } } }, "enable_digit_class":{ @@ -196,43 +122,6 @@ "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } } }, "enable_lower_class":{ @@ -273,43 +162,6 @@ "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } - } }, "enable_upper_class":{ "PASSW_HARDENING": { @@ -348,43 +200,6 @@ "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } } }, "enable_special_class":{ @@ -424,45 +239,8 @@ "num_dumps": "3", "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" } - }, - "AAA": { - "authentication": { - "login": "local" - }, - "authorization": { - "login": "local" - }, - "accounting": { - "login": "local" - } - }, - "TACPLUS": { - "global": { - "auth_type": "chap", - "timeout": 5, - "passkey": "dellsonic", - "src_intf": "Ethernet0" - } - }, - "TACPLUS_SERVER": { - "192.168.1.1" : { - "priority": 5, - "tcp_port": 50, - "timeout": 10, - "auth_type": "chap", - "passkey": "dellsonic", - "vrf": "default" - }, - "192.168.1.2" : { - "priority": 2, - "tcp_port": 51, - "timeout": 15, - "auth_type": "pap", - "passkey": "dellsonic1", - "vrf": "mgmt" - } } - }, + } } ] ] diff --git a/src/sonic-yang-models/yang-models/sonic-passwh.yang b/src/sonic-yang-models/yang-models/sonic-passwh.yang deleted file mode 100755 index 4cf9b1ac7316..000000000000 --- a/src/sonic-yang-models/yang-models/sonic-passwh.yang +++ /dev/null @@ -1,75 +0,0 @@ -module sonic-passwh { - yang-version 1.1; - namespace "http://github.com/Azure/sonic-passwh"; - prefix password; - - description "PASSWORD HARDENING YANG Module for SONiC OS"; - - revision 2021-10-12 { - description "First Revision"; - } - - container sonic-passwh { - - typedef feature_state { - type enumeration { - enum enabled; - enum disabled; - } - } - - container PASSWORD { - description "PASSWORD HARDENING part of config_db.json"; - container POLICIES { - leaf state { - description "state of the feature"; - type feature_state; - } - leaf expiration { - description "expiration time (days unit)"; - type uint32 { - range 1..365; - } - } - leaf expiration_warning { - description "expiration warning time (days unit)"; - type uint8 { - range 1..30; - } - } - leaf history_cnt { - description "num of old password that the system will recorded"; - type uint8 { - range 1..100; - } - } - leaf len_min { - description "password min length"; - type uint8 { - range 1..32; - } - } - leaf reject_user_passw_match{ - description "username password match"; - type boolean; - } - leaf lower_class{ - description "password lower chars policy"; - type boolean; - } - leaf upper_class{ - description "password upper chars policy"; - type boolean; - } - leaf digits_class{ - description "password digits chars policy"; - type boolean; - } - leaf special_class{ - description "password special chars policy"; - type boolean; - } - }/*container policies */ - } /* container PASSWH */ - }/* container sonic-passwh */ -}/* end of module sonic-passwh */ \ No newline at end of file From 956388640fd1b5a44010d89d5074c01a54d7a0fc Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Tue, 8 Mar 2022 17:40:42 +0200 Subject: [PATCH 04/18] move init_cfg.json.j2 changes to separate pull request with YANG model --- files/build_templates/init_cfg.json.j2 | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/files/build_templates/init_cfg.json.j2 b/files/build_templates/init_cfg.json.j2 index 24fc4bd64733..e1320214e02c 100644 --- a/files/build_templates/init_cfg.json.j2 +++ b/files/build_templates/init_cfg.json.j2 @@ -83,20 +83,6 @@ "state" : "disabled", {% endif %} "rate_limit_interval" : "600" }{%if not loop.last %},{% endif -%} -{% endfor %} - }, - "PASSW_HARDENING": { - "POLICIES":{ - "state": "disabled", - "expiration": "180", - "expiration_warning": "15", - "history_ctr": "10", - "len_min": "8", - "reject_user_passw_match": "True", - "lower_class": "True", - "upper_class": "True", - "digit_class": "True", - "special_class": "True" - } +{% endfor %} } } From 49d1195b33fc1da13c197330b1bd108945794e3b Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Thu, 10 Mar 2022 18:25:56 +0200 Subject: [PATCH 05/18] modify age calculation & fix some values in the common-password.j2 file --- .../templates/common-password.j2 | 4 +- src/sonic-host-services/scripts/hostcfgd | 78 +++++-------------- 2 files changed, 23 insertions(+), 59 deletions(-) diff --git a/src/sonic-host-services-data/templates/common-password.j2 b/src/sonic-host-services-data/templates/common-password.j2 index 2f52679d4bbb..36efd33b7b73 100644 --- a/src/sonic-host-services-data/templates/common-password.j2 +++ b/src/sonic-host-services-data/templates/common-password.j2 @@ -26,9 +26,9 @@ {% if passw_policies %} {% if passw_policies['state'] == 'enabled' %} -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen={{passw_policies['len_min']}} {% if passw_policies['upper_class'] %} ucredit=-1 {% endif %} {% if passw_policies['lower_class'] %} lcredit=-1 {% endif %} {% if passw_policies['digit_class'] %} dcredit=-1 {% endif %} {% if passw_policies['special_class'] %} ocredit=-1 {% endif %} {% if passw_policies['reject_user_passw_match'] %} reject_username {% endif %} enforce_for_root +password requisite pam_cracklib.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %} ucredit=-1 {% endif %} {% if passw_policies['lower_class'] %} lcredit=-1 {% endif %} {% if passw_policies['digit_class'] %} dcredit=-1 {% endif %} {% if passw_policies['special_class'] %} ocredit=-1 {% endif %} {% if passw_policies['reject_user_passw_match'] %} reject_username {% endif %} enforce_for_root -password required pam_pwhistory.so remember={{passw_policies['history_ctr']}} use_authtok +password required pam_pwhistory.so {% if passw_policies['history_ctr'] %}remember={{passw_policies['history_ctr']}}{% endif %} use_authtok {% endif %} {% endif %} diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index fc441786c173..198123e34524 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -600,31 +600,31 @@ class AaaCfg(object): curr_expiration_warning = LINUX_DEFAULT_PASS_WARN_AGE if passw_policies: - if passw_policies['state'] == 'enabled': - if 'expiration' in passw_policies: - if int(self.passw_policies['expiration']) != 0: # value '0' meaning age policy is disabled - # the logic is to modify the expiration time according the last updated modificatiion - # - curr_expiration = int(passw_policies['expiration']) + if 'state' in passw_policies: + if passw_policies['state'] == 'enabled': + if 'expiration' in passw_policies: + if int(self.passw_policies['expiration']) != 0: # value '0' meaning age policy is disabled + # the logic is to modify the expiration time according the last updated modificatiion + # + curr_expiration = int(passw_policies['expiration']) - if 'expiration_warning' in passw_policies: - if int(self.passw_policies['expiration_warning']) != 0: # value '0' meaning age policy is disabled - curr_expiration_warning = int(passw_policies['expiration_warning']) + if 'expiration_warning' in passw_policies: + if int(self.passw_policies['expiration_warning']) != 0: # value '0' meaning age policy is disabled + curr_expiration_warning = int(passw_policies['expiration_warning']) - if passw_policies: - if self.is_passwd_aging_expire_update(curr_expiration, 'MAX_DAYS'): - # Set aging policy for existing users - self.passwd_aging_expire_modify(curr_expiration, 'MAX_DAYS') + if self.is_passwd_aging_expire_update(curr_expiration, 'MAX_DAYS'): + # Set aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration, 'MAX_DAYS') - # Aging policy for new users - self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_MAX_DAYS/c\PASS_MAX_DAYS " +str(curr_expiration)+"\'"]) + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_MAX_DAYS/c\PASS_MAX_DAYS " +str(curr_expiration)+"\'"]) - if self.is_passwd_aging_expire_update(curr_expiration_warning, 'WARN_DAYS'): - # Aging policy for existing users - self.passwd_aging_expire_modify(curr_expiration_warning, 'WARN_DAYS') + if self.is_passwd_aging_expire_update(curr_expiration_warning, 'WARN_DAYS'): + # Aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration_warning, 'WARN_DAYS') - # Aging policy for new users - self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_WARN_AGE/c\PASS_WARN_AGE " +str(curr_expiration_warning)+"\'"]) + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_WARN_AGE/c\PASS_WARN_AGE " +str(curr_expiration_warning)+"\'"]) def passwd_aging_expire_modify(self, curr_expiration, age_type): normal_accounts = self.get_normal_accounts() @@ -633,11 +633,8 @@ class AaaCfg(object): return chage_flag = AGE_DICT[age_type]['CHAGE_FLAG'] for normal_account in normal_accounts: - - new_curr_expiration = self.get_user_expire_time(normal_account, curr_expiration) - try: - chage_p_m = subprocess.Popen(('chage', chage_flag + str(new_curr_expiration), normal_account), stdout=subprocess.PIPE) + chage_p_m = subprocess.Popen(('chage', chage_flag + str(curr_expiration), normal_account), stdout=subprocess.PIPE) return_code_chage_p_m = chage_p_m.poll() if return_code_chage_p_m != 0: syslog.syslog(syslog.LOG_ERR, "failed: return code - {}".format(return_code_chage_p_m)) @@ -645,39 +642,6 @@ class AaaCfg(object): except subprocess.CalledProcessError as e: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(e.cmd, e.returncode, e.output)) - def get_user_expire_time(self, normal_account, curr_expiration): - last_passw_change = '' - new_curr_expiration = curr_expiration - REGEX_LAST_PASSW_CHANGE = r'^Last password change[ \t]*:[ \t]*(?P.*)' - - try: - chage = subprocess.Popen(('chage', '-l', '-i', normal_account), stdout=subprocess.PIPE) - chage_data = chage.stdout.readlines() - - for line in chage_data: - m1 = re.match(REGEX_LAST_PASSW_CHANGE, line.decode('utf-8')) - if m1: - last_passw_change = m1.group("last_passw_change") - break - - # update the expire passw time according the last passw change - if not last_passw_change: - return curr_expiration - - last_passw_change_obj = datetime.strptime(last_passw_change, '%Y-%m-%d') - - today = datetime.now() - last_passw_change_diff = today - last_passw_change_obj - if (curr_expiration - last_passw_change_diff.days) > 0: - new_curr_expiration = curr_expiration - last_passw_change_diff.days - else: - new_curr_expiration = 0 - - except subprocess.CalledProcessError as err: - syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) - - return new_curr_expiration - def is_passwd_aging_expire_update(self, curr_expiration, age_type): """ Function verify that the current age expiry policy values are equal from the old one Return update_age_status 'True' value meaning that was a modification from the last time, and vice versa. From eff356aefe188e04443a268c3186a13c1c28a9bb Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Wed, 16 Mar 2022 10:05:35 +0200 Subject: [PATCH 06/18] fix digits class name case --- .../templates/common-password.j2 | 10 +- src/sonic-host-services/scripts/hostcfgd | 4 +- .../tests/hostcfgd/hostcfgd_passwh_test.py | 2 +- .../common-password | 6 +- .../common-password | 39 -- .../login.defs | 340 ------------------ .../common-password | 8 +- .../tests/hostcfgd/test_passwh_vectors.py | 26 +- 8 files changed, 28 insertions(+), 407 deletions(-) delete mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password delete mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs diff --git a/src/sonic-host-services-data/templates/common-password.j2 b/src/sonic-host-services-data/templates/common-password.j2 index 36efd33b7b73..a580eb3854ac 100644 --- a/src/sonic-host-services-data/templates/common-password.j2 +++ b/src/sonic-host-services-data/templates/common-password.j2 @@ -26,18 +26,18 @@ {% if passw_policies %} {% if passw_policies['state'] == 'enabled' %} -password requisite pam_cracklib.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %} ucredit=-1 {% endif %} {% if passw_policies['lower_class'] %} lcredit=-1 {% endif %} {% if passw_policies['digit_class'] %} dcredit=-1 {% endif %} {% if passw_policies['special_class'] %} ocredit=-1 {% endif %} {% if passw_policies['reject_user_passw_match'] %} reject_username {% endif %} enforce_for_root +password requisite pam_cracklib.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %}ucredit=-1{% endif %} {% if passw_policies['lower_class'] %}lcredit=-1{% endif %} {% if passw_policies['digits_class'] %}dcredit=-1{% endif %} {% if passw_policies['special_class'] %}ocredit=-1{% endif %} {% if passw_policies['reject_user_passw_match'] %}reject_username{% endif %} enforce_for_root -password required pam_pwhistory.so {% if passw_policies['history_ctr'] %}remember={{passw_policies['history_ctr']}}{% endif %} use_authtok +password required pam_pwhistory.so {% if passw_policies['history_cnt'] %}remember={{passw_policies['history_cnt']}}{% endif %} use_authtok {% endif %} {% endif %} -password [success=1 default=ignore] pam_unix.so obscure yescrypt +password [success=1 default=ignore] pam_unix.so obscure yescrypt # here's the fallback if no module succeeds -password requisite pam_deny.so +password requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; # this avoids us returning an error just because nothing sets a success code # since the modules above will each just jump around -password required pam_permit.so +password required pam_permit.so # and here are more per-package modules (the "Additional" block) # end of pam-auth-update config diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 198123e34524..579bdc5ec560 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -540,8 +540,8 @@ class AaaCfg(object): data['lower_class'] = is_true(data['lower_class']) if 'upper_class' in data: data['upper_class'] = is_true(data['upper_class']) - if 'digit_class' in data: - data['digit_class'] = is_true(data['digit_class']) + if 'digits_class' in data: + data['digits_class'] = is_true(data['digits_class']) if 'special_class' in data: data['special_class'] = is_true(data['special_class']) diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index e1b92401abeb..4e271840b5fc 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -182,4 +182,4 @@ def test_hostcfgd_passwh_classes(self, test_name, test_data): None """ - self.check_config(test_name, test_data, "enable_digit_class") \ No newline at end of file + self.check_config(test_name, test_data, "enable_digits_class") \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password index 11efc43374a3..0da639249c94 100644 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_default_values/common-password @@ -25,12 +25,12 @@ # here are the per-package modules (the "Primary" block) -password [success=1 default=ignore] pam_unix.so obscure yescrypt +password [success=1 default=ignore] pam_unix.so obscure yescrypt # here's the fallback if no module succeeds -password requisite pam_deny.so +password requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; # this avoids us returning an error just because nothing sets a success code # since the modules above will each just jump around -password required pam_permit.so +password required pam_permit.so # and here are more per-package modules (the "Additional" block) # end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password deleted file mode 100644 index 0648437f708a..000000000000 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/common-password +++ /dev/null @@ -1,39 +0,0 @@ -#THIS IS AN AUTO-GENERATED FILE -# -# /etc/pam.d/common-password - password-related modules common to all services -# -# This file is included from other service-specific PAM config files, -# and should contain a list of modules that define the services to be -# used to change user passwords. The default is pam_unix. - -# Explanation of pam_unix options: -# The "yescrypt" option enables -#hashed passwords using the yescrypt algorithm, introduced in Debian -#11. Without this option, the default is Unix crypt. Prior releases -#used the option "sha512"; if a shadow password hash will be shared -#between Debian 11 and older releases replace "yescrypt" with "sha512" -#for compatibility . The "obscure" option replaces the old -#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage -#for other options. - -# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. -# To take advantage of this, it is recommended that you configure any -# local modules either before or after the default block, and use -# pam-auth-update to manage selection of other modules. See -# pam-auth-update(8) for details. - -# here are the per-package modules (the "Primary" block) - -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 dcredit=-1 enforce_for_root - -password required pam_pwhistory.so remember=0 use_authtok - -password [success=1 default=ignore] pam_unix.so obscure yescrypt -# here's the fallback if no module succeeds -password requisite pam_deny.so -# prime the stack with a positive return value if there isn't one already; -# this avoids us returning an error just because nothing sets a success code -# since the modules above will each just jump around -password required pam_permit.so -# and here are more per-package modules (the "Additional" block) -# end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs deleted file mode 100644 index db8baa4d2bf8..000000000000 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digit_class/login.defs +++ /dev/null @@ -1,340 +0,0 @@ -# -# /etc/login.defs - Configuration control definitions for the login package. -# -# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. -# If unspecified, some arbitrary (and possibly incorrect) value will -# be assumed. All other items are optional - if not specified then -# the described action or option will be inhibited. -# -# Comment lines (lines beginning with "#") and blank lines are ignored. -# -# Modified for Linux. --marekm - -# REQUIRED for useradd/userdel/usermod -# Directory where mailboxes reside, _or_ name of file, relative to the -# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, -# MAIL_DIR takes precedence. -# -# Essentially: -# - MAIL_DIR defines the location of users mail spool files -# (for mbox use) by appending the username to MAIL_DIR as defined -# below. -# - MAIL_FILE defines the location of the users mail spool files as the -# fully-qualified filename obtained by prepending the user home -# directory before $MAIL_FILE -# -# NOTE: This is no more used for setting up users MAIL environment variable -# which is, starting from shadow 4.0.12-1 in Debian, entirely the -# job of the pam_mail PAM modules -# See default PAM configuration files provided for -# login, su, etc. -# -# This is a temporary situation: setting these variables will soon -# move to /etc/default/useradd and the variables will then be -# no more supported -MAIL_DIR /var/mail -#MAIL_FILE .mail - -# -# Enable logging and display of /var/log/faillog login failure info. -# This option conflicts with the pam_tally PAM module. -# -FAILLOG_ENAB yes - -# -# Enable display of unknown usernames when login failures are recorded. -# -# WARNING: Unknown usernames may become world readable. -# See #290803 and #298773 for details about how this could become a security -# concern -LOG_UNKFAIL_ENAB no - -# -# Enable logging of successful logins -# -LOG_OK_LOGINS no - -# -# Enable "syslog" logging of su activity - in addition to sulog file logging. -# SYSLOG_SG_ENAB does the same for newgrp and sg. -# -SYSLOG_SU_ENAB yes -SYSLOG_SG_ENAB yes - -# -# If defined, all su activity is logged to this file. -# -#SULOG_FILE /var/log/sulog - -# -# If defined, file which maps tty line to TERM environment parameter. -# Each line of the file is in a format something like "vt100 tty01". -# -#TTYTYPE_FILE /etc/ttytype - -# -# If defined, login failures will be logged here in a utmp format -# last, when invoked as lastb, will read /var/log/btmp, so... -# -FTMP_FILE /var/log/btmp - -# -# If defined, the command name to display when running "su -". For -# example, if this is defined as "su" then a "ps" will display the -# command is "-su". If not defined, then "ps" would display the -# name of the shell actually being run, e.g. something like "-sh". -# -SU_NAME su - -# -# If defined, file which inhibits all the usual chatter during the login -# sequence. If a full pathname, then hushed mode will be enabled if the -# user's name or shell are found in the file. If not a full pathname, then -# hushed mode will be enabled if the file exists in the user's home directory. -# -HUSHLOGIN_FILE .hushlogin -#HUSHLOGIN_FILE /etc/hushlogins - -# -# *REQUIRED* The default PATH settings, for superuser and normal users. -# -# (they are minimal, add the rest in the shell startup files) -ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games - -# -# Terminal permissions -# -# TTYGROUP Login tty will be assigned this group ownership. -# TTYPERM Login tty will be set to this permission. -# -# If you have a "write" program which is "setgid" to a special group -# which owns the terminals, define TTYGROUP to the group number and -# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign -# TTYPERM to either 622 or 600. -# -# In Debian /usr/bin/bsd-write or similar programs are setgid tty -# However, the default and recommended value for TTYPERM is still 0600 -# to not allow anyone to write to anyone else console or terminal - -# Users can still allow other people to write them by issuing -# the "mesg y" command. - -TTYGROUP tty -TTYPERM 0600 - -# -# Login configuration initializations: -# -# ERASECHAR Terminal ERASE character ('\010' = backspace). -# KILLCHAR Terminal KILL character ('\025' = CTRL/U). -# UMASK Default "umask" value. -# -# The ERASECHAR and KILLCHAR are used only on System V machines. -# -# UMASK is the default umask value for pam_umask and is used by -# useradd and newusers to set the mode of the new home directories. -# 022 is the "historical" value in Debian for UMASK -# 027, or even 077, could be considered better for privacy -# There is no One True Answer here : each sysadmin must make up his/her -# mind. -# -# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value -# for private user groups, i. e. the uid is the same as gid, and username is -# the same as the primary group name: for these, the user permissions will be -# used as group permissions, e. g. 022 will become 002. -# -# Prefix these values with "0" to get octal, "0x" to get hexadecimal. -# -ERASECHAR 0177 -KILLCHAR 025 -UMASK 022 - -# -# Password aging controls: -# -# PASS_MAX_DAYS Maximum number of days a password may be used. -# PASS_MIN_DAYS Minimum number of days allowed between password changes. -# PASS_WARN_AGE Number of days warning given before a password expires. -# -PASS_MAX_DAYS 99999 -PASS_MIN_DAYS 0 -PASS_WARN_AGE 7 - -# -# Min/max values for automatic uid selection in useradd -# -UID_MIN 1000 -UID_MAX 60000 -# System accounts -#SYS_UID_MIN 100 -#SYS_UID_MAX 999 - -# -# Min/max values for automatic gid selection in groupadd -# -GID_MIN 1000 -GID_MAX 60000 -# System accounts -#SYS_GID_MIN 100 -#SYS_GID_MAX 999 - -# -# Max number of login retries if password is bad. This will most likely be -# overriden by PAM, since the default pam_unix module has it's own built -# in of 3 retries. However, this is a safe fallback in case you are using -# an authentication module that does not enforce PAM_MAXTRIES. -# -LOGIN_RETRIES 5 - -# -# Max time in seconds for login -# -LOGIN_TIMEOUT 60 - -# -# Which fields may be changed by regular users using chfn - use -# any combination of letters "frwh" (full name, room number, work -# phone, home phone). If not defined, no changes are allowed. -# For backward compatibility, "yes" = "rwh" and "no" = "frwh". -# -CHFN_RESTRICT rwh - -# -# Should login be allowed if we can't cd to the home directory? -# Default in no. -# -DEFAULT_HOME yes - -# -# If defined, this command is run when removing a user. -# It should remove any at/cron/print jobs etc. owned by -# the user to be removed (passed as the first argument). -# -#USERDEL_CMD /usr/sbin/userdel_local - -# -# If set to yes, userdel will remove the user's group if it contains no -# more members, and useradd will create by default a group with the name -# of the user. -# -# Other former uses of this variable such as setting the umask when -# user==primary group are not used in PAM environments, such as Debian -# -USERGROUPS_ENAB yes - -# -# Instead of the real user shell, the program specified by this parameter -# will be launched, although its visible name (argv[0]) will be the shell's. -# The program may do whatever it wants (logging, additional authentification, -# banner, ...) before running the actual shell. -# -# FAKE_SHELL /bin/fakeshell - -# -# If defined, either full pathname of a file containing device names or -# a ":" delimited list of device names. Root logins will be allowed only -# upon these devices. -# -# This variable is used by login and su. -# -#CONSOLE /etc/consoles -#CONSOLE console:tty01:tty02:tty03:tty04 - -# -# List of groups to add to the user's supplementary group set -# when logging in on the console (as determined by the CONSOLE -# setting). Default is none. -# -# Use with caution - it is possible for users to gain permanent -# access to these groups, even when not logged in on the console. -# How to do it is left as an exercise for the reader... -# -# This variable is used by login and su. -# -#CONSOLE_GROUPS floppy:audio:cdrom - -# -# If set to "yes", new passwords will be encrypted using the MD5-based -# algorithm compatible with the one used by recent releases of FreeBSD. -# It supports passwords of unlimited length and longer salt strings. -# Set to "no" if you need to copy encrypted passwords to other systems -# which don't understand the new algorithm. Default is "no". -# -# This variable is deprecated. You should use ENCRYPT_METHOD. -# -#MD5_CRYPT_ENAB no - -# -# If set to MD5 , MD5-based algorithm will be used for encrypting password -# If set to SHA256, SHA256-based algorithm will be used for encrypting password -# If set to SHA512, SHA512-based algorithm will be used for encrypting password -# If set to DES, DES-based algorithm will be used for encrypting password (default) -# Overrides the MD5_CRYPT_ENAB option -# -# Note: It is recommended to use a value consistent with -# the PAM modules configuration. -# -ENCRYPT_METHOD SHA512 - -# -# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. -# -# Define the number of SHA rounds. -# With a lot of rounds, it is more difficult to brute forcing the password. -# But note also that it more CPU resources will be needed to authenticate -# users. -# -# If not specified, the libc will choose the default number of rounds (5000). -# The values must be inside the 1000-999999999 range. -# If only one of the MIN or MAX values is set, then this value will be used. -# If MIN > MAX, the highest value will be used. -# -# SHA_CRYPT_MIN_ROUNDS 5000 -# SHA_CRYPT_MAX_ROUNDS 5000 - -################# OBSOLETED BY PAM ############## -# # -# These options are now handled by PAM. Please # -# edit the appropriate file in /etc/pam.d/ to # -# enable the equivelants of them. -# -############### - -#MOTD_FILE -#DIALUPS_CHECK_ENAB -#LASTLOG_ENAB -#MAIL_CHECK_ENAB -#OBSCURE_CHECKS_ENAB -#PORTTIME_CHECKS_ENAB -#SU_WHEEL_ONLY -#CRACKLIB_DICTPATH -#PASS_CHANGE_TRIES -#PASS_ALWAYS_WARN -#ENVIRON_FILE -#NOLOGINS_FILE -#ISSUE_FILE -#PASS_MIN_LEN -#PASS_MAX_LEN -#ULIMIT -#ENV_HZ -#CHFN_AUTH -#CHSH_AUTH -#FAIL_DELAY - -################# OBSOLETED ####################### -# # -# These options are no more handled by shadow. # -# # -# Shadow utilities will display a warning if they # -# still appear. # -# # -################################################### - -# CLOSE_SESSIONS -# LOGIN_STRING -# NO_PASSWORD_CONSOLE -# QMAIL_DIR - - - diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password index 099e46336f22..cf8d40b15c75 100644 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password @@ -24,16 +24,16 @@ # here are the per-package modules (the "Primary" block) -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root +password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root password required pam_pwhistory.so remember=10 use_authtok -password [success=1 default=ignore] pam_unix.so obscure yescrypt +password [success=1 default=ignore] pam_unix.so obscure yescrypt # here's the fallback if no module succeeds -password requisite pam_deny.so +password requisite pam_deny.so # prime the stack with a positive return value if there isn't one already; # this avoids us returning an error just because nothing sets a success code # since the modules above will each just jump around -password required pam_permit.so +password required pam_permit.so # and here are more per-package modules (the "Additional" block) # end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py index c95183b6ddfc..4b7a20e9f053 100644 --- a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py +++ b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py @@ -13,12 +13,12 @@ "state": "disabled", "expiration": "180", "expiration_warning": "15", - "history_ctr": "10", + "history_cnt": "10", "len_min": "8", "reject_user_passw_match": "True", "lower_class": "True", "upper_class": "True", - "digit_class": "True", + "digits_class": "True", "special_class": "True" } }, @@ -52,12 +52,12 @@ "state": "enabled", "expiration": "180", "expiration_warning": "15", - "history_ctr": "10", + "history_cnt": "10", "len_min": "8", "reject_user_passw_match": "True", "lower_class": "True", "upper_class": "True", - "digit_class": "True", + "digits_class": "True", "special_class": "True" } }, @@ -85,18 +85,18 @@ } } }, - "enable_digit_class":{ + "enable_digits_class":{ "PASSW_HARDENING": { "POLICIES":{ "state": "enabled", "expiration": "0", "expiration_warning": "0", - "history_ctr": "0", + "history_cnt": "0", "len_min": "8", "reject_user_passw_match": "False", "lower_class": "False", "upper_class": "False", - "digit_class": "True", + "digits_class": "True", "special_class": "False" } }, @@ -130,12 +130,12 @@ "state": "enabled", "expiration": "0", "expiration_warning": "0", - "history_ctr": "0", + "history_cnt": "0", "len_min": "8", "reject_user_passw_match": "False", "lower_class": "True", "upper_class": "False", - "digit_class": "False", + "digits_class": "False", "special_class": "False" } }, @@ -169,12 +169,12 @@ "state": "enabled", "expiration": "0", "expiration_warning": "0", - "history_ctr": "0", + "history_cnt": "0", "len_min": "8", "reject_user_passw_match": "False", "lower_class": "False", "upper_class": "True", - "digit_class": "False", + "digits_class": "False", "special_class": "False" } }, @@ -208,12 +208,12 @@ "state": "enabled", "expiration": "0", "expiration_warning": "0", - "history_ctr": "0", + "history_cnt": "0", "len_min": "8", "reject_user_passw_match": "False", "lower_class": "False", "upper_class": "False", - "digit_class": "False", + "digits_class": "False", "special_class": "True" } }, From 5ab7c2b65bb79ff70a5bb1bd441b58e5aea2db68 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Thu, 24 Mar 2022 12:56:26 +0200 Subject: [PATCH 07/18] fix unitest hostcfgd_passwh_test.py by adding enable_digits_class sample --- .../common-password | 39 ++ .../login.defs | 340 ++++++++++++++++++ 2 files changed, 379 insertions(+) create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/login.defs diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password new file mode 100644 index 000000000000..0c9a2623e7f5 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password @@ -0,0 +1,39 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-password - password-related modules common to all services +# +# This file is included from other service-specific PAM config files, +# and should contain a list of modules that define the services to be +# used to change user passwords. The default is pam_unix. + +# Explanation of pam_unix options: +# The "yescrypt" option enables +#hashed passwords using the yescrypt algorithm, introduced in Debian +#11. Without this option, the default is Unix crypt. Prior releases +#used the option "sha512"; if a shadow password hash will be shared +#between Debian 11 and older releases replace "yescrypt" with "sha512" +#for compatibility . The "obscure" option replaces the old +#`OBSCURE_CHECKS_ENAB' option in login.defs. See the pam_unix manpage +#for other options. + +# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. +# To take advantage of this, it is recommended that you configure any +# local modules either before or after the default block, and use +# pam-auth-update to manage selection of other modules. See +# pam-auth-update(8) for details. + +# here are the per-package modules (the "Primary" block) + +password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 dcredit=-1 enforce_for_root + +password required pam_pwhistory.so remember=0 use_authtok + +password [success=1 default=ignore] pam_unix.so obscure yescrypt +# here's the fallback if no module succeeds +password requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +password required pam_permit.so +# and here are more per-package modules (the "Additional" block) +# end of pam-auth-update config \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/login.defs b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/login.defs new file mode 100644 index 000000000000..db8baa4d2bf8 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/login.defs @@ -0,0 +1,340 @@ +# +# /etc/login.defs - Configuration control definitions for the login package. +# +# Three items must be defined: MAIL_DIR, ENV_SUPATH, and ENV_PATH. +# If unspecified, some arbitrary (and possibly incorrect) value will +# be assumed. All other items are optional - if not specified then +# the described action or option will be inhibited. +# +# Comment lines (lines beginning with "#") and blank lines are ignored. +# +# Modified for Linux. --marekm + +# REQUIRED for useradd/userdel/usermod +# Directory where mailboxes reside, _or_ name of file, relative to the +# home directory. If you _do_ define MAIL_DIR and MAIL_FILE, +# MAIL_DIR takes precedence. +# +# Essentially: +# - MAIL_DIR defines the location of users mail spool files +# (for mbox use) by appending the username to MAIL_DIR as defined +# below. +# - MAIL_FILE defines the location of the users mail spool files as the +# fully-qualified filename obtained by prepending the user home +# directory before $MAIL_FILE +# +# NOTE: This is no more used for setting up users MAIL environment variable +# which is, starting from shadow 4.0.12-1 in Debian, entirely the +# job of the pam_mail PAM modules +# See default PAM configuration files provided for +# login, su, etc. +# +# This is a temporary situation: setting these variables will soon +# move to /etc/default/useradd and the variables will then be +# no more supported +MAIL_DIR /var/mail +#MAIL_FILE .mail + +# +# Enable logging and display of /var/log/faillog login failure info. +# This option conflicts with the pam_tally PAM module. +# +FAILLOG_ENAB yes + +# +# Enable display of unknown usernames when login failures are recorded. +# +# WARNING: Unknown usernames may become world readable. +# See #290803 and #298773 for details about how this could become a security +# concern +LOG_UNKFAIL_ENAB no + +# +# Enable logging of successful logins +# +LOG_OK_LOGINS no + +# +# Enable "syslog" logging of su activity - in addition to sulog file logging. +# SYSLOG_SG_ENAB does the same for newgrp and sg. +# +SYSLOG_SU_ENAB yes +SYSLOG_SG_ENAB yes + +# +# If defined, all su activity is logged to this file. +# +#SULOG_FILE /var/log/sulog + +# +# If defined, file which maps tty line to TERM environment parameter. +# Each line of the file is in a format something like "vt100 tty01". +# +#TTYTYPE_FILE /etc/ttytype + +# +# If defined, login failures will be logged here in a utmp format +# last, when invoked as lastb, will read /var/log/btmp, so... +# +FTMP_FILE /var/log/btmp + +# +# If defined, the command name to display when running "su -". For +# example, if this is defined as "su" then a "ps" will display the +# command is "-su". If not defined, then "ps" would display the +# name of the shell actually being run, e.g. something like "-sh". +# +SU_NAME su + +# +# If defined, file which inhibits all the usual chatter during the login +# sequence. If a full pathname, then hushed mode will be enabled if the +# user's name or shell are found in the file. If not a full pathname, then +# hushed mode will be enabled if the file exists in the user's home directory. +# +HUSHLOGIN_FILE .hushlogin +#HUSHLOGIN_FILE /etc/hushlogins + +# +# *REQUIRED* The default PATH settings, for superuser and normal users. +# +# (they are minimal, add the rest in the shell startup files) +ENV_SUPATH PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV_PATH PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games + +# +# Terminal permissions +# +# TTYGROUP Login tty will be assigned this group ownership. +# TTYPERM Login tty will be set to this permission. +# +# If you have a "write" program which is "setgid" to a special group +# which owns the terminals, define TTYGROUP to the group number and +# TTYPERM to 0620. Otherwise leave TTYGROUP commented out and assign +# TTYPERM to either 622 or 600. +# +# In Debian /usr/bin/bsd-write or similar programs are setgid tty +# However, the default and recommended value for TTYPERM is still 0600 +# to not allow anyone to write to anyone else console or terminal + +# Users can still allow other people to write them by issuing +# the "mesg y" command. + +TTYGROUP tty +TTYPERM 0600 + +# +# Login configuration initializations: +# +# ERASECHAR Terminal ERASE character ('\010' = backspace). +# KILLCHAR Terminal KILL character ('\025' = CTRL/U). +# UMASK Default "umask" value. +# +# The ERASECHAR and KILLCHAR are used only on System V machines. +# +# UMASK is the default umask value for pam_umask and is used by +# useradd and newusers to set the mode of the new home directories. +# 022 is the "historical" value in Debian for UMASK +# 027, or even 077, could be considered better for privacy +# There is no One True Answer here : each sysadmin must make up his/her +# mind. +# +# If USERGROUPS_ENAB is set to "yes", that will modify this UMASK default value +# for private user groups, i. e. the uid is the same as gid, and username is +# the same as the primary group name: for these, the user permissions will be +# used as group permissions, e. g. 022 will become 002. +# +# Prefix these values with "0" to get octal, "0x" to get hexadecimal. +# +ERASECHAR 0177 +KILLCHAR 025 +UMASK 022 + +# +# Password aging controls: +# +# PASS_MAX_DAYS Maximum number of days a password may be used. +# PASS_MIN_DAYS Minimum number of days allowed between password changes. +# PASS_WARN_AGE Number of days warning given before a password expires. +# +PASS_MAX_DAYS 99999 +PASS_MIN_DAYS 0 +PASS_WARN_AGE 7 + +# +# Min/max values for automatic uid selection in useradd +# +UID_MIN 1000 +UID_MAX 60000 +# System accounts +#SYS_UID_MIN 100 +#SYS_UID_MAX 999 + +# +# Min/max values for automatic gid selection in groupadd +# +GID_MIN 1000 +GID_MAX 60000 +# System accounts +#SYS_GID_MIN 100 +#SYS_GID_MAX 999 + +# +# Max number of login retries if password is bad. This will most likely be +# overriden by PAM, since the default pam_unix module has it's own built +# in of 3 retries. However, this is a safe fallback in case you are using +# an authentication module that does not enforce PAM_MAXTRIES. +# +LOGIN_RETRIES 5 + +# +# Max time in seconds for login +# +LOGIN_TIMEOUT 60 + +# +# Which fields may be changed by regular users using chfn - use +# any combination of letters "frwh" (full name, room number, work +# phone, home phone). If not defined, no changes are allowed. +# For backward compatibility, "yes" = "rwh" and "no" = "frwh". +# +CHFN_RESTRICT rwh + +# +# Should login be allowed if we can't cd to the home directory? +# Default in no. +# +DEFAULT_HOME yes + +# +# If defined, this command is run when removing a user. +# It should remove any at/cron/print jobs etc. owned by +# the user to be removed (passed as the first argument). +# +#USERDEL_CMD /usr/sbin/userdel_local + +# +# If set to yes, userdel will remove the user's group if it contains no +# more members, and useradd will create by default a group with the name +# of the user. +# +# Other former uses of this variable such as setting the umask when +# user==primary group are not used in PAM environments, such as Debian +# +USERGROUPS_ENAB yes + +# +# Instead of the real user shell, the program specified by this parameter +# will be launched, although its visible name (argv[0]) will be the shell's. +# The program may do whatever it wants (logging, additional authentification, +# banner, ...) before running the actual shell. +# +# FAKE_SHELL /bin/fakeshell + +# +# If defined, either full pathname of a file containing device names or +# a ":" delimited list of device names. Root logins will be allowed only +# upon these devices. +# +# This variable is used by login and su. +# +#CONSOLE /etc/consoles +#CONSOLE console:tty01:tty02:tty03:tty04 + +# +# List of groups to add to the user's supplementary group set +# when logging in on the console (as determined by the CONSOLE +# setting). Default is none. +# +# Use with caution - it is possible for users to gain permanent +# access to these groups, even when not logged in on the console. +# How to do it is left as an exercise for the reader... +# +# This variable is used by login and su. +# +#CONSOLE_GROUPS floppy:audio:cdrom + +# +# If set to "yes", new passwords will be encrypted using the MD5-based +# algorithm compatible with the one used by recent releases of FreeBSD. +# It supports passwords of unlimited length and longer salt strings. +# Set to "no" if you need to copy encrypted passwords to other systems +# which don't understand the new algorithm. Default is "no". +# +# This variable is deprecated. You should use ENCRYPT_METHOD. +# +#MD5_CRYPT_ENAB no + +# +# If set to MD5 , MD5-based algorithm will be used for encrypting password +# If set to SHA256, SHA256-based algorithm will be used for encrypting password +# If set to SHA512, SHA512-based algorithm will be used for encrypting password +# If set to DES, DES-based algorithm will be used for encrypting password (default) +# Overrides the MD5_CRYPT_ENAB option +# +# Note: It is recommended to use a value consistent with +# the PAM modules configuration. +# +ENCRYPT_METHOD SHA512 + +# +# Only used if ENCRYPT_METHOD is set to SHA256 or SHA512. +# +# Define the number of SHA rounds. +# With a lot of rounds, it is more difficult to brute forcing the password. +# But note also that it more CPU resources will be needed to authenticate +# users. +# +# If not specified, the libc will choose the default number of rounds (5000). +# The values must be inside the 1000-999999999 range. +# If only one of the MIN or MAX values is set, then this value will be used. +# If MIN > MAX, the highest value will be used. +# +# SHA_CRYPT_MIN_ROUNDS 5000 +# SHA_CRYPT_MAX_ROUNDS 5000 + +################# OBSOLETED BY PAM ############## +# # +# These options are now handled by PAM. Please # +# edit the appropriate file in /etc/pam.d/ to # +# enable the equivelants of them. +# +############### + +#MOTD_FILE +#DIALUPS_CHECK_ENAB +#LASTLOG_ENAB +#MAIL_CHECK_ENAB +#OBSCURE_CHECKS_ENAB +#PORTTIME_CHECKS_ENAB +#SU_WHEEL_ONLY +#CRACKLIB_DICTPATH +#PASS_CHANGE_TRIES +#PASS_ALWAYS_WARN +#ENVIRON_FILE +#NOLOGINS_FILE +#ISSUE_FILE +#PASS_MIN_LEN +#PASS_MAX_LEN +#ULIMIT +#ENV_HZ +#CHFN_AUTH +#CHSH_AUTH +#FAIL_DELAY + +################# OBSOLETED ####################### +# # +# These options are no more handled by shadow. # +# # +# Shadow utilities will display a warning if they # +# still appear. # +# # +################################################### + +# CLOSE_SESSIONS +# LOGIN_STRING +# NO_PASSWORD_CONSOLE +# QMAIL_DIR + + + From a4d452b7ee3823f8f18aec6d2b459c3d9adc5b6b Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Sun, 27 Mar 2022 14:29:37 +0300 Subject: [PATCH 08/18] add mock table to passw hardening unitest in result of changes in commit: 2919b4820f95bd49ceb579986f5c58b4e3532c6e --- src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index 4e271840b5fc..686140c6dbae 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -36,6 +36,7 @@ hostcfgd.SubscriberStateTable = MockSubscriberStateTable hostcfgd.Select = MockSelect hostcfgd.DBConnector = MockDBConnector +hostcfgd.Table = mock.Mock() AGE_DICT = { 'MAX_DAYS': {'REGEX_DAYS': r'^PASS_MAX_DAYS[ \t]*(?P\d*)', 'DAYS': 'max_days', 'CHAGE_FLAG': '-M '}, 'WARN_DAYS': {'REGEX_DAYS': r'^PASS_WARN_AGE[ \t]*(?P\d*)', 'DAYS': 'warn_days', 'CHAGE_FLAG': '-W '} From 93072bda525517c15682ce96a1b4e16a11822508 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Mon, 28 Mar 2022 13:47:21 +0300 Subject: [PATCH 09/18] fix credit disabled, by setting 0 instead to be clear --- src/sonic-host-services-data/templates/common-password.j2 | 4 ++-- .../PASSWORD_HARDENING_enable_digits_class/common-password | 4 ++-- .../PASSWORD_HARDENING_enable_feature/common-password | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sonic-host-services-data/templates/common-password.j2 b/src/sonic-host-services-data/templates/common-password.j2 index a580eb3854ac..c5fd3f889c24 100644 --- a/src/sonic-host-services-data/templates/common-password.j2 +++ b/src/sonic-host-services-data/templates/common-password.j2 @@ -26,9 +26,9 @@ {% if passw_policies %} {% if passw_policies['state'] == 'enabled' %} -password requisite pam_cracklib.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %}ucredit=-1{% endif %} {% if passw_policies['lower_class'] %}lcredit=-1{% endif %} {% if passw_policies['digits_class'] %}dcredit=-1{% endif %} {% if passw_policies['special_class'] %}ocredit=-1{% endif %} {% if passw_policies['reject_user_passw_match'] %}reject_username{% endif %} enforce_for_root +password requisite pam_cracklib.so retry=3 maxrepeat=0 {% if passw_policies['len_min'] %}minlen={{passw_policies['len_min']}}{% endif %} {% if passw_policies['upper_class'] %}ucredit=-1{% else %}ucredit=0{% endif %} {% if passw_policies['lower_class'] %}lcredit=-1{% else %}lcredit=0{% endif %} {% if passw_policies['digits_class'] %}dcredit=-1{% else %}dcredit=0{% endif %} {% if passw_policies['special_class'] %}ocredit=-1{% else %}ocredit=0{% endif %} {% if passw_policies['reject_user_passw_match'] %}reject_username{% endif %} enforce_for_root -password required pam_pwhistory.so {% if passw_policies['history_cnt'] %}remember={{passw_policies['history_cnt']}}{% endif %} use_authtok +password required pam_pwhistory.so {% if passw_policies['history_cnt'] %}remember={{passw_policies['history_cnt']}}{% endif %} use_authtok enforce_for_root {% endif %} {% endif %} diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password index 0c9a2623e7f5..841074728a4f 100644 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_digits_class/common-password @@ -24,9 +24,9 @@ # here are the per-package modules (the "Primary" block) -password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 dcredit=-1 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=0 use_authtok +password required pam_pwhistory.so remember=0 use_authtok enforce_for_root password [success=1 default=ignore] pam_unix.so obscure yescrypt # here's the fallback if no module succeeds diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password index cf8d40b15c75..a66c1b1ade0c 100644 --- a/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/PASSWORD_HARDENING_enable_feature/common-password @@ -26,7 +26,7 @@ password requisite pam_cracklib.so retry=3 maxrepeat=0 minlen=8 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 reject_username enforce_for_root -password required pam_pwhistory.so remember=10 use_authtok +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 From 3a8b5fd17d6fa92138131737aae008d14b9c708e Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Tue, 29 Mar 2022 14:45:54 +0300 Subject: [PATCH 10/18] removed unused import --- src/sonic-host-services/scripts/hostcfgd | 1 - src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py | 1 - src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index c25b5b1ea3cc..cd38a6b9f663 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -10,7 +10,6 @@ import syslog import signal import re import jinja2 -from datetime import datetime from sonic_py_common import device_info from swsscommon.swsscommon import SubscriberStateTable, DBConnector, Select from swsscommon.swsscommon import ConfigDBConnector, TableConsumable, Table diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index 686140c6dbae..faed0f187eea 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -6,7 +6,6 @@ import sys import subprocess import re -from swsscommon import swsscommon from parameterized import parameterized from unittest import TestCase, mock diff --git a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py index 4b7a20e9f053..acf1c7671125 100644 --- a/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py +++ b/src/sonic-host-services/tests/hostcfgd/test_passwh_vectors.py @@ -1,5 +1,3 @@ -from unittest.mock import call - """ hostcfgd test password hardening vector """ From e9c9edf06b897a014631b3d7568ad7907569a127 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Mon, 18 Apr 2022 17:49:20 +0300 Subject: [PATCH 11/18] passw-hardening, fix unitest mocks tables --- .../tests/hostcfgd/hostcfgd_passwh_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index faed0f187eea..adb8f565f88a 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -10,8 +10,7 @@ from parameterized import parameterized from unittest import TestCase, mock from tests.hostcfgd.test_passwh_vectors import HOSTCFGD_TEST_PASSWH_VECTOR -from tests.common.mock_configdb import MockConfigDb, MockSubscriberStateTable -from tests.common.mock_configdb import MockSelect, MockDBConnector +from tests.common.mock_configdb import MockConfigDb, MockDBConnector test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) modules_path = os.path.dirname(test_path) @@ -32,8 +31,6 @@ # Mock swsscommon classes hostcfgd.ConfigDBConnector = MockConfigDb -hostcfgd.SubscriberStateTable = MockSubscriberStateTable -hostcfgd.Select = MockSelect hostcfgd.DBConnector = MockDBConnector hostcfgd.Table = mock.Mock() From 8e507e86cd06f0a8bac64c6b9728000cae657d33 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Wed, 27 Apr 2022 10:28:04 +0300 Subject: [PATCH 12/18] [passw-hardening] remove misstype line in hostcfgd --- src/sonic-host-services/scripts/hostcfgd | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 4d17663de5e0..318817f1db5b 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -1282,7 +1282,6 @@ class HostConfigDaemon: passwh = init_data['PASSW_HARDENING'] self.feature_handler.sync_state_field(features) - self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server, passwh) self.iptables.load(lpbk_table) self.ntpcfg.load(ntp_global, ntp_server) From e4bfacee60f3529290690f17b6f978d7bf551c3f Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Thu, 28 Apr 2022 16:33:28 +0300 Subject: [PATCH 13/18] [password-hardening] move passw logic from AaaCfg class to PasswHardening class --- src/sonic-host-services/scripts/hostcfgd | 364 +++++++++--------- .../tests/hostcfgd/hostcfgd_passwh_test.py | 2 +- .../tests/hostcfgd/hostcfgd_radius_test.py | 7 +- .../tests/hostcfgd/hostcfgd_tacacs_test.py | 4 +- 4 files changed, 195 insertions(+), 182 deletions(-) diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 318817f1db5b..d7835bcf0936 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -497,9 +497,6 @@ class AaaCfg(object): self.tacplus_global = {} self.tacplus_servers = {} - self.passw_policies_default = {} - self.passw_policies = {} - self.radius_global_default = { 'priority': 0, 'auth_port': RADIUS_SERVER_AUTH_PORT_DEFAULT, @@ -520,11 +517,9 @@ class AaaCfg(object): self.hostname = "" # Load conf from ConfigDb - def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf, policies_conf): + def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf): for row in aaa_conf: self.aaa_update(row, aaa_conf[row], modify_conf=False) - for row in policies_conf: - self.passw_policies_update(row, policies_conf[row], modify_conf=False) for row in tac_global_conf: self.tacacs_global_update(row, tac_global_conf[row], modify_conf=False) for row in tacplus_conf: @@ -534,6 +529,7 @@ class AaaCfg(object): self.radius_global_update(row, rad_global_conf[row], modify_conf=False) for row in radius_conf: self.radius_server_update(row, radius_conf[row], modify_conf=False) + self.modify_conf_file() def aaa_update(self, key, data, modify_conf=True): @@ -550,31 +546,6 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() - def passw_policies_update(self, key, data, modify_conf=True): - syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - key: {}".format(key)) - syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - data: {}".format(data)) - - if data == {}: - self.passw_policies = {} - else: - # TODO: maybe create an input validation function for this val, and add val to all the inputs. - if 'reject_user_passw_match' in data: - data['reject_user_passw_match'] = is_true(data['reject_user_passw_match']) - if 'lower_class' in data: - data['lower_class'] = is_true(data['lower_class']) - if 'upper_class' in data: - data['upper_class'] = is_true(data['upper_class']) - if 'digits_class' in data: - data['digits_class'] = is_true(data['digits_class']) - if 'special_class' in data: - data['special_class'] = is_true(data['special_class']) - - if key == 'POLICIES': - self.passw_policies = data - - if modify_conf: - self.modify_conf_file() - def pick_src_intf_ipaddrs(self, keys, src_intf): new_ipv4_addr = "" new_ipv6_addr = "" @@ -598,135 +569,6 @@ class AaaCfg(object): return(new_ipv4_addr, new_ipv6_addr) - def set_passw_hardening_policies(self, passw_policies): - # Password Hardening flow - # When feature is enabled, the passw_policies from CONFIG_DB will be set in the pam files /etc/pam.d/common-password and /etc/login.def. - # When the feature is disabled, the files above will be generate with the linux default (without secured passw_policies). - syslog.syslog(syslog.LOG_DEBUG, "modify_conf_file: passw_policies - {}".format(passw_policies)) - - template_passwh_file = os.path.abspath(PAM_PASSWORD_CONF_TEMPLATE) - env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) - env.filters['sub'] = sub - template_passwh = env.get_template(template_passwh_file) - - # Render common-password file with passw hardening policies if any. Other render without them. - pam_passwh_conf = template_passwh.render(debug=self.debug, passw_policies=passw_policies) - - # Use rename(), which is atomic (on the same fs) to avoid empty file - with open(PAM_PASSWORD_CONF + ".tmp", 'w') as f: - f.write(pam_passwh_conf) - os.chmod(PAM_PASSWORD_CONF + ".tmp", 0o644) - os.rename(PAM_PASSWORD_CONF + ".tmp", PAM_PASSWORD_CONF) - - # Age policy - # When feature disabled or age policy disabled, expiry days policy should be as linux default, other, accoriding CONFIG_DB. - curr_expiration = LINUX_DEFAULT_PASS_MAX_DAYS - curr_expiration_warning = LINUX_DEFAULT_PASS_WARN_AGE - - if passw_policies: - if 'state' in passw_policies: - if passw_policies['state'] == 'enabled': - if 'expiration' in passw_policies: - if int(self.passw_policies['expiration']) != 0: # value '0' meaning age policy is disabled - # the logic is to modify the expiration time according the last updated modificatiion - # - curr_expiration = int(passw_policies['expiration']) - - if 'expiration_warning' in passw_policies: - if int(self.passw_policies['expiration_warning']) != 0: # value '0' meaning age policy is disabled - curr_expiration_warning = int(passw_policies['expiration_warning']) - - if self.is_passwd_aging_expire_update(curr_expiration, 'MAX_DAYS'): - # Set aging policy for existing users - self.passwd_aging_expire_modify(curr_expiration, 'MAX_DAYS') - - # Aging policy for new users - self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_MAX_DAYS/c\PASS_MAX_DAYS " +str(curr_expiration)+"\'"]) - - if self.is_passwd_aging_expire_update(curr_expiration_warning, 'WARN_DAYS'): - # Aging policy for existing users - self.passwd_aging_expire_modify(curr_expiration_warning, 'WARN_DAYS') - - # Aging policy for new users - self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_WARN_AGE/c\PASS_WARN_AGE " +str(curr_expiration_warning)+"\'"]) - - def passwd_aging_expire_modify(self, curr_expiration, age_type): - normal_accounts = self.get_normal_accounts() - if not normal_accounts: - syslog.syslog(syslog.LOG_ERR,"failed, no normal users found in /etc/passwd") - return - chage_flag = AGE_DICT[age_type]['CHAGE_FLAG'] - for normal_account in normal_accounts: - try: - chage_p_m = subprocess.Popen(('chage', chage_flag + str(curr_expiration), normal_account), stdout=subprocess.PIPE) - return_code_chage_p_m = chage_p_m.poll() - if return_code_chage_p_m != 0: - syslog.syslog(syslog.LOG_ERR, "failed: return code - {}".format(return_code_chage_p_m)) - - except subprocess.CalledProcessError as e: - syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(e.cmd, e.returncode, e.output)) - - def is_passwd_aging_expire_update(self, curr_expiration, age_type): - """ Function verify that the current age expiry policy values are equal from the old one - Return update_age_status 'True' value meaning that was a modification from the last time, and vice versa. - """ - update_age_status = False - regex_days = AGE_DICT[age_type]['REGEX_DAYS'] - days_type = AGE_DICT[age_type]['DAYS'] - - with open(ETC_LOGIN_DEF, 'r') as f: - login_def_data = f.readlines() - - for line in login_def_data: - m1 = re.match(regex_days, line) - if m1: - days_num = int(m1.group(days_type)) - break - - if curr_expiration != days_num: - update_age_status = True - - return update_age_status - - def get_normal_accounts(self): - # Get user list - try: - getent_out = subprocess.check_output(['getent', 'passwd']).decode('utf-8').split('\n') - except subprocess.CalledProcessError as err: - syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) - - # Get range of normal users - REGEX_UID_MAX = r'^UID_MAX[ \t]*(?P\d*)' - REGEX_UID_MIN = r'^UID_MIN[ \t]*(?P\d*)' - uid_max = None - uid_min = None - - with open(ETC_LOGIN_DEF, 'r') as f: - login_def_data = f.readlines() - - for line in login_def_data: - m1 = re.match(REGEX_UID_MAX, line) - m2 = re.match(REGEX_UID_MIN, line) - if m1: - uid_max = int(m1.group("uid_max")) - if m2: - uid_min = int(m2.group("uid_min")) - - if not uid_max or not uid_min: - syslog.syslog(syslog.LOG_ERR,"failed, no UID_MAX/UID_MIN founded in login.def file") - return False - - # Get normal user list - normal_accounts = [] - for account in getent_out[0:-1]: # last item is always empty - account_spl = account.split(':') - account_number = int(account_spl[2]) - if account_number >= uid_min and account_number <= uid_max: - normal_accounts.append(account_spl[ACCOUNT_NAME]) - - normal_accounts.append('root') # root is also a candidate to be age modify. - return normal_accounts - def tacacs_global_update(self, key, data, modify_conf=True): if key == 'global': self.tacplus_global = data @@ -860,12 +702,6 @@ class AaaCfg(object): cmd = "sed -e {0} {1} > {1}.new; mv -f {1} {1}.old; mv -f {1}.new {1}".format(' -e '.join(operations), filename) os.system(cmd) - def modify_single_file_inplace(self, filename, operations=None): - if operations: - cmd = "sed -i {0} {1}".format(' -i '.join(operations), filename) - syslog.syslog(syslog.LOG_DEBUG, "modify_single_file_inplace: cmd - {}".format(cmd)) - os.system(cmd) - def modify_conf_file(self): authentication = self.authentication_default.copy() authentication.update(self.authentication) @@ -873,8 +709,6 @@ class AaaCfg(object): authorization.update(self.authorization) accounting = self.accounting_default.copy() accounting.update(self.accounting) - passw_policies = self.passw_policies_default.copy() - passw_policies.update(self.passw_policies) tacplus_global = self.tacplus_global_default.copy() tacplus_global.update(self.tacplus_global) if 'src_ip' in tacplus_global: @@ -933,10 +767,6 @@ class AaaCfg(object): env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) env.filters['sub'] = sub template = env.get_template(template_file) - - # set new Password Hardening policies. - self.set_passw_hardening_policies(passw_policies) - if 'radius' in authentication['login']: pam_conf = template.render(debug=self.debug, trace=self.trace, auth=authentication, servers=radsrvs_conf) else: @@ -1041,6 +871,188 @@ class AaaCfg(object): .format(err.cmd, err.returncode, err.output)) +class PasswHardening(object): + def __init__(self): + self.passw_policies_default = {} + self.passw_policies = {} + + self.debug = False + self.trace = False + + def load(self, policies_conf): + for row in policies_conf: + self.passw_policies_update(row, policies_conf[row], modify_conf=False) + # TODO + self.modify_passw_conf_file() + + def passw_policies_update(self, key, data, modify_conf=True): + syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - key: {}".format(key)) + syslog.syslog(syslog.LOG_DEBUG, "passw_policies_update - data: {}".format(data)) + + if data == {}: + self.passw_policies = {} + else: + # TODO: maybe create an input validation function for this val, and add val to all the inputs. + if 'reject_user_passw_match' in data: + data['reject_user_passw_match'] = is_true(data['reject_user_passw_match']) + if 'lower_class' in data: + data['lower_class'] = is_true(data['lower_class']) + if 'upper_class' in data: + data['upper_class'] = is_true(data['upper_class']) + if 'digits_class' in data: + data['digits_class'] = is_true(data['digits_class']) + if 'special_class' in data: + data['special_class'] = is_true(data['special_class']) + + if key == 'POLICIES': + self.passw_policies = data + + if modify_conf: + self.modify_passw_conf_file() + + def modify_single_file_inplace(self, filename, operations=None): + if operations: + cmd = "sed -i {0} {1}".format(' -i '.join(operations), filename) + syslog.syslog(syslog.LOG_DEBUG, "modify_single_file_inplace: cmd - {}".format(cmd)) + os.system(cmd) + + def set_passw_hardening_policies(self, passw_policies): + # Password Hardening flow + # When feature is enabled, the passw_policies from CONFIG_DB will be set in the pam files /etc/pam.d/common-password and /etc/login.def. + # When the feature is disabled, the files above will be generate with the linux default (without secured passw_policies). + syslog.syslog(syslog.LOG_DEBUG, "modify_conf_file: passw_policies - {}".format(passw_policies)) + + template_passwh_file = os.path.abspath(PAM_PASSWORD_CONF_TEMPLATE) + env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) + env.filters['sub'] = sub + template_passwh = env.get_template(template_passwh_file) + + # Render common-password file with passw hardening policies if any. Other render without them. + pam_passwh_conf = template_passwh.render(debug=self.debug, passw_policies=passw_policies) + + # Use rename(), which is atomic (on the same fs) to avoid empty file + with open(PAM_PASSWORD_CONF + ".tmp", 'w') as f: + f.write(pam_passwh_conf) + os.chmod(PAM_PASSWORD_CONF + ".tmp", 0o644) + os.rename(PAM_PASSWORD_CONF + ".tmp", PAM_PASSWORD_CONF) + + # Age policy + # When feature disabled or age policy disabled, expiry days policy should be as linux default, other, accoriding CONFIG_DB. + curr_expiration = LINUX_DEFAULT_PASS_MAX_DAYS + curr_expiration_warning = LINUX_DEFAULT_PASS_WARN_AGE + + if passw_policies: + if 'state' in passw_policies: + if passw_policies['state'] == 'enabled': + if 'expiration' in passw_policies: + if int(self.passw_policies['expiration']) != 0: # value '0' meaning age policy is disabled + # the logic is to modify the expiration time according the last updated modificatiion + # + curr_expiration = int(passw_policies['expiration']) + + if 'expiration_warning' in passw_policies: + if int(self.passw_policies['expiration_warning']) != 0: # value '0' meaning age policy is disabled + curr_expiration_warning = int(passw_policies['expiration_warning']) + + if self.is_passwd_aging_expire_update(curr_expiration, 'MAX_DAYS'): + # Set aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration, 'MAX_DAYS') + + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_MAX_DAYS/c\PASS_MAX_DAYS " +str(curr_expiration)+"\'"]) + + if self.is_passwd_aging_expire_update(curr_expiration_warning, 'WARN_DAYS'): + # Aging policy for existing users + self.passwd_aging_expire_modify(curr_expiration_warning, 'WARN_DAYS') + + # Aging policy for new users + self.modify_single_file_inplace(ETC_LOGIN_DEF, ["\'/^PASS_WARN_AGE/c\PASS_WARN_AGE " +str(curr_expiration_warning)+"\'"]) + + def passwd_aging_expire_modify(self, curr_expiration, age_type): + normal_accounts = self.get_normal_accounts() + if not normal_accounts: + syslog.syslog(syslog.LOG_ERR,"failed, no normal users found in /etc/passwd") + return + chage_flag = AGE_DICT[age_type]['CHAGE_FLAG'] + for normal_account in normal_accounts: + try: + chage_p_m = subprocess.Popen(('chage', chage_flag + str(curr_expiration), normal_account), stdout=subprocess.PIPE) + return_code_chage_p_m = chage_p_m.poll() + if return_code_chage_p_m != 0: + syslog.syslog(syslog.LOG_ERR, "failed: return code - {}".format(return_code_chage_p_m)) + + except subprocess.CalledProcessError as e: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(e.cmd, e.returncode, e.output)) + + def is_passwd_aging_expire_update(self, curr_expiration, age_type): + """ Function verify that the current age expiry policy values are equal from the old one + Return update_age_status 'True' value meaning that was a modification from the last time, and vice versa. + """ + update_age_status = False + regex_days = AGE_DICT[age_type]['REGEX_DAYS'] + days_type = AGE_DICT[age_type]['DAYS'] + + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(regex_days, line) + if m1: + days_num = int(m1.group(days_type)) + break + + if curr_expiration != days_num: + update_age_status = True + + return update_age_status + + def get_normal_accounts(self): + # Get user list + try: + getent_out = subprocess.check_output(['getent', 'passwd']).decode('utf-8').split('\n') + except subprocess.CalledProcessError as err: + syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) + + # Get range of normal users + REGEX_UID_MAX = r'^UID_MAX[ \t]*(?P\d*)' + REGEX_UID_MIN = r'^UID_MIN[ \t]*(?P\d*)' + uid_max = None + uid_min = None + + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(REGEX_UID_MAX, line) + m2 = re.match(REGEX_UID_MIN, line) + if m1: + uid_max = int(m1.group("uid_max")) + if m2: + uid_min = int(m2.group("uid_min")) + + if not uid_max or not uid_min: + syslog.syslog(syslog.LOG_ERR,"failed, no UID_MAX/UID_MIN founded in login.def file") + return False + + # Get normal user list + normal_accounts = [] + for account in getent_out[0:-1]: # last item is always empty + account_spl = account.split(':') + account_number = int(account_spl[2]) + if account_number >= uid_min and account_number <= uid_max: + normal_accounts.append(account_spl[ACCOUNT_NAME]) + + normal_accounts.append('root') # root is also a candidate to be age modify. + return normal_accounts + + def modify_passw_conf_file(self): + passw_policies = self.passw_policies_default.copy() + passw_policies.update(self.passw_policies) + + # set new Password Hardening policies. + self.set_passw_hardening_policies(passw_policies) + + class KdumpCfg(object): def __init__(self, CfgDb): self.config_db = CfgDb @@ -1264,6 +1276,9 @@ class HostConfigDaemon: self.hostname_cache="" self.aaacfg = AaaCfg() + # Initialize PasswHardening + self.passwcfg = PasswHardening() + # Initialize PamLimitsCfg self.pamLimitsCfg = PamLimitsCfg(self.config_db) self.pamLimitsCfg.update_config_file() @@ -1282,10 +1297,11 @@ class HostConfigDaemon: passwh = init_data['PASSW_HARDENING'] self.feature_handler.sync_state_field(features) - self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server, passwh) + self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) self.iptables.load(lpbk_table) self.ntpcfg.load(ntp_global, ntp_server) self.kdumpCfg.load(kdump) + self.passwcfg.load(passwh) dev_meta = self.config_db.get_table('DEVICE_METADATA') if 'localhost' in dev_meta: @@ -1307,7 +1323,7 @@ class HostConfigDaemon: syslog.syslog(syslog.LOG_INFO, 'AAA Update: key: {}, op: {}, data: {}'.format(key, op, data)) def passwh_handler(self, key, op, data): - self.aaacfg.passw_policies_update(key, data) + self.passwcfg.passw_policies_update(key, data) syslog.syslog(syslog.LOG_INFO, 'PASSW_HARDENING Update: key: {}, op: {}, data: {}'.format(key, op, data)) def tacacs_server_handler(self, key, op, data): diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py index adb8f565f88a..da68ad861207 100755 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_passwh_test.py @@ -103,7 +103,7 @@ def check_config(self, test_name, test_data, config_name): syslog.syslog(syslog.LOG_ERR, "failed: get_table 'PASSW_HARDENING', exception={}".format(e)) passwh_table = [] - host_config_daemon.aaacfg.load([],[],[],[],[],passwh_table) + host_config_daemon.passwcfg.load(passwh_table) diff_output = "" diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py index 6951904deff2..c08cd1829add 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py @@ -53,16 +53,15 @@ def test_hostcfgd_radius(self, test_name, test_data): Returns: None """ + t_path = templates_path op_path = output_path + "/" + test_name sop_path = sample_output_path + "/" + test_name - hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" - hostcfgd.PAM_PASSWORD_CONF = op_path + "/common-password" hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" @@ -71,7 +70,6 @@ def test_hostcfgd_radius(self, test_name, test_data): hostcfgd.ETC_PAMD_LOGIN = op_path + "/login" hostcfgd.RADIUS_PAM_AUTH_CONF_DIR = op_path + "/" - # import pdb; pdb.set_trace() shutil.rmtree( op_path, ignore_errors=True) os.mkdir( op_path) @@ -92,7 +90,8 @@ def test_hostcfgd_radius(self, test_name, test_data): host_config_daemon.config_db.get_table('RADIUS_SERVER') except: radius_server = [] - host_config_daemon.aaacfg.load(aaa,[],[],radius_global,radius_server, []) + + host_config_daemon.aaacfg.load(aaa,[],[],radius_global,radius_server) dcmp = filecmp.dircmp(sop_path, op_path) diff_output = "" for name in dcmp.diff_files: diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py index 909937f55322..a6478c08dc0d 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_tacacs_test.py @@ -49,12 +49,10 @@ def check_config(self, test_name, test_data, config_name): op_path = output_path + "/" + test_name + "_" + config_name sop_path = sample_output_path + "/" + test_name + "_" + config_name - hostcfgd.PAM_PASSWORD_CONF_TEMPLATE = t_path + "/common-password.j2" hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" - hostcfgd.PAM_PASSWORD_CONF = op_path + "/common-password" hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" @@ -84,7 +82,7 @@ def check_config(self, test_name, test_data, config_name): except: tacacs_server = [] - host_config_daemon.aaacfg.load(aaa,tacacs_global,tacacs_server,[],[],[]) + host_config_daemon.aaacfg.load(aaa,tacacs_global,tacacs_server,[],[]) dcmp = filecmp.dircmp(sop_path, op_path) diff_output = "" for name in dcmp.diff_files: From 458691cedc2a3127eb93c0b8a329885a860b1114 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Sun, 1 May 2022 10:40:10 +0300 Subject: [PATCH 14/18] [password-hardening]fix few comments from PR: https://github.com/Azure/sonic-buildimage/pull/10323/files about verification when reading files and add exception --- src/sonic-host-services/scripts/hostcfgd | 45 ++++++++++++------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index d7835bcf0936..fe111c9a9012 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -882,7 +882,7 @@ class PasswHardening(object): def load(self, policies_conf): for row in policies_conf: self.passw_policies_update(row, policies_conf[row], modify_conf=False) - # TODO + self.modify_passw_conf_file() def passw_policies_update(self, key, data, modify_conf=True): @@ -892,7 +892,6 @@ class PasswHardening(object): if data == {}: self.passw_policies = {} else: - # TODO: maybe create an input validation function for this val, and add val to all the inputs. if 'reject_user_passw_match' in data: data['reject_user_passw_match'] = is_true(data['reject_user_passw_match']) if 'lower_class' in data: @@ -989,17 +988,18 @@ class PasswHardening(object): Return update_age_status 'True' value meaning that was a modification from the last time, and vice versa. """ update_age_status = False + days_num = None regex_days = AGE_DICT[age_type]['REGEX_DAYS'] days_type = AGE_DICT[age_type]['DAYS'] - - with open(ETC_LOGIN_DEF, 'r') as f: - login_def_data = f.readlines() - - for line in login_def_data: - m1 = re.match(regex_days, line) - if m1: - days_num = int(m1.group(days_type)) - break + if os.path.exists(ETC_LOGIN_DEF): + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(regex_days, line) + if m1: + days_num = int(m1.group(days_type)) + break if curr_expiration != days_num: update_age_status = True @@ -1012,23 +1012,24 @@ class PasswHardening(object): getent_out = subprocess.check_output(['getent', 'passwd']).decode('utf-8').split('\n') except subprocess.CalledProcessError as err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}".format(err.cmd, err.returncode, err.output)) + return False # Get range of normal users REGEX_UID_MAX = r'^UID_MAX[ \t]*(?P\d*)' REGEX_UID_MIN = r'^UID_MIN[ \t]*(?P\d*)' uid_max = None uid_min = None - - with open(ETC_LOGIN_DEF, 'r') as f: - login_def_data = f.readlines() - - for line in login_def_data: - m1 = re.match(REGEX_UID_MAX, line) - m2 = re.match(REGEX_UID_MIN, line) - if m1: - uid_max = int(m1.group("uid_max")) - if m2: - uid_min = int(m2.group("uid_min")) + if os.path.exists(ETC_LOGIN_DEF): + with open(ETC_LOGIN_DEF, 'r') as f: + login_def_data = f.readlines() + + for line in login_def_data: + m1 = re.match(REGEX_UID_MAX, line) + m2 = re.match(REGEX_UID_MIN, line) + if m1: + uid_max = int(m1.group("uid_max")) + if m2: + uid_min = int(m2.group("uid_min")) if not uid_max or not uid_min: syslog.syslog(syslog.LOG_ERR,"failed, no UID_MAX/UID_MIN founded in login.def file") From eb977ca66f27e9453757c38f9654a2f08311ce69 Mon Sep 17 00:00:00 2001 From: Shilong Liu Date: Mon, 30 May 2022 16:02:27 +0800 Subject: [PATCH 15/18] [ci] Publish logs when building image job is canceled by timeout. (#10919) --- .azure-pipelines/azure-pipelines-image-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/azure-pipelines-image-template.yml b/.azure-pipelines/azure-pipelines-image-template.yml index ae103ed28868..9df0b3ffa6ef 100644 --- a/.azure-pipelines/azure-pipelines-image-template.yml +++ b/.azure-pipelines/azure-pipelines-image-template.yml @@ -55,7 +55,7 @@ jobs: artifact: 'sonic-buildimage.$(GROUP_NAME)$(GROUP_EXTNAME)' displayName: "Archive sonic image" - publish: $(Build.ArtifactStagingDirectory) - condition: failed() + condition: or(failed(), canceled()) artifact: 'sonic-buildimage.$(GROUP_NAME)$(GROUP_EXTNAME)$(System.JobAttempt)' displayName: "Archive failed sonic image" - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: From 0f423afbac7e6c1dbe9d8209dde3c2abae7f06a9 Mon Sep 17 00:00:00 2001 From: Guohan Lu Date: Mon, 30 May 2022 01:25:51 -0700 Subject: [PATCH 16/18] [CODEOWNERS]: update code owners for various repos (#10980) Signed-off-by: Guohan Lu --- .github/CODEOWNERS | 74 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6482f6837d16..5a3aad7b8f69 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,9 @@ /Makefile.work @qiluo-msft @xumia @lguohan /slave.mk @qiluo-msft @xumia @lguohan /scripts @qiluo-msft @xumia @lguohan +/src/sonic-build-hooks/ @Azure/sonic-build +/src/debootstrap/ @Azure/sonic-build +/src/sonic-fips/ @Azure/sonic-build # installer /installer/ @qiluo-msft @@ -31,8 +34,77 @@ /dockers/docker-config-engine*/ @qiluo-msft /dockers/docker-snmp/ @qiluo-msft -# src +# kernel +/src/sonic-linux-kernel/ @Azure/sonic-kernel + +# devices +/device/ @Azure/sonic-platform +/src/sonic-platform-common/ @Azure/sonic-platform +/src/sonic-platform-daemons/ @Azure/sonic-platform +/src/sonic-platform-pde/ @Azure/sonic-platform +/src/lm-sensors/ @Azure/sonic-platform +/src/flashrom/ @Azure/sonic-platform + +# common library /src/initramfs-tools/ @qiluo-msft +/src/redis-dump-load/ @Azure/sonic-management +/src/sonic-py-common/ @Azure/sonic-management +/src/sonic-py-swsssdk/ @Azure/sonic-management +/src/sonic-swss-common/ @Azure/sonic-management +/src/bash/ @Azure/sonic-management +/src/tacacs/ @Azure/sonic-management +/src/radius/ @Azure/sonic-management +/src/swig/ @Azure/sonic-management +/src/socat/ @Azure/sonic-management + +# redis +/src/redis/ @Azure/sonic-management +/src/hiredis/ @Azure/sonic-management # yang /src/sonic-yang-models/ @praveen-li @dgsudharsan @rathnasabapathyv @venkatmahalingam @qiluo-msft +/src/sonic-yang-mgmt/ @Azure/sonic-management +/src/libyang/ @Azure/sonic-management +/src/libyang1/ @Azure/sonic-management +/src/libyang2/ @Azure/sonic-management + +# bgpcfgd +/src/sonic-bgpcfgd/ @StormLiangMS + +# sonic-config-engine +/src/sonic-config-engine/ @Azure/sonic-management + +# sonic-utilities +/src/sonic-utilities/ @Azure/sonic-management + +# sonic-telemetry +/src/sonic-telemetry/ @Azure/sonic-management + +# snmp +/src/sonic-snmpagent/ @Azure/sonic-management +/src/snmpd/ @Azure/sonic-management + +# dhcp relay +/src/dhcp6relay/ @Azure/sonic-fundamentals +/src/dhcpmon/ @Azure/sonic-fundamentals +/src/isc-dhcp/ @Azure/sonic-fundamentals + +# sflow +/src/sflow/ @Azure/sonic-dataplane + +# sonic restapi +/src/sonic-restapi/ @Azure/sonic-dataplane + +# sonic swss +/src/sonic-swss/ @Azure/sonic-dataplane + +# linux networking, e.g., libnl3, iproute2, ifupdown2, ethtool +/src/libnl3/ @Azure/sonic-dataplane +/src/iproute2/ @Azure/sonic-dataplane +/src/ifupdown2/ @Azure/sonic-dataplane +/src/ethtool/ @Azure/sonic-dataplane + +# ptf +/src/ptf/ @Azure/sonic-fundamentals +/src/ptf-py3/ @Azure/sonic-fundamentals +/src/scapy/ @Azure/sonic-fundamentals From 8c4ef501540ff126dc268777fdcaaa56a747c3f0 Mon Sep 17 00:00:00 2001 From: xumia <59720581+xumia@users.noreply.github.com> Date: Mon, 30 May 2022 16:50:06 +0800 Subject: [PATCH 17/18] [Ci]: Fix the target directory not empty issue when publishing artifacts #10972 Why I did it Fix the target directory not empty issue when publishing artifacts. Some of the artifacts are published to $(Build.ArtifactStagingDirectory)/target/ before source code checked out. --- .azure-pipelines/azure-pipelines-image-template.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.azure-pipelines/azure-pipelines-image-template.yml b/.azure-pipelines/azure-pipelines-image-template.yml index 9df0b3ffa6ef..6a9c2790572f 100644 --- a/.azure-pipelines/azure-pipelines-image-template.yml +++ b/.azure-pipelines/azure-pipelines-image-template.yml @@ -48,7 +48,9 @@ jobs: ENABLE_DOCKER_BASE_PULL=y make PLATFORM=$(PLATFORM_AZP) PLATFORM_ARCH=$(PLATFORM_ARCH) $(BUILD_OPTIONS) configure displayName: 'Make configure' postSteps: - - script: mv target $(Build.ArtifactStagingDirectory)/ + - script: | + mkdir -p $(Build.ArtifactStagingDirectory)/target + mv target/* $(Build.ArtifactStagingDirectory)/target/ displayName: Copy Artifacts condition: always() - publish: $(Build.ArtifactStagingDirectory) From 29672479ee64d4cf9e2bc1276facdb537883c4e5 Mon Sep 17 00:00:00 2001 From: David Pilnik Date: Tue, 31 May 2022 14:43:38 +0300 Subject: [PATCH 18/18] [password-hardening]install cracklib from debian repo list instead download debian --- files/build_templates/sonic_debian_extension.j2 | 5 ++--- rules/cracklib.deb | 9 --------- rules/cracklib.mk | 10 ---------- slave.mk | 1 - 4 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 rules/cracklib.deb delete mode 100644 rules/cracklib.mk diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index f2b2269c9505..0adb2fe2beda 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -266,9 +266,8 @@ fi sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/sonic-device-data_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f -# Install cracklib (and its dependencies via 'apt-get -y install -f') -sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-cracklib_*.deb || \ - sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f +# package for supporting password hardening +sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install libpam-cracklib # Install pam-tacplus and nss-tacplus sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libtac2_*.deb || \ diff --git a/rules/cracklib.deb b/rules/cracklib.deb deleted file mode 100644 index 6065ddd81754..000000000000 --- a/rules/cracklib.deb +++ /dev/null @@ -1,9 +0,0 @@ -SPATH := $($(LIBPAM_CRACKLIB)_SRC_PATH) -DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/cracklib.mk rules/cracklib.dep -DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) -DEP_FILES += $(shell git ls-files $(SPATH)) - -$(SOCAT)_CACHE_MODE := GIT_CONTENT_SHA -$(SOCAT)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) -$(SOCAT)_DEP_FILES := $(DEP_FILES) - diff --git a/rules/cracklib.mk b/rules/cracklib.mk deleted file mode 100644 index 0e1f24d15cbb..000000000000 --- a/rules/cracklib.mk +++ /dev/null @@ -1,10 +0,0 @@ -# CRACKLIB packages - -PAM_CRACKLIB_VERSION = 1.4.0-9+deb11u1 -export PAM_CRACKLIB_VERSION - -LIBPAM_CRACKLIB = libpam-cracklib_$(PAM_CRACKLIB_VERSION)_$(CONFIGURED_ARCH).deb - -$(LIBPAM_CRACKLIB)_URL = "http://http.us.debian.org/debian/pool/main/p/pam/$(LIBPAM_CRACKLIB)" - -SONIC_ONLINE_DEBS += $(LIBPAM_CRACKLIB) diff --git a/slave.mk b/slave.mk index 1cd09a08c044..7b91ba0384c0 100644 --- a/slave.mk +++ b/slave.mk @@ -1037,7 +1037,6 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(addprefix $(IMAGE_DISTRO_DEBS_PATH)/,$(INITRAMFS_TOOLS) \ $(LINUX_KERNEL) \ $(SONIC_DEVICE_DATA) \ - $(LIBPAM_CRACKLIB) \ $(IFUPDOWN2) \ $(KDUMP_TOOLS) \ $(NTP) \