Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tests/system/data/test_grpconv/group_old_format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:*:0:
daemon:*:1:
tgroup1:$1$yQnIAZWV$gDAMB2IkqaONgrQiRdo4y.:1001:
3 changes: 3 additions & 0 deletions tests/system/data/test_grpunconv/group
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:x:0:
daemon:x:1:
tgroup1:x:1001:
3 changes: 3 additions & 0 deletions tests/system/data/test_grpunconv/gshadow
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:*:root:
daemon:*::
tgroup1:$1$yQnIAZWV$gDAMB2IkqaONgrQiRdo4y.::
3 changes: 3 additions & 0 deletions tests/system/data/test_pwconv/passwd_old_format
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:$1$NBLBLIXb$WUgojj1bNuxWEADQGt1m9.:0:0:root:/root:/bin/bash
daemon:*:1:1:daemon:/usr/sbin:/bin/sh
tuser1:$1$yQnIAZWV$gDAMB2IkqaONgrQiRdo4y.:1001:1001::/home/tuser1:/bin/bash
3 changes: 3 additions & 0 deletions tests/system/data/test_pwunconv/passwd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
tuser1:x:1001:1001::/home/tuser1:/bin/bash
3 changes: 3 additions & 0 deletions tests/system/data/test_pwunconv/shadow
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:$1$NBLBLIXb$WUgojj1bNuxWEADQGt1m9.:19999:0:99999:7:::
daemon:*:19999:0:99999:7:::
tuser1:$1$yQnIAZWV$gDAMB2IkqaONgrQiRdo4y.:19999:0:99999:7:::
52 changes: 52 additions & 0 deletions tests/system/framework/roles/shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,55 @@ def vipw(self, *args, editor_script: str | None = None) -> ProcessResult:
self.host.discard_file(file_to_discard)

return result

def pwconv(self, *args) -> ProcessResult:
"""
Convert passwd file to shadow password format.
"""
cmd_args = " ".join(args) if args else ""
self.logger.info(f"Converting passwd file to shadow format on {self.host.hostname}")
cmd = self.host.conn.run(f"pwconv {cmd_args}", log_level=ProcessLogLevel.Error)

self.host.discard_file("/etc/passwd")
self.host.discard_file("/etc/shadow")

return cmd

def pwunconv(self, *args) -> ProcessResult:
"""
Convert shadow password format back to passwd file.
"""
cmd_args = " ".join(args) if args else ""
self.logger.info(f"Converting shadow format back to passwd on {self.host.hostname}")
cmd = self.host.conn.run(f"pwunconv {cmd_args}", log_level=ProcessLogLevel.Error)

self.host.discard_file("/etc/passwd")
self.host.discard_file("/etc/shadow")

return cmd

def grpconv(self, *args) -> ProcessResult:
"""
Convert group file to shadow group format.
"""
cmd_args = " ".join(args) if args else ""
self.logger.info(f"Converting group file to shadow format on {self.host.hostname}")
cmd = self.host.conn.run(f"grpconv {cmd_args}", log_level=ProcessLogLevel.Error)

self.host.discard_file("/etc/group")
self.host.discard_file("/etc/gshadow")

return cmd

def grpunconv(self, *args) -> ProcessResult:
"""
Convert shadow group format back to group file.
"""
cmd_args = " ".join(args) if args else ""
self.logger.info(f"Converting shadow group format back to group on {self.host.hostname}")
cmd = self.host.conn.run(f"grpunconv {cmd_args}", log_level=ProcessLogLevel.Error)

self.host.discard_file("/etc/group")
self.host.discard_file("/etc/gshadow")

