feat(riposte-sextant): list the clients, and the identifier to deposit under (v1.1.0)
The refusal you get on the first run is "no Sextant client is paired with this SOAR client", and it names the identifier it did not recognise — but nothing told you which ones it would have recognised. This does. Clients with no pairing appear with an empty identifier, and are surfaced again under `unpaired`: they are the whole reason somebody runs this command twice, and spotting them in a list is exactly what nobody does. Verified against a running Sextant: the paired client comes back with its identifier, and the route refuses a caller with no ingestion token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
id: riposte-sextant
|
||||
name: Riposte Sextant
|
||||
version: 1.0.0
|
||||
version: 1.1.0
|
||||
description: >
|
||||
Riposte Sextant — SOC steering. Push what this SOAR already knows about a
|
||||
client's estate into their steering file: agent counters, measured on the
|
||||
vendor console by another command and mapped in the playbook that calls this
|
||||
one. Sextant never connects to anything itself; it receives. Bearer token
|
||||
authentication, stdlib-only, no extra Python dependencies.
|
||||
changelog: "1.0.0 — Initial release: push agent counters, and a connection test."
|
||||
changelog: "1.1.0 — List the clients Sextant knows, and the identifier to deposit under.\n1.0.0 — Initial release: push agent counters, and a connection test."
|
||||
category: reporting
|
||||
|
||||
config_schema:
|
||||
@@ -54,6 +54,22 @@ commands:
|
||||
client_id: { type: string, description: "The Sextant client the reading was filed under — the confirmation that the pairing pointed at the right one" }
|
||||
sent: { type: object, description: "The counters actually deposited, so the run log shows what was left unmeasured" }
|
||||
|
||||
- id: list_clients
|
||||
name: sextant-list-clients
|
||||
description: >
|
||||
List the clients Sextant knows and the identifier to deposit under. Answers
|
||||
the question you have while writing the playbook, and the refusal you get
|
||||
on the first run — "no Sextant client is paired with this SOAR client".
|
||||
Clients with no pairing appear with an empty identifier: those are the ones
|
||||
to go and pair, on their sheet in Sextant.
|
||||
risk: read
|
||||
inputs_schema: { properties: {} }
|
||||
outputs_schema:
|
||||
properties:
|
||||
clients:
|
||||
type: array
|
||||
description: "code, name, soar_client_id (empty when unpaired) and whether the client is being steered"
|
||||
|
||||
- id: test_connection
|
||||
name: sextant-test-connection
|
||||
description: "Check the URL and the ingestion token, without depositing anything."
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import json, os, sys, urllib.error, urllib.request
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def main():
|
||||
cfg = _cfg()
|
||||
base = str(cfg.get("base_url") or "").rstrip("/")
|
||||
if not base:
|
||||
raise Exception("base_url is not configured")
|
||||
req = urllib.request.Request(
|
||||
base + "/api/ingest/clients",
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + str(cfg.get("token") or ""),
|
||||
"User-Agent": "Riposte-SOAR/sextant",
|
||||
},
|
||||
method="GET",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
result = json.loads(r.read() or b"{}")
|
||||
clients = result.get("clients", [])
|
||||
# The unpaired ones are surfaced separately rather than left to be spotted in
|
||||
# a list: they are the whole reason somebody runs this command twice.
|
||||
unpaired = [c.get("code") for c in clients if not c.get("soar_client_id")]
|
||||
print(json.dumps({"clients": clients, "unpaired": unpaired}))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user