diff --git a/deploy/README.md b/deploy/README.md index 90e3f09..0846d78 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -42,7 +42,7 @@ chown toolstack:toolstack /etc/toolstack/sps.crt /etc/toolstack/sps-ca.crt Adjust account and install root to match the installer. Bootstrap creates only TLS material/config, not a vault. Vault lives in `/var/lib/toolstack/sps.vault.json`, -audit in `/var/log/toolstack/sps.audit`; `/etc` remains read-only in the services. +audit in `/var/lib/toolstack/audit/sps.audit` (not under `/var/log`: SPS refuses group-writable ancestors and Ubuntu keeps `/var/log` group-writable; existing installs should point `SP_AUDIT_LOG` there); `/etc` remains read-only in the services. Provision `sps init-vault` and `sps vault-set` as the service user with the venv, explicit `--config /etc/toolstack/sps.env`, and the same passphrase as the SPS runtime file. Keep SPS **stopped during all direct provisioning**. Never put real diff --git a/deploy/bootstrap-sps.py b/deploy/bootstrap-sps.py index 42c9b23..a11ffa3 100644 --- a/deploy/bootstrap-sps.py +++ b/deploy/bootstrap-sps.py @@ -11,7 +11,7 @@ def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('--config', default='/etc/toolstack/sps.env') p.add_argument('--state-dir', default='/var/lib/toolstack') - p.add_argument('--log-dir', default='/var/log/toolstack') + p.add_argument('--log-dir', default='/var/lib/toolstack/audit') args = p.parse_args() os.umask(0o077) config = Path(args.config).resolve() diff --git a/deploy/install.sh b/deploy/install.sh index fa3e313..f3138bb 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -52,6 +52,8 @@ sudo -u "$SERVICE_USER" sh -c "cd '$REPO_ROOT' && '$VENV_PIP' install --quiet '. # so set-password + the SPS audit logger land right instead of crashing on first write) install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 700 /var/lib/toolstack install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 700 /var/log/toolstack +# SPS refuses group-writable ancestors; Ubuntu keeps /var/log 0775, so audit lives under the state dir. +install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 700 /var/lib/toolstack/audit install -d -o root -g "$SERVICE_GROUP" -m 750 /etc/toolstack # 5. Admin password (the panel fails closed without one) @@ -100,7 +102,7 @@ Toolstack admin is installed. SPS still requires config/vault provisioning if ne Status: systemctl status toolstack-admin toolstack-sps Logs: journalctl -u toolstack-admin -f journalctl -u toolstack-sps -f - tail -f /var/log/toolstack/sps.audit (SPS secret-access audit log) + tail -f /var/lib/toolstack/audit/sps.audit (SPS secret-access audit log) Provision an agent (its bearer token prints once): sudo -u $SERVICE_USER env XDG_CONFIG_HOME=/var/lib XDG_STATE_HOME=/var/lib \\ diff --git a/deploy/sps.env.example b/deploy/sps.env.example index 5bd8045..649b16b 100644 --- a/deploy/sps.env.example +++ b/deploy/sps.env.example @@ -13,7 +13,7 @@ SP_TLS_CERT = "/etc/toolstack/sps.crt" SP_TLS_KEY = "/etc/toolstack/sps.key" SP_TLS_CA = "/etc/toolstack/sps-ca.crt" #SP_SECRET = -SP_AUDIT_LOG = "/var/log/toolstack/sps.audit" +SP_AUDIT_LOG = "/var/lib/toolstack/audit/sps.audit" # Choose exactly one plugin. SP_PLUGIN = "localfile" diff --git a/docs/sps.md b/docs/sps.md index b11be41..51d56d3 100644 --- a/docs/sps.md +++ b/docs/sps.md @@ -41,7 +41,7 @@ See [matrix](runtime-matrix.md) for unsupported Docker/Seatbelt/native-helper pa Use [deployment bootstrap](../deploy/README.md): `/etc/toolstack/sps.env` is private TOML, not a shell/systemd env file. The deployment wrapper passes `sps init` -`--state-dir /var/lib/toolstack --audit-log /var/log/toolstack/sps.audit`. +`--state-dir /var/lib/toolstack --audit-log /var/lib/toolstack/audit/sps.audit`. Choose dedicated private immediate parents for vault, audit and boot identity; unsafe existing paths are refused, never repaired by blind chmod. New nested parents are 0700, vault/audit/lock 0600, nonsecret boot identity 0644 under its diff --git a/sps/cli.py b/sps/cli.py index 9a9d0f3..270cc34 100644 --- a/sps/cli.py +++ b/sps/cli.py @@ -36,7 +36,7 @@ def init_runtime_paths(config: str | Path, *, state_dir: str | Path | None = Non state = Path(state_dir) if state_dir is not None else ( Path("/var/lib/toolstack") if production else config.parent) audit_path = Path(audit_log) if audit_log is not None else ( - Path("/var/log/toolstack/sps.audit") if production else config.parent / "sps-audit.jsonl") + Path("/var/lib/toolstack/audit/sps.audit") if production else config.parent / "sps-audit.jsonl") return state / "sps.vault.json", audit_path @@ -113,7 +113,7 @@ def _cmd_serve(args) -> None: warn_certificate_expiry(cfg.sp_tls_cert) plugin = plugin_loader.load_plugin(cfg) plugin.connect() - audit_log_path = cfg.sp_audit_log or "/var/log/toolstack/sps.audit" + audit_log_path = cfg.sp_audit_log or "/var/lib/toolstack/audit/sps.audit" audit_log = audit.AuditLogger(audit_log_path) boot_id = secrets.token_hex(16) ctx = AppContext( @@ -301,7 +301,7 @@ def main() -> None: p_init = sub.add_parser("init", help="write a starter sps.env (cert + key + CA)") p_init.add_argument("--config", default="/etc/toolstack/sps.env") p_init.add_argument("--state-dir", help="directory for sps.vault.json (default: /var/lib/toolstack for production config, otherwise beside config)") - p_init.add_argument("--audit-log", help="audit log file (default: /var/log/toolstack/sps.audit for production config, otherwise beside config)") + p_init.add_argument("--audit-log", help="audit log file (default: /var/lib/toolstack/audit/sps.audit for production config, otherwise beside config)") p_init.set_defaults(func=_cmd_init) p_iv = sub.add_parser("init-vault", help="create the localfile vault file (separate from init)") p_iv.add_argument("--config", default="/etc/toolstack/sps.env") diff --git a/sps/tests/test_cli.py b/sps/tests/test_cli.py index a0afeac..b34a1dd 100644 --- a/sps/tests/test_cli.py +++ b/sps/tests/test_cli.py @@ -15,7 +15,7 @@ class Bootstrap(unittest.TestCase): def test_production_and_temporary_path_selection_is_pure(self): # No filesystem operations: production paths must never be created here. self.assertEqual(init_runtime_paths("/etc/toolstack/sps.env"), ( - Path("/var/lib/toolstack/sps.vault.json"), Path("/var/log/toolstack/sps.audit"))) + Path("/var/lib/toolstack/sps.vault.json"), Path("/var/lib/toolstack/audit/sps.audit"))) self.assertEqual(init_runtime_paths("/temporary/config/sps.env"), ( Path("/temporary/config/sps.vault.json"), Path("/temporary/config/sps-audit.jsonl"))) self.assertEqual(init_runtime_paths("local.env"), ( @@ -24,7 +24,7 @@ def test_production_and_temporary_path_selection_is_pure(self): self.assertEqual(init_runtime_paths(config, state_dir="/explicit/state", audit_log="/explicit/log/audit"), ( Path("/explicit/state/sps.vault.json"), Path("/explicit/log/audit"))) self.assertEqual(init_runtime_paths("/etc/toolstack/sps.env", state_dir="/explicit/state")[1], - Path("/var/log/toolstack/sps.audit")) + Path("/var/lib/toolstack/audit/sps.audit")) self.assertEqual(init_runtime_paths("/temporary/config/sps.env", audit_log="/explicit/audit")[0], Path("/temporary/config/sps.vault.json"))