From 958f989ec877cf8fb13cd46e55d96af64a49faa2 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 26 Aug 2026 15:21:40 +0200 Subject: [PATCH 1/4] tests/system/framework/roles/shadow.py: implement binding for `pwck` Signed-off-by: Iker Pedrosa --- tests/system/framework/roles/shadow.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/system/framework/roles/shadow.py b/tests/system/framework/roles/shadow.py index 3d91271fa4..fd299a9275 100644 --- a/tests/system/framework/roles/shadow.py +++ b/tests/system/framework/roles/shadow.py @@ -548,3 +548,19 @@ def vipw(self, *args, editor_script: str | None = None) -> ProcessResult: self.host.discard_file(file_to_discard) return result + + def pwck(self, *args) -> ProcessResult: + """ + Verify the integrity of password files. + + The pwck command verifies that all entries in /etc/passwd and /etc/shadow + have the proper format and contain valid data. + """ + cmd_args = " ".join(args) + self.logger.info(f"Running pwck on {self.host.hostname}") + cmd = self.host.conn.run(f"pwck {cmd_args}", log_level=ProcessLogLevel.Error) + + self.host.discard_file("/etc/passwd") + self.host.discard_file("/etc/shadow") + + return cmd From 5e1b0b8b734990586a6f3e41cb0195f64fa81ace Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Wed, 26 Aug 2026 16:19:46 +0200 Subject: [PATCH 2/4] Tests: add pwck test for clean password files Signed-off-by: Iker Pedrosa --- tests/system/data/test_pwck/group | 3 ++ tests/system/data/test_pwck/passwd | 3 ++ tests/system/data/test_pwck/shadow | 3 ++ tests/system/tests/test_pwck.py | 48 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 tests/system/data/test_pwck/group create mode 100644 tests/system/data/test_pwck/passwd create mode 100644 tests/system/data/test_pwck/shadow create mode 100644 tests/system/tests/test_pwck.py diff --git a/tests/system/data/test_pwck/group b/tests/system/data/test_pwck/group new file mode 100644 index 0000000000..027ae5cc5f --- /dev/null +++ b/tests/system/data/test_pwck/group @@ -0,0 +1,3 @@ +root:x:0: +daemon:x:1: +ci:x:1000: diff --git a/tests/system/data/test_pwck/passwd b/tests/system/data/test_pwck/passwd new file mode 100644 index 0000000000..d7f15d0c8f --- /dev/null +++ b/tests/system/data/test_pwck/passwd @@ -0,0 +1,3 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/bin/sh +ci:x:1000:1000::/home/ci:/bin/bash diff --git a/tests/system/data/test_pwck/shadow b/tests/system/data/test_pwck/shadow new file mode 100644 index 0000000000..c042df3db9 --- /dev/null +++ b/tests/system/data/test_pwck/shadow @@ -0,0 +1,3 @@ +root:$1$NBLBLIXb$WUgojj1bNuxWEADQGt1m9.:19999:0:99999:7::: +daemon:*:19999:0:99999:7::: +ci:!:20692:0:99999:7::: diff --git a/tests/system/tests/test_pwck.py b/tests/system/tests/test_pwck.py new file mode 100644 index 0000000000..96d58cf007 --- /dev/null +++ b/tests/system/tests/test_pwck.py @@ -0,0 +1,48 @@ +""" +Test pwck +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from framework.roles.shadow import Shadow +from framework.topology import KnownTopology + + +@pytest.mark.topology(KnownTopology.Shadow) +def test_pwck__validates_clean_files(shadow: Shadow): + """ + :title: pwck validates clean password files in read-only mode + :setup: + 1. Remove existing passwd, shadow and group files + 2. Copy passwd, shadow and group files + :steps: + 1. Run pwck integrity check in read-only mode + 2. Verify command exit status + :expectedresults: + 1. pwck completes successfully + 2. Command succeeds + :cleanup: + 1. Restore original /etc/group + :customerscenario: False + """ + shadow.host.fs.rm("/etc/passwd") + shadow.host.fs.rm("/etc/shadow") + shadow.host.fs.rm("/etc/group") + + test_dir = Path(__file__).parent.parent + passwd_file = test_dir / "data" / "test_pwck" / "passwd" + shadow.host.fs.upload(str(passwd_file.resolve()), "/etc/passwd") + shadow_file = test_dir / "data" / "test_pwck" / "shadow" + shadow.host.fs.upload(str(shadow_file.resolve()), "/etc/shadow") + group_file = test_dir / "data" / "test_pwck" / "group" + shadow.host.fs.upload(str(group_file.resolve()), "/etc/group") + + result = shadow.pwck("-r") + assert result.rc == 0 + + # The modified /etc/group will cause the test to fail during teardown, thus restore it + shadow.host.fs.restore("/etc/group") From 2f52cd70707695807722e386f8b1ee541160b575 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 27 Aug 2026 13:01:50 +0200 Subject: [PATCH 3/4] tests/system/framework/roles/shadow.py: implement binding for `grpck` Signed-off-by: Iker Pedrosa --- tests/system/framework/roles/shadow.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/system/framework/roles/shadow.py b/tests/system/framework/roles/shadow.py index fd299a9275..0779c8a562 100644 --- a/tests/system/framework/roles/shadow.py +++ b/tests/system/framework/roles/shadow.py @@ -564,3 +564,19 @@ def pwck(self, *args) -> ProcessResult: self.host.discard_file("/etc/shadow") return cmd + + def grpck(self, *args) -> ProcessResult: + """ + Verify the integrity of group files. + + The grpck command verifies that all entries in /etc/group and /etc/gshadow + have the proper format and contain valid data. + """ + cmd_args = " ".join(args) + self.logger.info(f"Running grpck on {self.host.hostname}") + cmd = self.host.conn.run(f"grpck {cmd_args}", log_level=ProcessLogLevel.Error) + + self.host.discard_file("/etc/group") + self.host.discard_file("/etc/gshadow") + + return cmd From 53623c8d699996417ff5210f7bd64b70c7be00b9 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Thu, 27 Aug 2026 14:30:05 +0200 Subject: [PATCH 4/4] Tests: add grpck test for clean password files Signed-off-by: Iker Pedrosa --- tests/system/data/test_grpck/group | 3 +++ tests/system/data/test_grpck/gshadow | 3 +++ tests/system/tests/test_grpck.py | 40 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 tests/system/data/test_grpck/group create mode 100644 tests/system/data/test_grpck/gshadow create mode 100644 tests/system/tests/test_grpck.py diff --git a/tests/system/data/test_grpck/group b/tests/system/data/test_grpck/group new file mode 100644 index 0000000000..c5cb28c9b0 --- /dev/null +++ b/tests/system/data/test_grpck/group @@ -0,0 +1,3 @@ +root:x:0: +daemon:x:1: +tgroup1:x:1001: diff --git a/tests/system/data/test_grpck/gshadow b/tests/system/data/test_grpck/gshadow new file mode 100644 index 0000000000..ebd6e1a559 --- /dev/null +++ b/tests/system/data/test_grpck/gshadow @@ -0,0 +1,3 @@ +root:*:root: +daemon:*:: +tgroup1:$1$yQnIAZWV$gDAMB2IkqaONgrQiRdo4y.:: diff --git a/tests/system/tests/test_grpck.py b/tests/system/tests/test_grpck.py new file mode 100644 index 0000000000..3062b9eafc --- /dev/null +++ b/tests/system/tests/test_grpck.py @@ -0,0 +1,40 @@ +""" +Test grpck +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from framework.roles.shadow import Shadow +from framework.topology import KnownTopology + + +@pytest.mark.topology(KnownTopology.Shadow) +def test_grpck__validates_clean_files(shadow: Shadow): + """ + :title: grpck validates clean group files in read-only mode + :setup: + 1. Remove existing group and gshadow files + 2. Copy group and gshadow files + :steps: + 1. Run grpck integrity check in read-only mode + 2. Verify command exit status + :expectedresults: + 1. grpck completes successfully + 2. Command succeeds + :customerscenario: False + """ + shadow.host.fs.rm("/etc/group") + shadow.host.fs.rm("/etc/gshadow") + + test_dir = Path(__file__).parent.parent + group_file = test_dir / "data" / "test_grpck" / "group" + shadow.host.fs.upload(str(group_file.resolve()), "/etc/group") + gshadow_file = test_dir / "data" / "test_grpck" / "gshadow" + shadow.host.fs.upload(str(gshadow_file.resolve()), "/etc/gshadow") + + result = shadow.grpck("-r") + assert result.rc == 0