From 1bc8e2cce0f4712709d29f74e2ef853ff21faec0 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Fri, 29 May 2026 10:54:37 +0900 Subject: [PATCH] Fix output format --- assets/ignore_group_file | 3 +- main.py | 108 ++++++++++++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/assets/ignore_group_file b/assets/ignore_group_file index b69df7c..57cc054 100644 --- a/assets/ignore_group_file +++ b/assets/ignore_group_file @@ -58,7 +58,7 @@ prometheus-exporter proxy rdma root -runit +# runit salt sambashare saned @@ -80,7 +80,6 @@ systemd-timesync tape tcpdump tty -uallt_e users utempter utmp diff --git a/main.py b/main.py index 0c56a44..f4fc93c 100755 --- a/main.py +++ b/main.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 from __future__ import annotations -from typing import Any import argparse import json from collections import Counter from dataclasses import dataclass from pathlib import Path +from typing import Any def main(): @@ -78,13 +78,6 @@ def main(): for user in users.values(): user.email = f"{user.name}@{args.email_domain}" - if not args.output_dir.exists(): - args.output_dir.mkdir(parents=True) - user_dir = args.output_dir / "users" - user_dir.mkdir(exist_ok=True) - group_dir = args.output_dir / "groups" - group_dir.mkdir(exist_ok=True) - # print_group_stats(groups, users) for user in users.values(): @@ -94,12 +87,7 @@ def main(): if user.name in shadow: user.set_shadow_entry(shadow[user.name]) - with open(args.output_dir / "users" / f"{user.name}.json", "w") as f: - json.dump(user.to_systemd_user_record(), f, indent=2) - - for group in groups.values(): - with open(args.output_dir / "groups" / f"{group.name}.json", "w") as f: - json.dump(group.to_systemd_group_record(), f, indent=2) + output_to_directory(users, groups, args.output_dir) def ensure_no_overlapping_uids(users: dict[str, User]): @@ -327,13 +315,16 @@ class User: if self.memberOf: result["memberOf"] = self.memberOf - if self.password is not None: - result["privileged"] = { - "hashedPassword": self.password, - } - return result + def to_systemd_privileged_user_record(self) -> dict[str, Any] | None: + if self.password is None: + return None + + return { + "hashedPassword": self.password, + } + @dataclass class Group: @@ -348,6 +339,10 @@ class Group: "members": self.members, } + # TODO: parse gshadow + def to_systemd_privileged_group_record(self) -> dict[str, Any] | None: + return None + def print_group_stats( groups: dict[str, Group], @@ -373,5 +368,80 @@ def print_group_stats( ) +def output_to_directory( + users: dict[str, User], + groups: dict[str, Group], + output_dir: Path, +): + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + for user in users.values(): + user_record = user.to_systemd_user_record() + with open(output_dir / f"{user.name}.user", "w") as f: + json.dump(user_record, f, indent=2) + + symlink_path = output_dir / f"{user.uid}.user" + if symlink_path.exists(): + symlink_path.unlink() + symlink_path.symlink_to(f"./{user.name}.user") + + privileged_user_record = user.to_systemd_privileged_user_record() + if privileged_user_record: + priv_path = output_dir / f"{user.name}.user-privileged" + priv_path.touch(exist_ok=True) + priv_path.chmod(0o600) + with open(priv_path, "w") as f: + json.dump(privileged_user_record, f, indent=2) + + symlink_path = output_dir / f"{user.uid}.user-privileged" + if symlink_path.exists(): + symlink_path.unlink() + symlink_path.symlink_to(f"./{user.name}.user-privileged") + + for group in groups.values(): + group_record = group.to_systemd_group_record() + with open(output_dir / f"{group.name}.group", "w") as f: + json.dump(group_record, f, indent=2) + + symlink_path = output_dir / f"{group.gid}.group" + if symlink_path.exists(): + symlink_path.unlink() + symlink_path.symlink_to(f"./{group.name}.group") + + privileged_group_record = group.to_systemd_privileged_group_record() + if privileged_group_record: + priv_path = output_dir / f"{group.name}.group-privileged" + priv_path.touch(exist_ok=True) + priv_path.chmod(0o600) + with open(priv_path, "w") as f: + json.dump(privileged_group_record, f, indent=2) + + symlink_path = output_dir / f"{group.gid}.group-privileged" + if symlink_path.exists(): + symlink_path.unlink() + symlink_path.symlink_to(f"./{group.name}.group-privileged") + + groups_by_gid = {group.gid: group for group in groups.values()} + for user in users.values(): + primary_group = groups_by_gid.get(user.gid) + if not primary_group: + print( + f"Warning: User {user.name} has primary GID {user.gid} which does not correspond to any group" + ) + else: + with open( + output_dir / f"{user.name}:{primary_group.name}.membership", "w" + ) as f: + json.dump({}, f) + + for group in groups.values(): + if user.name in group.members: + with open( + output_dir / f"{user.name}:{group.name}.membership", "w" + ) as f: + json.dump({}, f) + + if __name__ == "__main__": main()