From ed85d08af83ddb6f25cf0fe18efca481c46874a6 Mon Sep 17 00:00:00 2001 From: Dennis Lanov Date: Wed, 15 Jul 2026 17:56:18 -0500 Subject: [PATCH] hostcfgd: wait for chage before checking status Wait for the chage process to finish before evaluating its return code, preventing successful password-aging updates from logging false errors. Add focused tests for successful and failed chage execution. Signed-off-by: Dennis Lanov --- scripts/hostcfgd | 2 +- tests/hostcfgd/hostcfgd_passwh_test.py | 39 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/scripts/hostcfgd b/scripts/hostcfgd index d55f1811..0359d2aa 100644 --- a/scripts/hostcfgd +++ b/scripts/hostcfgd @@ -966,7 +966,7 @@ class PasswHardening(object): 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() + return_code_chage_p_m = chage_p_m.wait() if return_code_chage_p_m != 0: syslog.syslog(syslog.LOG_ERR, "failed: return code - {}".format(return_code_chage_p_m)) diff --git a/tests/hostcfgd/hostcfgd_passwh_test.py b/tests/hostcfgd/hostcfgd_passwh_test.py index 67dbf16b..574b7a6d 100755 --- a/tests/hostcfgd/hostcfgd_passwh_test.py +++ b/tests/hostcfgd/hostcfgd_passwh_test.py @@ -5,6 +5,7 @@ import os import sys import subprocess +import syslog import re from parameterized import parameterized @@ -180,3 +181,41 @@ def test_hostcfgd_passwh_classes(self, test_name, test_data): """ self.check_config(test_name, test_data, "enable_digits_class") + + def test_passwd_aging_expire_modify_success(self): + """ + Test passwd_aging_expire_modify() does not log an error when chage succeeds + """ + passwh = hostcfgd.PasswHardening() + passwh.get_normal_accounts = mock.Mock(return_value=["testuser"]) + + with mock.patch('hostcfgd.subprocess.Popen') as mock_popen, \ + mock.patch('hostcfgd.syslog.syslog') as mock_syslog: + mock_popen.return_value.wait.return_value = 0 + + passwh.passwd_aging_expire_modify(100, 'MAX_DAYS') + + mock_popen.assert_called_once_with(('chage', '-M 100', 'testuser'), stdout=subprocess.PIPE) + mock_popen.return_value.wait.assert_called_once_with() + + mock_syslog.assert_not_called() + + def test_passwd_aging_expire_modify_failure(self): + """ + Test passwd_aging_expire_modify() logs an error when chage exits with a nonzero status + """ + passwh = hostcfgd.PasswHardening() + passwh.get_normal_accounts = mock.Mock(return_value=["testuser"]) + + with mock.patch('hostcfgd.subprocess.Popen') as mock_popen, \ + mock.patch('hostcfgd.syslog.syslog') as mock_syslog: + mock_popen.return_value.wait.return_value = 1 + + passwh.passwd_aging_expire_modify(100, 'MAX_DAYS') + + mock_popen.assert_called_once_with(('chage', '-M 100', 'testuser'), stdout=subprocess.PIPE) + mock_popen.return_value.wait.assert_called_once_with() + mock_syslog.assert_called_once_with( + syslog.LOG_ERR, + "failed: return code - 1" + )