implementet encryption and decryption to the audit to provide a complete lak of context for a petential attacker
This commit is contained in:
+21
-38
@@ -388,6 +388,8 @@ ALLOWED_COVER_DOMAINS = {
|
|||||||
"www.googleapis.com"
|
"www.googleapis.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone"]
|
||||||
|
|
||||||
# Apply the configuration for general use throughout the app
|
# Apply the configuration for general use throughout the app
|
||||||
APP_VERSION = __version__
|
APP_VERSION = __version__
|
||||||
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
|
||||||
@@ -726,9 +728,9 @@ def _append_audit_event_standalone(event_type, payload):
|
|||||||
al.append_audit_event(
|
al.append_audit_event(
|
||||||
db=db,
|
db=db,
|
||||||
event_type=event_type,
|
event_type=event_type,
|
||||||
actor=encrypt_text(session.get('username', 'system')),
|
actor=session.get('username', 'system'),
|
||||||
payload=encrypt_text(str(payload)),
|
payload=str(payload),
|
||||||
request_ip=encrypt_text(request.remote_addr),
|
request_ip=request.remote_addr,
|
||||||
source='web',
|
source='web',
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -8044,23 +8046,14 @@ def admin_audit_dashboard():
|
|||||||
al.ensure_audit_indexes(db)
|
al.ensure_audit_indexes(db)
|
||||||
verify_result = al.verify_audit_chain(db)
|
verify_result = al.verify_audit_chain(db)
|
||||||
|
|
||||||
audit_rows = list(
|
audit_rows = list(db['audit_log'].find(...).sort('chain_index', -1).limit(200))
|
||||||
db['audit_log'].find(
|
|
||||||
{},
|
# DEC_START: Decrypt the sensitive fields for display
|
||||||
{
|
for row in audit_rows:
|
||||||
'chain_index': 1,
|
if "payload" in row:
|
||||||
'event_type': 1,
|
# decrypt_document_fields acts in-place
|
||||||
'actor': 1,
|
decrypt_document_fields(row["payload"], SENSITIVE_AUDIT_FIELDS)
|
||||||
'source': 1,
|
# DEC_END
|
||||||
'ip': 1,
|
|
||||||
'timestamp': 1,
|
|
||||||
'created_at': 1,
|
|
||||||
'entry_hash': 1,
|
|
||||||
'prev_hash': 1,
|
|
||||||
'payload': 1,
|
|
||||||
}
|
|
||||||
).sort('chain_index', -1).limit(200)
|
|
||||||
)
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin_audit.html',
|
'admin_audit.html',
|
||||||
@@ -8105,28 +8098,18 @@ def admin_audit_export_pdf_official():
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
audit_rows = list(
|
audit_rows = list(db['audit_log'].find(...).sort('chain_index', -1).limit(limit))
|
||||||
db['audit_log'].find(
|
|
||||||
{},
|
# DEC_START: Decrypt sensitive fields for the PDF report
|
||||||
{
|
for row in audit_rows:
|
||||||
'chain_index': 1,
|
if "payload" in row:
|
||||||
'event_type': 1,
|
decrypt_document_fields(row["payload"], SENSITIVE_AUDIT_FIELDS)
|
||||||
'actor': 1,
|
# DEC_END
|
||||||
'source': 1,
|
|
||||||
'ip': 1,
|
|
||||||
'timestamp': 1,
|
|
||||||
'created_at': 1,
|
|
||||||
'entry_hash': 1,
|
|
||||||
'prev_hash': 1,
|
|
||||||
'payload': 1,
|
|
||||||
}
|
|
||||||
).sort('chain_index', -1).limit(limit)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get school information from settings or use defaults
|
# Get school information from settings or use defaults
|
||||||
school_info = _get_school_info_for_export()
|
school_info = _get_school_info_for_export()
|
||||||
|
|
||||||
# Generate PDF
|
# Generate PDF with now-decrypted audit_rows
|
||||||
pdf_content = pdf_export.generate_audit_pdf(
|
pdf_content = pdf_export.generate_audit_pdf(
|
||||||
verify_result=verify_result,
|
verify_result=verify_result,
|
||||||
event_counts=event_counts,
|
event_counts=event_counts,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
from Web.modules.inventarsystem.data_protection import encrypt_document_fields, decrypt_document_fields
|
||||||
|
|
||||||
from pymongo.errors import DuplicateKeyError
|
from pymongo.errors import DuplicateKeyError
|
||||||
|
|
||||||
@@ -24,21 +25,34 @@ def _entry_hash(prev_hash, payload):
|
|||||||
base = f"{prev_hash}|{_stable_json(payload)}"
|
base = f"{prev_hash}|{_stable_json(payload)}"
|
||||||
return hashlib.sha256(base.encode("utf-8")).hexdigest()
|
return hashlib.sha256(base.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
def get_decrypted_audit_logs(db, query=None, decrypt_fields=None):
|
||||||
def append_audit_event(db, event_type, actor, payload, request_ip=None, source="web", max_retries=5):
|
|
||||||
"""
|
"""
|
||||||
Append an audit event to a tamper-evident chain.
|
Retrieve and decrypt audit logs for analysis.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
db: MongoDB database handle.
|
query (dict): MongoDB query filter.
|
||||||
event_type (str): Event category.
|
decrypt_fields (list): Fields within the 'payload' that should be decrypted.
|
||||||
actor (str): User/system who performed the action.
|
"""
|
||||||
payload (dict): Event details.
|
logs = db["audit_log"]
|
||||||
request_ip (str, optional): Request origin.
|
cursor = logs.find(query or {}).sort("chain_index", 1)
|
||||||
source (str): Source subsystem.
|
|
||||||
|
|
||||||
Returns:
|
results = []
|
||||||
dict: Inserted audit entry.
|
for entry in cursor:
|
||||||
|
# Decrypt specific fields if provided
|
||||||
|
if decrypt_fields:
|
||||||
|
decrypt_document_fields(entry.get("payload", {}), decrypt_fields)
|
||||||
|
results.append(entry)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def append_audit_event(db, event_type, actor, payload, request_ip=None, source="web", max_retries=5, encrypt_fields=None):
|
||||||
|
"""
|
||||||
|
Append an audit event, optionally encrypting specific payload fields.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
...
|
||||||
|
encrypt_fields (list, optional): List of keys in 'payload' to encrypt.
|
||||||
"""
|
"""
|
||||||
logs = db["audit_log"]
|
logs = db["audit_log"]
|
||||||
attempts = 0
|
attempts = 0
|
||||||
@@ -49,15 +63,24 @@ def append_audit_event(db, event_type, actor, payload, request_ip=None, source="
|
|||||||
chain_index = int(previous.get("chain_index", 0)) + 1 if previous else 1
|
chain_index = int(previous.get("chain_index", 0)) + 1 if previous else 1
|
||||||
|
|
||||||
timestamp = datetime.datetime.utcnow()
|
timestamp = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
# 1. Create the payload dictionary
|
||||||
|
event_payload = payload or {}
|
||||||
|
|
||||||
|
# 2. Encrypt sensitive fields in-place if requested
|
||||||
|
if encrypt_fields:
|
||||||
|
encrypt_document_fields(event_payload, encrypt_fields)
|
||||||
|
|
||||||
entry_payload = {
|
entry_payload = {
|
||||||
"event_type": event_type,
|
"event_type": event_type,
|
||||||
"actor": actor or "system",
|
"actor": actor or "system",
|
||||||
"source": source,
|
"source": source,
|
||||||
"ip": request_ip or "",
|
"ip": request_ip or "",
|
||||||
"payload": payload or {},
|
"payload": event_payload,
|
||||||
"timestamp": timestamp.isoformat() + "Z",
|
"timestamp": timestamp.isoformat() + "Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 3. Hash the payload (which now contains encrypted values)
|
||||||
entry_hash = _entry_hash(prev_hash, entry_payload)
|
entry_hash = _entry_hash(prev_hash, entry_payload)
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|||||||
Reference in New Issue
Block a user