remove_runv_jails.py (4855B)
1 #!/usr/bin/env python3 2 """ 3 Remove o modelo antigo de jail SSH runv-jailed de membros existentes. 4 5 Desfaz, de forma idempotente, o que ``runv_jail.ensure_runv_jail_for_user`` aplicava: 6 bind mount em /srv/jail/<user>/home/<user>, linha em /etc/fstab, grupo runv-jailed 7 e directório /srv/jail/<user>. Também remove o drop-in SSH global da jail. 8 9 Execute como root no servidor Debian. 10 """ 11 12 from __future__ import annotations 13 14 import argparse 15 import grp 16 import logging 17 import os 18 import pwd 19 import subprocess 20 import sys 21 from pathlib import Path 22 23 _SCRIPT_DIR = Path(__file__).resolve().parent 24 if str(_SCRIPT_DIR) not in sys.path: 25 sys.path.insert(0, str(_SCRIPT_DIR)) 26 27 from admin_guard import ensure_admin_cli 28 import runv_jail 29 30 SSHD_DROPIN = Path("/etc/ssh/sshd_config.d/90-runv-jailed.conf") 31 32 33 def setup_logging(verbose: bool) -> logging.Logger: 34 log = logging.getLogger("remove_runv_jails") 35 log.setLevel(logging.DEBUG if verbose else logging.INFO) 36 log.handlers.clear() 37 h = logging.StreamHandler(sys.stderr) 38 h.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 39 log.addHandler(h) 40 return log 41 42 43 def require_root(dry_run: bool, log: logging.Logger) -> None: 44 if dry_run: 45 return 46 if os.geteuid() != 0: 47 log.error("execute como root (ou use --dry-run)") 48 raise SystemExit(2) 49 50 51 def group_members() -> list[str]: 52 try: 53 g = grp.getgrnam(runv_jail.RUNV_JAILED_GROUP) 54 except KeyError: 55 return [] 56 names = set(g.gr_mem) 57 for pw in pwd.getpwall(): 58 if pw.pw_gid == g.gr_gid: 59 names.add(pw.pw_name) 60 return sorted(n for n in names if not runv_jail.jail_skip_username(n)) 61 62 63 def remove_sshd_dropin(*, dry_run: bool, log: logging.Logger) -> None: 64 if not SSHD_DROPIN.exists(): 65 log.info("drop-in SSH jail ausente: %s", SSHD_DROPIN) 66 return 67 if dry_run: 68 log.info("[dry-run] removeria %s e recarregaria ssh", SSHD_DROPIN) 69 return 70 old_body = SSHD_DROPIN.read_bytes() 71 SSHD_DROPIN.unlink() 72 log.info("removido drop-in SSH jail: %s", SSHD_DROPIN) 73 test = subprocess.run(["sshd", "-t"], capture_output=True, text=True, timeout=30) 74 if test.returncode != 0: 75 SSHD_DROPIN.write_bytes(old_body) 76 err = (test.stderr or test.stdout or "").strip() 77 raise RuntimeError( 78 f"sshd -t falhou após remover {SSHD_DROPIN}; ficheiro restaurado: {err}" 79 ) 80 for unit in ("ssh", "sshd"): 81 r = subprocess.run(["systemctl", "reload", unit], capture_output=True, text=True, timeout=60) 82 if r.returncode == 0: 83 log.info("systemctl reload %s concluído", unit) 84 return 85 log.warning("não foi possível recarregar ssh/sshd automaticamente; recarregue manualmente") 86 87 88 def parse_args(argv: list[str] | None = None) -> argparse.Namespace: 89 p = argparse.ArgumentParser(description="Remove runv-jailed e /srv/jail de membros existentes.") 90 p.add_argument("--dry-run", action="store_true", help="mostra sem alterar") 91 p.add_argument("--verbose", "-v", action="store_true", help="log detalhado") 92 p.add_argument("--user", metavar="USER", help="remove jail apenas deste utilizador") 93 p.add_argument( 94 "--keep-sshd-dropin", 95 action="store_true", 96 help="não remover /etc/ssh/sshd_config.d/90-runv-jailed.conf", 97 ) 98 return p.parse_args(argv) 99 100 101 def main(argv: list[str] | None = None) -> int: 102 args = parse_args(argv) 103 ensure_admin_cli(script_name=Path(__file__).name, dry_run=bool(args.dry_run)) 104 log = setup_logging(args.verbose) 105 require_root(bool(args.dry_run), log) 106 107 users = [args.user.strip()] if args.user else group_members() 108 users = [u for u in users if u and not runv_jail.jail_skip_username(u)] 109 failures = 0 110 if not users: 111 log.info("nenhum membro em %s", runv_jail.RUNV_JAILED_GROUP) 112 for username in users: 113 try: 114 pw = pwd.getpwnam(username) 115 except KeyError: 116 log.warning("%s não existe em passwd; ignorado", username) 117 continue 118 log.info("--- removendo jail de %s", username) 119 try: 120 runv_jail.teardown_runv_jail_for_user( 121 username, 122 Path(pw.pw_dir), 123 log, 124 dry_run=bool(args.dry_run), 125 ) 126 except Exception as e: 127 failures += 1 128 log.error("%s: falha ao remover jail: %s", username, e) 129 continue 130 131 if not args.keep_sshd_dropin: 132 try: 133 remove_sshd_dropin(dry_run=bool(args.dry_run), log=log) 134 except RuntimeError as e: 135 log.error("%s", e) 136 return 1 137 138 if failures: 139 log.error("concluído com %d falha(s)", failures) 140 return 1 141 log.info("concluído") 142 return 0 143 144 145 if __name__ == "__main__": 146 raise SystemExit(main())