return cmd
2 changes: 1 addition & 1 deletion tests/system/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ flaky
jc
passlib
pytest
git+https://github.com/next-actions/pytest-mh@1.0.21
git+https://github.com/next-actions/pytest-mh@1.0.30
git+https://github.com/next-actions/pytest-ticket
70 changes: 70 additions & 0 deletions tests/system/tests/test_grpconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""
Test grpconv
"""

from __future__ import annotations

import re
from pathlib import Path

import pytest

from framework.misc import shadow_password_pattern
from framework.roles.shadow import Shadow
from framework.topology import KnownTopology


@pytest.mark.topology(KnownTopology.Shadow)
@pytest.mark.builtwith(shadow="gshadow")
def test_grpconv__basic_conversion(shadow: Shadow):
"""
:title: Basic group to shadow conversion
:setup:
1. Remove existing group and gshadow files
2. Copy group file with old format (passwords in group attribute)
:steps:
1. Convert group file
2. Check group entries
3. Check gshadow file exists with correct permissions
4. Check gshadow entries
:expectedresults:
1. group file converted successfully
2. group entriy exist with correct passwd field
3. gshadow file exists with correct permissions
4. gshadow entries exist with correct attributes
:customerscenario: False
"""
shadow.host.fs.rm("/etc/group")
shadow.host.fs.rm("/etc/gshadow")

data_file = Path(__file__).parent.parent / "data" / "test_grpconv" / "group_old_format"
shadow.host.fs.upload(str(data_file.resolve()), "/etc/group")

shadow.grpconv()

group_entry = shadow.tools.getent.group("tgroup1")
assert group_entry is not None, "tgroup1 should exist in group"
assert group_entry.name == "tgroup1", "Incorrect groupname"
assert group_entry.password == "x", "tgroup1 group should have 'x' after grpconv"
assert group_entry.gid == 1001, "Incorrect GID"

assert shadow.host.fs.exists("/etc/gshadow"), "/etc/gshadow should exist"
gshadow_stat = shadow.host.fs.stat("/etc/gshadow")
assert gshadow_stat.access == "0400", f"gshadow file should have 0400 permissions, got {gshadow_stat.access}"
assert gshadow_stat.user == "root", f"gshadow file should be owned by root, got {gshadow_stat.user}"

gshadow_root = shadow.tools.getent.gshadow("root")
assert gshadow_root is not None, "root should exist in gshadow"
assert gshadow_root.name == "root", "Incorrect groupname"
assert gshadow_root.password == "*", "Incorrect password"

gshadow_daemon = shadow.tools.getent.gshadow("daemon")
assert gshadow_daemon is not None, "daemon should exist in gshadow"
assert gshadow_daemon.name == "daemon", "Incorrect groupname"
assert gshadow_daemon.password == "*", "Incorrect password"

gshadow_tgroup1 = shadow.tools.getent.gshadow("tgroup1")
assert gshadow_tgroup1 is not None, "tgroup1 should exist in gshadow"
assert gshadow_tgroup1.name == "tgroup1", "Incorrect groupname"
assert gshadow_tgroup1.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), gshadow_tgroup1.password), "Incorrect password"
65 changes: 65 additions & 0 deletions tests/system/tests/test_grpunconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Test grpunconv
"""

from __future__ import annotations

import re
from pathlib import Path

import pytest

from framework.misc import shadow_password_pattern
from framework.roles.shadow import Shadow
from framework.topology import KnownTopology


@pytest.mark.topology(KnownTopology.Shadow)
@pytest.mark.builtwith(shadow="gshadow")
def test_grpunconv__basic_conversion(shadow: Shadow):
"""
:title: Basic shadow to group conversion
:setup:
1. Remove existing group and gshadow files
2. Copy group and gshadow files
:steps:
1. Convert shadow format back to group format
2. Check gshadow file is removed
3. Check group entries
:expectedresults:
1. shadow format converted successfully
2. gshadow file is removed
3. group entries exist with correct attributes
: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_grpunconv" / "group"
shadow.host.fs.upload(str(group_file.resolve()), "/etc/group")
gshadow_file = test_dir / "data" / "test_grpunconv" / "gshadow"
shadow.host.fs.upload(str(gshadow_file.resolve()), "/etc/gshadow")

shadow.grpunconv()

assert not shadow.host.fs.exists("/etc/gshadow"), "/etc/gshadow should not exist after grpunconv"

group_root = shadow.tools.getent.group("root")
assert group_root is not None, "root should exist in group"
assert group_root.name == "root", "Incorrect groupname"
assert group_root.password == "*", "Incorrect password"
assert group_root.gid == 0, "Incorrect GID"

group_daemon = shadow.tools.getent.group("daemon")
assert group_daemon is not None, "daemon should exist in group"
assert group_daemon.name == "daemon", "Incorrect groupname"
assert group_daemon.password == "*", "Incorrect password"
assert group_daemon.gid == 1, "Incorrect GID"

group_tgroup1 = shadow.tools.getent.group("tgroup1")
assert group_tgroup1 is not None, "tgroup1 should exist in group"
assert group_tgroup1.name == "tgroup1", "Incorrect groupname"
assert group_tgroup1.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), group_tgroup1.password), "Incorrect password"
assert group_tgroup1.gid == 1001, "Incorrect GID"
77 changes: 77 additions & 0 deletions tests/system/tests/test_pwconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Test pwconv
"""

from __future__ import annotations

import re
from pathlib import Path

import pytest

from framework.misc import days_since_epoch, shadow_password_pattern
from framework.roles.shadow import Shadow
from framework.topology import KnownTopology


@pytest.mark.topology(KnownTopology.Shadow)
def test_pwconv__basic_conversion(shadow: Shadow):
"""
:title: Basic passwd to shadow conversion
:setup:
1. Remove existing passwd and shadow files
2. Copy passwd file with old format (passwords in passwd attribute)
:steps:
1. Convert passwd file
2. Check passwd entry
3. Check shadow file exists with correct permissions
4. Check shadow entries
:expectedresults:
1. passwd file converted successfully
2. passwd entry exists with correct passwd field
3. shadow file exists with correct permissions
4. shadow entries exist with correct attributes
:customerscenario: False
"""
shadow.host.fs.rm("/etc/passwd")
shadow.host.fs.rm("/etc/shadow")

data_file = Path(__file__).parent.parent / "data" / "test_pwconv" / "passwd_old_format"
shadow.host.fs.upload(str(data_file.resolve()), "/etc/passwd")

shadow.pwconv()

