diff --git a/Backend/api/.env.example b/Backend/api/.env.example index ea9ceb0..8a97e48 100644 --- a/Backend/api/.env.example +++ b/Backend/api/.env.example @@ -40,6 +40,12 @@ S1_HEC_TOKEN=your-hec-token S1_SDL_API_TOKEN=your-sdl-api-token S1_HEC_URL=https://your-instance.sentinelone.net/api/v1/cloud_connect/events/raw +# UAM Alert Ingest (Service Account - separate from HEC) +S1_UAM_INGEST_URL=https://ingest.us1.sentinelone.net +S1_UAM_SERVICE_TOKEN=your-service-account-token +S1_UAM_ACCOUNT_ID=your-account-id +S1_UAM_SITE_ID= + # CORS Origins (comma-separated) BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8080 diff --git a/Backend/api/app/alerts/__init__.py b/Backend/api/app/alerts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Backend/api/app/alerts/templates/advanced_sample_alert.json b/Backend/api/app/alerts/templates/advanced_sample_alert.json new file mode 100644 index 0000000..6a74f95 --- /dev/null +++ b/Backend/api/app/alerts/templates/advanced_sample_alert.json @@ -0,0 +1,106 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "HELIOS Advanced Sample Alert", + "desc": "Advanced sample alert with related events, generated by HELIOS alert engine", + "related_events": [ + { + "type": "Process Creation", + "attacks": [ + { + "tactic": { + "uid": "TA0001", + "name": "Initial Access" + }, + "technique": { + "uid": "T1566.001", + "name": "Spearphishing Attachment" + }, + "version": "13.1" + } + ], + "uid": "placeholder_uid", + "observables": [ + { + "name": "file.name", + "type_id": 7, + "value": "malicious_payload.exe" + }, + { + "name": "process.pid", + "type_id": 29, + "value": "4821" + } + ], + "severity_id": 4, + "time": "DYNAMIC", + "message": "Suspicious process created from spearphishing attachment" + }, + { + "type": "Network Connection", + "attacks": [ + { + "tactic": { + "uid": "TA0011", + "name": "Command and Control" + }, + "technique": { + "uid": "T1071.001", + "name": "Web Protocols" + }, + "version": "13.1" + } + ], + "uid": "placeholder_uid", + "observables": [ + { + "name": "dst_endpoint.ip", + "type_id": 2, + "value": "198.51.100.23" + }, + { + "name": "dst_endpoint.port", + "type_id": 29, + "value": "443" + } + ], + "severity_id": 3, + "time": "DYNAMIC", + "message": "Outbound C2 connection detected over HTTPS" + } + ] + }, + "resources": [ + { + "uid": "helios-asset-001", + "name": "helios-endpoint-01" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "HELIOS", + "vendor_name": "RoarinPenguin" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 1 +} diff --git a/Backend/api/app/alerts/templates/default_alert.json b/Backend/api/app/alerts/templates/default_alert.json new file mode 100644 index 0000000..636ee83 --- /dev/null +++ b/Backend/api/app/alerts/templates/default_alert.json @@ -0,0 +1,85 @@ +{ + "activity_id": 1, + "attack_surface_ids": [1], + "category_uid": 2, + "category_name": "Findings", + "class_name": "S1 Security Alert", + "class_uid": 99602001, + "type_name": "S1 Security Alert: Create", + "type_uid": 9960200101, + "finding_info": { + "title": "Suspicious Activity", + "uid": "placeholder_uid", + "desc": "Malware Detection - Suspicious file activity detected" + }, + "evidences": [ + { + "process": { + "file": { + "name": "malicious_file.exe", + "path": "/usr/local/bin/malicious_file.exe", + "size": 1024000, + "hashes": [ + { + "algorithm_id": 6, + "value": "2c4d2c6262d46313990e31bf7b6437028a566ba8" + }, + { + "algorithm_id": 1, + "value": "d41d8cd98f00b204e9800998ecf8427e" + }, + { + "algorithm_id": 2, + "value": "da39a3ee5e6b4b0d3255bfef95601890afd80709" + } + ], + "signature": { + "algorithm_id": 3, + "certificate": { + "subject": "CN=CodeSign Cert, O=Acme Corp, C=US", + "serial_number": "1234567890ABCDEF", + "expiration_time": 1718400000000, + "fingerprints": [ + { + "algorithm_id": 6, + "value": "2c4d2c6262d46313990e31bf7b6437028a566ba8" + }, + { + "algorithm_id": 1, + "value": "d41d8cd98f00b204e9800998ecf8427e" + } + ], + "issuer": "Acme Corp" + } + }, + "type_id": 3 + } + } + } + ], + "resources": [ + { + "name": "endpoint-workstation-01", + "uid": "res-001" + } + ], + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "HELIOS", + "vendor_name": "RoarinPenguin" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "severity_id": 1, + "state_id": 1, + "s1_classification_id": 0, + "confidence_id": 1 +} diff --git a/Backend/api/app/alerts/templates/o365_admin_consent_all.json b/Backend/api/app/alerts/templates/o365_admin_consent_all.json new file mode 100644 index 0000000..f530c99 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_admin_consent_all.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Admin Consent Granted for All Principals", + "desc": "Admin consent granted for all applications/principals. Critical privilege escalation risk." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "critical", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 5, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_antiphish_rule_disabled.json b/Backend/api/app/alerts/templates/o365_antiphish_rule_disabled.json new file mode 100644 index 0000000..c01ef10 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_antiphish_rule_disabled.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Deactivation or Removal of Anti-Phish Rule", + "desc": "Anti-phishing rule deactivated or removed. Critical email security control disabled." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_app_role_assigned.json b/Backend/api/app/alerts/templates/o365_app_role_assigned.json new file mode 100644 index 0000000..8bc990b --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_app_role_assigned.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Application Role Assigned to Service Principal", + "desc": "Application role assigned to service principal. Review for unauthorized privilege assignment." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_attachment_removed.json b/Backend/api/app/alerts/templates/o365_attachment_removed.json new file mode 100644 index 0000000..f203901 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_attachment_removed.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Malicious Email Attachment Removed After Delivery", + "desc": "Malicious email attachment removed post-delivery. ZAP protection activated." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_audit_bypass.json b/Backend/api/app/alerts/templates/o365_audit_bypass.json new file mode 100644 index 0000000..b4cffa3 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_audit_bypass.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Mailbox Audit Logging Bypass", + "desc": "Mailbox audit logging bypass detected. Potential stealth activity attempt." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_auto_delete_rule.json b/Backend/api/app/alerts/templates/o365_auto_delete_rule.json new file mode 100644 index 0000000..ad31fd8 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_auto_delete_rule.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Inbox Rule to Automatically Delete Messages", + "desc": "Inbox rule auto-deleting messages. Potential evidence removal or attack concealment." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_bec_inbox_rule.json b/Backend/api/app/alerts/templates/o365_bec_inbox_rule.json new file mode 100644 index 0000000..130ef41 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_bec_inbox_rule.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Inbox Rule Containing BEC Keywords", + "desc": "Inbox rule with BEC keywords detected. Potential business email compromise setup." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_bec_rss_redirect.json b/Backend/api/app/alerts/templates/o365_bec_rss_redirect.json new file mode 100644 index 0000000..995ebae --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_bec_rss_redirect.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Potential Business Email Compromise via RSS Feed Redirection", + "desc": "BEC via RSS feed redirection detected. Sophisticated email attack vector." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_bec_short_param.json b/Backend/api/app/alerts/templates/o365_bec_short_param.json new file mode 100644 index 0000000..9417a6b --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_bec_short_param.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Potential Business Email Compromise via Short Parameter Name", + "desc": "BEC via short parameter name detected. Review for suspicious email configuration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_brute_force_success.json b/Backend/api/app/alerts/templates/o365_brute_force_success.json new file mode 100644 index 0000000..e4ee154 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_brute_force_success.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Successful Brute Force Attack", + "desc": "Successful brute force attack detected. Account compromised." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "critical", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 5, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_ca_policy_deleted.json b/Backend/api/app/alerts/templates/o365_ca_policy_deleted.json new file mode 100644 index 0000000..ecdbf56 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_ca_policy_deleted.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Conditional Access Policy Deleted", + "desc": "Conditional access policy deleted. Potential security control weakening." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_ca_policy_updated.json b/Backend/api/app/alerts/templates/o365_ca_policy_updated.json new file mode 100644 index 0000000..d259410 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_ca_policy_updated.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Conditional Access Policy Updated", + "desc": "Conditional access policy updated. Review for unauthorized security control changes." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_cloudsponge_activity.json b/Backend/api/app/alerts/templates/o365_cloudsponge_activity.json new file mode 100644 index 0000000..14d8c6a --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_cloudsponge_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 CloudSponge Application Activity", + "desc": "CloudSponge application activity detected. Review for authorized contact synchronization." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_connector_removed.json b/Backend/api/app/alerts/templates/o365_connector_removed.json new file mode 100644 index 0000000..af77af8 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_connector_removed.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Outbound Connector Removal", + "desc": "Outbound connector removed. Review mail flow configuration changes." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_copilot_jailbreak.json b/Backend/api/app/alerts/templates/o365_copilot_jailbreak.json new file mode 100644 index 0000000..6143bcd --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_copilot_jailbreak.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Copilot Jailbreaked Interaction", + "desc": "Copilot jailbreak interaction detected. Potential AI security bypass attempt." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_dlp_policy_deleted.json b/Backend/api/app/alerts/templates/o365_dlp_policy_deleted.json new file mode 100644 index 0000000..9075492 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_dlp_policy_deleted.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Deletion of Data Loss Prevention (DLP) Policy", + "desc": "DLP policy deleted. Potential data protection control removal." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_emclient_activity.json b/Backend/api/app/alerts/templates/o365_emclient_activity.json new file mode 100644 index 0000000..b498fb9 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_emclient_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 eM Client Application Activity", + "desc": "eM Client application activity detected. Review for authorized email client access." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_external_redirection.json b/Backend/api/app/alerts/templates/o365_external_redirection.json new file mode 100644 index 0000000..15b0cd9 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_external_redirection.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Inbox Rule Redirection to External Address", + "desc": "Inbox rule redirecting to external address. Potential data exfiltration channel." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_fasthttp_activity.json b/Backend/api/app/alerts/templates/o365_fasthttp_activity.json new file mode 100644 index 0000000..9e2911b --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_fasthttp_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 FastHTTP User Agent Activity", + "desc": "FastHTTP user agent activity detected. Review for automated or suspicious access patterns." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_fastmail_activity.json b/Backend/api/app/alerts/templates/o365_fastmail_activity.json new file mode 100644 index 0000000..732b0f6 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_fastmail_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Fastmail Application Activity", + "desc": "Fastmail application activity detected. Review for authorized email client access." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_federation_domain.json b/Backend/api/app/alerts/templates/o365_federation_domain.json new file mode 100644 index 0000000..5b03647 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_federation_domain.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 New or Modified Federation Domain", + "desc": "Federation domain created or modified. Critical identity configuration change." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_forwarding_rule.json b/Backend/api/app/alerts/templates/o365_forwarding_rule.json new file mode 100644 index 0000000..e73e8a2 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_forwarding_rule.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 New Mailbox Forwarding Rule", + "desc": "New mailbox forwarding rule created. Potential data exfiltration setup." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_full_access_app.json b/Backend/api/app/alerts/templates/o365_full_access_app.json new file mode 100644 index 0000000..90ead46 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_full_access_app.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Mailbox Permission Full Access As App Granted", + "desc": "Full access mailbox permission granted to app. Critical data access risk." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_inbound_connector.json b/Backend/api/app/alerts/templates/o365_inbound_connector.json new file mode 100644 index 0000000..da6aa60 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_inbound_connector.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 New Inbound Connector", + "desc": "New inbound connector created. Review mail flow configuration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_inbox_rule_redirect.json b/Backend/api/app/alerts/templates/o365_inbox_rule_redirect.json new file mode 100644 index 0000000..aae746e --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_inbox_rule_redirect.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "First Seen Office 365 Inbox Rule to Redirect Message", + "desc": "New inbox rule detected that redirects messages. Potential business email compromise or data exfiltration attempt." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_intune_ca_bypass.json b/Backend/api/app/alerts/templates/o365_intune_ca_bypass.json new file mode 100644 index 0000000..03a1ef8 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_intune_ca_bypass.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Conditional Access Bypass via Intune Company Portal App", + "desc": "Conditional access bypassed via Intune Company Portal. Potential MFA bypass attempt." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_mail_transport_rule.json b/Backend/api/app/alerts/templates/o365_mail_transport_rule.json new file mode 100644 index 0000000..7405490 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_mail_transport_rule.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Creation of Mail Transport Rule", + "desc": "New mail transport rule created. Potential email flow manipulation or data exfiltration setup." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_mailbox_delegation.json b/Backend/api/app/alerts/templates/o365_mailbox_delegation.json new file mode 100644 index 0000000..7093855 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_mailbox_delegation.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Mailbox Permissions Delegation", + "desc": "Mailbox permissions delegated. Review for unauthorized access grants." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_malware_filter_disabled.json b/Backend/api/app/alerts/templates/o365_malware_filter_disabled.json new file mode 100644 index 0000000..fdf4f23 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_malware_filter_disabled.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Deactivation or Removal of Malware Filter Rule", + "desc": "Malware filter rule deactivated or removed. Critical email security control disabled." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_malware_policy_deleted.json b/Backend/api/app/alerts/templates/o365_malware_policy_deleted.json new file mode 100644 index 0000000..4443a96 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_malware_policy_deleted.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Deletion of Malware Filter Policy", + "desc": "Malware filter policy deleted. Critical email security control removed." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_management_group_role.json b/Backend/api/app/alerts/templates/o365_management_group_role.json new file mode 100644 index 0000000..68b5929 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_management_group_role.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Assignment of Management Group Role", + "desc": "Management group role assigned. Potential privilege escalation or persistence mechanism." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_noncompliant_login.json b/Backend/api/app/alerts/templates/o365_noncompliant_login.json new file mode 100644 index 0000000..f5d91dd --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_noncompliant_login.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Login by Non Compliant Device", + "desc": "Login from non-compliant device detected. Potential policy violation or compromised device." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_oauth_email_name.json b/Backend/api/app/alerts/templates/o365_oauth_email_name.json new file mode 100644 index 0000000..c968ddf --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_oauth_email_name.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 OAuth Application with Email Address in Name", + "desc": "OAuth application with email in name detected. Review for suspicious app registration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_oauth_nonalpha.json b/Backend/api/app/alerts/templates/o365_oauth_nonalpha.json new file mode 100644 index 0000000..9d3bc57 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_oauth_nonalpha.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 OAuth Application with Non-Alphanumeric Name", + "desc": "OAuth application with non-alphanumeric name. Potential suspicious app registration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_outbound_connector.json b/Backend/api/app/alerts/templates/o365_outbound_connector.json new file mode 100644 index 0000000..a247ce4 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_outbound_connector.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 New Outbound Connector", + "desc": "New outbound connector created. Review mail flow configuration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_perfectdata_activity.json b/Backend/api/app/alerts/templates/o365_perfectdata_activity.json new file mode 100644 index 0000000..86c2ad0 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_perfectdata_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 PerfectData Application Activity", + "desc": "PerfectData application activity detected. Review for authorized data processing." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_rclone_download.json b/Backend/api/app/alerts/templates/o365_rclone_download.json new file mode 100644 index 0000000..525c5f1 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_rclone_download.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Rclone Application Download Activity", + "desc": "Rclone download activity. Potential bulk data exfiltration tool." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 27 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_rclone_modify.json b/Backend/api/app/alerts/templates/o365_rclone_modify.json new file mode 100644 index 0000000..624bc5c --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_rclone_modify.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Rclone Application Data Modification Activity", + "desc": "Rclone data modification activity. Potential bulk data exfiltration tool." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 27 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_rdp_sharepoint_access.json b/Backend/api/app/alerts/templates/o365_rdp_sharepoint_access.json new file mode 100644 index 0000000..58ec997 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_rdp_sharepoint_access.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Access or Download of .RDP Files in SharePoint", + "desc": "Access or download of RDP files from SharePoint detected. Potential remote desktop access or lateral movement preparation." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_rdp_upload.json b/Backend/api/app/alerts/templates/o365_rdp_upload.json new file mode 100644 index 0000000..fec96a8 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_rdp_upload.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Upload of .RDP Files to SharePoint", + "desc": "RDP files uploaded to SharePoint. Potential lateral movement preparation." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 27 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_restricted_sending.json b/Backend/api/app/alerts/templates/o365_restricted_sending.json new file mode 100644 index 0000000..8574d74 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_restricted_sending.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 User Restricted from Sending Email Messages", + "desc": "User restricted from sending emails. Potential compromised account activity." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sc_high_alert.json b/Backend/api/app/alerts/templates/o365_sc_high_alert.json new file mode 100644 index 0000000..88ac195 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sc_high_alert.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Security and Compliance High Severity Alert Triggered", + "desc": "High severity security alert triggered in Security & Compliance Center." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sc_info_alert.json b/Backend/api/app/alerts/templates/o365_sc_info_alert.json new file mode 100644 index 0000000..2b45f2a --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sc_info_alert.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Security and Compliance Informational Severity Alert Triggered", + "desc": "Informational severity alert triggered in Security & Compliance Center." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "low", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 2, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sc_low_alert.json b/Backend/api/app/alerts/templates/o365_sc_low_alert.json new file mode 100644 index 0000000..4b8e7da --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sc_low_alert.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Security and Compliance Low Severity Alert Triggered", + "desc": "Low severity alert triggered in Security & Compliance Center." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "low", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 2, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sc_medium_alert.json b/Backend/api/app/alerts/templates/o365_sc_medium_alert.json new file mode 100644 index 0000000..81dddca --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sc_medium_alert.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Security and Compliance Medium Severity Alert Triggered", + "desc": "Medium severity alert triggered in Security & Compliance Center." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_service_principal.json b/Backend/api/app/alerts/templates/o365_service_principal.json new file mode 100644 index 0000000..550c98c --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_service_principal.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Service Principal Addition", + "desc": "New service principal added. Review for authorized application access." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sigparser_activity.json b/Backend/api/app/alerts/templates/o365_sigparser_activity.json new file mode 100644 index 0000000..121096a --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sigparser_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 SigParser Application Activity", + "desc": "SigParser application activity detected. Review for authorized email processing." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_sneaky_2fa.json b/Backend/api/app/alerts/templates/o365_sneaky_2fa.json new file mode 100644 index 0000000..ebf441a --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_sneaky_2fa.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Sneaky 2FA User Agent Activity", + "desc": "Sneaky 2FA user agent detected. Potential MFA bypass attempt." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_spike_activity.json b/Backend/api/app/alerts/templates/o365_spike_activity.json new file mode 100644 index 0000000..7142c8e --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_spike_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Spike Application Activity", + "desc": "Spike application activity detected. Review for authorized email marketing." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_supermailer_activity.json b/Backend/api/app/alerts/templates/o365_supermailer_activity.json new file mode 100644 index 0000000..7423d29 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_supermailer_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Supermailer Application Activity", + "desc": "Supermailer application activity detected. Review for authorized bulk email." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_threats_zap.json b/Backend/api/app/alerts/templates/o365_threats_zap.json new file mode 100644 index 0000000..c0c82ad --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_threats_zap.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Threats Detected via Zero-Hour Auto Purge", + "desc": "Threats detected and removed via ZAP. Post-delivery protection active." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_transport_rule_disabled.json b/Backend/api/app/alerts/templates/o365_transport_rule_disabled.json new file mode 100644 index 0000000..78c3737 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_transport_rule_disabled.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Deactivation or Removal of Mail Transport Rule", + "desc": "Mail transport rule deactivated or removed. Review email security configuration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_url_removed.json b/Backend/api/app/alerts/templates/o365_url_removed.json new file mode 100644 index 0000000..d6aa97e --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_url_removed.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Malicious URL Email Removed After Delivery", + "desc": "Malicious URL email removed post-delivery. ZAP protection activated." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_zap_removed.json b/Backend/api/app/alerts/templates/o365_zap_removed.json new file mode 100644 index 0000000..f1f76d8 --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_zap_removed.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 Malicious URL Emails Removed via Zero-hour Auto Purge", + "desc": "Emails removed via Zero-hour Auto Purge. Post-delivery protection active." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/o365_zoominfo_activity.json b/Backend/api/app/alerts/templates/o365_zoominfo_activity.json new file mode 100644 index 0000000..831efaf --- /dev/null +++ b/Backend/api/app/alerts/templates/o365_zoominfo_activity.json @@ -0,0 +1,42 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Office 365 ZoomInfo Application Activity", + "desc": "ZoomInfo application activity detected. Review for authorized contact enrichment." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [ + 1 + ], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} \ No newline at end of file diff --git a/Backend/api/app/alerts/templates/proofpoint_attachment_delivered.json b/Backend/api/app/alerts/templates/proofpoint_attachment_delivered.json new file mode 100644 index 0000000..68b1619 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_attachment_delivered.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Malicious Email Attachment Delivered", + "desc": "Email containing malicious attachment was delivered to recipient. Attachment may contain malware, ransomware, or exploit code." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_email_alert.json b/Backend/api/app/alerts/templates/proofpoint_email_alert.json new file mode 100644 index 0000000..1b0a9d1 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_email_alert.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Malicious Email Link Clicked", + "desc": "A user clicked a malicious URL in an email flagged by Proofpoint TAP. The link was classified as phishing and has been observed in active campaigns targeting enterprise credentials." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 1 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_impostor_unblocked.json b/Backend/api/app/alerts/templates/proofpoint_impostor_unblocked.json new file mode 100644 index 0000000..7a773da --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_impostor_unblocked.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Impostor Email Unblocked", + "desc": "Email impersonating a trusted domain or individual was delivered after policy override. High risk of business email compromise or credential theft." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_large_attachments.json b/Backend/api/app/alerts/templates/proofpoint_large_attachments.json new file mode 100644 index 0000000..a311154 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_large_attachments.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint High Volume of Large Attachments to Recipient", + "desc": "Unusual volume of large email attachments detected to a single recipient. Pattern may indicate data exfiltration or bulk malicious file distribution." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 1 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_outbound_phishing.json b/Backend/api/app/alerts/templates/proofpoint_outbound_phishing.json new file mode 100644 index 0000000..06ce0b2 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_outbound_phishing.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Outbound Phishing Email", + "desc": "Outbound email detected with phishing characteristics. Internal account may be compromised or used for phishing campaign." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "critical", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 5, + "state_id": 1, + "s1_classification_id": 28 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_phishing_link_clicked.json b/Backend/api/app/alerts/templates/proofpoint_phishing_link_clicked.json new file mode 100644 index 0000000..52e9633 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_phishing_link_clicked.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Phishing Email Link Clicked", + "desc": "User clicked on a phishing link in an email that was previously flagged as malicious. Potential credential theft or malware delivery." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_phishing_unblocked.json b/Backend/api/app/alerts/templates/proofpoint_phishing_unblocked.json new file mode 100644 index 0000000..2299747 --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_phishing_unblocked.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Phishing Email Unblocked", + "desc": "Phishing email was delivered after policy override. High risk of credential theft or account compromise." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 28 +} diff --git a/Backend/api/app/alerts/templates/proofpoint_source_code_attachments.json b/Backend/api/app/alerts/templates/proofpoint_source_code_attachments.json new file mode 100644 index 0000000..49b5f7b --- /dev/null +++ b/Backend/api/app/alerts/templates/proofpoint_source_code_attachments.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "Proofpoint Source Code Files In Email Attachment", + "desc": "Email contains source code files as attachments. Potential intellectual property theft or code exfiltration." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "medium", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Proofpoint TAP", + "vendor_name": "Proofpoint" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 3, + "state_id": 1, + "s1_classification_id": 27 +} diff --git a/Backend/api/app/alerts/templates/sample_alert.json b/Backend/api/app/alerts/templates/sample_alert.json new file mode 100644 index 0000000..fa20f5f --- /dev/null +++ b/Backend/api/app/alerts/templates/sample_alert.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "HELIOS Sample Alert", + "desc": "Sample alert generated by HELIOS alert engine" + }, + "resources": [ + { + "uid": "helios-asset-001", + "name": "helios-endpoint-01" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "HELIOS", + "vendor_name": "RoarinPenguin" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 1 +} diff --git a/Backend/api/app/alerts/templates/sharepoint_data_exfil_alert.json b/Backend/api/app/alerts/templates/sharepoint_data_exfil_alert.json new file mode 100644 index 0000000..83a16ba --- /dev/null +++ b/Backend/api/app/alerts/templates/sharepoint_data_exfil_alert.json @@ -0,0 +1,40 @@ +{ + "finding_info": { + "uid": "placeholder_uid", + "title": "SharePoint Data Exfiltration Detected", + "desc": "User jeanluc@starfleet.com accessed and downloaded multiple sensitive documents from SharePoint sites including Personnel Records, Command Codes, and Enterprise Schematics. Pattern consistent with data exfiltration after initial compromise." + }, + "resources": [ + { + "uid": "jeanluc@starfleet.com", + "name": "jeanluc@starfleet.com" + } + ], + "severity": "high", + "category_uid": 2, + "class_uid": 99602001, + "class_name": "S1 Security Alert", + "type_uid": 9960200101, + "type_name": "S1 Security Alert: Create", + "category_name": "Findings", + "activity_id": 1, + "metadata": { + "version": "1.1.0", + "extension": { + "name": "s1", + "uid": "998", + "version": "0.1.0" + }, + "product": { + "name": "Microsoft 365", + "vendor_name": "Microsoft" + }, + "logged_time": "DYNAMIC", + "modified_time": "DYNAMIC" + }, + "time": "DYNAMIC", + "attack_surface_ids": [1], + "severity_id": 4, + "state_id": 1, + "s1_classification_id": 27 +} diff --git a/Backend/api/app/main.py b/Backend/api/app/main.py index 88c15f3..2cfcd21 100644 --- a/Backend/api/app/main.py +++ b/Backend/api/app/main.py @@ -16,7 +16,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from app.core.config import settings -from app.routers import generators, parsers, health, scenarios, export, metrics, search, categories, destinations, uploads, parser_sync +from app.routers import generators, parsers, health, scenarios, export, metrics, search, categories, destinations, uploads, parser_sync, alerts from app.routers import settings as settings_router from app.utils.logging import setup_logging from app.core.simple_auth import validate_api_keys_config @@ -237,6 +237,12 @@ async def root(): tags=["settings"] ) +app.include_router( + alerts.router, + prefix=f"{settings.API_V1_STR}/alerts", + tags=["alerts"] +) + if __name__ == "__main__": import uvicorn uvicorn.run( diff --git a/Backend/api/app/models/destination.py b/Backend/api/app/models/destination.py index ce49ad4..89446ea 100644 --- a/Backend/api/app/models/destination.py +++ b/Backend/api/app/models/destination.py @@ -24,6 +24,12 @@ class Destination(Base): config_write_token_encrypted = Column(Text, nullable=True) # For putFile API powerquery_read_token_encrypted = Column(Text, nullable=True) # For PowerQuery Log Read Access + # UAM Alert Ingest (Service Account - separate from HEC) + uam_ingest_url = Column(String, nullable=True) # e.g., https://ingest.us1.sentinelone.net + uam_account_id = Column(String, nullable=True) # SentinelOne account ID + uam_site_id = Column(String, nullable=True) # Optional SentinelOne site ID + uam_service_token_encrypted = Column(Text, nullable=True) # Encrypted Service Account token + # Syslog fields ip = Column(String, nullable=True) port = Column(Integer, nullable=True) @@ -62,6 +68,12 @@ def to_dict(self, include_token=False, encryption_service=None): result['config_api_url'] = self.config_api_url result['has_config_write_token'] = bool(self.config_write_token_encrypted) result['has_powerquery_read_token'] = bool(self.powerquery_read_token_encrypted) + + # UAM Alert Ingest settings + result['uam_ingest_url'] = self.uam_ingest_url + result['uam_account_id'] = self.uam_account_id + result['uam_site_id'] = self.uam_site_id + result['has_uam_service_token'] = bool(self.uam_service_token_encrypted) elif self.type == 'syslog': result['ip'] = self.ip result['port'] = self.port diff --git a/Backend/api/app/routers/alerts.py b/Backend/api/app/routers/alerts.py new file mode 100644 index 0000000..a766454 --- /dev/null +++ b/Backend/api/app/routers/alerts.py @@ -0,0 +1,159 @@ +""" +Alert management and egress API endpoints +========================================== + +Sends alerts to SentinelOne via the UAM ingest API. +This is a separate API from HEC and requires its own Service Account token. +""" +from fastapi import APIRouter, HTTPException, Depends +from pydantic import BaseModel, Field +from typing import Optional, Dict, Any, List +import logging + +from app.core.simple_auth import require_read_access, require_write_access +from app.models.responses import BaseResponse +from app.services.alert_service import alert_service + +logger = logging.getLogger(__name__) + +router = APIRouter() + + +class AlertSendRequest(BaseModel): + """Request model for sending an alert from a template""" + template_id: str = Field(..., description="Alert template ID (e.g. 'default_alert')") + token: str = Field(..., description="Service Account bearer token for UAM API") + account_id: str = Field(..., description="SentinelOne account ID") + uam_ingest_url: str = Field( + default="https://ingest.us1.sentinelone.net", + description="UAM ingest base URL" + ) + site_id: Optional[str] = Field(None, description="Optional SentinelOne site ID") + count: int = Field(1, ge=1, le=100, description="Number of alerts to send") + overrides: Optional[Dict[str, Any]] = Field( + None, + description="Optional field overrides (e.g. title, severity_id, finding_title)" + ) + + +class CustomAlertSendRequest(BaseModel): + """Request model for sending a custom alert JSON""" + alert_json: Dict[str, Any] = Field(..., description="Full alert JSON payload") + token: str = Field(..., description="Service Account bearer token for UAM API") + account_id: str = Field(..., description="SentinelOne account ID") + uam_ingest_url: str = Field( + default="https://ingest.us1.sentinelone.net", + description="UAM ingest base URL" + ) + site_id: Optional[str] = Field(None, description="Optional SentinelOne site ID") + auto_generate_uid: bool = Field( + True, + description="Auto-generate fresh UID and timestamp" + ) + + +@router.get("/templates", response_model=BaseResponse) +async def list_alert_templates( + _: str = Depends(require_read_access) +): + """List all available alert templates""" + templates = alert_service.list_templates() + return BaseResponse( + success=True, + data={ + "templates": templates, + "total": len(templates) + } + ) + + +@router.get("/templates/{template_id}", response_model=BaseResponse) +async def get_alert_template( + template_id: str, + _: str = Depends(require_read_access) +): + """Get a specific alert template by ID""" + template = alert_service.get_template(template_id) + if not template: + raise HTTPException(status_code=404, detail=f"Alert template '{template_id}' not found") + + return BaseResponse( + success=True, + data={ + "template_id": template_id, + "template": template + } + ) + + +@router.post("/send", response_model=BaseResponse) +async def send_alert( + req: AlertSendRequest, + _: str = Depends(require_write_access) +): + """ + Send one or more alerts from a template to the UAM ingest API. + + Requires: + - A Service Account token (different from HEC token) + - Account ID (and optionally Site ID) for the S1-Scope header + - UAM ingest URL (e.g. https://ingest.us1.sentinelone.net) + """ + try: + results = alert_service.send_alert( + template_id=req.template_id, + token=req.token, + account_id=req.account_id, + uam_ingest_url=req.uam_ingest_url, + site_id=req.site_id, + overrides=req.overrides, + count=req.count, + ) + + successful = sum(1 for r in results if r.get("success")) + failed = len(results) - successful + + return BaseResponse( + success=failed == 0, + data={ + "results": results, + "summary": { + "total": len(results), + "successful": successful, + "failed": failed, + } + } + ) + except Exception as e: + logger.error(f"Failed to send alert: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/send-custom", response_model=BaseResponse) +async def send_custom_alert( + req: CustomAlertSendRequest, + _: str = Depends(require_write_access) +): + """ + Send a custom alert JSON to the UAM ingest API. + + Provide the full alert payload. If auto_generate_uid is True (default), + a fresh UID and timestamp will be injected automatically. + """ + try: + result = alert_service.send_custom_alert( + alert_json=req.alert_json, + token=req.token, + account_id=req.account_id, + uam_ingest_url=req.uam_ingest_url, + site_id=req.site_id, + auto_generate_uid=req.auto_generate_uid, + ) + + return BaseResponse( + success=result.get("success", False), + data=result + ) + except Exception as e: + logger.error(f"Failed to send custom alert: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) diff --git a/Backend/api/app/routers/destinations.py b/Backend/api/app/routers/destinations.py index d5f18ff..673b5b9 100644 --- a/Backend/api/app/routers/destinations.py +++ b/Backend/api/app/routers/destinations.py @@ -28,6 +28,12 @@ class DestinationCreate(BaseModel): config_write_token: Optional[str] = Field(None, description="Config API token for reading and writing parsers") powerquery_read_token: Optional[str] = Field(None, description="PowerQuery Log Read Access token for querying SIEM data") + # UAM Alert Ingest (Service Account) + uam_ingest_url: Optional[str] = Field(None, description="UAM ingest URL (e.g., https://ingest.us1.sentinelone.net)") + uam_account_id: Optional[str] = Field(None, description="SentinelOne account ID for S1-Scope header") + uam_site_id: Optional[str] = Field(None, description="Optional SentinelOne site ID for S1-Scope header") + uam_service_token: Optional[str] = Field(None, description="Service Account bearer token for UAM alert API") + # Syslog fields ip: Optional[str] = Field(None, description="Syslog IP (required for syslog destinations)") port: Optional[int] = Field(None, description="Syslog port (required for syslog destinations)") @@ -42,6 +48,10 @@ class DestinationUpdate(BaseModel): config_api_url: Optional[str] = None config_write_token: Optional[str] = None powerquery_read_token: Optional[str] = None + uam_ingest_url: Optional[str] = None + uam_account_id: Optional[str] = None + uam_site_id: Optional[str] = None + uam_service_token: Optional[str] = None ip: Optional[str] = None port: Optional[int] = None protocol: Optional[str] = None @@ -62,6 +72,10 @@ class DestinationResponse(BaseModel): config_api_url: Optional[str] = None # Config API URL for parser management has_config_write_token: Optional[bool] = None # True if config API token is set has_powerquery_read_token: Optional[bool] = None # True if PowerQuery read token is set + uam_ingest_url: Optional[str] = None # UAM ingest URL + uam_account_id: Optional[str] = None # SentinelOne account ID + uam_site_id: Optional[str] = None # Optional site ID + has_uam_service_token: Optional[bool] = None # True if UAM service token is set class DestinationWithToken(DestinationResponse): @@ -139,6 +153,10 @@ async def create_destination( config_api_url=destination.config_api_url, config_write_token=destination.config_write_token, powerquery_read_token=destination.powerquery_read_token, + uam_ingest_url=destination.uam_ingest_url, + uam_account_id=destination.uam_account_id, + uam_site_id=destination.uam_site_id, + uam_service_token=destination.uam_service_token, ip=destination.ip, port=destination.port, protocol=destination.protocol @@ -272,6 +290,10 @@ async def update_destination( config_api_url=update.config_api_url, config_write_token=update.config_write_token, powerquery_read_token=update.powerquery_read_token, + uam_ingest_url=update.uam_ingest_url, + uam_account_id=update.uam_account_id, + uam_site_id=update.uam_site_id, + uam_service_token=update.uam_service_token, ip=update.ip, port=update.port, protocol=update.protocol @@ -397,3 +419,56 @@ async def get_destination_config_tokens( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to decrypt config token" ) + + +@router.get("/{dest_id}/uam-token") +async def get_destination_uam_token( + dest_id: str, + session: AsyncSession = Depends(get_session), + auth_info: tuple = Depends(get_api_key) +): + """ + Get decrypted UAM Service Account token for a destination (internal use only) + + Returns the decrypted UAM service token along with account_id, site_id, and ingest URL + """ + service = DestinationService(session) + destination = await service.get_destination(dest_id) + if not destination: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Destination '{dest_id}' not found" + ) + + if destination.type != 'hec': + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Only HEC destinations have UAM tokens" + ) + + if not destination.uam_service_token_encrypted: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="No UAM Service Account token found for this destination" + ) + + if not destination.uam_account_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No UAM Account ID configured for this destination" + ) + + try: + token = service.decrypt_token(destination.uam_service_token_encrypted) + return { + "token": token, + "account_id": destination.uam_account_id, + "site_id": destination.uam_site_id, + "uam_ingest_url": destination.uam_ingest_url or "https://ingest.us1.sentinelone.net" + } + except Exception as e: + logger.error(f"Failed to decrypt UAM token: {e}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to decrypt UAM token" + ) diff --git a/Backend/api/app/services/alert_service.py b/Backend/api/app/services/alert_service.py new file mode 100644 index 0000000..5061ab3 --- /dev/null +++ b/Backend/api/app/services/alert_service.py @@ -0,0 +1,380 @@ +""" +Alert service for sending UAM alerts to SentinelOne +==================================================== + +Uses the UAM ingest API (different from HEC). Requires: +- A Service Account token (separate from HEC token) +- S1-Scope header: {accountId} or {accountId}:{siteId} +- Gzip-compressed JSON payload +- POST to {uam_ingest_url}/v1/alerts +""" +import json +import gzip +import uuid +import time +import copy +import logging +from datetime import datetime +from typing import Dict, Any, List, Optional +from pathlib import Path + +import requests + +logger = logging.getLogger(__name__) + +# Directory containing alert template JSON files +TEMPLATES_DIR = Path(__file__).parent.parent / "alerts" / "templates" + + +class AlertService: + def __init__(self): + self.templates: Dict[str, Dict[str, Any]] = {} + self._load_templates() + + def _load_templates(self): + """Load all alert templates from the templates directory""" + if not TEMPLATES_DIR.exists(): + logger.warning(f"Alert templates directory not found: {TEMPLATES_DIR}") + return + + for json_file in TEMPLATES_DIR.glob("*.json"): + try: + with open(json_file, "r") as f: + template = json.load(f) + template_id = json_file.stem + self.templates[template_id] = template + logger.info(f"Loaded alert template: {template_id}") + except Exception as e: + logger.error(f"Failed to load alert template {json_file}: {e}") + + def list_templates(self) -> List[Dict[str, Any]]: + """List available alert templates with metadata""" + result = [] + for template_id, template in self.templates.items(): + display_title = ( + template.get("title") + or template.get("finding_info", {}).get("title") + or template.get("class_name") + or "Untitled" + ) + result.append({ + "id": template_id, + "title": display_title, + "class_name": template.get("class_name", ""), + "severity_id": template.get("severity_id", 0), + "finding_title": template.get("finding_info", {}).get("title", ""), + }) + return result + + def get_template(self, template_id: str) -> Optional[Dict[str, Any]]: + """Get a specific alert template by ID""" + return self.templates.get(template_id) + + def prepare_scenario_alert( + self, + template_id: str, + event_time: datetime, + user_email: str, + overrides: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Prepare alert with scenario-specific timestamp and user context + + Args: + template_id: Template ID to use + event_time: When the alert should be timestamped (datetime object) + user_email: User email to associate with the alert + overrides: Additional field overrides + + Returns: + Prepared alert dict ready for egress + """ + template = self.get_template(template_id) + if not template: + raise ValueError(f"Template {template_id} not found") + + alert = copy.deepcopy(template) + + # Inject fresh UID + if "finding_info" not in alert: + alert["finding_info"] = {} + alert["finding_info"]["uid"] = str(uuid.uuid4()) + + # Set scenario-specific timestamp (milliseconds since epoch) + time_ms = int(event_time.timestamp() * 1000) + alert["time"] = time_ms + + # Update metadata timestamps + if "metadata" not in alert: + alert["metadata"] = {} + alert["metadata"]["logged_time"] = time_ms + alert["metadata"]["modified_time"] = time_ms + + # Set user as the resource + alert["resources"] = [{ + "uid": user_email, + "name": user_email + }] + + # Apply additional overrides + if overrides: + for key, value in overrides.items(): + if "." in key: + # Handle nested keys like "finding_info.title" + keys = key.split(".") + current = alert + for k in keys[:-1]: + if k not in current: + current[k] = {} + current = current[k] + current[keys[-1]] = value + else: + alert[key] = value + + return alert + + def prepare_alert( + self, + template: Dict[str, Any], + overrides: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """ + Prepare an alert from a template by injecting fresh UID and timestamp. + + Args: + template: The base alert template dict + overrides: Optional dict of top-level field overrides + (e.g. title, severity_id, finding_info.title) + + Returns: + A new alert dict ready for egress + """ + alert = copy.deepcopy(template) + + # Inject fresh UID + if "finding_info" not in alert: + alert["finding_info"] = {} + alert["finding_info"]["uid"] = str(uuid.uuid4()) + + # Replace all "DYNAMIC" placeholders with current time (milliseconds since epoch) + time_ms = int(time.time() * 1000) + self._replace_dynamic(alert, time_ms) + + # Generate UIDs for related events + if "related_events" in alert.get("finding_info", {}): + for event in alert["finding_info"]["related_events"]: + event["uid"] = str(uuid.uuid4()) + + # Apply overrides + if overrides: + for key, value in overrides.items(): + if key == "finding_title" and "finding_info" in alert: + alert["finding_info"]["title"] = value + else: + alert[key] = value + + return alert + + def _replace_dynamic(self, obj: Any, time_ms: int) -> None: + """Recursively replace all 'DYNAMIC' string values with the given timestamp.""" + if isinstance(obj, dict): + for key in obj: + if obj[key] == "DYNAMIC": + obj[key] = time_ms + elif isinstance(obj[key], (dict, list)): + self._replace_dynamic(obj[key], time_ms) + elif isinstance(obj, list): + for i, item in enumerate(obj): + if item == "DYNAMIC": + obj[i] = time_ms + elif isinstance(item, (dict, list)): + self._replace_dynamic(item, time_ms) + + def build_scope(self, account_id: str, site_id: Optional[str] = None) -> str: + """Build the S1-Scope header value""" + if site_id: + return f"{account_id}:{site_id}" + return account_id + + def egress_alert( + self, + alert: Dict[str, Any], + scope: str, + token: str, + uam_ingest_url: str, + ) -> Dict[str, Any]: + """ + Send a single alert to the SentinelOne UAM ingest endpoint. + + Args: + alert: The prepared alert dict + scope: S1-Scope header value ({accountId} or {accountId}:{siteId}) + token: Service Account bearer token + uam_ingest_url: Base URL (e.g. https://ingest.us1.sentinelone.net) + + Returns: + Dict with status, statusText, and data on success; error info on failure + """ + headers = { + "Authorization": f"Bearer {token}", + "S1-Scope": scope, + "Content-Encoding": "gzip", + "Content-Type": "application/json", + } + + # UAM ingest API expects a single alert object (batch not supported for alerts) + payload = json.dumps(alert).encode("utf-8") + gzipped_alert = gzip.compress(payload) + logger.info(f"Compressed alert payload: {len(payload)} bytes -> {len(gzipped_alert)} bytes (gzip)") + + url = uam_ingest_url.rstrip("/") + "/v1/alerts" + + try: + logger.info(f"Sending POST request to {url}") + response = requests.post(url, headers=headers, data=gzipped_alert, timeout=30) + response.raise_for_status() + resp_body = response.text if response.content else "" + logger.info( + "UAM alert response: status=%s body=%s headers=%s", + response.status_code, resp_body[:500], + dict(response.headers) + ) + resp_data = {} + if response.content: + try: + resp_data = response.json() + except Exception: + resp_data = {"raw": resp_body[:500]} + return { + "success": True, + "status": response.status_code, + "status_text": response.reason, + "data": resp_data, + } + except requests.exceptions.HTTPError as err: + error_body = "" + try: + error_body = err.response.text + except Exception: + pass + logger.error("UAM alert HTTP error: %s - %s", err, error_body) + return { + "success": False, + "status": err.response.status_code if err.response is not None else 0, + "error": str(err), + "detail": error_body, + } + except requests.exceptions.RequestException as err: + logger.error("UAM alert request error: %s", err) + return { + "success": False, + "status": 0, + "error": str(err), + } + + def send_alert( + self, + template_id: str, + token: str, + account_id: str, + uam_ingest_url: str, + site_id: Optional[str] = None, + overrides: Optional[Dict[str, Any]] = None, + count: int = 1, + ) -> List[Dict[str, Any]]: + """ + High-level method: prepare and send one or more alerts from a template. + + Args: + template_id: ID of the alert template to use + token: Service Account bearer token + account_id: SentinelOne account ID + uam_ingest_url: Base ingest URL + site_id: Optional site ID + overrides: Optional field overrides + count: Number of alerts to send + + Returns: + List of result dicts (one per alert sent) + """ + template = self.get_template(template_id) + if not template: + return [{"success": False, "error": f"Template '{template_id}' not found"}] + + scope = self.build_scope(account_id, site_id) + results = [] + + for i in range(count): + alert = self.prepare_alert(template, overrides) + result = self.egress_alert(alert, scope, token, uam_ingest_url) + result["alert_index"] = i + result["alert_uid"] = alert.get("finding_info", {}).get("uid", "") + results.append(result) + + return results + + def send_custom_alert( + self, + alert_json: Dict[str, Any], + token: str, + account_id: str, + uam_ingest_url: str, + site_id: Optional[str] = None, + auto_generate_uid: bool = True, + ) -> Dict[str, Any]: + """ + Send a custom alert JSON (not from a template). + + Args: + alert_json: Full alert dict provided by the user + token: Service Account bearer token + account_id: SentinelOne account ID + uam_ingest_url: Base ingest URL + site_id: Optional site ID + auto_generate_uid: If True, inject fresh UID and timestamp + + Returns: + Result dict + """ + if auto_generate_uid: + alert = self.prepare_alert(alert_json) + else: + alert = copy.deepcopy(alert_json) + + scope = self.build_scope(account_id, site_id) + return self.egress_alert(alert, scope, token, uam_ingest_url) + + def send_prepared_alert(self, alert: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]: + """Send a prepared alert to a UAM destination + + Args: + alert: Prepared alert dict + destination: Destination dict with UAM credentials + + Returns: + Dict with success status and response details + """ + # Extract UAM credentials from destination + token = destination.get('uam_service_token') + account_id = destination.get('uam_account_id') + site_id = destination.get('uam_site_id') + ingest_url = destination.get('uam_ingest_url') + + if not all([token, account_id, ingest_url]): + return { + "success": False, + "status": 0, + "error": "Missing required UAM credentials (token, account_id, or ingest_url)" + } + + # Build scope + scope = self.build_scope(account_id, site_id) + + # Send alert + result = self.egress_alert(alert, scope, token, ingest_url) + + # The egress_alert already returns success field + return result + + +# Singleton instance +alert_service = AlertService() diff --git a/Backend/api/app/services/destination_service.py b/Backend/api/app/services/destination_service.py index 8ad97b2..e386fa5 100644 --- a/Backend/api/app/services/destination_service.py +++ b/Backend/api/app/services/destination_service.py @@ -76,6 +76,10 @@ async def create_destination( config_api_url: Optional[str] = None, config_write_token: Optional[str] = None, powerquery_read_token: Optional[str] = None, + uam_ingest_url: Optional[str] = None, + uam_account_id: Optional[str] = None, + uam_site_id: Optional[str] = None, + uam_service_token: Optional[str] = None, ip: Optional[str] = None, port: Optional[int] = None, protocol: Optional[str] = None @@ -133,6 +137,14 @@ async def create_destination( destination.config_write_token_encrypted = self.encryption.encrypt(config_write_token) if powerquery_read_token: destination.powerquery_read_token_encrypted = self.encryption.encrypt(powerquery_read_token) + if uam_ingest_url: + destination.uam_ingest_url = uam_ingest_url.rstrip('/') + if uam_account_id: + destination.uam_account_id = uam_account_id + if uam_site_id: + destination.uam_site_id = uam_site_id + if uam_service_token: + destination.uam_service_token_encrypted = self.encryption.encrypt(uam_service_token) elif dest_type == 'syslog': destination.ip = ip destination.port = port @@ -173,6 +185,10 @@ async def update_destination( config_api_url: Optional[str] = None, config_write_token: Optional[str] = None, powerquery_read_token: Optional[str] = None, + uam_ingest_url: Optional[str] = None, + uam_account_id: Optional[str] = None, + uam_site_id: Optional[str] = None, + uam_service_token: Optional[str] = None, ip: Optional[str] = None, port: Optional[int] = None, protocol: Optional[str] = None @@ -196,6 +212,14 @@ async def update_destination( destination.config_write_token_encrypted = self.encryption.encrypt(config_write_token) if powerquery_read_token: destination.powerquery_read_token_encrypted = self.encryption.encrypt(powerquery_read_token) + if uam_ingest_url: + destination.uam_ingest_url = uam_ingest_url.rstrip('/') + if uam_account_id: + destination.uam_account_id = uam_account_id + if uam_site_id is not None: # Allow clearing site_id with empty string + destination.uam_site_id = uam_site_id or None + if uam_service_token: + destination.uam_service_token_encrypted = self.encryption.encrypt(uam_service_token) elif destination.type == 'syslog': if ip: destination.ip = ip diff --git a/Backend/scenarios/apollo_ransomware_scenario.py b/Backend/scenarios/apollo_ransomware_scenario.py index 078bdf9..2564efd 100644 --- a/Backend/scenarios/apollo_ransomware_scenario.py +++ b/Backend/scenarios/apollo_ransomware_scenario.py @@ -30,10 +30,14 @@ import os import sys import uuid +import copy +import gzip import threading from datetime import datetime, timedelta, timezone from typing import Dict, List, Optional +import requests + script_dir = os.path.dirname(os.path.abspath(__file__)) backend_dir = os.path.dirname(script_dir) sys.path.insert(0, backend_dir) @@ -142,6 +146,34 @@ "fallback_behavior": "offset_from_now" } +# Alert configuration for scenario phases +ALERT_PHASE_MAPPING = { + "šŸ“¬ PHASE 2: Email Interaction": { + "template": "proofpoint_email_alert", + "offset_minutes": 2, # 2 min after delivery (user clicks link) + "overrides": { + "finding_info.title": "Malicious Email Link Clicked", + "finding_info.desc": f"User {VICTIM_PROFILE['email']} clicked malicious link in phishing email from {ATTACKER_PROFILE['sender_email']}" + } + }, + "šŸ“¤ PHASE 4: Data Exfiltration": { + "template": "sharepoint_data_exfil_alert", + "offset_minutes": 20, # 20 min after initial compromise + "overrides": { + "finding_info.title": "Data Exfiltration from SharePoint", + "finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded sensitive documents including Personnel Records and Command Codes" + } + }, + "rdp_download": { + "template": "o365_rdp_sharepoint_access", + "offset_minutes": 25, # 25 min after initial compromise + "overrides": { + "finding_info.title": "Apollo Ransomware - RDP Files Downloaded", + "finding_info.desc": f"User {VICTIM_PROFILE['email']} downloaded RDP files from SharePoint - potential lateral movement preparation" + } + } +} + def resolve_time_anchors(siem_context: Dict, anchors_config: List[Dict]) -> Dict[str, datetime]: """Resolve time anchors from SIEM query results or pre-resolved anchors""" @@ -219,6 +251,117 @@ def create_event(timestamp: str, source: str, phase: str, event_data: dict) -> D return {"timestamp": timestamp, "source": source, "phase": phase, "event": event_data} +def load_alert_template(template_id: str) -> Optional[Dict]: + """Load an alert template JSON from the templates directory""" + templates_dir = os.path.join(backend_dir, 'api', 'app', 'alerts', 'templates') + template_path = os.path.join(templates_dir, f"{template_id}.json") + if not os.path.exists(template_path): + print(f" āš ļø Template not found: {template_path}") + return None + with open(template_path, 'r') as f: + return json.load(f) + + +def send_phase_alert( + phase_name: str, + base_time: datetime, + uam_config: dict +) -> bool: + """Send alert for a specific phase with correct timing. + + Standalone implementation — loads template from disk and sends + directly via requests + gzip. No AlertService dependency. + """ + if phase_name not in ALERT_PHASE_MAPPING: + return False + + mapping = ALERT_PHASE_MAPPING[phase_name] + + # Load template + template = load_alert_template(mapping["template"]) + if not template: + return False + + alert = copy.deepcopy(template) + + # Calculate alert timestamp + alert_time = base_time + timedelta(minutes=mapping["offset_minutes"]) + time_ms = int(alert_time.timestamp() * 1000) + + # Inject fresh UID + if "finding_info" not in alert: + alert["finding_info"] = {} + alert["finding_info"]["uid"] = str(uuid.uuid4()) + + # Set timestamps + alert["time"] = time_ms + if "metadata" not in alert: + alert["metadata"] = {} + alert["metadata"]["logged_time"] = time_ms + alert["metadata"]["modified_time"] = time_ms + + # Set user as the resource + alert["resources"] = [{ + "uid": VICTIM_PROFILE["email"], + "name": VICTIM_PROFILE["email"] + }] + + # Apply overrides + overrides = mapping.get("overrides", {}) + for key, value in overrides.items(): + if "." in key: + keys = key.split(".") + current = alert + for k in keys[:-1]: + if k not in current: + current[k] = {} + current = current[k] + current[keys[-1]] = value + else: + alert[key] = value + + # Send alert via UAM ingest API + try: + ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts' + scope = uam_config['uam_account_id'] + if uam_config.get('uam_site_id'): + scope = f"{scope}:{uam_config['uam_site_id']}" + + headers = { + "Authorization": f"Bearer {uam_config['uam_service_token']}", + "S1-Scope": scope, + "Content-Encoding": "gzip", + "Content-Type": "application/json", + } + + payload = json.dumps(alert).encode("utf-8") + gzipped = gzip.compress(payload) + + print(f"\n šŸ“¤ Alert Details:") + print(f" Template: {mapping['template']}") + print(f" Title: {alert.get('finding_info', {}).get('title', 'N/A')}") + print(f" User: {VICTIM_PROFILE['email']}") + print(f" Time: {alert_time.isoformat()} ({time_ms}ms)") + print(f" URL: {ingest_url}") + print(f" Scope: {scope}") + print(f" Payload: {len(payload)} bytes -> {len(gzipped)} bytes (gzip)") + print(f" Full JSON: {json.dumps(alert, indent=2)}") + + resp = requests.post(ingest_url, headers=headers, data=gzipped, timeout=30) + + print(f" Response: {resp.status_code} {resp.reason}") + if resp.content: + print(f" Body: {resp.text[:200]}") + + return resp.status_code == 202 + + except Exception as e: + print(f" āœ— Alert send failed: {e}") + import traceback + traceback.print_exc() + return False + + def generate_proofpoint_phishing_delivery(base_time: datetime) -> List[Dict]: """Generate Proofpoint email delivery event for malicious XLSX""" events = [] @@ -423,6 +566,21 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]: m365_download['RequestedBy'] = VICTIM_PROFILE['name'] events.append(create_event(download_time, "microsoft_365_collaboration", "sharepoint_exfil", m365_download)) + # Add RDP file download event - 5 minutes after exfil starts + rdp_time = get_scenario_time(base_time, 25, 0) + m365_rdp = microsoft_365_collaboration_log() + m365_rdp['TimeStamp'] = rdp_time + m365_rdp['UserId'] = VICTIM_PROFILE['email'] + m365_rdp['ClientIP'] = VICTIM_PROFILE['client_ip'] + m365_rdp['Operation'] = 'FileDownloaded' + m365_rdp['Workload'] = 'SharePoint' + m365_rdp['ObjectId'] = "/sites/IT-Admin/Shared Documents/Remote/enterprise-access.rdp" + m365_rdp['FileName'] = "enterprise-access.rdp" + m365_rdp['SiteUrl'] = "https://starfleet.sharepoint.com/sites/IT-Admin" + m365_rdp['Details'] = f"User {VICTIM_PROFILE['email']} downloaded RDP file - potential lateral movement tool" + m365_rdp['RequestedBy'] = VICTIM_PROFILE['name'] + events.append(create_event(rdp_time, "microsoft_365_collaboration", "rdp_download", m365_rdp)) + return events @@ -476,6 +634,31 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> print(f"Base Time: {base_time.isoformat()}") print("=" * 80 + "\n") + # Initialize alert detonation from env vars + alerts_enabled = os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true' + uam_config = None + + if alerts_enabled: + uam_ingest_url = os.getenv('UAM_INGEST_URL', '') + uam_account_id = os.getenv('UAM_ACCOUNT_ID', '') + uam_service_token = os.getenv('UAM_SERVICE_TOKEN', '') + uam_site_id = os.getenv('UAM_SITE_ID', '') + + if uam_ingest_url and uam_account_id and uam_service_token: + uam_config = { + 'uam_ingest_url': uam_ingest_url, + 'uam_account_id': uam_account_id, + 'uam_service_token': uam_service_token, + 'uam_site_id': uam_site_id, + } + print("\n🚨 ALERT DETONATION ENABLED") + print(f" UAM Ingest: {uam_ingest_url}") + print(f" Account ID: {uam_account_id}") + print("=" * 80) + else: + print("āš ļø SCENARIO_ALERTS_ENABLED=true but UAM credentials missing") + alerts_enabled = False + all_events = [] # Build phases with appropriate base times @@ -509,6 +692,18 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> phase_events = generator_func(phase_base_time) all_events.extend(phase_events) print(f" āœ“ Generated {len(phase_events)} events") + + # Send corresponding alert if enabled and phase has alert mapping + if alerts_enabled and phase_name in ALERT_PHASE_MAPPING: + print(f" šŸ“¤ Sending alert for {phase_name}...", end=" ") + success = send_phase_alert(phase_name, phase_base_time, uam_config) + print(f"{'āœ“' if success else 'āœ—'}") + + # Send RDP alert after data exfiltration phase + if alerts_enabled and phase_name == "šŸ“¤ PHASE 4: Data Exfiltration": + print(f" šŸ“¤ Sending RDP download alert...", end=" ") + success = send_phase_alert("rdp_download", phase_base_time, uam_config) + print(f"{'āœ“' if success else 'āœ—'}") all_events.sort(key=lambda x: x["timestamp"]) diff --git a/Frontend/log_generator_ui.py b/Frontend/log_generator_ui.py index 85131c2..bae4a28 100644 --- a/Frontend/log_generator_ui.py +++ b/Frontend/log_generator_ui.py @@ -248,6 +248,14 @@ def update_destination(dest_id): payload['config_write_token'] = data['config_write_token'] if data.get('powerquery_read_token'): payload['powerquery_read_token'] = data['powerquery_read_token'] + if data.get('uam_ingest_url'): + payload['uam_ingest_url'] = data['uam_ingest_url'] + if data.get('uam_account_id'): + payload['uam_account_id'] = data['uam_account_id'] + if 'uam_site_id' in data: + payload['uam_site_id'] = data['uam_site_id'] + if data.get('uam_service_token'): + payload['uam_service_token'] = data['uam_service_token'] if not payload: return jsonify({'error': 'No fields provided to update'}), 400 @@ -576,6 +584,26 @@ def run_correlation_scenario(): except Exception as e: return jsonify({'error': f'Failed to resolve destination: {str(e)}'}), 500 + # Resolve UAM credentials for alert detonation + uam_ingest_url = '' + uam_account_id = '' + uam_site_id = '' + uam_service_token = '' + try: + uam_resp = requests.get( + f"{API_BASE_URL}/api/v1/destinations/{destination_id}/uam-token", + headers=_get_api_headers(), + timeout=10 + ) + if uam_resp.status_code == 200: + uam_data = uam_resp.json() + uam_ingest_url = uam_data.get('uam_ingest_url', '') + uam_account_id = uam_data.get('account_id', '') + uam_site_id = uam_data.get('site_id', '') + uam_service_token = uam_data.get('token', '') + except Exception as e: + logger.warning(f"Could not resolve UAM credentials for alerts: {e}") + def generate_and_stream(): try: yield "INFO: Starting correlation scenario execution...\n" @@ -618,6 +646,18 @@ def generate_and_stream(): if trace_id: env['S1_TRACE_ID'] = trace_id + # Pass UAM credentials for alert detonation + if uam_ingest_url and uam_account_id and uam_service_token: + env['SCENARIO_ALERTS_ENABLED'] = 'true' + env['UAM_INGEST_URL'] = uam_ingest_url + env['UAM_ACCOUNT_ID'] = uam_account_id + env['UAM_SERVICE_TOKEN'] = uam_service_token + if uam_site_id: + env['UAM_SITE_ID'] = uam_site_id + yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n" + else: + yield "INFO: āš ļø Alert detonation disabled (no UAM credentials on destination)\n" + # Pass SIEM context as JSON env var if siem_context: env['SIEM_CONTEXT'] = json.dumps(siem_context) @@ -886,6 +926,26 @@ def run_scenario(): logger.error(f"Failed to resolve destination: {e}") return jsonify({'error': f'Failed to resolve destination: {str(e)}'}), 500 + # Resolve UAM credentials for alert detonation + uam_ingest_url = '' + uam_account_id = '' + uam_site_id = '' + uam_service_token = '' + try: + uam_resp = requests.get( + f"{API_BASE_URL}/api/v1/destinations/{destination_id}/uam-token", + headers=_get_api_headers(), + timeout=10 + ) + if uam_resp.status_code == 200: + uam_data = uam_resp.json() + uam_ingest_url = uam_data.get('uam_ingest_url', '') + uam_account_id = uam_data.get('account_id', '') + uam_site_id = uam_data.get('site_id', '') + uam_service_token = uam_data.get('token', '') + except Exception as e: + logger.warning(f"Could not resolve UAM credentials for alerts: {e}") + def generate_and_stream(): try: yield "INFO: Starting scenario execution...\n" @@ -979,6 +1039,18 @@ def generate_and_stream(): if trace_id: env['S1_TRACE_ID'] = trace_id + # Pass UAM credentials for alert detonation + if uam_ingest_url and uam_account_id and uam_service_token: + env['SCENARIO_ALERTS_ENABLED'] = 'true' + env['UAM_INGEST_URL'] = uam_ingest_url + env['UAM_ACCOUNT_ID'] = uam_account_id + env['UAM_SERVICE_TOKEN'] = uam_service_token + if uam_site_id: + env['UAM_SITE_ID'] = uam_site_id + yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n" + else: + yield "INFO: āš ļø Alert detonation disabled (no UAM credentials on destination)\n" + # Add event generators and all category subdirectories to Python path event_generators_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'Backend', 'event_generators')) @@ -1980,6 +2052,127 @@ def _normalize_hec_url(u: str) -> str: return Response(stream_with_context(generate_and_stream()), mimetype='text/plain') +# ============================================================================= +# ALERTS ENDPOINTS (UAM Ingest API) +# ============================================================================= + +@app.route('/alerts/templates', methods=['GET']) +def list_alert_templates(): + """List available alert templates""" + try: + resp = requests.get( + f"{API_BASE_URL}/api/v1/alerts/templates", + headers=_get_api_headers(), + timeout=10 + ) + if resp.status_code == 200: + return jsonify(resp.json().get('data', {})) + else: + return jsonify({'templates': [], 'total': 0}) + except Exception as e: + logger.error(f"Failed to fetch alert templates: {e}") + return jsonify({'templates': [], 'total': 0}) + + +@app.route('/alerts/templates/', methods=['GET']) +def get_alert_template(template_id): + """Get a specific alert template""" + try: + resp = requests.get( + f"{API_BASE_URL}/api/v1/alerts/templates/{template_id}", + headers=_get_api_headers(), + timeout=10 + ) + if resp.status_code == 200: + return jsonify(resp.json().get('data', {})) + else: + return jsonify({'error': f'Template not found: {template_id}'}), 404 + except Exception as e: + logger.error(f"Failed to fetch alert template: {e}") + return jsonify({'error': str(e)}), 500 + + +@app.route('/alerts/send', methods=['POST']) +def send_alert(): + """Send alert(s) from a template to the UAM ingest API""" + try: + payload = request.get_json(silent=True) or {} + headers = _get_api_headers() + headers['Content-Type'] = 'application/json' + + resp = requests.post( + f"{API_BASE_URL}/api/v1/alerts/send", + headers=headers, + json=payload, + timeout=60 + ) + + if resp.status_code == 200: + return jsonify(resp.json().get('data', {})) + else: + error_msg = resp.text + try: + error_msg = resp.json().get('detail', resp.text) + except Exception: + pass + return jsonify({'error': error_msg}), resp.status_code + except Exception as e: + logger.error(f"Failed to send alert: {e}") + return jsonify({'error': str(e)}), 500 + + +@app.route('/alerts/send-custom', methods=['POST']) +def send_custom_alert(): + """Send a custom alert JSON to the UAM ingest API""" + try: + payload = request.get_json(silent=True) or {} + headers = _get_api_headers() + headers['Content-Type'] = 'application/json' + + resp = requests.post( + f"{API_BASE_URL}/api/v1/alerts/send-custom", + headers=headers, + json=payload, + timeout=60 + ) + + if resp.status_code == 200: + return jsonify(resp.json().get('data', {})) + else: + error_msg = resp.text + try: + error_msg = resp.json().get('detail', resp.text) + except Exception: + pass + return jsonify({'error': error_msg}), resp.status_code + except Exception as e: + logger.error(f"Failed to send custom alert: {e}") + return jsonify({'error': str(e)}), 500 + + +@app.route('/alerts/resolve-uam/', methods=['GET']) +def resolve_uam_credentials(dest_id): + """Resolve UAM credentials (token, account_id, site_id, ingest_url) from a destination""" + try: + resp = requests.get( + f"{API_BASE_URL}/api/v1/destinations/{dest_id}/uam-token", + headers=_get_api_headers(), + timeout=10 + ) + if resp.status_code == 200: + return jsonify(resp.json()) + else: + error_msg = resp.text + try: + error_msg = resp.json().get('detail', resp.text) + except Exception: + pass + return jsonify({'error': error_msg}), resp.status_code + except Exception as e: + logger.error(f"Failed to resolve UAM credentials: {e}") + return jsonify({'error': str(e)}), 500 + + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=8000) diff --git a/Frontend/templates/log_generator.html b/Frontend/templates/log_generator.html index 45560e4..5194526 100644 --- a/Frontend/templates/log_generator.html +++ b/Frontend/templates/log_generator.html @@ -303,6 +303,10 @@ + + + + + +
+

