**Approach (brief)** Read CSV of IPs, query SIEM for events matching each IP, then tag matching events with tag "threat-intel-hit". Use resilient HTTP client with exponential backoff, honoring Retry-After for rate limits, idempotent PATCH operations, and structured logging for audit.**Sample Python script**python
import csv, time, logging, requests
from urllib.parse import quote
# config
SIEM_BASE = "https://siem.example/api"
API_KEY = "REDACTED"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
TAG_BODY = {"tags": ["threat-intel-hit"]}
# logging
logging.basicConfig(filename='siem_tagging.log', level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s')
def backoff_sleep(attempt, retry_after=None):
if retry_after:
sleep = int(retry_after)
else:
sleep = min(60, (2 ** attempt) + (attempt * 0.1))
time.sleep(sleep)
def request_with_retries(method, url, **kwargs):
max_attempts = 5
for attempt in range(1, max_attempts+1):
resp = requests.request(method, url, headers=HEADERS, timeout=30, **kwargs)
if resp.status_code in (200, 201, 204):
return resp
if resp.status_code == 429:
retry_after = resp.headers.get("Retry-After")
logging.warning("Rate limited: %s %s attempt=%d retry_after=%s", method, url, attempt, retry_after)
backoff_sleep(attempt, retry_after)
continue
if 500 <= resp.status_code < 600:
logging.warning("Transient SIEM error %d for %s attempt=%d", resp.status_code, url, attempt)
backoff_sleep(attempt)
continue
# non-retryable
logging.error("Non-retryable error %d: %s %s", resp.status_code, method, url)
return resp
raise RuntimeError(f"Max retries exceeded for {url}")
def find_events_for_ip(ip):
q = quote(f"source.ip:{ip} OR destination.ip:{ip}")
url = f"{SIEM_BASE}/search?query={q}"
resp = request_with_retries("GET", url)
resp.raise_for_status()
return resp.json().get("events", [])
def tag_event(event_id):
# Use idempotent PATCH/PUT; API should be designed to add tag if absent
url = f"{SIEM_BASE}/events/{event_id}/tags"
resp = request_with_retries("PUT", url, json=TAG_BODY)
if resp.status_code in (200,201,204):
logging.info("Tagged event %s", event_id)
else:
logging.error("Failed to tag %s status=%d body=%s", event_id, resp.status_code, resp.text)
def main(csv_path):
with open(csv_path) as f:
reader = csv.reader(f)
for row in reader:
ip = row[0].strip()
logging.info("Processing IP %s", ip)
try:
events = find_events_for_ip(ip)
for ev in events:
event_id = ev["id"]
# idempotency: check tags already present to avoid duplicate calls
if "threat-intel-hit" in ev.get("tags", []):
logging.info("Event %s already tagged", event_id); continue
tag_event(event_id)
except Exception as e:
logging.exception("Error processing %s: %s", ip, e)
if __name__ == "__main__":
main("suspicious_ips.csv")
**Why this handles concerns**- Rate limits: Honors Retry-After and exponential backoff.- Transient errors: Retries 5 times for 5xx with backoff.- Idempotency: Uses PUT to add tags and checks existing tags before tagging to avoid duplicates.- Logging/audit: Structured file logs for each IP/event, warnings on rate limits and errors.- Security: API key stored in config/secret manager in real deployment; add batching and parallelism limited by rate budget.