passwd_entry = shadow.tools.getent.passwd("tuser1")
assert passwd_entry is not None, "tuser1 should still exist in passwd"
assert passwd_entry.password == "x", "tuser1 passwd should have 'x' after pwconv"

assert shadow.host.fs.exists("/etc/shadow"), "/etc/shadow should exist"
shadow_stat = shadow.host.fs.stat("/etc/shadow")
assert shadow_stat.access == "0400", f"Shadow file should have 0400 permissions, got {shadow_stat.access}"
assert shadow_stat.user == "root", f"Shadow file should be owned by root, got {shadow_stat.user}"

shadow_root = shadow.tools.getent.shadow("root")
assert shadow_root is not None, "User should be found"
assert shadow_root.name == "root", "Incorrect username"
assert shadow_root.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), shadow_root.password), "Incorrect password"
assert shadow_root.last_changed == days_since_epoch(), "Incorrect last changed"
assert shadow_root.max_days == 99999, "Incorrect max days"
assert shadow_root.warn_days == 7, "Incorrect warn days"

shadow_daemon = shadow.tools.getent.shadow("daemon")
assert shadow_daemon is not None, "User should be found"
assert shadow_daemon.name == "daemon", "Incorrect username"
assert shadow_daemon.password == "*", "Incorrect password"
assert shadow_daemon.last_changed == days_since_epoch(), "Incorrect last changed"
assert shadow_daemon.max_days == 99999, "Incorrect max days"
assert shadow_daemon.warn_days == 7, "Incorrect warn days"

shadow_tuser1 = shadow.tools.getent.shadow("tuser1")
assert shadow_tuser1 is not None, "User should be found"
assert shadow_tuser1.name == "tuser1", "Incorrect username"
assert shadow_tuser1.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), shadow_tuser1.password), "Incorrect password"
assert shadow_tuser1.last_changed == days_since_epoch(), "Incorrect last changed"
assert shadow_tuser1.max_days == 99999, "Incorrect max days"
assert shadow_tuser1.warn_days == 7, "Incorrect warn days"
74 changes: 74 additions & 0 deletions tests/system/tests/test_pwunconv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Test pwunconv
"""

from __future__ import annotations

import re
from pathlib import Path

import pytest

from framework.misc import shadow_password_pattern
from framework.roles.shadow import Shadow
from framework.topology import KnownTopology


@pytest.mark.topology(KnownTopology.Shadow)
def test_pwunconv__basic_conversion(shadow: Shadow):
"""
:title: Basic shadow to passwd conversion
:setup:
1. Remove existing passwd and shadow files
2. Copy passwd and shadow files
:steps:
1. Convert shadow format back to passwd format
2. Check shadow file is removed
3. Check passwd entries
:expectedresults:
1. shadow format converted successfully
2. shadow file is removed
3. passwd entries exist with correct attributes
:customerscenario: False
"""
shadow.host.fs.rm("/etc/passwd")
shadow.host.fs.rm("/etc/shadow")

test_dir = Path(__file__).parent.parent
passwd_file = test_dir / "data" / "test_pwunconv" / "passwd"
shadow.host.fs.upload(str(passwd_file.resolve()), "/etc/passwd")
shadow_file = test_dir / "data" / "test_pwunconv" / "shadow"
shadow.host.fs.upload(str(shadow_file.resolve()), "/etc/shadow")

shadow.pwunconv()

assert not shadow.host.fs.exists("/etc/shadow"), "/etc/shadow should not exist after pwunconv"

passwd_root = shadow.tools.getent.passwd("root")
assert passwd_root is not None, "root should exist in passwd"
assert passwd_root.name == "root", "Incorrect username"
assert passwd_root.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), passwd_root.password), "Incorrect password"
assert passwd_root.uid == 0, "Incorrect UID"
assert passwd_root.gid == 0, "Incorrect GID"
assert passwd_root.home == "/root", "Incorrect home"
assert passwd_root.shell == "/bin/bash", "Incorrect shell"

passwd_daemon = shadow.tools.getent.passwd("daemon")
assert passwd_daemon is not None, "daemon should exist in passwd"
assert passwd_daemon.name == "daemon", "Incorrect username"
assert passwd_daemon.password == "*", "Incorrect password"
assert passwd_daemon.uid == 1, "Incorrect UID"
assert passwd_daemon.gid == 1, "Incorrect GID"
assert passwd_daemon.home == "/usr/sbin", "Incorrect home"
assert passwd_daemon.shell == "/bin/sh", "Incorrect shell"

passwd_tuser1 = shadow.tools.getent.passwd("tuser1")
assert passwd_tuser1 is not None, "tuser1 should exist in passwd"
assert passwd_tuser1.name == "tuser1", "Incorrect username"
assert passwd_tuser1.password is not None, "Password should not be None"
assert re.match(shadow_password_pattern(), passwd_tuser1.password), "Incorrect password"
assert passwd_tuser1.uid == 1001, "Incorrect UID"
assert passwd_tuser1.gid == 1001, "Incorrect GID"
assert passwd_tuser1.home == "/home/tuser1", "Incorrect home"
assert passwd_tuser1.shell == "/bin/bash", "Incorrect shell"
Loading