From a20b43e50227109e2d470e7a6f0c3e421b1e0903 Mon Sep 17 00:00:00 2001 From: Hua Liu <58683130+liuh-80@users.noreply.github.com> Date: Mon, 27 Mar 2023 00:30:05 -0700 Subject: [PATCH] [202012] Check config file not empty after modify it in hostcfgd. (#14385) **What I did** Check /etc/pam.d/sshd integrity after modify it in hostcfgd. **Why I did it** Found some incident that /etc/pam.d/sshd become empty file during OR upgrade. **How I verified it** Pass all UT. Add new UT to cover new code. **Details if related** This is a manually cherry-pick PR for https://github.com/sonic-net/sonic-host-services/pull/36 --- src/sonic-host-services/scripts/hostcfgd | 15 +++++ .../tests/hostcfgd/hostcfgd_test.py | 67 ++++++++++++++++++- .../tests/hostcfgd/sample_output/sshd_empty | 0 .../hostcfgd/sample_output/sshd_not_empty | 1 + 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/sshd_empty create mode 100644 src/sonic-host-services/tests/hostcfgd/sample_output/sshd_not_empty diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index a14d4cf026..21f80275df 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -210,11 +210,26 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() + def check_file_not_empty(self, filename): + exists = os.path.exists(filename) + if not exists: + syslog.syslog(syslog.LOG_ERR, "file size check failed: {} is missing".format(filename)) + return + + size = os.path.getsize(filename) + if size == 0: + syslog.syslog(syslog.LOG_ERR, "file size check failed: {} is empty, file corrupted".format(filename)) + return + + syslog.syslog(syslog.LOG_INFO, "file size check pass: {} size is ({}) bytes".format(filename, size)) + def modify_single_file(self, filename, operations=None): if operations: 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) + self.check_file_not_empty(filename) + def modify_conf_file(self): auth = self.auth_default.copy() auth.update(self.auth) diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py index 1608f79e59..93eccc2d6a 100644 --- a/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_test.py @@ -225,4 +225,69 @@ class TestHostcfgdDaemon(TestCase): expected = [call('sonic-kdump-config --disable', shell=True), call('sonic-kdump-config --num_dumps 3', shell=True), call('sonic-kdump-config --memory 0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M', shell=True)] - mocked_subprocess.check_call.assert_has_calls(expected, any_order=True) \ No newline at end of file + mocked_subprocess.check_call.assert_has_calls(expected, any_order=True) + +class TesAaaCfgd(TestCase): + """ + Test hostcfd daemon - AaaCfgd + """ + def setUp(self): + MockConfigDb.CONFIG_DB['NTP'] = {'global': {'vrf': 'mgmt', 'src_intf': 'eth0'}} + MockConfigDb.CONFIG_DB['NTP_SERVER'] = {'0.debian.pool.ntp.org': {}} + + def tearDown(self): + MockConfigDb.CONFIG_DB = {} + + def test_aaa_sshd_not_empty(self): + with mock.patch('hostcfgd.subprocess') as mocked_subprocess: + popen_mock = mock.Mock() + attrs = {'communicate.return_value': ('output', 'error')} + popen_mock.configure_mock(**attrs) + mocked_subprocess.Popen.return_value = popen_mock + + test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sample_output_path = os.path.join(test_path, "hostcfgd/sample_output/") + + aaacfgd = hostcfgd.AaaCfg() + aaacfgd.modify_single_file(os.path.join(sample_output_path, "sshd_not_empty")) + + # render with empty sshd config file and check error log + test_file = os.path.join(sample_output_path, "sshd_empty") + original_syslog = hostcfgd.syslog + with mock.patch('hostcfgd.syslog.syslog') as mocked_syslog: + mocked_syslog.LOG_ERR = original_syslog.LOG_ERR + aaacfgd.modify_single_file(test_file) + + # check sys log + expected = [ + mock.call(mocked_syslog.LOG_ERR, "file size check failed: {} is empty, file corrupted".format(test_file)) + ] + mocked_syslog.assert_has_calls(expected) + + # check with missing sshd config file and check error log + test_file = os.path.join(sample_output_path, "sshd_missing") + with mock.patch('hostcfgd.syslog.syslog') as mocked_syslog: + mocked_syslog.LOG_ERR = original_syslog.LOG_ERR + + # missing file can't test by render config file, + # because missing file case difficult to reproduce: code always generate a empty file. + aaacfgd.check_file_not_empty(test_file) + + # check sys log + expected = [ + mock.call(mocked_syslog.LOG_ERR, "file size check failed: {} is missing".format(test_file)) + ] + mocked_syslog.assert_has_calls(expected) + + # render with empty sshd config file and check error log + test_file = os.path.join(sample_output_path, "sshd_not_empty") + original_syslog = hostcfgd.syslog + with mock.patch('hostcfgd.syslog.syslog') as mocked_syslog: + mocked_syslog.LOG_INFO = original_syslog.LOG_INFO + aaacfgd.modify_single_file(test_file) + + # check sys log + expected = [ + mock.call(mocked_syslog.LOG_INFO, "file size check pass: {} size is ({}) bytes".format(test_file, 21)) + ] + mocked_syslog.assert_has_calls(expected) diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/sshd_empty b/src/sonic-host-services/tests/hostcfgd/sample_output/sshd_empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/sshd_not_empty b/src/sonic-host-services/tests/hostcfgd/sample_output/sshd_not_empty new file mode 100644 index 0000000000..94fb85d3ad --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/sshd_not_empty @@ -0,0 +1 @@ +#SSHD NOT EMPTY TEST