Connectors import telemetry and evidence from security tools (SIEM, EDR, logs) to validate detections.
Every connector should implement:
(ns purpleops-bas.connectors.my-connector
(:require
[clj-http.client :as http]
[cheshire.core :as json]
[taoensso.timbre :as log]))
;; Parse raw event into standard format
(defn parse-event [raw-event]
{:timestamp ...
:source "MyTool"
:event-type ...
:raw-data raw-event})
;; Import from file
(defn import-from-file [filepath]
(map parse-event (load-data filepath)))
;; Import from API
(defn import-from-api [config]
(map parse-event (query-api config)))All connectors should return events in this format:
{:timestamp #inst "2026-01-06T10:30:00Z"
:source "SourceName" ;; SIEM, EDR, Firewall, etc.
:event-type "EventType" ;; Alert, Detection, Log, etc.
:severity "high" ;; Optional
:confidence 0.85 ;; Optional
:fields {...} ;; Tool-specific fields
:raw-data {...}} ;; Original event(ns purpleops-bas.connectors.custom-siem
(:require
[clj-http.client :as http]
[cheshire.core :as json]
[taoensso.timbre :as log]))
(defn parse-custom-event [event]
{:timestamp (:event_time event)
:source "CustomSIEM"
:event-type (:alert_type event)
:severity (:severity event)
:fields {:rule-name (:rule event)
:src-ip (:src_ip event)
:dst-ip (:dst_ip event)}
:raw-data event})
(defn import-from-api [{:keys [base-url api-key query]}]
(try
(let [response (http/post (str base-url "/api/v1/search")
{:headers {"X-API-Key" api-key}
:body (json/generate-string {:query query})
:content-type :json
:as :json})]
(map parse-custom-event (get-in response [:body :results])))
(catch Exception e
(log/error e "Failed to query Custom SIEM")
[])))
(defn import-from-file [filepath]
(try
(let [data (-> filepath slurp (json/parse-string true))]
(map parse-custom-event (:events data)))
(catch Exception e
(log/error e "Failed to import from file")
[])))-
Add namespace to
connectors/src/purpleops_bas/connectors/ -
Register in orchestrator - modify
orchestrator/src/.../critic.clj:
(require '[purpleops-bas.connectors.custom-siem :as custom-siem])
(defn import-detections [run-id sources]
(concat
(when (contains? sources :custom-siem)
(custom-siem/import-from-api {...}))
;; other sources
))- Test import:
(custom-siem/import-from-file "test-data/events.json")- SIEM:
purpleops-bas.connectors.siem- Splunk, Elastic - EDR:
purpleops-bas.connectors.edr- Defender, CrowdStrike - Logs:
purpleops-bas.connectors.logs- syslog, Windows Event, CloudTrail
Store credentials securely:
;; Use environment variables
(def api-key (System/getenv "CUSTOM_SIEM_API_KEY"))
;; Or load from config
(def config (load-config))
(def api-key (get-in config [:connectors :custom-siem :api-key]))Implement rate limiting for API calls:
(def rate-limiter (atom {:last-call 0 :min-interval-ms 1000}))
(defn rate-limited-call [f]
(let [now (System/currentTimeMillis)
last-call (:last-call @rate-limiter)
interval (:min-interval-ms @rate-limiter)]
(when (< (- now last-call) interval)
(Thread/sleep (- interval (- now last-call))))
(swap! rate-limiter assoc :last-call (System/currentTimeMillis))
(f)))Always handle errors gracefully:
(defn import-with-retry [import-fn retries]
(loop [attempts retries]
(try
(import-fn)
(catch Exception e
(if (> attempts 1)
(do
(log/warn "Import failed, retrying..." e)
(Thread/sleep 2000)
(recur (dec attempts)))
(do
(log/error e "Import failed after retries")
[]))))))# In REPL
clj -M:dev
(require '[purpleops-bas.connectors.custom-siem :as cs])
;; Test file import
(cs/import-from-file "test-data/sample.json")
;; Test API import (with test credentials)
(cs/import-from-api {:base-url "https://test.siem.local"
:api-key "test-key"
:query "search index=main"})- Parse consistently - use standard event format
- Handle missing fields - provide defaults
- Log errors - use timbre for logging
- Test with real data - validate parsing logic
- Document fields - explain what each field means
- Version compatibility - handle API changes
- Review existing connectors in
connectors/src/ - Check Clojure HTTP client docs: https://github.com/dakrone/clj-http
- Ask in team chat for connector examples