fix: create the audit log 0600 in a 0700 directory and refuse symlinks (SEC-11)
This commit is contained in:
1 parent
fd5e4b71f3
commit
7307214ab6
4 files changed
+138
-3
No files matched your search
@@ -5,6 +5,8 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
@@ -33,12 +35,37 @@ class AuditLogger:
|
||||
"""
|
||||
self.settings = get_settings()
|
||||
self.log_path = log_path or self.settings.resolved_audit_log_path
|
||||
self.log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# A new state directory is private (0700); an existing one is left alone.
|
||||
self.log_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
self._lock = threading.Lock()
|
||||
# Held open for the process lifetime; closed in close().
|
||||
self._log_file = open(self.log_path, "a+", encoding="utf-8") # noqa: SIM115
|
||||
self._log_file = self._open_log()
|
||||
self._last_hash = self._read_last_hash()
|
||||
|
||||
def _open_log(self) -> Any:
|
||||
"""Open the log for append: mode 0600 when created, and never through a symlink.
|
||||
|
||||
Raises:
|
||||
OSError: The path cannot be opened (a symlink at the path gives ``ELOOP``).
|
||||
"""
|
||||
flags = os.O_WRONLY | os.O_APPEND | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0)
|
||||
descriptor = os.open(self.log_path, flags, 0o600)
|
||||
try:
|
||||
info = os.fstat(descriptor)
|
||||
if not stat.S_ISREG(info.st_mode):
|
||||
raise OSError(f"{self.log_path} is not a regular file")
|
||||
if (
|
||||
info.st_uid == getattr(os, "geteuid", lambda: info.st_uid)()
|
||||
and stat.S_IMODE(info.st_mode) & 0o077
|
||||
):
|
||||
# An older log created under a permissive umask: logins and paths of
|
||||
# every call must not stay readable by other local users.
|
||||
os.fchmod(descriptor, 0o600)
|
||||
return os.fdopen(descriptor, "a", encoding="utf-8")
|
||||
except BaseException:
|
||||
os.close(descriptor)
|
||||
raise
|
||||
|
||||
def _read_last_hash(self) -> str:
|
||||
"""Read the previous hash from the last log entry."""
|
||||
try:
|
||||
|
||||
@@ -8,6 +8,7 @@ actionable message on stderr (never on stdout, which carries JSON-RPC on stdio).
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import errno
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
@@ -428,7 +429,9 @@ def _audit_startup_error(settings: Settings) -> str | None:
|
||||
try:
|
||||
get_audit_logger()
|
||||
except OSError as exc:
|
||||
reason = exc.strerror or type(exc).__name__
|
||||
reason = exc.strerror or str(exc) or type(exc).__name__
|
||||
if exc.errno == errno.ELOOP:
|
||||
reason = "the path is a symbolic link, which is refused"
|
||||
return (
|
||||
f"cannot open the audit log {settings.resolved_audit_log_path} for append "
|
||||
f"({reason}). Fix the permissions or point AUDIT_LOG_PATH (or DATA_DIR) at a "
|
||||
|
||||
Reference in new issue
Block a user