122 lines
3.4 KiB
Python
122 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Shared policy for the small set of user-facing audit events."""
|
|
|
|
import datetime as dt
|
|
import hashlib
|
|
import os
|
|
from typing import Mapping, Optional, Tuple
|
|
|
|
try:
|
|
from .account_policy import account_name
|
|
except ImportError:
|
|
from account_policy import account_name
|
|
|
|
AUDIT_ACTIONS = frozenset({"read", "write", "move", "delete"})
|
|
|
|
OPERATION_ACTIONS = {
|
|
"read": "read",
|
|
"pread": "read",
|
|
"pread_recv": "read",
|
|
"pread_send": "read",
|
|
"sendfile": "read",
|
|
"offload_read_recv": "read",
|
|
"offload_read_send": "read",
|
|
"write": "write",
|
|
"pwrite": "write",
|
|
"pwrite_recv": "write",
|
|
"pwrite_send": "write",
|
|
"recvfile": "write",
|
|
"offload_write_recv": "write",
|
|
"offload_write_send": "write",
|
|
"renameat": "move",
|
|
"rename": "move",
|
|
"unlinkat": "delete",
|
|
"unlink": "delete",
|
|
"rmdir": "delete",
|
|
}
|
|
|
|
def action_for(operation: str) -> Optional[str]:
|
|
return OPERATION_ACTIONS.get(operation.strip().casefold())
|
|
|
|
def skipped_user_suffixes() -> Tuple[str, ...]:
|
|
raw = os.getenv("AUDIT_SKIP_USER_SUFFIXES", "_svc,_ServiceAcc")
|
|
return tuple(
|
|
suffix.strip().casefold()
|
|
for suffix in raw.split(",")
|
|
if suffix.strip()
|
|
)
|
|
|
|
def skip_user(user: str) -> bool:
|
|
account = account_name(user).casefold()
|
|
return any(account.endswith(suffix) for suffix in skipped_user_suffixes())
|
|
|
|
DeduplicationKey = Tuple[str, ...]
|
|
|
|
def deduplication_key(event: Mapping[str, object]) -> Optional[DeduplicationKey]:
|
|
action = str(event.get("action", "")).casefold()
|
|
share = str(event.get("share", ""))
|
|
if action != "read" and share.casefold() != "fslogix":
|
|
return None
|
|
try:
|
|
timestamp = dt.datetime.fromisoformat(
|
|
str(event.get("timestamp", "")).replace("Z", "+00:00")
|
|
)
|
|
if timestamp.tzinfo is None:
|
|
timestamp = timestamp.replace(tzinfo=dt.timezone.utc)
|
|
second = (
|
|
timestamp.astimezone(dt.timezone.utc)
|
|
.replace(microsecond=0)
|
|
.isoformat()
|
|
)
|
|
except ValueError:
|
|
second = str(event.get("timestamp", ""))
|
|
|
|
success = bool(event.get("success", False))
|
|
return (
|
|
second,
|
|
action,
|
|
str(event.get("user", "")),
|
|
str(event.get("clientIp", "")),
|
|
share,
|
|
str(event.get("path", "")),
|
|
"success" if success else "failure",
|
|
"" if success else str(event.get("result", "")),
|
|
)
|
|
|
|
def deduplication_fingerprint(
|
|
event: Mapping[str, object]
|
|
) -> Optional[bytes]:
|
|
key = deduplication_key(event)
|
|
if key is None:
|
|
return None
|
|
digest = hashlib.blake2b(digest_size=16)
|
|
for value in key:
|
|
encoded = value.encode("utf-8", errors="surrogatepass")
|
|
digest.update(len(encoded).to_bytes(4, "big"))
|
|
digest.update(encoded)
|
|
return digest.digest()
|
|
|
|
def deduplication_fingerprint_values(
|
|
timestamp: object,
|
|
action: object,
|
|
user: object,
|
|
client_ip: object,
|
|
share: object,
|
|
path: object,
|
|
success: object,
|
|
result: object,
|
|
) -> bytes:
|
|
"""Fingerprint SQLite columns with the live-ingest policy."""
|
|
fingerprint = deduplication_fingerprint({
|
|
"action": action,
|
|
"timestamp": timestamp,
|
|
"user": user,
|
|
"clientIp": client_ip,
|
|
"share": share,
|
|
"path": path,
|
|
"success": bool(success),
|
|
"result": result,
|
|
})
|
|
assert fingerprint is not None
|
|
return fingerprint
|