diff --git a/gatehouse_app/api/v1/organizations/cas.py b/gatehouse_app/api/v1/organizations/cas.py index ad1ac71..e9ce377 100644 --- a/gatehouse_app/api/v1/organizations/cas.py +++ b/gatehouse_app/api/v1/organizations/cas.py @@ -6,6 +6,8 @@ from gatehouse_app.utils.response import api_response from gatehouse_app.utils.decorators import login_required, require_admin from gatehouse_app.extensions import db from gatehouse_app.api.v1.organizations._helpers import _get_system_ca_dict +from gatehouse_app.utils.constants import AuditAction +from gatehouse_app.services.audit_service import AuditService @api_v1_bp.route("/organizations//cas", methods=["GET"]) @@ -182,13 +184,12 @@ def delete_org_ca(org_id, ca_id): ca.is_active = False ca.delete(soft=True) - AuditLog.log( + AuditService.log_action( action=AuditAction.CA_DELETED, user_id=g.current_user.id, + organization_id=org_id, resource_type="CA", resource_id=ca_id, - organization_id=org_id, - ip_address=request.remote_addr, description=f"CA '{ca_name}' ({ca_type}) deleted", ) return api_response(data={"ca_id": ca_id}, message="CA deleted successfully") @@ -206,8 +207,6 @@ def rotate_org_ca(org_id, ca_id): from gatehouse_app.models.organization.organization import Organization from gatehouse_app.utils.crypto import compute_ssh_fingerprint from gatehouse_app.utils.ca_key_encryption import encrypt_ca_key - from gatehouse_app.utils.constants import AuditAction - from gatehouse_app.models import AuditLog from sshkey_tools.keys import Ed25519PrivateKey, RsaPrivateKey, EcdsaPrivateKey org = Organization.query.filter_by(id=org_id, deleted_at=None).first() @@ -244,14 +243,13 @@ def rotate_org_ca(org_id, ca_id): ca.key_type = KeyType(new_key_type) db.session.commit() - AuditLog.log( + AuditService.log_action( action=AuditAction.CA_KEY_ROTATED, user_id=g.current_user.id, + organization_id=org_id, resource_type="CA", resource_id=ca_id, - organization_id=org_id, - ip_address=request.remote_addr, - description=(f"CA '{ca.name}' key rotated. Old fingerprint: {old_fingerprint}, New fingerprint: {new_fingerprint}. Reason: {reason}"), + description=f"CA '{ca.name}' key rotated. Old fingerprint: {old_fingerprint}, New fingerprint: {new_fingerprint}. Reason: {reason}", ) return api_response(data={"ca": ca.to_dict(), "old_fingerprint": old_fingerprint}, message="CA key rotated successfully. Update TrustedUserCAKeys / known_hosts on your servers.") diff --git a/gatehouse_app/api/v1/policies.py b/gatehouse_app/api/v1/policies.py index c97465f..47ac5ea 100644 --- a/gatehouse_app/api/v1/policies.py +++ b/gatehouse_app/api/v1/policies.py @@ -6,8 +6,7 @@ from gatehouse_app.utils.response import api_response from gatehouse_app.utils.decorators import login_required, require_admin, full_access_required from gatehouse_app.services.mfa_policy_service import MfaPolicyService from gatehouse_app.services.organization_service import OrganizationService -from gatehouse_app.services.audit_service import AuditService -from gatehouse_app.utils.constants import MfaPolicyMode, MfaRequirementOverride, MfaComplianceStatus, AuditAction +from gatehouse_app.utils.constants import MfaPolicyMode, MfaRequirementOverride, MfaComplianceStatus class UpdateOrgPolicySchema(Schema): @@ -291,16 +290,6 @@ def update_user_security_policy(org_id, user_id): updated_by_user_id=g.current_user.id, ) - # Log the override change with details - AuditService.log_action( - action=AuditAction.USER_SECURITY_POLICY_OVERRIDE_UPDATE, - user_id=g.current_user.id, - organization_id=org_id, - resource_type="user", - resource_id=user_id, - description=f"User security policy override changed to {data['mfa_override_mode']} for user {user_id}", - ) - return api_response( data={ "user_security_policy": { diff --git a/gatehouse_app/api/v1/ssh/admin.py b/gatehouse_app/api/v1/ssh/admin.py index 1676339..73f1d3e 100644 --- a/gatehouse_app/api/v1/ssh/admin.py +++ b/gatehouse_app/api/v1/ssh/admin.py @@ -2,7 +2,7 @@ from flask import request, g from gatehouse_app.api.v1.ssh._helpers import ssh_bp from gatehouse_app.utils.constants import AuditAction, OrganizationRole -from gatehouse_app.models import AuditLog +from gatehouse_app.services.audit_service import AuditService from gatehouse_app.utils.decorators import login_required from gatehouse_app.utils.response import api_response @@ -78,7 +78,14 @@ def add_ca_permission(ca_id): db.session.add(perm) db.session.commit() - AuditLog.log(action=AuditAction.CA_UPDATED, user_id=user.id, resource_type="CAPermission", resource_id=perm.id, ip_address=request.remote_addr, description=f"Granted '{permission}' on CA '{ca.name}' to user {target_user.email}") + AuditService.log_action( + action=AuditAction.CA_UPDATED, + user_id=user.id, + organization_id=ca.organization_id, + resource_type="CAPermission", + resource_id=perm.id, + description=f"Granted '{permission}' on CA '{ca.name}' to user {target_user.email}", + ) d = perm.to_dict() d["user_email"] = target_user.email @@ -102,10 +109,21 @@ def remove_ca_permission(ca_id, target_user_id): if not membership or membership.role not in (OrganizationRole.ADMIN, OrganizationRole.OWNER): return api_response(success=False, message="Admin access required", status=403, error_type="FORBIDDEN") + target_user = User.query.filter_by(id=target_user_id, deleted_at=None).first() + if not target_user: + return api_response(success=False, message="User not found", status=404, error_type="NOT_FOUND") + perm = CAPermission.query.filter_by(ca_id=ca_id, user_id=target_user_id, deleted_at=None).first() if not perm: return api_response(success=False, message="Permission not found", status=404, error_type="NOT_FOUND") perm.delete(soft=True) - AuditLog.log(action=AuditAction.CA_UPDATED, user_id=user.id, resource_type="CAPermission", resource_id=perm.id, ip_address=request.remote_addr, description=f"Revoked permission on CA '{ca.name}' from user {target_user_id}") + AuditService.log_action( + action=AuditAction.CA_UPDATED, + user_id=user.id, + organization_id=ca.organization_id, + resource_type="CAPermission", + resource_id=perm.id, + description=f"Revoked permission on CA '{ca.name}' from user {target_user.email}", + ) return api_response(data={}, message="Permission revoked") diff --git a/gatehouse_app/api/v1/ssh/certs.py b/gatehouse_app/api/v1/ssh/certs.py index 4babcb4..e4875e0 100644 --- a/gatehouse_app/api/v1/ssh/certs.py +++ b/gatehouse_app/api/v1/ssh/certs.py @@ -8,7 +8,7 @@ from gatehouse_app.api.v1.ssh._helpers import ( from gatehouse_app.services.ssh_ca_signing_service import SSHCertificateSigningRequest from gatehouse_app.exceptions import SSHKeyNotFoundError, SSHCertificateError from gatehouse_app.utils.constants import AuditAction, OrganizationRole -from gatehouse_app.models import AuditLog +from gatehouse_app.services.audit_service import AuditService from gatehouse_app.models.ssh_ca.certificate_audit_log import CertificateAuditLog from gatehouse_app.utils.decorators import login_required from gatehouse_app.utils.response import api_response @@ -68,10 +68,11 @@ def sign_certificate(): expiry_hours = data.get('expiry_hours') requested_org_id = data.get('organization_id') - AuditLog.log( + AuditService.log_action( action=AuditAction.SSH_CERT_REQUESTED, - user_id=user_id, resource_type='SSHCertificate', ip_address=request.remote_addr, - description=(f'{user.email} requested a certificate' + (f' for principals: {", ".join(requested_principals)}' if requested_principals else '')), + user_id=user_id, + resource_type="SSHCertificate", + description=f"{user.email} requested a certificate" + (f" for principals: {', '.join(requested_principals)}" if requested_principals else ""), ) # Validate organization_id if provided @@ -209,10 +210,24 @@ def sign_certificate(): ca_private_key_pem = decrypt_ca_key(db_ca.private_key) response = ssh_ca_service.sign_certificate(signing_request, ca_private_key=ca_private_key_pem, ca_obj=db_ca) except SSHCertificateError as e: - AuditLog.log(action=AuditAction.SSH_CERT_FAILED, user_id=user_id, resource_type='SSHCertificate', ip_address=request.remote_addr, success=False, error_message=str(e)) + AuditService.log_action( + action=AuditAction.SSH_CERT_FAILED, + user_id=user_id, + resource_type="SSHCertificate", + description=f"Certificate signing failed", + success=False, + error_message=str(e), + ) return api_response(success=False, message=str(e), status=400, error_type="SIGNING_FAILED") except Exception as e: - AuditLog.log(action=AuditAction.SSH_CERT_FAILED, user_id=user_id, resource_type='SSHCertificate', ip_address=request.remote_addr, success=False, error_message=str(e)) + AuditService.log_action( + action=AuditAction.SSH_CERT_FAILED, + user_id=user_id, + resource_type="SSHCertificate", + description=f"Certificate signing failed", + success=False, + error_message=str(e), + ) return api_response(success=False, message="Certificate signing failed", status=500, error_type="SERVER_ERROR") cert_record = _persist_certificate( @@ -221,12 +236,14 @@ def sign_certificate(): cert_type_str=cert_type, cert_identity=cert_identity, ) - AuditLog.log( - action=AuditAction.SSH_CERT_ISSUED, user_id=user_id, - resource_type='SSHCertificate', resource_id=cert_record.id if cert_record else key_id, - ip_address=request.remote_addr, - description=f'Certificate serial={response.serial} issued for {user.email}; principals: {", ".join(principals)}', - extra_data={'serial': response.serial, 'key_id': cert_identity, 'principals': principals, 'ca_id': str(db_ca.id), 'ssh_key_id': str(key_id), 'organization_id': str(target_org.id), 'organization_name': target_org.name}, + AuditService.log_action( + action=AuditAction.SSH_CERT_ISSUED, + user_id=user_id, + organization_id=str(target_org.id), + resource_type="SSHCertificate", + resource_id=cert_record.id if cert_record else key_id, + metadata={"serial": response.serial, "key_id": cert_identity, "principals": principals, "ca_id": str(db_ca.id), "ssh_key_id": str(key_id)}, + description=f"Certificate serial={response.serial} issued for {user.email}; principals: {', '.join(principals)}", ) if cert_record: @@ -340,7 +357,15 @@ def sign_host_certificate(): ca_private_key_pem = decrypt_ca_key(host_ca.private_key) response = ssh_ca_service.sign_certificate(signing_request, ca_private_key=ca_private_key_pem, ca_obj=host_ca) except Exception as exc: - AuditLog.log(action=AuditAction.SSH_CERT_FAILED, user_id=user_id, resource_type="SSHCertificate", ip_address=request.remote_addr, success=False, error_message=str(exc)) + AuditService.log_action( + action=AuditAction.SSH_CERT_FAILED, + user_id=user_id, + organization_id=host_ca.organization_id, + resource_type="SSHCertificate", + description=f"Host certificate signing failed", + success=False, + error_message=str(exc), + ) return api_response(success=False, message=f"Host certificate signing failed: {exc}", status=500, error_type="SIGNING_FAILED") cert_record = _persist_certificate( @@ -349,12 +374,14 @@ def sign_host_certificate(): cert_type_str="host", cert_identity=cert_identity, ) - AuditLog.log( - action=AuditAction.SSH_CERT_ISSUED, user_id=user_id, - resource_type="SSHCertificate", resource_id=cert_record.id if cert_record else None, - ip_address=request.remote_addr, + AuditService.log_action( + action=AuditAction.SSH_CERT_ISSUED, + user_id=user_id, + organization_id=host_ca.organization_id, + resource_type="SSHCertificate", + resource_id=cert_record.id if cert_record else None, + metadata={"serial": response.serial, "principals": principals, "ca_id": str(host_ca.id), "cert_type": "host"}, description=f"Host certificate serial={response.serial} issued for {primary_principal} by {user.email}", - extra_data={"serial": response.serial, "principals": principals, "ca_id": str(host_ca.id), "cert_type": "host"}, ) result = { @@ -415,7 +442,13 @@ def revoke_certificate(cert_id): return api_response(success=False, message='Certificate is already revoked', status=409, error_type='ALREADY_REVOKED') cert.revoke(reason=reason) - AuditLog.log(action=AuditAction.SSH_CERT_REVOKED, user_id=user_id, resource_type='SSHCertificate', resource_id=cert_id, ip_address=request.remote_addr, description=f'Revoked: {reason}') + AuditService.log_action( + action=AuditAction.SSH_CERT_REVOKED, + user_id=user_id, + resource_type="SSHCertificate", + resource_id=cert_id, + description=f"Certificate revoked: {reason}", + ) # Get organization from certificate's CA for audit logging from gatehouse_app.models.ssh_ca.ca import CA diff --git a/gatehouse_app/api/v1/ssh/keys.py b/gatehouse_app/api/v1/ssh/keys.py index e028130..ed41294 100644 --- a/gatehouse_app/api/v1/ssh/keys.py +++ b/gatehouse_app/api/v1/ssh/keys.py @@ -4,7 +4,7 @@ from flask import request, g from gatehouse_app.api.v1.ssh._helpers import ssh_bp, ssh_key_service from gatehouse_app.exceptions import SSHKeyError, SSHKeyNotFoundError, ValidationError, SSHKeyAlreadyExistsError from gatehouse_app.utils.constants import AuditAction -from gatehouse_app.models import AuditLog +from gatehouse_app.services.audit_service import AuditService from gatehouse_app.utils.decorators import login_required from gatehouse_app.utils.response import api_response @@ -34,7 +34,13 @@ def add_ssh_key(): try: ssh_key, is_new = ssh_key_service.add_ssh_key(user_id=user_id, public_key=public_key, description=description) if is_new: - AuditLog.log(action=AuditAction.SSH_KEY_ADDED, user_id=user_id, resource_type='SSHKey', resource_id=ssh_key.id, ip_address=request.remote_addr) + AuditService.log_action( + action=AuditAction.SSH_KEY_ADDED, + user_id=user_id, + resource_type="SSHKey", + resource_id=ssh_key.id, + description=f"SSH key added", + ) return api_response(success=True, message='SSH key added', data=ssh_key.to_dict(), status=201) else: return api_response(success=True, message='SSH key already exists', data=ssh_key.to_dict(), status=200) @@ -68,7 +74,13 @@ def delete_ssh_key(key_id): if ssh_key.user_id != user_id: return api_response(success=False, message='Forbidden', status=403, error_type='FORBIDDEN') ssh_key_service.delete_ssh_key(key_id) - AuditLog.log(action=AuditAction.SSH_KEY_DELETED, user_id=user_id, resource_type='SSHKey', resource_id=key_id, ip_address=request.remote_addr) + AuditService.log_action( + action=AuditAction.SSH_KEY_DELETED, + user_id=user_id, + resource_type="SSHKey", + resource_id=key_id, + description=f"SSH key deleted", + ) return api_response(success=True, message='SSH key deleted', data={'status': 'deleted'}, status=200) except SSHKeyNotFoundError: return api_response(success=False, message='SSH key not found', status=404, error_type='NOT_FOUND') @@ -96,10 +108,25 @@ def verify_ssh_key(key_id): return api_response(success=False, message='signature is required', status=400, error_type='BAD_REQUEST') try: verified = ssh_key_service.verify_ssh_key_ownership(key_id, signature) - AuditLog.log(action=AuditAction.SSH_KEY_VERIFIED, user_id=user_id, resource_type='SSHKey', resource_id=key_id, ip_address=request.remote_addr, success=verified) + AuditService.log_action( + action=AuditAction.SSH_KEY_VERIFIED, + user_id=user_id, + resource_type="SSHKey", + resource_id=key_id, + description=f"SSH key verified", + success=verified, + ) return api_response(success=True, message='Verification complete', data={'verified': verified}, status=200) except Exception as e: - AuditLog.log(action=AuditAction.SSH_KEY_VALIDATION_FAILED, user_id=user_id, resource_type='SSHKey', resource_id=key_id, ip_address=request.remote_addr, success=False, error_message=str(e)) + AuditService.log_action( + action=AuditAction.SSH_KEY_VALIDATION_FAILED, + user_id=user_id, + resource_type="SSHKey", + resource_id=key_id, + description=f"SSH key validation failed", + success=False, + error_message=str(e), + ) return api_response(success=False, message=str(e), status=400, error_type='VERIFICATION_FAILED') else: challenge = ssh_key_service.generate_verification_challenge(key_id) diff --git a/gatehouse_app/api/v1/users/admin.py b/gatehouse_app/api/v1/users/admin.py index 8a2b671..d4db372 100644 --- a/gatehouse_app/api/v1/users/admin.py +++ b/gatehouse_app/api/v1/users/admin.py @@ -443,7 +443,7 @@ def admin_restore_user(user_id): _db.session.commit() AuditService.log_action( - action=AuditAction.USER_UNSUSPEND, # closest existing action + action=AuditAction.USER_RESTORE, user_id=caller.id, organization_id=_get_admin_access(caller, target).organization_id, resource_type="user", resource_id=str(target.id), diff --git a/gatehouse_app/api/v1/zerotier.py b/gatehouse_app/api/v1/zerotier.py index 244784f..09ea560 100644 --- a/gatehouse_app/api/v1/zerotier.py +++ b/gatehouse_app/api/v1/zerotier.py @@ -22,7 +22,7 @@ from gatehouse_app.models import ( ) from gatehouse_app.models.organization import Organization from gatehouse_app.models.organization.organization_member import OrganizationMember -from gatehouse_app.utils.constants import OrganizationRole +from gatehouse_app.utils.constants import OrganizationRole, AuditAction from gatehouse_app.exceptions import ( ValidationError as AppValidationError, ZeroTierAPIError, @@ -1154,7 +1154,7 @@ def set_zerotier_config(org_id): from gatehouse_app.services.audit_service import AuditService AuditService.log_action( - action="org.zerotier_config.updated", + action=AuditAction.ZT_CONFIG_UPDATED, user_id=g.current_user.id, organization_id=org_id, resource_type="organization", @@ -1206,7 +1206,7 @@ def delete_zerotier_config(org_id): from gatehouse_app.services.audit_service import AuditService AuditService.log_action( - action="org.zerotier_config.deleted", + action=AuditAction.ZT_CONFIG_DELETED, user_id=g.current_user.id, organization_id=org_id, resource_type="organization", diff --git a/gatehouse_app/models/auth/audit_log.py b/gatehouse_app/models/auth/audit_log.py index 7ccf106..96d2394 100644 --- a/gatehouse_app/models/auth/audit_log.py +++ b/gatehouse_app/models/auth/audit_log.py @@ -1,7 +1,6 @@ """Audit log model.""" from gatehouse_app.extensions import db from gatehouse_app.models.base import BaseModel -from gatehouse_app.utils.constants import AuditAction class AuditLog(BaseModel): @@ -42,19 +41,3 @@ class AuditLog(BaseModel): def __repr__(self): """String representation of AuditLog.""" return f"" - - @classmethod - def log(cls, action, user_id=None, **kwargs) -> "AuditLog": - """Create an audit log entry. - - Args: - action: AuditAction enum value - user_id: ID of the user performing the action - **kwargs: Additional audit log fields - - Returns: - AuditLog instance - """ - log_entry = cls(action=action, user_id=user_id, **kwargs) - log_entry.save() - return log_entry diff --git a/gatehouse_app/services/device_service.py b/gatehouse_app/services/device_service.py index 706f134..fe51135 100644 --- a/gatehouse_app/services/device_service.py +++ b/gatehouse_app/services/device_service.py @@ -7,6 +7,7 @@ from gatehouse_app.extensions import db from gatehouse_app.models import Device from gatehouse_app.models.user import User from gatehouse_app.services.audit_service import AuditService +from gatehouse_app.utils.constants import AuditAction from gatehouse_app.exceptions import ( DeviceNotFoundError, DeviceAlreadyExistsError, @@ -74,7 +75,7 @@ def register_device( device.save() AuditService.log_action( - action="device.registered", + action=AuditAction.DEVICE_REGISTERED, user_id=user_id, organization_id=organization_id, resource_type="device", @@ -142,7 +143,7 @@ def update_device( device.update(**kwargs) AuditService.log_action( - action="device.updated", + action=AuditAction.DEVICE_UPDATED, user_id=user_id, organization_id=device.organization_id, resource_type="device", @@ -175,7 +176,7 @@ def remove_device(device_id: str, user_id: str) -> None: device.delete(soft=True) AuditService.log_action( - action="device.removed", + action=AuditAction.DEVICE_REMOVED, user_id=user_id, organization_id=device.organization_id, resource_type="device", diff --git a/gatehouse_app/services/mfa_policy_service.py b/gatehouse_app/services/mfa_policy_service.py index 83e09d7..78709a5 100644 --- a/gatehouse_app/services/mfa_policy_service.py +++ b/gatehouse_app/services/mfa_policy_service.py @@ -871,7 +871,7 @@ class MfaPolicyService: org_ids = [org.organization_id for org in suspended_orgs] AuditService.log_action( - action=AuditAction.USER_LOGIN, + action=AuditAction.LOGIN_BLOCKED_COMPLIANCE, user_id=user.id, organization_id=org_ids[0] if org_ids else None, description=f"Login attempt while compliance suspended. Suspended orgs: {org_ids}", @@ -898,7 +898,7 @@ class MfaPolicyService: user_agent: Client user agent """ AuditService.log_action( - action=AuditAction.USER_LOGIN, # Reusing USER_LOGIN for audit + action=AuditAction.MFA_COMPLIANCE_BYPASS_ATTEMPT, user_id=user.id, resource_type="endpoint", resource_id=endpoint, diff --git a/gatehouse_app/services/network_access_service.py b/gatehouse_app/services/network_access_service.py index 58d7db5..c7fcbe9 100644 --- a/gatehouse_app/services/network_access_service.py +++ b/gatehouse_app/services/network_access_service.py @@ -23,6 +23,7 @@ from gatehouse_app.utils.constants import ( ApprovalState, ActivationEndReason, KillSwitchScope, + AuditAction, ) from gatehouse_app.exceptions import ( ApprovalNotFoundError, @@ -89,7 +90,7 @@ def request_access( _ensure_zerotier_member(device.node_id, portal_network_id, authorized=False) AuditService.log_action( - action="zt.approval.reopened", + action=AuditAction.ZT_APPROVAL_REOPENED, user_id=user_id, organization_id=organization_id, resource_type="network_access_request", @@ -122,7 +123,7 @@ def request_access( _ensure_zerotier_member(device.node_id, portal_network_id, authorized=False) AuditService.log_action( - action="zt.approval.requested", + action=AuditAction.ZT_APPROVAL_REQUESTED, user_id=user_id, organization_id=organization_id, resource_type="network_access_request", @@ -206,7 +207,7 @@ def assign_access( _ensure_zerotier_member(device.node_id, portal_network_id, authorized=False) AuditService.log_action( - action="zt.approval.granted", + action=AuditAction.ZT_APPROVAL_GRANTED, user_id=granted_by_user_id, organization_id=organization_id, resource_type="network_access_request", @@ -238,7 +239,7 @@ def approve_request( request.save() AuditService.log_action( - action="zt.approval.granted", + action=AuditAction.ZT_APPROVAL_GRANTED, user_id=approver_user_id, organization_id=request.organization_id, resource_type="network_access_request", @@ -266,7 +267,7 @@ def reject_request( request.save() AuditService.log_action( - action="zt.approval.rejected", + action=AuditAction.ZT_APPROVAL_REJECTED, user_id=rejecter_user_id, organization_id=request.organization_id, resource_type="network_access_request", @@ -307,7 +308,7 @@ def revoke_access( logger.warning(f"[revoke_access] Could not deauthorize {device.node_id}: {exc}") AuditService.log_action( - action="zt.approval.revoked", + action=AuditAction.ZT_APPROVAL_REVOKED, user_id=revoker_user_id, organization_id=request.organization_id, resource_type="network_access_request", @@ -417,7 +418,7 @@ def activate_request( _authorize_in_zerotier(device.node_id, network.zerotier_network_id, request) AuditService.log_action( - action="zt.membership.activated", + action=AuditAction.ZT_MEMBERSHIP_ACTIVATED, user_id=user_id, organization_id=request.organization_id, resource_type="activation_session", @@ -485,7 +486,7 @@ def deactivate_request( request.save() AuditService.log_action( - action="zt.membership.deactivated", + action=AuditAction.ZT_MEMBERSHIP_DEACTIVATED, user_id=deactivated_by_user_id, organization_id=request.organization_id, resource_type="network_access_request", @@ -548,7 +549,7 @@ def kill_switch( # Log audit AuditService.log_action( - action="zt.kill_switch.activated", + action=AuditAction.ZT_KILL_SWITCH_ACTIVATED, user_id=user_id, organization_id=org_id, resource_type="network_access_request", @@ -637,7 +638,7 @@ def _authorize_in_zerotier( zt_membership.save() AuditService.log_action( - action="zt.member.authorized", + action=AuditAction.ZT_MEMBER_AUTHORIZED, user_id=request.user_id, organization_id=request.organization_id, resource_type="zerotier_membership", @@ -677,7 +678,7 @@ def _deauthorize_in_zerotier(node_id: str, zerotier_network_id: str, zt_membership.save() AuditService.log_action( - action="zt.member.deauthorized", + action=AuditAction.ZT_MEMBER_DEAUTHORIZED, user_id=None, organization_id=zt_membership.organization_id, resource_type="zerotier_membership", @@ -785,7 +786,7 @@ def join_network_for_device( _ensure_zerotier_member(device.node_id, portal_network_id, authorized=False) AuditService.log_action( - action="zt.membership.created", + action=AuditAction.ZT_MEMBERSHIP_CREATED, user_id=user_id, organization_id=organization_id, resource_type="network_access_request", @@ -832,7 +833,7 @@ def revoke_request_soft( request.save() AuditService.log_action( - action="zt.request.revoked", + action=AuditAction.ZT_REQUEST_REVOKED, user_id=revoker_user_id, organization_id=request.organization_id, resource_type="network_access_request", diff --git a/gatehouse_app/services/notification_service.py b/gatehouse_app/services/notification_service.py index fb9639a..f29bf4a 100644 --- a/gatehouse_app/services/notification_service.py +++ b/gatehouse_app/services/notification_service.py @@ -123,7 +123,7 @@ class NotificationService: f"({days_until_deadline} days remaining)" ) AuditService.log_action( - action=AuditAction.MFA_POLICY_USER_COMPLIANT, + action=AuditAction.MFA_NOTIFICATION_SENT, user_id=user.id, organization_id=compliance.organization_id, description=f"MFA deadline reminder sent. Days remaining: {days_until_deadline}", @@ -196,7 +196,7 @@ class NotificationService: ) logger.info(f"Sent MFA suspension notification to {user.email}") AuditService.log_action( - action=AuditAction.MFA_POLICY_USER_SUSPENDED, + action=AuditAction.MFA_SUSPENSION_NOTIFICATION_SENT, user_id=user.id, organization_id=compliance.organization_id, description="MFA compliance suspension notification sent", diff --git a/gatehouse_app/services/oauth_flow/login.py b/gatehouse_app/services/oauth_flow/login.py index c5fd4c0..1378e2f 100644 --- a/gatehouse_app/services/oauth_flow/login.py +++ b/gatehouse_app/services/oauth_flow/login.py @@ -246,7 +246,7 @@ def handle_login_callback( auth_method.save() AuditService.log_action( - action="user.register", + action=AuditAction.USER_REGISTER, user_id=user.id, organization_id=state_record.organization_id, resource_type="user", diff --git a/gatehouse_app/services/oauth_flow/register.py b/gatehouse_app/services/oauth_flow/register.py index c3790d5..b17ac9b 100644 --- a/gatehouse_app/services/oauth_flow/register.py +++ b/gatehouse_app/services/oauth_flow/register.py @@ -142,7 +142,7 @@ def handle_register_callback( state_record.mark_used() AuditService.log_action( - action="user.register", + action=AuditAction.USER_REGISTER, user_id=user.id, organization_id=state_record.organization_id, resource_type="user", diff --git a/gatehouse_app/services/organization_service.py b/gatehouse_app/services/organization_service.py index 9d6e6ae..a65102e 100644 --- a/gatehouse_app/services/organization_service.py +++ b/gatehouse_app/services/organization_service.py @@ -353,7 +353,7 @@ class OrganizationService: resource_type="organization_member", resource_id=member.id, metadata={"added_user_id": user_id, "role": role.value}, - description=f"Member added to organization with role: {role.value}", + description=f"Member {user_id} added to organization with role: {role.value}", ) return member @@ -398,7 +398,7 @@ class OrganizationService: resource_type="organization_member", resource_id=member.id, metadata={"removed_user_id": user_id}, - description="Member removed from organization", + description=f"Member {user_id} removed from organization", ) @staticmethod @@ -438,7 +438,7 @@ class OrganizationService: "old_role": old_role.value, "new_role": new_role.value, }, - description=f"Member role changed from {old_role.value} to {new_role.value}", + description=f"Member {user_id} role changed from {old_role.value} to {new_role.value}", ) return member diff --git a/gatehouse_app/services/portal_network_service.py b/gatehouse_app/services/portal_network_service.py index 4be9e31..86577e5 100644 --- a/gatehouse_app/services/portal_network_service.py +++ b/gatehouse_app/services/portal_network_service.py @@ -9,7 +9,7 @@ from gatehouse_app.models.organization import Organization from gatehouse_app.models.user import User from gatehouse_app.services.audit_service import AuditService from gatehouse_app.services import zerotier_api_service as zt -from gatehouse_app.utils.constants import NetworkRequestMode, NetworkEnvironment +from gatehouse_app.utils.constants import NetworkRequestMode, NetworkEnvironment, AuditAction from gatehouse_app.exceptions import ( NetworkNotFoundError, InvalidNetworkIdError, @@ -110,7 +110,7 @@ def create_network( deleted.save() AuditService.log_action( - action="zt.network.restored", + action=AuditAction.ZT_NETWORK_RESTORED, user_id=owner_user_id, organization_id=organization_id, resource_type="portal_network", @@ -157,7 +157,7 @@ def create_network( ) AuditService.log_action( - action="zt.network.created", + action=AuditAction.ZT_NETWORK_CREATED, user_id=owner_user_id, organization_id=organization_id, resource_type="portal_network", @@ -246,7 +246,7 @@ def update_network( network.update(**kwargs) AuditService.log_action( - action="zt.network.updated", + action=AuditAction.ZT_NETWORK_UPDATED, user_id=user_id, organization_id=network.organization_id, resource_type="portal_network", @@ -292,7 +292,7 @@ def delete_network(network_id: str, user_id: str) -> None: db.session.commit() AuditService.log_action( - action="zt.network.deleted", + action=AuditAction.ZT_NETWORK_DELETED, user_id=user_id, organization_id=network.organization_id, resource_type="portal_network", diff --git a/gatehouse_app/services/zerotier_reconciliation_service.py b/gatehouse_app/services/zerotier_reconciliation_service.py index 32fe366..344be89 100644 --- a/gatehouse_app/services/zerotier_reconciliation_service.py +++ b/gatehouse_app/services/zerotier_reconciliation_service.py @@ -16,6 +16,7 @@ from gatehouse_app.services import zerotier_api_service as zt from gatehouse_app.utils.constants import ( ActivationEndReason, ApprovalState, + AuditAction, ) logger = logging.getLogger(__name__) @@ -452,7 +453,7 @@ def _expire_session(session: ActivationSession) -> None: from gatehouse_app.services.audit_service import AuditService AuditService.log_action( - action="zt.activation.expired", + action=AuditAction.ZT_ACTIVATION_EXPIRED, user_id=session.user_id, organization_id=session.organization_id, resource_type="activation_session", diff --git a/gatehouse_app/utils/constants.py b/gatehouse_app/utils/constants.py index adbb669..1af09a0 100644 --- a/gatehouse_app/utils/constants.py +++ b/gatehouse_app/utils/constants.py @@ -64,9 +64,16 @@ class AuditAction(str, Enum): USER_HARD_DELETE = "user.hard_delete" USER_SUSPEND = "user.suspend" USER_UNSUSPEND = "user.unsuspend" + USER_RESTORE = "user.restore" PASSWORD_CHANGE = "user.password_change" PASSWORD_RESET = "user.password_reset" + # Login/security events + LOGIN_BLOCKED_COMPLIANCE = "login.blocked.compliance" + MFA_COMPLIANCE_BYPASS_ATTEMPT = "mfa.compliance.bypass_attempt" + MFA_NOTIFICATION_SENT = "mfa.notification.sent" + MFA_SUSPENSION_NOTIFICATION_SENT = "mfa.suspension_notification.sent" + # Organization actions ORG_CREATE = "org.create" ORG_UPDATE = "org.update" @@ -155,6 +162,32 @@ class AuditAction(str, Enum): DEPARTMENT_MEMBER_ADDED = "department.member.added" DEPARTMENT_MEMBER_REMOVED = "department.member.removed" + # ZeroTier network actions + ZT_APPROVAL_REOPENED = "zt.approval.reopened" + ZT_APPROVAL_REQUESTED = "zt.approval.requested" + ZT_APPROVAL_GRANTED = "zt.approval.granted" + ZT_APPROVAL_REJECTED = "zt.approval.rejected" + ZT_APPROVAL_REVOKED = "zt.approval.revoked" + ZT_MEMBERSHIP_ACTIVATED = "zt.membership.activated" + ZT_MEMBERSHIP_DEACTIVATED = "zt.membership.deactivated" + ZT_MEMBERSHIP_CREATED = "zt.membership.created" + ZT_MEMBER_AUTHORIZED = "zt.member.authorized" + ZT_MEMBER_DEAUTHORIZED = "zt.member.deauthorized" + ZT_REQUEST_REVOKED = "zt.request.revoked" + ZT_KILL_SWITCH_ACTIVATED = "zt.kill_switch.activated" + ZT_ACTIVATION_EXPIRED = "zt.activation.expired" + ZT_NETWORK_CREATED = "zt.network.created" + ZT_NETWORK_UPDATED = "zt.network.updated" + ZT_NETWORK_DELETED = "zt.network.deleted" + ZT_NETWORK_RESTORED = "zt.network.restored" + ZT_CONFIG_UPDATED = "org.zerotier_config.updated" + ZT_CONFIG_DELETED = "org.zerotier_config.deleted" + + # Device actions + DEVICE_REGISTERED = "device.registered" + DEVICE_UPDATED = "device.updated" + DEVICE_REMOVED = "device.removed" + class OIDCGrantType(str, Enum): """OIDC grant types."""