feat(10-01): add audit event logging to config backup operations
- config_snapshot_created event after successful snapshot INSERT - config_snapshot_skipped_duplicate event on dedup match - config_diff_generated event after diff INSERT - config_backup_manual_trigger event on manual trigger success - All log_action calls wrapped in try/except for safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -161,10 +161,10 @@ Plans:
|
|||||||
2. `config_snapshot_skipped_duplicate` audit event logged when a duplicate snapshot is detected
|
2. `config_snapshot_skipped_duplicate` audit event logged when a duplicate snapshot is detected
|
||||||
3. `config_diff_generated` audit event logged when a diff is created between snapshots
|
3. `config_diff_generated` audit event logged when a diff is created between snapshots
|
||||||
4. `config_backup_manual_trigger` audit event logged when an operator triggers a manual backup
|
4. `config_backup_manual_trigger` audit event logged when an operator triggers a manual backup
|
||||||
**Plans**: TBD
|
**Plans**: 1 plan
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 10-01: Audit event emission for all config backup operations
|
- [ ] 10-01-PLAN.md — Audit event emission for all config backup operations
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
|
|||||||
@@ -837,6 +837,23 @@ async def trigger_config_snapshot(
|
|||||||
reply_data = json.loads(reply.data)
|
reply_data = json.loads(reply.data)
|
||||||
|
|
||||||
if reply_data.get("status") == "success":
|
if reply_data.get("status") == "success":
|
||||||
|
try:
|
||||||
|
from app.services.audit_service import log_action
|
||||||
|
await log_action(
|
||||||
|
db,
|
||||||
|
tenant_id,
|
||||||
|
current_user.user_id,
|
||||||
|
"config_backup_manual_trigger",
|
||||||
|
resource_type="config_snapshot",
|
||||||
|
device_id=device_id,
|
||||||
|
details={
|
||||||
|
"sha256_hash": reply_data.get("sha256_hash"),
|
||||||
|
"triggered_by": str(current_user.user_id),
|
||||||
|
},
|
||||||
|
ip_address=request.client.host if request.client else None,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"sha256_hash": reply_data.get("sha256_hash"),
|
"sha256_hash": reply_data.get("sha256_hash"),
|
||||||
|
|||||||
@@ -162,6 +162,27 @@ async def generate_and_store_diff(
|
|||||||
lines_removed,
|
lines_removed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from app.services.audit_service import log_action
|
||||||
|
import uuid as _uuid
|
||||||
|
await log_action(
|
||||||
|
db=None,
|
||||||
|
tenant_id=_uuid.UUID(tenant_id),
|
||||||
|
user_id=None,
|
||||||
|
action="config_diff_generated",
|
||||||
|
resource_type="config_diff",
|
||||||
|
resource_id=str(diff_id),
|
||||||
|
device_id=_uuid.UUID(device_id),
|
||||||
|
details={
|
||||||
|
"old_snapshot_id": str(old_snapshot_id),
|
||||||
|
"new_snapshot_id": new_snapshot_id,
|
||||||
|
"lines_added": lines_added,
|
||||||
|
"lines_removed": lines_removed,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# 11. Parse structured changes (best-effort)
|
# 11. Parse structured changes (best-effort)
|
||||||
try:
|
try:
|
||||||
changes = parse_diff_changes(diff_text)
|
changes = parse_diff_changes(diff_text)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
import uuid as _uuid
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError
|
|||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.database import AdminAsyncSessionLocal
|
from app.database import AdminAsyncSessionLocal
|
||||||
|
from app.services.audit_service import log_action
|
||||||
from app.services.config_diff_service import generate_and_store_diff
|
from app.services.config_diff_service import generate_and_store_diff
|
||||||
from app.services.openbao_service import OpenBaoTransitService
|
from app.services.openbao_service import OpenBaoTransitService
|
||||||
|
|
||||||
@@ -111,6 +113,18 @@ async def handle_config_snapshot(msg) -> None:
|
|||||||
device_id,
|
device_id,
|
||||||
)
|
)
|
||||||
config_snapshot_dedup_skipped_total.inc()
|
config_snapshot_dedup_skipped_total.inc()
|
||||||
|
try:
|
||||||
|
await log_action(
|
||||||
|
db=None,
|
||||||
|
tenant_id=_uuid.UUID(tenant_id),
|
||||||
|
user_id=None,
|
||||||
|
action="config_snapshot_skipped_duplicate",
|
||||||
|
resource_type="config_snapshot",
|
||||||
|
device_id=_uuid.UUID(device_id),
|
||||||
|
details={"sha256_hash": sha256_hash},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
await msg.ack()
|
await msg.ack()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -173,6 +187,20 @@ async def handle_config_snapshot(msg) -> None:
|
|||||||
await msg.nak()
|
await msg.nak()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await log_action(
|
||||||
|
db=None,
|
||||||
|
tenant_id=_uuid.UUID(tenant_id),
|
||||||
|
user_id=None,
|
||||||
|
action="config_snapshot_created",
|
||||||
|
resource_type="config_snapshot",
|
||||||
|
resource_id=str(new_snapshot_id),
|
||||||
|
device_id=_uuid.UUID(device_id),
|
||||||
|
details={"sha256_hash": sha256_hash},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# --- Diff generation (best-effort) ---
|
# --- Diff generation (best-effort) ---
|
||||||
try:
|
try:
|
||||||
await generate_and_store_diff(device_id, tenant_id, str(new_snapshot_id), session)
|
await generate_and_store_diff(device_id, tenant_id, str(new_snapshot_id), session)
|
||||||
|
|||||||
Reference in New Issue
Block a user