Files
net-kingdom/local-identity/src/local_identity/audit.py
tegwick 52d44daec2 refactor(local-identity): post-Stage4 cleanups and micro-fixes
- audit: chmod only on file creation, not every append (TOCTOU fix)
- jwt_utils: add extract_unverified_payload() helper
- cli: use extract_unverified_payload + JWTError instead of inline decode
- keys: extract _public_key_bytes() helper, import _b64url from jwt_utils
- security: FileNotFoundError try/except instead of path.exists() (TOCTOU fix)
- serve: cache JWK response at server init instead of per-request recompute

138 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 08:25:21 +01:00

43 lines
1.2 KiB
Python

"""
Append-only audit log for local-identity.
All CLI commands and OIDC server events write one entry per operation to
~/.local-identity/audit.log (mode 600).
Format (TSV): ISO-timestamp command username outcome
Example:
2026-03-02T00:00:00Z init alice ok
2026-03-02T00:01:00Z serve/auth alice auth_request
2026-03-02T00:01:01Z serve/token alice token_issued
Failures (e.g. file permission errors) are silently swallowed — the audit
log must never interrupt the primary operation.
"""
import datetime
import os
from pathlib import Path
from .store import _store_dir
def _audit_log_path() -> Path:
return _store_dir() / "audit.log"
def log_event(command: str, username: str | None, outcome: str) -> None:
"""Append one audit entry. Silent on any I/O failure."""
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
entry = f"{timestamp}\t{command}\t{username or '-'}\t{outcome}\n"
path = _audit_log_path()
try:
is_new = not path.exists()
with open(path, "a", encoding="utf-8") as fh:
fh.write(entry)
if is_new:
os.chmod(path, 0o600)
except OSError:
pass