Alert Output

+
+ +
+
+ + + + +
+
+ ā‘¢ Destination +
+ + +
+ + + + + +
+

+ ā“˜ UAM credentials are configured in Settings → Edit Destination. The Service Account token is stored encrypted, separate from HEC. +

+
+
+
+ + +
+
+

UAM Alert Configuration (Optional)

+ For sending alerts via Service Account +
+

Configure a Service Account token and scope for sending UAM alerts. This is separate from HEC.

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+

Different from HEC token — this is a Service Account bearer token

+
+
+
+
+

UAM Alert Configuration

+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+
@@ -1067,6 +1269,7 @@

Select Parser Version

'scenarios-section': document.getElementById('scenarios-section'), 'upload-section': document.getElementById('upload-section'), 'correlation-section': document.getElementById('correlation-section'), + 'alerts-section': document.getElementById('alerts-section'), 'settings-section': document.getElementById('settings-section') }; @@ -1305,6 +1508,10 @@

Select Parser Version

document.getElementById('edit-dest-token').value = ''; document.getElementById('edit-config-api-url').value = d.config_api_url || 'https://xdr.us1.sentinelone.net'; document.getElementById('edit-config-write-token').value = ''; + document.getElementById('edit-uam-ingest-url').value = d.uam_ingest_url || 'https://ingest.us1.sentinelone.net'; + document.getElementById('edit-uam-account-id').value = d.uam_account_id || ''; + document.getElementById('edit-uam-site-id').value = d.uam_site_id || ''; + document.getElementById('edit-uam-service-token').value = ''; document.getElementById('edit-dest-modal').style.display = 'flex'; }); } else { @@ -1445,6 +1652,16 @@

Select Parser Version

if (configApiUrl) payload.config_api_url = configApiUrl; if (configWriteToken) payload.config_write_token = configWriteToken; if (powerqueryReadToken) payload.powerquery_read_token = powerqueryReadToken; + + // Add UAM alert settings (optional) + const uamIngestUrl = document.getElementById('dest-uam-ingest-url')?.value?.trim(); + const uamAccountId = document.getElementById('dest-uam-account-id')?.value?.trim(); + const uamSiteId = document.getElementById('dest-uam-site-id')?.value?.trim(); + const uamServiceToken = document.getElementById('dest-uam-service-token')?.value?.trim(); + if (uamIngestUrl) payload.uam_ingest_url = uamIngestUrl; + if (uamAccountId) payload.uam_account_id = uamAccountId; + if (uamSiteId) payload.uam_site_id = uamSiteId; + if (uamServiceToken) payload.uam_service_token = uamServiceToken; } else { payload.ip = document.getElementById('dest-syslog-ip').value.trim(); payload.port = parseInt(document.getElementById('dest-syslog-port').value, 10); @@ -1486,6 +1703,14 @@

Select Parser Version

if (configWriteEl) configWriteEl.value = ''; const powerqueryReadEl = document.getElementById('dest-powerquery-read-token'); if (powerqueryReadEl) powerqueryReadEl.value = ''; + const uamIngestUrlEl = document.getElementById('dest-uam-ingest-url'); + if (uamIngestUrlEl) uamIngestUrlEl.value = 'https://ingest.us1.sentinelone.net'; + const uamAccountIdEl = document.getElementById('dest-uam-account-id'); + if (uamAccountIdEl) uamAccountIdEl.value = ''; + const uamSiteIdEl = document.getElementById('dest-uam-site-id'); + if (uamSiteIdEl) uamSiteIdEl.value = ''; + const uamServiceTokenEl = document.getElementById('dest-uam-service-token'); + if (uamServiceTokenEl) uamServiceTokenEl.value = ''; // Switch to Generate tab to use it const genBtn = document.querySelector('.nav-tab[data-target="generate-section"]'); @@ -1836,6 +2061,10 @@

Select Parser Version

const configApiUrl = document.getElementById('edit-config-api-url').value.trim(); const configWriteToken = document.getElementById('edit-config-write-token').value.trim(); const powerqueryReadToken = document.getElementById('edit-powerquery-read-token').value.trim(); + const uamIngestUrl = document.getElementById('edit-uam-ingest-url').value.trim(); + const uamAccountId = document.getElementById('edit-uam-account-id').value.trim(); + const uamSiteId = document.getElementById('edit-uam-site-id').value.trim(); + const uamServiceToken = document.getElementById('edit-uam-service-token').value.trim(); try { const payload = {}; @@ -1845,6 +2074,10 @@

Select Parser Version

if (configApiUrl) payload.config_api_url = configApiUrl; if (configWriteToken) payload.config_write_token = configWriteToken; if (powerqueryReadToken) payload.powerquery_read_token = powerqueryReadToken; + if (uamIngestUrl) payload.uam_ingest_url = uamIngestUrl; + if (uamAccountId) payload.uam_account_id = uamAccountId; + payload.uam_site_id = uamSiteId; // Allow clearing with empty string + if (uamServiceToken) payload.uam_service_token = uamServiceToken; if (Object.keys(payload).length === 0) { alert('No changes to save'); @@ -2883,6 +3116,341 @@

Select Parser Version

// Initialize correlation scenarios fetchCorrelationScenarios(); + // ============================================================================= + // ALERTS (UAM Ingest API) + // ============================================================================= + + const alertTemplateSelect = document.getElementById('alert-template-select'); + const alertTemplateDetails = document.getElementById('alert-template-details'); + const alertForm = document.getElementById('alert-form'); + const sendAlertBtn = document.getElementById('send-alert-btn'); + const sendCustomAlertBtn = document.getElementById('send-custom-alert-btn'); + const alertOutputBox = document.getElementById('alert-output-box'); + const alertDestSelect = document.getElementById('alert-destination-select'); + const alertDestInfo = document.getElementById('alert-dest-info'); + const alertDestWarning = document.getElementById('alert-dest-warning'); + const alertScopePreview = document.getElementById('alert-scope-preview'); + + let alertTemplatesData = []; + let alertDestinations = []; // HEC destinations with UAM config + + // Populate alert destination selector from existing destinations + async function refreshAlertDestinations() { + try { + const res = await fetch('/destinations'); + const data = await res.json(); + const allDests = data.destinations || []; + // Filter to HEC destinations only + alertDestinations = allDests.filter(d => d.type === 'hec'); + + alertDestSelect.innerHTML = ''; + if (alertDestinations.length === 0) { + const opt = document.createElement('option'); + opt.value = ''; + opt.textContent = 'No HEC destinations configured'; + opt.disabled = true; + opt.selected = true; + alertDestSelect.appendChild(opt); + } else { + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = 'Select a destination...'; + placeholder.disabled = true; + placeholder.selected = true; + alertDestSelect.appendChild(placeholder); + + alertDestinations.forEach(d => { + const opt = document.createElement('option'); + opt.value = d.id; + const uamStatus = d.has_uam_service_token ? 'āœ“ UAM' : '⚠ No UAM'; + opt.textContent = `${d.name} [${uamStatus}]`; + alertDestSelect.appendChild(opt); + }); + } + } catch (err) { + console.error('Failed to fetch destinations for alerts:', err); + alertDestSelect.innerHTML = ''; + } + } + + // Handle alert destination selection — show UAM info + alertDestSelect.addEventListener('change', () => { + const destId = alertDestSelect.value; + const dest = alertDestinations.find(d => d.id === destId); + if (!dest) { + alertDestInfo.style.display = 'none'; + alertDestWarning.style.display = 'none'; + return; + } + + // Show info panel + document.getElementById('alert-dest-ingest-url').textContent = dest.uam_ingest_url || '—'; + document.getElementById('alert-dest-account-id').textContent = dest.uam_account_id || '—'; + document.getElementById('alert-dest-site-id').textContent = dest.uam_site_id || '(none)'; + document.getElementById('alert-dest-token-status').textContent = dest.has_uam_service_token ? 'āœ“ Configured' : 'āœ— Not set'; + + // Scope preview + if (dest.uam_account_id) { + alertScopePreview.textContent = dest.uam_site_id + ? `${dest.uam_account_id}:${dest.uam_site_id}` + : dest.uam_account_id; + } else { + alertScopePreview.textContent = '—'; + } + + alertDestInfo.style.display = ''; + + // Show warning if missing required UAM fields + if (!dest.has_uam_service_token || !dest.uam_account_id || !dest.uam_ingest_url) { + const missing = []; + if (!dest.uam_ingest_url) missing.push('UAM Ingest URL'); + if (!dest.uam_account_id) missing.push('Account ID'); + if (!dest.has_uam_service_token) missing.push('Service Account Token'); + document.getElementById('alert-dest-warning-text').textContent = + `Missing: ${missing.join(', ')}. Go to Settings → Edit Destination to configure.`; + alertDestWarning.style.display = ''; + } else { + alertDestWarning.style.display = 'none'; + } + }); + + // Fetch alert templates + async function fetchAlertTemplates() { + try { + const res = await fetch('/alerts/templates'); + const data = await res.json(); + alertTemplatesData = data.templates || []; + + alertTemplateSelect.innerHTML = ''; + if (alertTemplatesData.length === 0) { + const opt = document.createElement('option'); + opt.value = ''; + opt.textContent = 'No templates available'; + opt.disabled = true; + opt.selected = true; + alertTemplateSelect.appendChild(opt); + } else { + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = 'Select a template...'; + placeholder.disabled = true; + placeholder.selected = true; + alertTemplateSelect.appendChild(placeholder); + + alertTemplatesData.forEach(t => { + const opt = document.createElement('option'); + opt.value = t.id; + opt.textContent = `${t.title} (${t.class_name})`; + alertTemplateSelect.appendChild(opt); + }); + } + } catch (err) { + console.error('Failed to fetch alert templates:', err); + alertTemplateSelect.innerHTML = ''; + } + } + + // Handle template selection — show details and load full JSON into custom textarea + alertTemplateSelect.addEventListener('change', async () => { + const selectedId = alertTemplateSelect.value; + const template = alertTemplatesData.find(t => t.id === selectedId); + if (template) { + document.getElementById('alert-template-title').textContent = template.title; + document.getElementById('alert-template-class').textContent = template.class_name; + const severityMap = {1: 'Informational', 2: 'Low', 3: 'Medium', 4: 'High', 5: 'Critical'}; + document.getElementById('alert-template-severity').textContent = severityMap[template.severity_id] || `ID: ${template.severity_id}`; + document.getElementById('alert-template-finding').textContent = template.finding_title; + alertTemplateDetails.style.display = ''; + + // Fetch full template JSON and populate custom textarea as baseline + try { + const res = await fetch(`/alerts/templates/${encodeURIComponent(selectedId)}`); + const data = await res.json(); + if (data.template) { + document.getElementById('alert-custom-json').value = JSON.stringify(data.template, null, 2); + } + } catch (err) { + console.error('Failed to load full template JSON:', err); + } + } else { + alertTemplateDetails.style.display = 'none'; + } + }); + + // Helper: resolve UAM credentials from destination + async function resolveUamCredentials(destId) { + const res = await fetch(`/alerts/resolve-uam/${encodeURIComponent(destId)}`); + if (!res.ok) { + const errData = await res.json().catch(() => ({})); + throw new Error(errData.error || `Failed to resolve UAM credentials (${res.status})`); + } + return await res.json(); + } + + // Send alert from template + alertForm.addEventListener('submit', async (e) => { + e.preventDefault(); + + const templateId = alertTemplateSelect.value; + const destId = alertDestSelect.value; + const count = parseInt(document.getElementById('alert-count').value, 10) || 1; + + if (!templateId) { alert('Please select an alert template'); return; } + if (!destId) { alert('Please select a destination'); return; } + + // Build overrides + const overrides = {}; + const overrideTitle = document.getElementById('alert-override-title').value.trim(); + const overrideFindingTitle = document.getElementById('alert-override-finding-title').value.trim(); + const overrideSeverity = document.getElementById('alert-override-severity').value; + if (overrideTitle) overrides.title = overrideTitle; + if (overrideFindingTitle) overrides.finding_title = overrideFindingTitle; + if (overrideSeverity) overrides.severity_id = parseInt(overrideSeverity, 10); + + sendAlertBtn.disabled = true; + sendAlertBtn.innerText = 'Sending...'; + const destName = alertDestinations.find(d => d.id === destId)?.name || destId; + const templateName = alertTemplatesData.find(t => t.id === templateId)?.title || templateId; + alertOutputBox.innerText = `━━━ Alert Send (Template) ━━━\n`; + alertOutputBox.innerText += `Template: ${templateName} (${templateId})\n`; + alertOutputBox.innerText += `Destination: ${destName}\n`; + alertOutputBox.innerText += `Count: ${count}\n`; + if (Object.keys(overrides).length > 0) alertOutputBox.innerText += `Overrides: ${JSON.stringify(overrides)}\n`; + alertOutputBox.innerText += `\nResolving UAM credentials...\n`; + + try { + // Resolve UAM token + account info from destination + const uamCreds = await resolveUamCredentials(destId); + const { token, account_id, site_id, uam_ingest_url } = uamCreds; + + alertOutputBox.innerText += `Endpoint: ${uam_ingest_url}/v1/alerts\n`; + alertOutputBox.innerText += `S1-Scope: ${site_id ? account_id + ':' + site_id : account_id}\n`; + alertOutputBox.innerText += `Encoding: gzip\n`; + alertOutputBox.innerText += `\nSending ${count} alert(s)...\n\n`; + + const payload = { + template_id: templateId, + token: token, + account_id: account_id, + uam_ingest_url: uam_ingest_url, + count: count + }; + if (site_id) payload.site_id = site_id; + if (Object.keys(overrides).length > 0) payload.overrides = overrides; + + const res = await fetch('/alerts/send', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + const data = await res.json(); + + if (data.error) { + alertOutputBox.innerText += `\nāŒ Error: ${data.error}\n`; + if (data.detail) alertOutputBox.innerText += `Detail: ${data.detail}\n`; + } else { + const summary = data.summary || {}; + alertOutputBox.innerText += `āœ… Results: ${summary.successful || 0} successful, ${summary.failed || 0} failed\n\n`; + + (data.results || []).forEach((r, i) => { + if (r.success) { + alertOutputBox.innerText += ` [${i + 1}] āœ“ Status ${r.status} - UID: ${r.alert_uid}\n`; + if (r.data && Object.keys(r.data).length > 0) { + alertOutputBox.innerText += ` Response: ${JSON.stringify(r.data)}\n`; + } + } else { + alertOutputBox.innerText += ` [${i + 1}] āœ— ${r.error || 'Unknown error'}\n`; + if (r.detail) alertOutputBox.innerText += ` Detail: ${r.detail}\n`; + } + }); + } + } catch (err) { + console.error('Alert send error:', err); + alertOutputBox.innerText += `\nāŒ ${err.message}\n`; + } finally { + sendAlertBtn.disabled = false; + sendAlertBtn.innerText = 'Send Alert'; + } + }); + + // Send custom alert JSON + sendCustomAlertBtn.addEventListener('click', async () => { + const customJsonText = document.getElementById('alert-custom-json').value.trim(); + const destId = alertDestSelect.value; + const autoUid = document.getElementById('alert-custom-auto-uid').checked; + + if (!customJsonText) { alert('Please paste alert JSON in the Custom Alert JSON section'); return; } + if (!destId) { alert('Please select a destination'); return; } + + let alertJson; + try { + alertJson = JSON.parse(customJsonText); + } catch (parseErr) { + alert('Invalid JSON: ' + parseErr.message); + return; + } + + sendCustomAlertBtn.disabled = true; + sendCustomAlertBtn.innerText = 'Sending...'; + const destNameCustom = alertDestinations.find(d => d.id === destId)?.name || destId; + alertOutputBox.innerText = `━━━ Alert Send (Custom JSON) ━━━\n`; + alertOutputBox.innerText += `Destination: ${destNameCustom}\n`; + alertOutputBox.innerText += `Auto-gen UID: ${autoUid}\n`; + alertOutputBox.innerText += `Payload size: ${customJsonText.length} chars\n`; + alertOutputBox.innerText += `\nResolving UAM credentials...\n`; + + try { + const uamCreds = await resolveUamCredentials(destId); + const { token, account_id, site_id, uam_ingest_url } = uamCreds; + + alertOutputBox.innerText += `Endpoint: ${uam_ingest_url}/v1/alerts\n`; + alertOutputBox.innerText += `S1-Scope: ${site_id ? account_id + ':' + site_id : account_id}\n`; + alertOutputBox.innerText += `Encoding: gzip\n`; + alertOutputBox.innerText += `\nSending custom alert...\n\n`; + + const payload = { + alert_json: alertJson, + token: token, + account_id: account_id, + uam_ingest_url: uam_ingest_url, + auto_generate_uid: autoUid + }; + if (site_id) payload.site_id = site_id; + + const res = await fetch('/alerts/send-custom', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + const data = await res.json(); + + if (data.error) { + alertOutputBox.innerText += `āŒ Error: ${data.error}\n`; + if (data.detail) alertOutputBox.innerText += `Detail: ${data.detail}\n`; + } else if (data.success) { + alertOutputBox.innerText += `āœ… Alert sent successfully! Status: ${data.status}\n`; + if (data.data && Object.keys(data.data).length > 0) { + alertOutputBox.innerText += `Response: ${JSON.stringify(data.data)}\n`; + } + } else { + alertOutputBox.innerText += `āŒ Alert failed: ${data.error || 'Unknown error'}\n`; + if (data.detail) alertOutputBox.innerText += `Detail: ${data.detail}\n`; + } + } catch (err) { + console.error('Custom alert send error:', err); + alertOutputBox.innerText += `\nāŒ ${err.message}\n`; + } finally { + sendCustomAlertBtn.disabled = false; + sendCustomAlertBtn.innerText = 'Send Custom JSON'; + } + }); + + // Initialize alert templates and destinations + fetchAlertTemplates(); + refreshAlertDestinations(); + })(); });