Merge pull request #192 from christianlouis/codex/m14-ticketing-chatops-templates
feat: add ticketing and chatops templates
This commit is contained in:
@@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends
|
|||||||
|
|
||||||
from app.core.security import require_admin_auth
|
from app.core.security import require_admin_auth
|
||||||
from app.services.siem_templates import get_siem_templates
|
from app.services.siem_templates import get_siem_templates
|
||||||
|
from app.services.ticketing_chatops_templates import get_ticketing_chatops_templates
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@@ -12,3 +13,9 @@ router = APIRouter()
|
|||||||
async def siem_templates(_auth: dict = Depends(require_admin_auth)):
|
async def siem_templates(_auth: dict = Depends(require_admin_auth)):
|
||||||
"""Return versioned schemas and examples for SIEM ingestion."""
|
"""Return versioned schemas and examples for SIEM ingestion."""
|
||||||
return get_siem_templates()
|
return get_siem_templates()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/ticketing-chatops/templates")
|
||||||
|
async def ticketing_chatops_templates(_auth: dict = Depends(require_admin_auth)):
|
||||||
|
"""Return ticketing and chatops workflow templates."""
|
||||||
|
return get_ticketing_chatops_templates()
|
||||||
|
|||||||
@@ -0,0 +1,313 @@
|
|||||||
|
"""Ticketing and chatops integration templates."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from app.services.webhook_events import (
|
||||||
|
EVENT_ALERT_CREATED,
|
||||||
|
EVENT_ALERT_RESOLVED,
|
||||||
|
EVENT_COMPLIANCE_DROP,
|
||||||
|
EVENT_REPORTS_MISSING,
|
||||||
|
EVENT_SENDER_NEW,
|
||||||
|
)
|
||||||
|
|
||||||
|
TICKETING_CHATOPS_TEMPLATE_VERSION = "2026-05-23"
|
||||||
|
TICKETING_CHATOPS_SCHEMA_VERSION = "dmarq.workflow.template.v1"
|
||||||
|
|
||||||
|
WORKFLOW_EVENT_TYPES = [
|
||||||
|
EVENT_SENDER_NEW,
|
||||||
|
EVENT_COMPLIANCE_DROP,
|
||||||
|
EVENT_REPORTS_MISSING,
|
||||||
|
EVENT_ALERT_CREATED,
|
||||||
|
EVENT_ALERT_RESOLVED,
|
||||||
|
]
|
||||||
|
|
||||||
|
EVENT_WORKFLOW_MAPPINGS: Dict[str, Dict[str, Any]] = {
|
||||||
|
EVENT_SENDER_NEW: {
|
||||||
|
"severity": "medium",
|
||||||
|
"owner": "email-security",
|
||||||
|
"ticket_action": "create_or_update",
|
||||||
|
"chat_action": "notify_channel",
|
||||||
|
"dedupe_key_template": "dmarq:{event_type}:{domain}:{sender_ip}",
|
||||||
|
"summary_template": "Review new DMARC sending source for {domain}",
|
||||||
|
"noise_control": "Open one ticket per domain and sender IP. Suppress repeats for 7 days after acknowledgement.",
|
||||||
|
},
|
||||||
|
EVENT_COMPLIANCE_DROP: {
|
||||||
|
"severity": "high",
|
||||||
|
"owner": "email-security",
|
||||||
|
"ticket_action": "create_or_update",
|
||||||
|
"chat_action": "notify_channel_and_thread",
|
||||||
|
"dedupe_key_template": "dmarq:{event_type}:{domain}",
|
||||||
|
"summary_template": "Investigate DMARC compliance drop for {domain}",
|
||||||
|
"noise_control": "Escalate only when drop_points meets the configured alert threshold. Update the same ticket while active.",
|
||||||
|
},
|
||||||
|
EVENT_REPORTS_MISSING: {
|
||||||
|
"severity": "medium",
|
||||||
|
"owner": "mail-operations",
|
||||||
|
"ticket_action": "create_or_update",
|
||||||
|
"chat_action": "notify_channel",
|
||||||
|
"dedupe_key_template": "dmarq:{event_type}:{domain}",
|
||||||
|
"summary_template": "Restore missing DMARC aggregate reports for {domain}",
|
||||||
|
"noise_control": "Create one ticket per domain and keep reminders to one chat update per day.",
|
||||||
|
},
|
||||||
|
EVENT_ALERT_CREATED: {
|
||||||
|
"severity": "medium",
|
||||||
|
"owner": "email-security",
|
||||||
|
"ticket_action": "create_or_update",
|
||||||
|
"chat_action": "notify_channel",
|
||||||
|
"dedupe_key_template": "dmarq:{event_type}:{domain}:{alert_rule}",
|
||||||
|
"summary_template": "DMARQ alert: {title}",
|
||||||
|
"noise_control": "Route by alert_rule and reuse open tickets while the alert remains active.",
|
||||||
|
},
|
||||||
|
EVENT_ALERT_RESOLVED: {
|
||||||
|
"severity": "info",
|
||||||
|
"owner": "email-security",
|
||||||
|
"ticket_action": "resolve_or_comment",
|
||||||
|
"chat_action": "notify_thread",
|
||||||
|
"dedupe_key_template": "dmarq:{event_type}:{domain}:{alert_rule}",
|
||||||
|
"summary_template": "DMARQ alert resolved: {title}",
|
||||||
|
"noise_control": "Resolve the linked ticket when all active signals for the dedupe key are clear.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMPLE_CONTEXT: Dict[str, Any] = {
|
||||||
|
"event_type": EVENT_COMPLIANCE_DROP,
|
||||||
|
"event_id": "dmarq-compliance-drop-20260523-example-com",
|
||||||
|
"domain": "example.com",
|
||||||
|
"severity": "high",
|
||||||
|
"title": "DMARC compliance dropped for example.com",
|
||||||
|
"detail": "Compliance fell by 23.39 percentage points in the current window.",
|
||||||
|
"alert_rule": "compliance_drop",
|
||||||
|
"sender_ip": None,
|
||||||
|
"message_count": 1280,
|
||||||
|
"failed_count": 366,
|
||||||
|
"compliance_rate": 71.41,
|
||||||
|
"previous_compliance_rate": 94.8,
|
||||||
|
"drop_points": 23.39,
|
||||||
|
"domain_url": "https://dmarq.example/domains/example.com",
|
||||||
|
"report_url": "https://dmarq.example/domains/example.com/reports",
|
||||||
|
"dedupe_key": "dmarq:dmarq.compliance.drop:example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
JIRA_ISSUE_TEMPLATE: Dict[str, Any] = {
|
||||||
|
"operation": "create_or_update_issue",
|
||||||
|
"lookup": {
|
||||||
|
"jql": 'project = EMAILSEC AND labels = "dmarq" AND "Dedupe Key" ~ "{dedupe_key}" AND statusCategory != Done'
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"fields": {
|
||||||
|
"project": {"key": "EMAILSEC"},
|
||||||
|
"issuetype": {"name": "Task"},
|
||||||
|
"summary": "[DMARQ][{severity}] {title}",
|
||||||
|
"description": {
|
||||||
|
"type": "doc",
|
||||||
|
"version": 1,
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "{detail}",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paragraph",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Domain: {domain} | Compliance: {compliance_rate}% | Previous: {previous_compliance_rate}% | Drop: {drop_points} points",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"labels": ["dmarq", "email-security", "{alert_rule}"],
|
||||||
|
"priority": {"name": "High"},
|
||||||
|
"customfield_dedupe_key": "{dedupe_key}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"comment": "{event_time}: {detail}",
|
||||||
|
"fields": {"priority": {"name": "High"}},
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"transition": "Done",
|
||||||
|
"comment": "DMARQ reports this alert is resolved. Event: {event_id}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
GITHUB_ISSUE_TEMPLATE: Dict[str, Any] = {
|
||||||
|
"operation": "create_or_update_issue",
|
||||||
|
"repository": "security-operations/email-auth",
|
||||||
|
"lookup": {
|
||||||
|
"state": "open",
|
||||||
|
"labels": ["dmarq", "{alert_rule}", "dedupe:{dedupe_key}"],
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "[DMARQ][{severity}] {title}",
|
||||||
|
"body": "\n".join(
|
||||||
|
[
|
||||||
|
"## Signal",
|
||||||
|
"{detail}",
|
||||||
|
"",
|
||||||
|
"## Evidence",
|
||||||
|
"- Domain: `{domain}`",
|
||||||
|
"- Compliance rate: `{compliance_rate}%`",
|
||||||
|
"- Previous compliance rate: `{previous_compliance_rate}%`",
|
||||||
|
"- Drop: `{drop_points}` points",
|
||||||
|
"- Failed messages: `{failed_count}` of `{message_count}`",
|
||||||
|
"",
|
||||||
|
"## Links",
|
||||||
|
"- Domain: {domain_url}",
|
||||||
|
"- Reports: {report_url}",
|
||||||
|
"",
|
||||||
|
"Dedupe key: `{dedupe_key}`",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"labels": ["dmarq", "email-security", "{alert_rule}", "dedupe:{dedupe_key}"],
|
||||||
|
},
|
||||||
|
"update": {"comment": "{event_time}: {detail}\n\nDedupe key: `{dedupe_key}`"},
|
||||||
|
"resolve": {"state": "closed", "comment": "Resolved by DMARQ event `{event_id}`."},
|
||||||
|
}
|
||||||
|
|
||||||
|
SLACK_MESSAGE_TEMPLATE: Dict[str, Any] = {
|
||||||
|
"channel": "#email-security",
|
||||||
|
"thread_key": "{dedupe_key}",
|
||||||
|
"text": "[DMARQ][{severity}] {title}",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "header",
|
||||||
|
"text": {"type": "plain_text", "text": "DMARQ: {title}"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"text": {"type": "mrkdwn", "text": "{detail}"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"fields": [
|
||||||
|
{"type": "mrkdwn", "text": "*Domain*\n{domain}"},
|
||||||
|
{"type": "mrkdwn", "text": "*Severity*\n{severity}"},
|
||||||
|
{"type": "mrkdwn", "text": "*Compliance*\n{compliance_rate}%"},
|
||||||
|
{"type": "mrkdwn", "text": "*Drop*\n{drop_points} points"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "actions",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "button",
|
||||||
|
"text": {"type": "plain_text", "text": "Open domain"},
|
||||||
|
"url": "{domain_url}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "button",
|
||||||
|
"text": {"type": "plain_text", "text": "Open reports"},
|
||||||
|
"url": "{report_url}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
TEAMS_MESSAGE_TEMPLATE: Dict[str, Any] = {
|
||||||
|
"type": "message",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
||||||
|
"content": {
|
||||||
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||||
|
"type": "AdaptiveCard",
|
||||||
|
"version": "1.5",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "TextBlock",
|
||||||
|
"size": "Medium",
|
||||||
|
"weight": "Bolder",
|
||||||
|
"text": "DMARQ: {title}",
|
||||||
|
},
|
||||||
|
{"type": "TextBlock", "wrap": True, "text": "{detail}"},
|
||||||
|
{
|
||||||
|
"type": "FactSet",
|
||||||
|
"facts": [
|
||||||
|
{"title": "Domain", "value": "{domain}"},
|
||||||
|
{"title": "Severity", "value": "{severity}"},
|
||||||
|
{"title": "Compliance", "value": "{compliance_rate}%"},
|
||||||
|
{"title": "Drop", "value": "{drop_points} points"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"type": "Action.OpenUrl",
|
||||||
|
"title": "Open domain",
|
||||||
|
"url": "{domain_url}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Action.OpenUrl",
|
||||||
|
"title": "Open reports",
|
||||||
|
"url": "{report_url}",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
OPERATING_MODEL = [
|
||||||
|
"Use DMARQ webhook idempotency keys or the documented dedupe_key_template as the ticket correlation key.",
|
||||||
|
"Route ownership by event type: email-security for sender/compliance/alert signals, mail-operations for missing reports.",
|
||||||
|
"Create or update one ticket per active signal, and close or comment when a resolved event arrives.",
|
||||||
|
"Send chat notifications to a stable channel, then keep follow-up updates in the same thread whenever the destination supports threading.",
|
||||||
|
"Use severity and configured thresholds to suppress low-value repeats. Do not page on every aggregate report import.",
|
||||||
|
"Store Jira, GitHub, Slack, and Teams credentials in the receiving automation platform or secret manager, not in DMARQ payloads.",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ticketing_chatops_templates() -> Dict[str, Any]:
|
||||||
|
"""Return a copy of the ticketing and chatops template bundle."""
|
||||||
|
return copy.deepcopy(
|
||||||
|
{
|
||||||
|
"template_version": TICKETING_CHATOPS_TEMPLATE_VERSION,
|
||||||
|
"schema_version": TICKETING_CHATOPS_SCHEMA_VERSION,
|
||||||
|
"event_types": WORKFLOW_EVENT_TYPES,
|
||||||
|
"event_workflow_mappings": EVENT_WORKFLOW_MAPPINGS,
|
||||||
|
"sample_context": SAMPLE_CONTEXT,
|
||||||
|
"payload_templates": {
|
||||||
|
"jira": JIRA_ISSUE_TEMPLATE,
|
||||||
|
"github": GITHUB_ISSUE_TEMPLATE,
|
||||||
|
"slack": SLACK_MESSAGE_TEMPLATE,
|
||||||
|
"microsoft_teams": TEAMS_MESSAGE_TEMPLATE,
|
||||||
|
},
|
||||||
|
"operating_model": OPERATING_MODEL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_workflow_template_bundle(bundle: Dict[str, Any]) -> List[str]:
|
||||||
|
"""Validate bundled templates without pulling in a schema dependency."""
|
||||||
|
errors: List[str] = []
|
||||||
|
if bundle.get("schema_version") != TICKETING_CHATOPS_SCHEMA_VERSION:
|
||||||
|
errors.append("schema_version must be dmarq.workflow.template.v1")
|
||||||
|
if sorted(bundle.get("event_types", [])) != sorted(WORKFLOW_EVENT_TYPES):
|
||||||
|
errors.append("event_types must match supported workflow events")
|
||||||
|
|
||||||
|
mappings = bundle.get("event_workflow_mappings") or {}
|
||||||
|
for event_type in WORKFLOW_EVENT_TYPES:
|
||||||
|
mapping = mappings.get(event_type) or {}
|
||||||
|
if not mapping.get("dedupe_key_template"):
|
||||||
|
errors.append(f"{event_type} is missing a dedupe key template")
|
||||||
|
if not mapping.get("owner"):
|
||||||
|
errors.append(f"{event_type} is missing an owner")
|
||||||
|
if not mapping.get("ticket_action"):
|
||||||
|
errors.append(f"{event_type} is missing a ticket action")
|
||||||
|
|
||||||
|
templates = bundle.get("payload_templates") or {}
|
||||||
|
for destination in ["jira", "github", "slack", "microsoft_teams"]:
|
||||||
|
if destination not in templates:
|
||||||
|
errors.append(f"missing payload template: {destination}")
|
||||||
|
return errors
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.services.ticketing_chatops_templates import (
|
||||||
|
TICKETING_CHATOPS_SCHEMA_VERSION,
|
||||||
|
WORKFLOW_EVENT_TYPES,
|
||||||
|
get_ticketing_chatops_templates,
|
||||||
|
validate_workflow_template_bundle,
|
||||||
|
)
|
||||||
|
from app.services.webhook_events import EVENT_ALERT_RESOLVED, EVENT_COMPLIANCE_DROP
|
||||||
|
|
||||||
|
|
||||||
|
def _string_values(value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for item in value.values():
|
||||||
|
yield from _string_values(item)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
yield from _string_values(item)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
yield value
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_bundle_is_versioned_and_complete():
|
||||||
|
"""Workflow templates cover every ticketing/chatops event type."""
|
||||||
|
bundle = get_ticketing_chatops_templates()
|
||||||
|
|
||||||
|
assert bundle["schema_version"] == TICKETING_CHATOPS_SCHEMA_VERSION
|
||||||
|
assert sorted(bundle["event_types"]) == sorted(WORKFLOW_EVENT_TYPES)
|
||||||
|
assert validate_workflow_template_bundle(bundle) == []
|
||||||
|
assert bundle["event_workflow_mappings"][EVENT_COMPLIANCE_DROP]["severity"] == "high"
|
||||||
|
assert (
|
||||||
|
bundle["event_workflow_mappings"][EVENT_ALERT_RESOLVED]["ticket_action"]
|
||||||
|
== "resolve_or_comment"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_templates_have_no_embedded_credentials():
|
||||||
|
"""Examples must be safe to publish without leaking destination credentials."""
|
||||||
|
bundle = get_ticketing_chatops_templates()
|
||||||
|
encoded_values = json.dumps(list(_string_values(bundle))).lower()
|
||||||
|
|
||||||
|
for forbidden in ["xoxb-", "ghp_", "jira_pat", "incoming_webhook", "bearer "]:
|
||||||
|
assert forbidden not in encoded_values
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_validation_reports_template_drift():
|
||||||
|
"""Validator explains missing required workflow and destination fields."""
|
||||||
|
bundle = get_ticketing_chatops_templates()
|
||||||
|
bundle["schema_version"] = "unexpected"
|
||||||
|
bundle["event_types"] = [EVENT_COMPLIANCE_DROP]
|
||||||
|
bundle["event_workflow_mappings"][EVENT_COMPLIANCE_DROP].pop("dedupe_key_template")
|
||||||
|
bundle["event_workflow_mappings"][EVENT_COMPLIANCE_DROP].pop("owner")
|
||||||
|
bundle["event_workflow_mappings"][EVENT_COMPLIANCE_DROP].pop("ticket_action")
|
||||||
|
bundle["payload_templates"].pop("slack")
|
||||||
|
|
||||||
|
errors = validate_workflow_template_bundle(bundle)
|
||||||
|
|
||||||
|
assert "schema_version must be dmarq.workflow.template.v1" in errors
|
||||||
|
assert "event_types must match supported workflow events" in errors
|
||||||
|
assert f"{EVENT_COMPLIANCE_DROP} is missing a dedupe key template" in errors
|
||||||
|
assert f"{EVENT_COMPLIANCE_DROP} is missing an owner" in errors
|
||||||
|
assert f"{EVENT_COMPLIANCE_DROP} is missing a ticket action" in errors
|
||||||
|
assert "missing payload template: slack" in errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_templates_return_independent_copies():
|
||||||
|
"""Mutating one returned bundle does not alter future callers."""
|
||||||
|
bundle = get_ticketing_chatops_templates()
|
||||||
|
bundle["payload_templates"]["jira"]["operation"] = "mutated"
|
||||||
|
|
||||||
|
fresh_bundle = get_ticketing_chatops_templates()
|
||||||
|
|
||||||
|
assert fresh_bundle["payload_templates"]["jira"]["operation"] == "create_or_update_issue"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_endpoint_returns_workflow_templates(
|
||||||
|
authed_client: TestClient,
|
||||||
|
):
|
||||||
|
"""Administrators can fetch issue and chat message templates."""
|
||||||
|
response = authed_client.get("/api/v1/integrations/ticketing-chatops/templates")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
body = response.json()
|
||||||
|
assert body["schema_version"] == TICKETING_CHATOPS_SCHEMA_VERSION
|
||||||
|
assert set(body["payload_templates"]) == {
|
||||||
|
"jira",
|
||||||
|
"github",
|
||||||
|
"slack",
|
||||||
|
"microsoft_teams",
|
||||||
|
}
|
||||||
|
assert body["payload_templates"]["jira"]["operation"] == "create_or_update_issue"
|
||||||
|
assert body["payload_templates"]["slack"]["thread_key"] == "{dedupe_key}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ticketing_chatops_endpoint_requires_admin_auth(client: TestClient):
|
||||||
|
"""Workflow templates use the same admin boundary as integration settings."""
|
||||||
|
response = client.get("/api/v1/integrations/ticketing-chatops/templates")
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
+2
-2
@@ -230,7 +230,7 @@ Exit criteria:
|
|||||||
|
|
||||||
## Milestone 14: Public API, Webhooks, and Core Integrations
|
## Milestone 14: Public API, Webhooks, and Core Integrations
|
||||||
|
|
||||||
Status: In progress
|
Status: Delivered
|
||||||
|
|
||||||
Goal: let DMARQ integrate cleanly into existing security and operations workflows.
|
Goal: let DMARQ integrate cleanly into existing security and operations workflows.
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ Planned:
|
|||||||
- A stable, documented read-only API surface for posture and reporting queries. Delivered with scoped `reports:read`, `posture:read`, and `tls-reports:read` API tokens, public read-only endpoints, and per-token usage audit fields.
|
- A stable, documented read-only API surface for posture and reporting queries. Delivered with scoped `reports:read`, `posture:read`, and `tls-reports:read` API tokens, public read-only endpoints, and per-token usage audit fields.
|
||||||
- Webhook event delivery for key events (new sender source, compliance drop, missing reports, alert lifecycle). Delivered with encrypted webhook endpoints, signed delivery headers, idempotency keys, retry/backoff state, test sends, and delivery inspection.
|
- Webhook event delivery for key events (new sender source, compliance drop, missing reports, alert lifecycle). Delivered with encrypted webhook endpoints, signed delivery headers, idempotency keys, retry/backoff state, test sends, and delivery inspection.
|
||||||
- SIEM integration templates delivered with a stable `dmarq.siem.event.v1` schema, source/compliance/alert examples, Splunk HEC, Elastic ECS, and Microsoft Sentinel ingestion shapes, and sensitive-field redaction guidance.
|
- SIEM integration templates delivered with a stable `dmarq.siem.event.v1` schema, source/compliance/alert examples, Splunk HEC, Elastic ECS, and Microsoft Sentinel ingestion shapes, and sensitive-field redaction guidance.
|
||||||
- Ticketing/chatops integration templates (Jira, GitHub, Slack, Teams).
|
- Ticketing/chatops integration templates delivered with event-to-workflow mappings, Jira/GitHub issue templates, Slack/Teams message templates, dedupe keys, ownership rules, and noise-control guidance.
|
||||||
- Token/scoping model for API access that matches governance needs (service accounts, least privilege).
|
- Token/scoping model for API access that matches governance needs (service accounts, least privilege).
|
||||||
|
|
||||||
Exit criteria:
|
Exit criteria:
|
||||||
|
|||||||
@@ -554,8 +554,15 @@ DMARQ ships operator-ready templates for normalized SIEM ingestion:
|
|||||||
| Endpoint | Purpose |
|
| Endpoint | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `GET /api/v1/integrations/siem/templates` | Return versioned SIEM schemas, examples, config hints, and redaction guidance |
|
| `GET /api/v1/integrations/siem/templates` | Return versioned SIEM schemas, examples, config hints, and redaction guidance |
|
||||||
|
| `GET /api/v1/integrations/ticketing-chatops/templates` | Return Jira, GitHub, Slack, Teams, mapping, dedupe, and operating-model templates |
|
||||||
|
|
||||||
The SIEM template bundle includes the stable `dmarq.siem.event.v1` envelope,
|
The SIEM template bundle includes the stable `dmarq.siem.event.v1` envelope,
|
||||||
examples for sender, compliance-drop, and alert events, and ingestion shapes for
|
examples for sender, compliance-drop, and alert events, and ingestion shapes for
|
||||||
Splunk HEC, Elastic ECS, and Microsoft Sentinel custom logs. See
|
Splunk HEC, Elastic ECS, and Microsoft Sentinel custom logs. See
|
||||||
[SIEM Integration Templates](siem-integrations.md) for the full operator guide.
|
[SIEM Integration Templates](siem-integrations.md) for the full operator guide.
|
||||||
|
|
||||||
|
The ticketing/chatops bundle includes event-to-workflow mappings, Jira and
|
||||||
|
GitHub issue templates, Slack and Microsoft Teams message templates, and
|
||||||
|
deduplication guidance. See
|
||||||
|
[Ticketing and Chatops Templates](ticketing-chatops-integrations.md) for the
|
||||||
|
full operator guide.
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
# Ticketing and Chatops Templates
|
||||||
|
|
||||||
|
DMARQ publishes workflow templates for turning webhook events into tickets and
|
||||||
|
channel notifications. Use these templates with a relay, automation platform, or
|
||||||
|
SIEM rule that receives signed DMARQ webhook events.
|
||||||
|
|
||||||
|
## Template Endpoint
|
||||||
|
|
||||||
|
Administrators can fetch the template bundle from:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/integrations/ticketing-chatops/templates
|
||||||
|
```
|
||||||
|
|
||||||
|
The response contains:
|
||||||
|
|
||||||
|
- `schema_version`: stable workflow template identifier, currently `dmarq.workflow.template.v1`.
|
||||||
|
- `event_workflow_mappings`: event-to-owner, severity, action, and dedupe rules.
|
||||||
|
- `sample_context`: example values for rendering templates.
|
||||||
|
- `payload_templates`: Jira, GitHub Issues, Slack, and Microsoft Teams shapes.
|
||||||
|
- `operating_model`: ownership, dedupe, threading, and noise-control guidance.
|
||||||
|
|
||||||
|
## Event Routing
|
||||||
|
|
||||||
|
Use these default workflow mappings as the starting point:
|
||||||
|
|
||||||
|
| Event | Owner | Ticket Action | Chat Action |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `dmarq.sender.new` | email-security | create or update | notify channel |
|
||||||
|
| `dmarq.compliance.drop` | email-security | create or update | notify channel and thread |
|
||||||
|
| `dmarq.reports.missing` | mail-operations | create or update | notify channel |
|
||||||
|
| `dmarq.alert.created` | email-security | create or update | notify channel |
|
||||||
|
| `dmarq.alert.resolved` | email-security | resolve or comment | notify thread |
|
||||||
|
|
||||||
|
Use the DMARQ webhook `X-DMARQ-Idempotency-Key` header when available. If the
|
||||||
|
receiver is building a workflow from API output, use the documented
|
||||||
|
`dedupe_key_template` from the template endpoint.
|
||||||
|
|
||||||
|
## Jira
|
||||||
|
|
||||||
|
Create or update one issue per active signal. Look up open issues by project,
|
||||||
|
label, and dedupe key before creating a new one.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"operation": "create_or_update_issue",
|
||||||
|
"lookup": {
|
||||||
|
"jql": "project = EMAILSEC AND labels = \"dmarq\" AND \"Dedupe Key\" ~ \"{dedupe_key}\" AND statusCategory != Done"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"fields": {
|
||||||
|
"project": {"key": "EMAILSEC"},
|
||||||
|
"issuetype": {"name": "Task"},
|
||||||
|
"summary": "[DMARQ][{severity}] {title}",
|
||||||
|
"labels": ["dmarq", "email-security", "{alert_rule}"],
|
||||||
|
"priority": {"name": "High"},
|
||||||
|
"customfield_dedupe_key": "{dedupe_key}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"comment": "{event_time}: {detail}"
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"transition": "Done",
|
||||||
|
"comment": "DMARQ reports this alert is resolved. Event: {event_id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Store Jira credentials in the relay, CI/CD variable store, or ticketing
|
||||||
|
automation platform.
|
||||||
|
|
||||||
|
## GitHub Issues
|
||||||
|
|
||||||
|
For teams tracking operations work in GitHub, create or update one issue in the
|
||||||
|
operations repository:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"operation": "create_or_update_issue",
|
||||||
|
"repository": "security-operations/email-auth",
|
||||||
|
"lookup": {
|
||||||
|
"state": "open",
|
||||||
|
"labels": ["dmarq", "{alert_rule}", "dedupe:{dedupe_key}"]
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "[DMARQ][{severity}] {title}",
|
||||||
|
"labels": ["dmarq", "email-security", "{alert_rule}", "dedupe:{dedupe_key}"]
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"comment": "{event_time}: {detail}\n\nDedupe key: `{dedupe_key}`"
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"state": "closed",
|
||||||
|
"comment": "Resolved by DMARQ event `{event_id}`."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use labels for routing and dedupe. Keep human assignment rules in GitHub or the
|
||||||
|
relay instead of hard-coding personal owners in DMARQ payloads.
|
||||||
|
|
||||||
|
## Slack
|
||||||
|
|
||||||
|
Send a concise summary to a stable channel, then keep updates in a thread keyed
|
||||||
|
by the dedupe key:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channel": "#email-security",
|
||||||
|
"thread_key": "{dedupe_key}",
|
||||||
|
"text": "[DMARQ][{severity}] {title}",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "header",
|
||||||
|
"text": {"type": "plain_text", "text": "DMARQ: {title}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"text": {"type": "mrkdwn", "text": "{detail}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "section",
|
||||||
|
"fields": [
|
||||||
|
{"type": "mrkdwn", "text": "*Domain*\n{domain}"},
|
||||||
|
{"type": "mrkdwn", "text": "*Severity*\n{severity}"},
|
||||||
|
{"type": "mrkdwn", "text": "*Compliance*\n{compliance_rate}%"},
|
||||||
|
{"type": "mrkdwn", "text": "*Drop*\n{drop_points} points"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use channel mentions sparingly. Reserve paging or urgent mentions for
|
||||||
|
high-severity compliance drops or repeated missing-report windows.
|
||||||
|
|
||||||
|
## Microsoft Teams
|
||||||
|
|
||||||
|
Teams destinations can use an Adaptive Card:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
||||||
|
"content": {
|
||||||
|
"type": "AdaptiveCard",
|
||||||
|
"version": "1.5",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "TextBlock",
|
||||||
|
"size": "Medium",
|
||||||
|
"weight": "Bolder",
|
||||||
|
"text": "DMARQ: {title}"
|
||||||
|
},
|
||||||
|
{"type": "TextBlock", "wrap": true, "text": "{detail}"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the relay or workflow platform to map dedupe keys to Teams threads when the
|
||||||
|
destination supports it.
|
||||||
|
|
||||||
|
## Operating Model
|
||||||
|
|
||||||
|
- Use one ticket per active signal and dedupe by DMARQ idempotency key or the documented dedupe key.
|
||||||
|
- Route sender, compliance, and alert signals to email security; route missing-report signals to mail operations.
|
||||||
|
- Close or comment on existing tickets when `dmarq.alert.resolved` arrives.
|
||||||
|
- Send chat updates into the same thread while a signal remains active.
|
||||||
|
- Keep repeats quiet: daily reminders are usually enough for missing reports, and compliance drops should follow configured thresholds.
|
||||||
|
- Keep Jira, GitHub, Slack, and Teams credentials in the receiving platform or secret manager, not in DMARQ payloads.
|
||||||
@@ -106,6 +106,11 @@ For SIEM pipelines, DMARQ also exposes a versioned template bundle at
|
|||||||
`dmarq.siem.event.v1` event schema, Splunk HEC, Elastic ECS, and Microsoft
|
`dmarq.siem.event.v1` event schema, Splunk HEC, Elastic ECS, and Microsoft
|
||||||
Sentinel examples, plus redaction guidance for sensitive fields.
|
Sentinel examples, plus redaction guidance for sensitive fields.
|
||||||
|
|
||||||
|
For ticketing and chatops workflows, use
|
||||||
|
`/api/v1/integrations/ticketing-chatops/templates`. It includes event mappings,
|
||||||
|
Jira and GitHub issue templates, Slack and Microsoft Teams message templates,
|
||||||
|
dedupe keys, and recommended ownership/noise-control rules.
|
||||||
|
|
||||||
## API Access
|
## API Access
|
||||||
|
|
||||||
DMARQ provides an API for integration with other systems:
|
DMARQ provides an API for integration with other systems:
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ nav:
|
|||||||
- Technical Reference:
|
- Technical Reference:
|
||||||
- API Reference: reference/api.md
|
- API Reference: reference/api.md
|
||||||
- SIEM Integrations: reference/siem-integrations.md
|
- SIEM Integrations: reference/siem-integrations.md
|
||||||
|
- Ticketing and Chatops: reference/ticketing-chatops-integrations.md
|
||||||
- Architecture: reference/architecture.md
|
- Architecture: reference/architecture.md
|
||||||
- Database Schema: reference/database.md
|
- Database Schema: reference/database.md
|
||||||
- Development:
|
- Development:
|
||||||
|
|||||||
Reference in New Issue
Block a user