Compare commits
153 Commits
9da00ad7b9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bf273b959a | |||
| ba68d19e51 | |||
| a6e1245c79 | |||
| d06f8ea413 | |||
| 17318d4225 | |||
| c22982c8af | |||
| 1d475a6cf5 | |||
| 93309ac74d | |||
| a5095d3f0f | |||
| cebb3af796 | |||
| ba6bcd19f4 | |||
| aabe1a6837 | |||
| 50358ef8a1 | |||
| 82f22fdbb3 | |||
| f7c5e99cc5 | |||
| 6343a0c952 | |||
| 7efc873867 | |||
| 144089a099 | |||
| a4b5b44157 | |||
| 4ea3af6c1d | |||
| 952dc7efda | |||
| cba746a9f9 | |||
| 5d6f823189 | |||
| 098b90d6f3 | |||
| db902c35bc | |||
| 59e9211c0d | |||
| fcbd2ff135 | |||
| ef60cec0fa | |||
| 6d993f584f | |||
| 6d311740f9 | |||
| f6b8c84285 | |||
| 5ee7352652 | |||
| 534748f16a | |||
| a2c9f998c3 | |||
| ae7c494658 | |||
| 40252dadcb | |||
| ac2203f9a5 | |||
| 8248c60da8 | |||
| 4206b44e78 | |||
| 9cffefbb36 | |||
| f7791402a3 | |||
| c42a9cef03 | |||
| f6ff018f09 | |||
| c091800434 | |||
| 929b4166d6 | |||
| 381b42b6c7 | |||
| 5715f21603 | |||
| 7543a95711 | |||
| 7187e7b607 | |||
| 5bb465eb18 | |||
| a4dcd00cc7 | |||
| cf923301a7 | |||
| f833bfe3a5 | |||
| d6129f5d79 | |||
| c779c38dab | |||
| 4f94b59e17 | |||
| f09d00114f | |||
| a4241a5a5d | |||
| 232e310f77 | |||
| b231b648bc | |||
| 087b9abd19 | |||
| e91febd319 | |||
| 374e4184ff | |||
| 82353947d6 | |||
| ac47315b30 | |||
| 631292fcc7 | |||
| d8bb41b4c5 | |||
| 855f1b132c | |||
| 74bf6126fb | |||
| dd574e550b | |||
| 8a7e5a33a2 | |||
| 3c348b7601 | |||
| 2475c4b56e | |||
| 3be14f2f00 | |||
| 3a4d06441c | |||
| ce319c2fea | |||
| 8f3d9607df | |||
| a2f2b8efa0 | |||
| 3c282e8e53 | |||
| 771c90ef0d | |||
| 9fb5b65e4a | |||
| 16fe51ee99 | |||
| 5089e4466d | |||
| 8fa1ade945 | |||
| 54b077bfc9 | |||
| 6d38896892 | |||
| 223dfd34e3 | |||
| 83c1736d47 | |||
| bc100eef67 | |||
| 5f2e946c0a | |||
| 86fdd8483a | |||
| cd02c26b45 | |||
| ab68b3ae71 | |||
| 046fb79008 | |||
| 6548740890 | |||
| 16deec38f8 | |||
| cf950cf6e4 | |||
| 1949c98cf7 | |||
| addaeadae5 | |||
| b137e49be4 | |||
| c5699dd01a | |||
| 015131a2d1 | |||
| 82ad3a9369 | |||
| df8a2c1187 | |||
| 088694fdc2 | |||
| c1867a62a3 | |||
| c2cc4da675 | |||
| 2b5e7f37b4 | |||
| d469ed1d8f | |||
| 1ec065e91b | |||
| ee58358330 | |||
| 5bf679cfe0 | |||
| 70c66f7386 | |||
| 4020e13df9 | |||
| 2a1eb73115 | |||
| 81e39e8148 | |||
| d06ea406d3 | |||
| fe7dae8bd3 | |||
| 52356523c9 | |||
| a567558341 | |||
| 041cadade2 | |||
| 41e8aa8e7e | |||
| 0775500b15 | |||
| 9be28a2ff7 | |||
| 1292a8c798 | |||
| b6af7d1b68 | |||
| d5b7e38dd7 | |||
| 6de01f85bb | |||
| 86e7f11227 | |||
| 9f88891457 | |||
| 9892f2cf06 | |||
| be8bf9f822 | |||
| aca1db5cad | |||
| d0d72c5171 | |||
| fd64c245f4 | |||
| 6cc0bb61bc | |||
| 496ac6d8ce | |||
| 44d0836e1b | |||
| 796a95d5f8 | |||
| a5db725529 | |||
| ecc301d95d | |||
| 6b8dd13297 | |||
| 1c3936992b | |||
| 61da5ef145 | |||
| f34363b450 | |||
| fcf30da5fc | |||
| 847d18f79b | |||
| 5501297984 | |||
| 31d0a5938a | |||
| 62a44aea28 | |||
| 14e5e102a9 | |||
| 460dfaefba | |||
| f826181704 |
@@ -0,0 +1,112 @@
|
||||
id: anomali_threatstream
|
||||
name: Anomali ThreatStream
|
||||
version: 1.0.0
|
||||
description: "Anomali ThreatStream (API v2/v1) — threat intelligence: reputation lookups for IPs, domains, file hashes and URLs, indicator search, passive DNS, threat-model listing, and indicator import (with or without approval). API-key authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: ip/domain/file/url reputation, indicator search, passive DNS, threat models, import indicator."
|
||||
category: threat_intel
|
||||
|
||||
# Per-instance configuration. Auth header 'Authorization: apikey <username>:<api_key>'.
|
||||
config_schema:
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
description: "ThreatStream API URL"
|
||||
default: "https://api.threatstream.com"
|
||||
username:
|
||||
type: string
|
||||
description: "ThreatStream username"
|
||||
api_key:
|
||||
type: string
|
||||
description: "ThreatStream API key"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- username
|
||||
- api_key
|
||||
|
||||
commands:
|
||||
- id: ip_reputation
|
||||
name: anomali-ip-reputation
|
||||
description: "Look up threat intelligence for an IP address."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
ip: { type: string, description: "IP address" }
|
||||
required: [ip]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: domain_reputation
|
||||
name: anomali-domain-reputation
|
||||
description: "Look up threat intelligence for a domain."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
domain: { type: string, description: "Domain name" }
|
||||
required: [domain]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: file_reputation
|
||||
name: anomali-file-reputation
|
||||
description: "Look up threat intelligence for a file hash."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
file_hash: { type: string, description: "MD5/SHA1/SHA256 hash" }
|
||||
required: [file_hash]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: url_reputation
|
||||
name: anomali-url-reputation
|
||||
description: "Look up threat intelligence for a URL."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
url: { type: string, description: "URL" }
|
||||
required: [url]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_indicators
|
||||
name: anomali-get-indicators
|
||||
description: "Search indicators with a free-text query."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "ThreatStream search query (q=)" }
|
||||
limit: { type: number, description: "Max indicators (default 20)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: passive_dns
|
||||
name: anomali-passive-dns
|
||||
description: "Get passive DNS records for an IP or domain."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
value: { type: string, description: "IP or domain" }
|
||||
type: { type: string, description: "ip or domain (default ip)" }
|
||||
required: [value]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_threat_models
|
||||
name: anomali-get-threat-models
|
||||
description: "List threat models (actors, campaigns, incidents, ...)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "Optional name search" }
|
||||
limit: { type: number, description: "Max models (default 20)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: import_indicator
|
||||
name: anomali-import-indicator
|
||||
description: "Import an observable as an indicator (optionally requiring approval)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
value: { type: string, description: "Observable value (IP, domain, hash, URL)" }
|
||||
itype: { type: string, description: "Indicator type (e.g. mal_ip, mal_domain, apt_md5)" }
|
||||
confidence: { type: number, description: "Confidence 0-100 (default 50)" }
|
||||
approve: { type: boolean, description: "Import without approval (default false = requires approval)" }
|
||||
required: [value, itype]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: anomali-test-connection
|
||||
description: "Verify connectivity and the API key (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
domain = inputs.get("domain")
|
||||
if not domain:
|
||||
raise Exception("domain is required")
|
||||
return request("GET", "/api/v2/intelligence/", cfg, params={"value": domain, "type": "domain", "limit": 50})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
file_hash = inputs.get("file_hash")
|
||||
if not file_hash:
|
||||
raise Exception("file_hash is required")
|
||||
return request("GET", "/api/v2/intelligence/", cfg, params={"value": file_hash, "type": "md5", "limit": 50})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,53 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
limit = inputs.get("limit")
|
||||
return request("GET", "/api/v2/intelligence/", cfg, params={"q": query, "limit": int(limit or 20)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,53 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
limit = inputs.get("limit")
|
||||
return request("GET", "/api/v1/threat_model_search/", cfg, params={"name": query, "limit": int(limit or 20)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,63 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
value = inputs.get("value")
|
||||
if not value:
|
||||
raise Exception("value is required")
|
||||
itype = inputs.get("itype")
|
||||
if not itype:
|
||||
raise Exception("itype is required")
|
||||
confidence = inputs.get("confidence")
|
||||
approve = inputs.get("approve")
|
||||
body = {"objects": [{"value": value, "itype": itype, "confidence": int(confidence or 50)}]}
|
||||
params = {}
|
||||
if approve:
|
||||
params["approve"] = "true"
|
||||
return request("POST", "/api/v2/intelligence/", cfg, body=body, params=params or None)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
ip = inputs.get("ip")
|
||||
if not ip:
|
||||
raise Exception("ip is required")
|
||||
return request("GET", "/api/v2/intelligence/", cfg, params={"value": ip, "type": "ip", "limit": 50})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,57 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
value = inputs.get("value")
|
||||
if not value:
|
||||
raise Exception("value is required")
|
||||
itype = inputs.get("type") or "ip"
|
||||
path = "/api/v1/pdns/" + q(itype) + "/" + q(value) + "/"
|
||||
return request("GET", path, cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,52 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("GET", "/api/v2/intelligence/", cfg, params={"limit": 1})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("url") or "https://api.threatstream.com")).rstrip("/")
|
||||
|
||||
|
||||
def _auth(cfg):
|
||||
return "apikey " + str(cfg.get("username", "")) + ":" + str(cfg.get("api_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": _auth(cfg), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
url = inputs.get("url")
|
||||
if not url:
|
||||
raise Exception("url is required")
|
||||
return request("GET", "/api/v2/intelligence/", cfg, params={"value": url, "type": "url", "limit": 50})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,70 @@
|
||||
id: armis
|
||||
name: Armis
|
||||
version: 1.0.0
|
||||
description: "Armis (API v1) — device and asset visibility: search devices and alerts with AQL, read a device, and update an alert's status. Secret-key (token exchange) authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: search devices/alerts, get device, update alert status."
|
||||
category: asset_management
|
||||
|
||||
# Per-instance configuration. The secret key is exchanged for a short-lived
|
||||
# access token (sent as the 'Authorization' header).
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "Armis instance URL (e.g. https://yourtenant.armis.com)"
|
||||
secret_key:
|
||||
type: string
|
||||
description: "Armis secret key"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- base_url
|
||||
- secret_key
|
||||
|
||||
commands:
|
||||
- id: search_devices
|
||||
name: armis-search-devices
|
||||
description: "Search devices with an AQL expression."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
aql: { type: string, description: "AQL filter appended to 'in:devices' (e.g. riskLevel:High)" }
|
||||
length: { type: number, description: "Max results (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_device
|
||||
name: armis-get-device
|
||||
description: "Get a single device by ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: search_alerts
|
||||
name: armis-search-alerts
|
||||
description: "Search alerts with an AQL expression."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
aql: { type: string, description: "AQL filter appended to 'in:alerts' (e.g. status:Unhandled)" }
|
||||
length: { type: number, description: "Max results (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: update_alert
|
||||
name: armis-update-alert
|
||||
description: "Update an alert's status."
|
||||
inputs_schema:
|
||||
properties:
|
||||
alert_id: { type: string, description: "Alert ID" }
|
||||
status: { type: string, description: "New status (UNHANDLED, SUPPRESSED, or RESOLVED)" }
|
||||
required: [alert_id, status]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: armis-test-connection
|
||||
description: "Verify the secret key via the token exchange (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,69 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return str(cfg.get("base_url", "")).rstrip("/") + "/api/v1"
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
form = urllib.parse.urlencode({"secret_key": str(cfg.get("secret_key", ""))}).encode("utf-8")
|
||||
req = urllib.request.Request(_base(cfg) + "/access_token/", data=form,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
at = (tok.get("data") or {}).get("access_token")
|
||||
if not at:
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return at
|
||||
|
||||
|
||||
def request(method, path, cfg, token, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
return request("GET", "/devices/" + q(device_id) + "/", cfg, token)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,66 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return str(cfg.get("base_url", "")).rstrip("/") + "/api/v1"
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
form = urllib.parse.urlencode({"secret_key": str(cfg.get("secret_key", ""))}).encode("utf-8")
|
||||
req = urllib.request.Request(_base(cfg) + "/access_token/", data=form,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
at = (tok.get("data") or {}).get("access_token")
|
||||
if not at:
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return at
|
||||
|
||||
|
||||
def request(method, path, cfg, token, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
aql = inputs.get("aql")
|
||||
length = inputs.get("length")
|
||||
aql_str = "in:alerts" + ((" " + aql) if aql else "")
|
||||
return request("GET", "/search/", cfg, token, params={"aql": aql_str, "length": int(length or 50)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,66 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return str(cfg.get("base_url", "")).rstrip("/") + "/api/v1"
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
form = urllib.parse.urlencode({"secret_key": str(cfg.get("secret_key", ""))}).encode("utf-8")
|
||||
req = urllib.request.Request(_base(cfg) + "/access_token/", data=form,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
at = (tok.get("data") or {}).get("access_token")
|
||||
if not at:
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return at
|
||||
|
||||
|
||||
def request(method, path, cfg, token, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
aql = inputs.get("aql")
|
||||
length = inputs.get("length")
|
||||
aql_str = "in:devices" + ((" " + aql) if aql else "")
|
||||
return request("GET", "/search/", cfg, token, params={"aql": aql_str, "length": int(length or 50)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,64 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return str(cfg.get("base_url", "")).rstrip("/") + "/api/v1"
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
form = urllib.parse.urlencode({"secret_key": str(cfg.get("secret_key", ""))}).encode("utf-8")
|
||||
req = urllib.request.Request(_base(cfg) + "/access_token/", data=form,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
at = (tok.get("data") or {}).get("access_token")
|
||||
if not at:
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return at
|
||||
|
||||
|
||||
def request(method, path, cfg, token, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
request("GET", "/search/", cfg, token, params={"aql": "in:devices", "length": 1})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,75 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return str(cfg.get("base_url", "")).rstrip("/") + "/api/v1"
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
form = urllib.parse.urlencode({"secret_key": str(cfg.get("secret_key", ""))}).encode("utf-8")
|
||||
req = urllib.request.Request(_base(cfg) + "/access_token/", data=form,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Accept": "application/json"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
at = (tok.get("data") or {}).get("access_token")
|
||||
if not at:
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return at
|
||||
|
||||
|
||||
def request(method, path, cfg, token, body=None, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
alert_id = inputs.get("alert_id")
|
||||
status = inputs.get("status")
|
||||
if not alert_id:
|
||||
raise Exception("alert_id is required")
|
||||
if not status:
|
||||
raise Exception("status is required")
|
||||
resp = request("PATCH", "/alerts/" + q(alert_id) + "/", cfg, token, body={"status": status})
|
||||
if not resp:
|
||||
return {"ok": True, "alert_id": alert_id}
|
||||
return resp
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,66 @@
|
||||
id: automox
|
||||
name: Automox
|
||||
version: 1.0.0
|
||||
description: "Automox (API) — endpoint patch and configuration management: list and read devices, list policies, and queue a command (e.g. install updates or run a policy) on a device. API-key authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: list/get devices, list policies, run device command."
|
||||
category: endpoint
|
||||
|
||||
# Per-instance configuration. Auth header 'Authorization: Bearer <api_key>'.
|
||||
config_schema:
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: "Automox API key"
|
||||
x-soar-sensitive: true
|
||||
org_id:
|
||||
type: string
|
||||
description: "Organization ID"
|
||||
required:
|
||||
- api_key
|
||||
- org_id
|
||||
|
||||
commands:
|
||||
- id: list_devices
|
||||
name: automox-list-devices
|
||||
description: "List devices."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
limit: { type: number, description: "Max devices (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_device
|
||||
name: automox-get-device
|
||||
description: "Get a single device by ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device (server) ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_policies
|
||||
name: automox-list-policies
|
||||
description: "List policies."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: run_command
|
||||
name: automox-run-command
|
||||
description: "Queue a command on a device (e.g. InstallUpdate, Reboot)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device (server) ID" }
|
||||
command_type: { type: string, description: "Command type (e.g. InstallUpdate, Reboot, GetOS)" }
|
||||
required: [device_id, command_type]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: automox-test-connection
|
||||
description: "Verify the API key (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,49 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://console.automox.com/api"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
p = {k: v for k, v in (params or {}).items() if v not in (None, "")}
|
||||
p["o"] = str(cfg.get("org_id", ""))
|
||||
url = BASE + path + "?" + urllib.parse.urlencode(p)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("api_key", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
return request("GET", "/servers/" + q(device_id), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,48 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://console.automox.com/api"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
p = {k: v for k, v in (params or {}).items() if v not in (None, "")}
|
||||
p["o"] = str(cfg.get("org_id", ""))
|
||||
url = BASE + path + "?" + urllib.parse.urlencode(p)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("api_key", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
limit = inputs.get("limit")
|
||||
limit = int(limit) if limit not in (None, "") else 50
|
||||
return request("GET", "/servers", cfg, params={"limit": limit})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,46 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://console.automox.com/api"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
p = {k: v for k, v in (params or {}).items() if v not in (None, "")}
|
||||
p["o"] = str(cfg.get("org_id", ""))
|
||||
url = BASE + path + "?" + urllib.parse.urlencode(p)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("api_key", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
return request("GET", "/policies", cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,55 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://console.automox.com/api"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
p = {k: v for k, v in (params or {}).items() if v not in (None, "")}
|
||||
p["o"] = str(cfg.get("org_id", ""))
|
||||
url = BASE + path + "?" + urllib.parse.urlencode(p)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("api_key", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
command_type = inputs.get("command_type")
|
||||
if not command_type:
|
||||
raise Exception("command_type is required")
|
||||
resp = request("POST", "/servers/" + q(device_id) + "/queues", cfg, body={"command_type_name": command_type})
|
||||
if not resp:
|
||||
return {"ok": True, "device_id": device_id, "command_type": command_type}
|
||||
return resp
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,47 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://console.automox.com/api"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
p = {k: v for k, v in (params or {}).items() if v not in (None, "")}
|
||||
p["o"] = str(cfg.get("org_id", ""))
|
||||
url = BASE + path + "?" + urllib.parse.urlencode(p)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("api_key", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("GET", "/servers", cfg, params={"limit": 1})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,126 @@
|
||||
id: aws
|
||||
name: AWS
|
||||
version: 1.0.0
|
||||
description: "Amazon Web Services (EC2, IAM, STS) — cloud containment: describe instances and security groups, authorize/revoke security-group ingress rules, change an instance's security groups (isolate), stop instances, and deactivate a compromised IAM access key. AWS Signature V4 authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: EC2 describe instances/security-groups, authorize/revoke ingress, modify instance security groups, stop instances; IAM list/update access keys; STS caller identity."
|
||||
category: cloud
|
||||
|
||||
# Per-instance configuration. Requests are signed with AWS Signature V4.
|
||||
# Use an IAM user/role access key with EC2 + IAM permissions. session_token is
|
||||
# only needed for temporary (STS) credentials.
|
||||
config_schema:
|
||||
properties:
|
||||
access_key_id:
|
||||
type: string
|
||||
description: "AWS access key ID"
|
||||
secret_access_key:
|
||||
type: string
|
||||
description: "AWS secret access key"
|
||||
x-soar-sensitive: true
|
||||
region:
|
||||
type: string
|
||||
description: "Default AWS region (e.g. eu-west-1)"
|
||||
default: "us-east-1"
|
||||
session_token:
|
||||
type: string
|
||||
description: "Optional STS session token (for temporary credentials)"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- access_key_id
|
||||
- secret_access_key
|
||||
|
||||
commands:
|
||||
- id: describe_instances
|
||||
name: aws-describe-instances
|
||||
description: "Describe EC2 instances (optionally a single instance by ID)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
instance_id: { type: string, description: "Optional instance ID to fetch a single instance" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: describe_security_groups
|
||||
name: aws-describe-security-groups
|
||||
description: "Describe EC2 security groups (optionally a single group by ID)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
group_id: { type: string, description: "Optional security group ID" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: authorize_security_group_ingress
|
||||
name: aws-authorize-security-group-ingress
|
||||
description: "Add an inbound rule to a security group."
|
||||
inputs_schema:
|
||||
properties:
|
||||
group_id: { type: string, description: "Security group ID" }
|
||||
protocol: { type: string, description: "IP protocol (tcp, udp, icmp, or -1 for all)" }
|
||||
from_port: { type: number, description: "Start port" }
|
||||
to_port: { type: number, description: "End port" }
|
||||
cidr: { type: string, description: "Source CIDR (e.g. 203.0.113.0/24)" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: [group_id, protocol, cidr]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: revoke_security_group_ingress
|
||||
name: aws-revoke-security-group-ingress
|
||||
description: "Remove an inbound rule from a security group (containment)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
group_id: { type: string, description: "Security group ID" }
|
||||
protocol: { type: string, description: "IP protocol (tcp, udp, icmp, or -1 for all)" }
|
||||
from_port: { type: number, description: "Start port" }
|
||||
to_port: { type: number, description: "End port" }
|
||||
cidr: { type: string, description: "Source CIDR to revoke" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: [group_id, protocol, cidr]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: modify_instance_security_groups
|
||||
name: aws-modify-instance-security-groups
|
||||
description: "Replace the security groups attached to an instance (e.g. move it to an isolation group)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
instance_id: { type: string, description: "Instance ID" }
|
||||
group_ids: { type: string, description: "Comma-separated security group IDs to set" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: [instance_id, group_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: stop_instances
|
||||
name: aws-stop-instances
|
||||
description: "Stop one or more EC2 instances."
|
||||
inputs_schema:
|
||||
properties:
|
||||
instance_ids: { type: string, description: "Comma-separated instance IDs" }
|
||||
force: { type: boolean, description: "Force stop (default false)" }
|
||||
region: { type: string, description: "Region override" }
|
||||
required: [instance_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_access_keys
|
||||
name: aws-list-access-keys
|
||||
description: "List a user's IAM access keys."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
user_name: { type: string, description: "IAM user name (omit to use the calling user)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: update_access_key
|
||||
name: aws-update-access-key
|
||||
description: "Activate or deactivate an IAM access key (deactivate to contain a compromised key)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
access_key_id: { type: string, description: "The access key ID to update" }
|
||||
status: { type: string, description: "Active or Inactive" }
|
||||
user_name: { type: string, description: "IAM user name (omit to use the calling user)" }
|
||||
required: [access_key_id, status]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: aws-test-connection
|
||||
description: "Verify credentials via STS GetCallerIdentity (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,151 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
group_id = inputs.get("group_id")
|
||||
if not group_id:
|
||||
raise Exception("group_id is required")
|
||||
protocol = inputs.get("protocol")
|
||||
if not protocol:
|
||||
raise Exception("protocol is required")
|
||||
cidr = inputs.get("cidr")
|
||||
if not cidr:
|
||||
raise Exception("cidr is required")
|
||||
|
||||
params = {
|
||||
"GroupId": group_id,
|
||||
"IpPermissions.1.IpProtocol": protocol,
|
||||
"IpPermissions.1.IpRanges.1.CidrIp": cidr,
|
||||
}
|
||||
from_port = inputs.get("from_port")
|
||||
if from_port is not None and str(from_port).strip() != "":
|
||||
params["IpPermissions.1.FromPort"] = int(from_port)
|
||||
to_port = inputs.get("to_port")
|
||||
if to_port is not None and str(to_port).strip() != "":
|
||||
params["IpPermissions.1.ToPort"] = int(to_port)
|
||||
|
||||
return ec2("AuthorizeSecurityGroupIngress", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,131 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
instance_id = inputs.get("instance_id")
|
||||
params = {"InstanceId.1": instance_id} if instance_id else {}
|
||||
return ec2("DescribeInstances", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,131 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
group_id = inputs.get("group_id")
|
||||
params = {"GroupId.1": group_id} if group_id else {}
|
||||
return ec2("DescribeSecurityGroups", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,131 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
user_name = inputs.get("user_name")
|
||||
params = {"UserName": user_name} if user_name else {}
|
||||
return iam("ListAccessKeys", params, cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,143 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
instance_id = inputs.get("instance_id")
|
||||
if not instance_id:
|
||||
raise Exception("instance_id is required")
|
||||
group_ids_raw = inputs.get("group_ids")
|
||||
if not group_ids_raw or not str(group_ids_raw).strip():
|
||||
raise Exception("group_ids is required")
|
||||
group_ids = [s.strip() for s in str(group_ids_raw).split(",") if s.strip()]
|
||||
if not group_ids:
|
||||
raise Exception("group_ids is required")
|
||||
|
||||
params = {"InstanceId": instance_id}
|
||||
for i, gid in enumerate(group_ids, start=1):
|
||||
params["GroupId.%d" % i] = gid
|
||||
|
||||
return ec2("ModifyInstanceAttribute", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,151 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
group_id = inputs.get("group_id")
|
||||
if not group_id:
|
||||
raise Exception("group_id is required")
|
||||
protocol = inputs.get("protocol")
|
||||
if not protocol:
|
||||
raise Exception("protocol is required")
|
||||
cidr = inputs.get("cidr")
|
||||
if not cidr:
|
||||
raise Exception("cidr is required")
|
||||
|
||||
params = {
|
||||
"GroupId": group_id,
|
||||
"IpPermissions.1.IpProtocol": protocol,
|
||||
"IpPermissions.1.IpRanges.1.CidrIp": cidr,
|
||||
}
|
||||
from_port = inputs.get("from_port")
|
||||
if from_port is not None and str(from_port).strip() != "":
|
||||
params["IpPermissions.1.FromPort"] = int(from_port)
|
||||
to_port = inputs.get("to_port")
|
||||
if to_port is not None and str(to_port).strip() != "":
|
||||
params["IpPermissions.1.ToPort"] = int(to_port)
|
||||
|
||||
return ec2("RevokeSecurityGroupIngress", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,148 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
instance_ids_raw = inputs.get("instance_ids")
|
||||
if not instance_ids_raw or not str(instance_ids_raw).strip():
|
||||
raise Exception("instance_ids is required")
|
||||
instance_ids = [s.strip() for s in str(instance_ids_raw).split(",") if s.strip()]
|
||||
if not instance_ids:
|
||||
raise Exception("instance_ids is required")
|
||||
|
||||
force = inputs.get("force", False)
|
||||
if isinstance(force, str):
|
||||
force = force.strip().lower() in ("true", "1", "yes")
|
||||
else:
|
||||
force = bool(force)
|
||||
|
||||
params = {}
|
||||
for i, iid in enumerate(instance_ids, start=1):
|
||||
params["InstanceId.%d" % i] = iid
|
||||
if force:
|
||||
params["Force"] = "true"
|
||||
|
||||
return ec2("StopInstances", params, cfg, inputs)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,130 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
identity = sts("GetCallerIdentity", {}, cfg)
|
||||
return {"ok": True, "identity": identity}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,143 @@
|
||||
import json, os, sys, hmac, hashlib, datetime
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _sign_key(key, date_stamp, region, service):
|
||||
def _h(k, m):
|
||||
return hmac.new(k, m.encode("utf-8"), hashlib.sha256).digest()
|
||||
k_date = _h(("AWS4" + key).encode("utf-8"), date_stamp)
|
||||
k_region = _h(k_date, region)
|
||||
k_service = _h(k_region, service)
|
||||
return _h(k_service, "aws4_request")
|
||||
|
||||
|
||||
def _strip_ns(tag):
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _xml_to_dict(elem):
|
||||
d = {}
|
||||
children = list(elem)
|
||||
if not children:
|
||||
return (elem.text or "").strip()
|
||||
for c in children:
|
||||
tag = _strip_ns(c.tag)
|
||||
val = _xml_to_dict(c)
|
||||
if tag in d:
|
||||
if not isinstance(d[tag], list):
|
||||
d[tag] = [d[tag]]
|
||||
d[tag].append(val)
|
||||
else:
|
||||
d[tag] = val
|
||||
return d
|
||||
|
||||
|
||||
def aws_query(service, host, region, action, version, params, cfg):
|
||||
# params: dict of extra query params for this Action
|
||||
body_params = {"Action": action, "Version": version}
|
||||
body_params.update({k: str(v) for k, v in params.items() if v is not None})
|
||||
body = urllib.parse.urlencode(sorted(body_params.items()))
|
||||
|
||||
access_key = str(cfg.get("access_key_id", ""))
|
||||
secret_key = str(cfg.get("secret_access_key", ""))
|
||||
session_token = cfg.get("session_token") or ""
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
||||
date_stamp = now.strftime("%Y%m%d")
|
||||
|
||||
method = "POST"
|
||||
canonical_uri = "/"
|
||||
canonical_querystring = ""
|
||||
payload_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
canonical_headers = "content-type:application/x-www-form-urlencoded; charset=utf-8\n" \
|
||||
"host:" + host + "\n" \
|
||||
"x-amz-date:" + amz_date + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date"
|
||||
if session_token:
|
||||
canonical_headers += "x-amz-security-token:" + session_token + "\n"
|
||||
signed_headers = "content-type;host;x-amz-date;x-amz-security-token"
|
||||
canonical_request = "\n".join([method, canonical_uri, canonical_querystring,
|
||||
canonical_headers, signed_headers, payload_hash])
|
||||
|
||||
algorithm = "AWS4-HMAC-SHA256"
|
||||
credential_scope = date_stamp + "/" + region + "/" + service + "/aws4_request"
|
||||
string_to_sign = "\n".join([algorithm, amz_date, credential_scope,
|
||||
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()])
|
||||
signing_key = _sign_key(secret_key, date_stamp, region, service)
|
||||
signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
|
||||
authorization = (algorithm + " Credential=" + access_key + "/" + credential_scope +
|
||||
", SignedHeaders=" + signed_headers + ", Signature=" + signature)
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": amz_date,
|
||||
"Authorization": authorization,
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if session_token:
|
||||
headers["X-Amz-Security-Token"] = session_token
|
||||
|
||||
req = urllib.request.Request("https://" + host + "/", data=body.encode("utf-8"), headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
root = ET.fromstring(raw)
|
||||
return {_strip_ns(root.tag): _xml_to_dict(root)}
|
||||
|
||||
|
||||
def _region(cfg, inputs):
|
||||
return (inputs.get("region") if inputs else None) or cfg.get("region") or "us-east-1"
|
||||
|
||||
|
||||
def ec2(action, params, cfg, inputs):
|
||||
region = _region(cfg, inputs)
|
||||
return aws_query("ec2", "ec2." + region + ".amazonaws.com", region, action, "2016-11-15", params, cfg)
|
||||
|
||||
|
||||
def iam(action, params, cfg):
|
||||
return aws_query("iam", "iam.amazonaws.com", "us-east-1", action, "2010-05-08", params, cfg)
|
||||
|
||||
|
||||
def sts(action, params, cfg):
|
||||
return aws_query("sts", "sts.amazonaws.com", "us-east-1", action, "2011-06-15", params, cfg)
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
access_key_id = inputs.get("access_key_id")
|
||||
if not access_key_id:
|
||||
raise Exception("access_key_id is required")
|
||||
status = inputs.get("status")
|
||||
if not status:
|
||||
raise Exception("status is required")
|
||||
if status not in ("Active", "Inactive"):
|
||||
raise Exception("status must be Active or Inactive")
|
||||
|
||||
params = {"AccessKeyId": access_key_id, "Status": status}
|
||||
user_name = inputs.get("user_name")
|
||||
if user_name and str(user_name).strip():
|
||||
params["UserName"] = user_name
|
||||
|
||||
return iam("UpdateAccessKey", params, cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,76 @@
|
||||
id: axonius
|
||||
name: Axonius
|
||||
version: 1.0.0
|
||||
description: "Axonius (REST API) — cybersecurity asset management: query devices and users with AQL filters, get a device by ID, and count devices matching a filter. API-key + API-secret authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: list/get devices, device count, list users."
|
||||
category: asset_management
|
||||
|
||||
# Per-instance configuration. Auth uses the 'api-key' and 'api-secret' headers.
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "Axonius instance URL (e.g. https://axonius.example.com)"
|
||||
api_key:
|
||||
type: string
|
||||
description: "API key"
|
||||
x-soar-sensitive: true
|
||||
api_secret:
|
||||
type: string
|
||||
description: "API secret"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- base_url
|
||||
- api_key
|
||||
- api_secret
|
||||
|
||||
commands:
|
||||
- id: list_devices
|
||||
name: axonius-list-devices
|
||||
description: "Query devices with an optional AQL filter."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
filter: { type: string, description: "AQL filter (e.g. specific_data.data.hostname == \"host01\")" }
|
||||
limit: { type: number, description: "Max devices (default 50)" }
|
||||
offset: { type: number, description: "Offset (default 0)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_device
|
||||
name: axonius-get-device
|
||||
description: "Get a single device by its internal Axonius ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Internal Axonius device ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: device_count
|
||||
name: axonius-device-count
|
||||
description: "Count devices matching an AQL filter."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
filter: { type: string, description: "AQL filter (empty = all devices)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_users
|
||||
name: axonius-list-users
|
||||
description: "Query users with an optional AQL filter."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
filter: { type: string, description: "AQL filter" }
|
||||
limit: { type: number, description: "Max users (default 50)" }
|
||||
offset: { type: number, description: "Offset (default 0)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: axonius-test-connection
|
||||
description: "Verify connectivity and credentials (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,52 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + "/api" + path
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"api-key": str(cfg.get("api_key", "")),
|
||||
"api-secret": str(cfg.get("api_secret", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
filter_ = inputs.get("filter")
|
||||
|
||||
body = {"data": {}}
|
||||
if filter_:
|
||||
body["data"]["filter"] = filter_
|
||||
|
||||
return request("POST", "/devices/count", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,50 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + "/api" + path
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"api-key": str(cfg.get("api_key", "")),
|
||||
"api-secret": str(cfg.get("api_secret", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
|
||||
return request("GET", "/devices/" + q(device_id), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,61 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + "/api" + path
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"api-key": str(cfg.get("api_key", "")),
|
||||
"api-secret": str(cfg.get("api_secret", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
filter_ = inputs.get("filter")
|
||||
limit = inputs.get("limit")
|
||||
offset = inputs.get("offset")
|
||||
|
||||
body = {
|
||||
"data": {
|
||||
"page": {
|
||||
"limit": int(limit or 50),
|
||||
"offset": int(offset or 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter_:
|
||||
body["data"]["filter"] = filter_
|
||||
|
||||
return request("POST", "/devices", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,61 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + "/api" + path
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"api-key": str(cfg.get("api_key", "")),
|
||||
"api-secret": str(cfg.get("api_secret", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
filter_ = inputs.get("filter")
|
||||
limit = inputs.get("limit")
|
||||
offset = inputs.get("offset")
|
||||
|
||||
body = {
|
||||
"data": {
|
||||
"page": {
|
||||
"limit": int(limit or 50),
|
||||
"offset": int(offset or 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter_:
|
||||
body["data"]["filter"] = filter_
|
||||
|
||||
return request("POST", "/users", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,47 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + "/api" + path
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"api-key": str(cfg.get("api_key", "")),
|
||||
"api-secret": str(cfg.get("api_secret", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("POST", "/devices/count", cfg, body={"data": {}})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,120 @@
|
||||
id: azure_security
|
||||
name: Microsoft Azure
|
||||
version: 1.0.0
|
||||
description: "Microsoft Azure (Resource Manager: Defender for Cloud + Network) — cloud containment: list and read Defender for Cloud security alerts and update their state, read the secure score, list/read network security groups (NSGs), and add or delete NSG security rules (deny inbound to isolate). Azure AD OAuth2 client-credentials authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: list/get security alerts, update alert state, secure score, list/get NSGs, create/delete NSG security rules."
|
||||
category: cloud
|
||||
|
||||
# Per-instance configuration. Uses application (client-credentials) OAuth2 with
|
||||
# a service principal that has Reader + Security Admin + Network Contributor on
|
||||
# the subscription.
|
||||
config_schema:
|
||||
properties:
|
||||
tenant_id:
|
||||
type: string
|
||||
description: "Azure AD tenant ID"
|
||||
client_id:
|
||||
type: string
|
||||
description: "Service principal (client) ID"
|
||||
client_secret:
|
||||
type: string
|
||||
description: "Service principal client secret"
|
||||
x-soar-sensitive: true
|
||||
subscription_id:
|
||||
type: string
|
||||
description: "Azure subscription ID"
|
||||
required:
|
||||
- tenant_id
|
||||
- client_id
|
||||
- client_secret
|
||||
- subscription_id
|
||||
|
||||
commands:
|
||||
- id: list_alerts
|
||||
name: azure-list-alerts
|
||||
description: "List Microsoft Defender for Cloud security alerts in the subscription."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_alert
|
||||
name: azure-get-alert
|
||||
description: "Get a single security alert by its full ARM resource ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
alert_id: { type: string, description: "Full ARM resource ID of the alert (from azure-list-alerts)" }
|
||||
required: [alert_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: update_alert_state
|
||||
name: azure-update-alert-state
|
||||
description: "Change a security alert's state (dismiss, resolve, activate, or inProgress)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
alert_id: { type: string, description: "Full ARM resource ID of the alert" }
|
||||
state: { type: string, description: "dismiss | resolve | activate | inProgress" }
|
||||
required: [alert_id, state]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_secure_score
|
||||
name: azure-get-secure-score
|
||||
description: "Get the subscription's Defender for Cloud secure score."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_nsgs
|
||||
name: azure-list-nsgs
|
||||
description: "List network security groups in the subscription."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_nsg
|
||||
name: azure-get-nsg
|
||||
description: "Get a single network security group."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource_group: { type: string, description: "Resource group name" }
|
||||
nsg_name: { type: string, description: "NSG name" }
|
||||
required: [resource_group, nsg_name]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: create_nsg_rule
|
||||
name: azure-create-nsg-rule
|
||||
description: "Create or update an NSG security rule (e.g. a Deny inbound rule to isolate a resource)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource_group: { type: string, description: "Resource group name" }
|
||||
nsg_name: { type: string, description: "NSG name" }
|
||||
rule_name: { type: string, description: "Security rule name" }
|
||||
priority: { type: number, description: "Rule priority (100-4096)" }
|
||||
direction: { type: string, description: "Inbound or Outbound (default Inbound)" }
|
||||
access: { type: string, description: "Allow or Deny (default Deny)" }
|
||||
protocol: { type: string, description: "Tcp, Udp, or * (default *)" }
|
||||
source: { type: string, description: "Source address prefix (CIDR or *, default *)" }
|
||||
destination: { type: string, description: "Destination address prefix (default *)" }
|
||||
destination_port: { type: string, description: "Destination port range (default *)" }
|
||||
required: [resource_group, nsg_name, rule_name, priority]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: delete_nsg_rule
|
||||
name: azure-delete-nsg-rule
|
||||
description: "Delete an NSG security rule."
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource_group: { type: string, description: "Resource group name" }
|
||||
nsg_name: { type: string, description: "NSG name" }
|
||||
rule_name: { type: string, description: "Security rule name" }
|
||||
required: [resource_group, nsg_name, rule_name]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: azure-test-connection
|
||||
description: "Verify connectivity and the service-principal credentials (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,103 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
resource_group = inputs.get("resource_group")
|
||||
if not resource_group:
|
||||
raise Exception("resource_group is required")
|
||||
nsg_name = inputs.get("nsg_name")
|
||||
if not nsg_name:
|
||||
raise Exception("nsg_name is required")
|
||||
rule_name = inputs.get("rule_name")
|
||||
if not rule_name:
|
||||
raise Exception("rule_name is required")
|
||||
priority = inputs.get("priority")
|
||||
if priority in (None, ""):
|
||||
raise Exception("priority is required")
|
||||
|
||||
direction = inputs.get("direction")
|
||||
access = inputs.get("access")
|
||||
protocol = inputs.get("protocol")
|
||||
source = inputs.get("source")
|
||||
destination = inputs.get("destination")
|
||||
destination_port = inputs.get("destination_port")
|
||||
|
||||
path = ("/subscriptions/" + sub(cfg) + "/resourceGroups/" + q(resource_group) +
|
||||
"/providers/Microsoft.Network/networkSecurityGroups/" + q(nsg_name) +
|
||||
"/securityRules/" + q(rule_name))
|
||||
body = {
|
||||
"properties": {
|
||||
"priority": int(priority),
|
||||
"direction": direction or "Inbound",
|
||||
"access": access or "Deny",
|
||||
"protocol": protocol or "*",
|
||||
"sourceAddressPrefix": source or "*",
|
||||
"destinationAddressPrefix": destination or "*",
|
||||
"sourcePortRange": "*",
|
||||
"destinationPortRange": destination_port or "*",
|
||||
}
|
||||
}
|
||||
return arm("PUT", path, token, "2023-09-01", body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,84 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
resource_group = inputs.get("resource_group")
|
||||
if not resource_group:
|
||||
raise Exception("resource_group is required")
|
||||
nsg_name = inputs.get("nsg_name")
|
||||
if not nsg_name:
|
||||
raise Exception("nsg_name is required")
|
||||
rule_name = inputs.get("rule_name")
|
||||
if not rule_name:
|
||||
raise Exception("rule_name is required")
|
||||
|
||||
path = ("/subscriptions/" + sub(cfg) + "/resourceGroups/" + q(resource_group) +
|
||||
"/providers/Microsoft.Network/networkSecurityGroups/" + q(nsg_name) +
|
||||
"/securityRules/" + q(rule_name))
|
||||
result = arm("DELETE", path, token, "2023-09-01")
|
||||
if not result:
|
||||
return {"ok": True, "deleted": rule_name}
|
||||
return result
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,67 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
alert_id = inputs.get("alert_id")
|
||||
if not alert_id:
|
||||
raise Exception("alert_id is required")
|
||||
return arm("GET", None, token, "2022-01-01", full_url=ARM + str(alert_id))
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,76 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
resource_group = inputs.get("resource_group")
|
||||
if not resource_group:
|
||||
raise Exception("resource_group is required")
|
||||
nsg_name = inputs.get("nsg_name")
|
||||
if not nsg_name:
|
||||
raise Exception("nsg_name is required")
|
||||
path = ("/subscriptions/" + sub(cfg) + "/resourceGroups/" + q(resource_group) +
|
||||
"/providers/Microsoft.Network/networkSecurityGroups/" + q(nsg_name))
|
||||
return arm("GET", path, token, "2023-09-01")
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,68 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
path = "/subscriptions/" + sub(cfg) + "/providers/Microsoft.Security/secureScores/ascScore"
|
||||
return arm("GET", path, token, "2020-01-01")
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,68 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
path = "/subscriptions/" + sub(cfg) + "/providers/Microsoft.Security/alerts"
|
||||
return arm("GET", path, token, "2022-01-01")
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,68 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
path = "/subscriptions/" + sub(cfg) + "/providers/Microsoft.Network/networkSecurityGroups"
|
||||
return arm("GET", path, token, "2023-09-01")
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,69 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
sub = lambda cfg: str(cfg.get("subscription_id", ""))
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
path = "/subscriptions/" + sub(cfg)
|
||||
arm("GET", path, token, "2022-12-01")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,78 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
ARM = "https://management.azure.com"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
url = "https://login.microsoftonline.com/" + str(cfg.get("tenant_id", "")) + "/oauth2/v2.0/token"
|
||||
form = urllib.parse.urlencode({
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": str(cfg.get("client_id", "")),
|
||||
"client_secret": str(cfg.get("client_secret", "")),
|
||||
"scope": "https://management.azure.com/.default",
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=form, headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def arm(method, path, token, api_version, body=None, params=None, full_url=None):
|
||||
url = (full_url if full_url else ARM + path)
|
||||
qp = {"api-version": api_version}
|
||||
if params:
|
||||
qp.update({k: v for k, v in params.items() if v not in (None, "")})
|
||||
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(qp)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + token, "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
token = _token(cfg)
|
||||
print(json.dumps(fn(cfg, token, inputs)))
|
||||
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)
|
||||
|
||||
|
||||
VALID_STATES = ("dismiss", "resolve", "activate", "inProgress")
|
||||
|
||||
|
||||
def main(cfg, token, inputs):
|
||||
alert_id = inputs.get("alert_id")
|
||||
if not alert_id:
|
||||
raise Exception("alert_id is required")
|
||||
state = inputs.get("state")
|
||||
if not state:
|
||||
raise Exception("state is required")
|
||||
if state not in VALID_STATES:
|
||||
raise Exception("state must be one of: " + ", ".join(VALID_STATES))
|
||||
result = arm("POST", None, token, "2022-01-01", body=None, full_url=ARM + str(alert_id) + "/" + state)
|
||||
if not result:
|
||||
return {"ok": True, "state": state}
|
||||
return result
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,77 @@
|
||||
id: beyondtrust_password_safe
|
||||
name: BeyondTrust Password Safe
|
||||
version: 1.0.0
|
||||
description: "BeyondTrust Password Safe (Secrets Safe REST API v3) — privileged access and credential retrieval: list managed accounts and systems, request a credential release, and retrieve the credential. API-key (PS-Auth) session authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: list managed accounts/systems, create release request, get credential."
|
||||
category: identity
|
||||
|
||||
# Per-instance configuration. Auth signs in with an API key + runas user
|
||||
# (header 'Authorization: PS-Auth key=<api_key>; runas=<runas_user>;'), which
|
||||
# establishes a session reused for the request.
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "BeyondTrust URL (e.g. https://beyondtrust.example.com)"
|
||||
api_key:
|
||||
type: string
|
||||
description: "API registration key"
|
||||
x-soar-sensitive: true
|
||||
runas_user:
|
||||
type: string
|
||||
description: "Username to run as"
|
||||
insecure:
|
||||
type: boolean
|
||||
description: "Trust any TLS certificate (not secure)"
|
||||
default: false
|
||||
required:
|
||||
- base_url
|
||||
- api_key
|
||||
- runas_user
|
||||
|
||||
commands:
|
||||
- id: list_managed_accounts
|
||||
name: beyondtrust-list-managed-accounts
|
||||
description: "List managed accounts."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
account_name: { type: string, description: "Optional account name filter" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_managed_systems
|
||||
name: beyondtrust-list-managed-systems
|
||||
description: "List managed systems."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: create_release_request
|
||||
name: beyondtrust-create-release-request
|
||||
description: "Request a credential release for a managed account."
|
||||
inputs_schema:
|
||||
properties:
|
||||
system_id: { type: string, description: "Managed system ID" }
|
||||
account_id: { type: string, description: "Managed account ID" }
|
||||
duration_minutes: { type: number, description: "Access duration in minutes (default 30)" }
|
||||
reason: { type: string, description: "Reason for the request" }
|
||||
required: [system_id, account_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_credential
|
||||
name: beyondtrust-get-credential
|
||||
description: "Retrieve the credential for an approved request."
|
||||
inputs_schema:
|
||||
properties:
|
||||
request_id: { type: string, description: "Request ID (from create-release-request)" }
|
||||
required: [request_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: beyondtrust-test-connection
|
||||
description: "Verify the sign-in (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,103 @@
|
||||
import json, os, sys, ssl, http.cookiejar
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("base_url", "")).rstrip("/") + "/BeyondTrust/api/public/v3"
|
||||
ctx = _ctx(cfg)
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
|
||||
urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPSHandler(),
|
||||
)
|
||||
|
||||
def _auth_header(self):
|
||||
return "PS-Auth key=" + str(self.cfg.get("api_key", "")) + "; runas=" + str(self.cfg.get("runas_user", "")) + ";"
|
||||
|
||||
def signin(self):
|
||||
req = urllib.request.Request(self.base + "/Auth/SignAppin", data=b"",
|
||||
headers={"Authorization": self._auth_header(), "Accept": "application/json"}, method="POST")
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def call(self, method, path, body=None, params=None):
|
||||
url = self.base + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Accept": "application/json", "Authorization": self._auth_header()}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def signout(self):
|
||||
try:
|
||||
req = urllib.request.Request(self.base + "/Auth/Signout", data=b"", method="POST")
|
||||
self.opener.open(req, timeout=30).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.signin()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.signout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
system_id = inputs.get("system_id")
|
||||
account_id = inputs.get("account_id")
|
||||
duration_minutes = inputs.get("duration_minutes")
|
||||
reason = inputs.get("reason")
|
||||
if not system_id:
|
||||
raise Exception("system_id is required")
|
||||
if not account_id:
|
||||
raise Exception("account_id is required")
|
||||
body = {
|
||||
"SystemId": int(system_id),
|
||||
"AccountId": int(account_id),
|
||||
"DurationMinutes": int(duration_minutes) if duration_minutes else 30,
|
||||
"Reason": reason or "Riposte SOAR",
|
||||
"AccessType": "View",
|
||||
}
|
||||
return client.call("POST", "/Requests", body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,93 @@
|
||||
import json, os, sys, ssl, http.cookiejar
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("base_url", "")).rstrip("/") + "/BeyondTrust/api/public/v3"
|
||||
ctx = _ctx(cfg)
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
|
||||
urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPSHandler(),
|
||||
)
|
||||
|
||||
def _auth_header(self):
|
||||
return "PS-Auth key=" + str(self.cfg.get("api_key", "")) + "; runas=" + str(self.cfg.get("runas_user", "")) + ";"
|
||||
|
||||
def signin(self):
|
||||
req = urllib.request.Request(self.base + "/Auth/SignAppin", data=b"",
|
||||
headers={"Authorization": self._auth_header(), "Accept": "application/json"}, method="POST")
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def call(self, method, path, body=None, params=None):
|
||||
url = self.base + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Accept": "application/json", "Authorization": self._auth_header()}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def signout(self):
|
||||
try:
|
||||
req = urllib.request.Request(self.base + "/Auth/Signout", data=b"", method="POST")
|
||||
self.opener.open(req, timeout=30).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.signin()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.signout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
request_id = inputs.get("request_id")
|
||||
if not request_id:
|
||||
raise Exception("request_id is required")
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
response = client.call("GET", "/Credentials/" + q(request_id))
|
||||
return {"request_id": request_id, "credential": response}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,89 @@
|
||||
import json, os, sys, ssl, http.cookiejar
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("base_url", "")).rstrip("/") + "/BeyondTrust/api/public/v3"
|
||||
ctx = _ctx(cfg)
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
|
||||
urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPSHandler(),
|
||||
)
|
||||
|
||||
def _auth_header(self):
|
||||
return "PS-Auth key=" + str(self.cfg.get("api_key", "")) + "; runas=" + str(self.cfg.get("runas_user", "")) + ";"
|
||||
|
||||
def signin(self):
|
||||
req = urllib.request.Request(self.base + "/Auth/SignAppin", data=b"",
|
||||
headers={"Authorization": self._auth_header(), "Accept": "application/json"}, method="POST")
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def call(self, method, path, body=None, params=None):
|
||||
url = self.base + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Accept": "application/json", "Authorization": self._auth_header()}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def signout(self):
|
||||
try:
|
||||
req = urllib.request.Request(self.base + "/Auth/Signout", data=b"", method="POST")
|
||||
self.opener.open(req, timeout=30).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.signin()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.signout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
account_name = inputs.get("account_name")
|
||||
return client.call("GET", "/ManagedAccounts", params={"accountName": account_name})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,88 @@
|
||||
import json, os, sys, ssl, http.cookiejar
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("base_url", "")).rstrip("/") + "/BeyondTrust/api/public/v3"
|
||||
ctx = _ctx(cfg)
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
|
||||
urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPSHandler(),
|
||||
)
|
||||
|
||||
def _auth_header(self):
|
||||
return "PS-Auth key=" + str(self.cfg.get("api_key", "")) + "; runas=" + str(self.cfg.get("runas_user", "")) + ";"
|
||||
|
||||
def signin(self):
|
||||
req = urllib.request.Request(self.base + "/Auth/SignAppin", data=b"",
|
||||
headers={"Authorization": self._auth_header(), "Accept": "application/json"}, method="POST")
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def call(self, method, path, body=None, params=None):
|
||||
url = self.base + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Accept": "application/json", "Authorization": self._auth_header()}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def signout(self):
|
||||
try:
|
||||
req = urllib.request.Request(self.base + "/Auth/Signout", data=b"", method="POST")
|
||||
self.opener.open(req, timeout=30).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.signin()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.signout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
return client.call("GET", "/ManagedSystems")
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,89 @@
|
||||
import json, os, sys, ssl, http.cookiejar
|
||||
import urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("base_url", "")).rstrip("/") + "/BeyondTrust/api/public/v3"
|
||||
ctx = _ctx(cfg)
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
|
||||
urllib.request.HTTPSHandler(context=ctx) if ctx else urllib.request.HTTPSHandler(),
|
||||
)
|
||||
|
||||
def _auth_header(self):
|
||||
return "PS-Auth key=" + str(self.cfg.get("api_key", "")) + "; runas=" + str(self.cfg.get("runas_user", "")) + ";"
|
||||
|
||||
def signin(self):
|
||||
req = urllib.request.Request(self.base + "/Auth/SignAppin", data=b"",
|
||||
headers={"Authorization": self._auth_header(), "Accept": "application/json"}, method="POST")
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def call(self, method, path, body=None, params=None):
|
||||
url = self.base + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Accept": "application/json", "Authorization": self._auth_header()}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with self.opener.open(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def signout(self):
|
||||
try:
|
||||
req = urllib.request.Request(self.base + "/Auth/Signout", data=b"", method="POST")
|
||||
self.opener.open(req, timeout=30).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.signin()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.signout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
client.call("GET", "/ManagedSystems")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,74 @@
|
||||
id: binaryedge
|
||||
name: BinaryEdge
|
||||
version: 1.0.0
|
||||
description: "BinaryEdge (API v2) — internet exposure intelligence: query current and historical open ports/services for an IP, run a search, enumerate a domain's subdomains, check an email against data leaks, and read the subscription quota. API-key authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: host lookup (current/historical), search, domain subdomains, data-leak email check, subscription."
|
||||
category: enrichment
|
||||
|
||||
# Per-instance configuration. The API key is sent as the 'X-Key' header.
|
||||
config_schema:
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: "BinaryEdge API key"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- api_key
|
||||
|
||||
commands:
|
||||
- id: host
|
||||
name: binaryedge-host
|
||||
description: "Get the most recent open ports and services for an IP."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
ip: { type: string, description: "IP address" }
|
||||
required: [ip]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: host_historical
|
||||
name: binaryedge-host-historical
|
||||
description: "Get historical open ports and services for an IP."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
ip: { type: string, description: "IP address" }
|
||||
required: [ip]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: search
|
||||
name: binaryedge-search
|
||||
description: "Search hosts/services by a BinaryEdge query."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "BinaryEdge search query (e.g. type:elasticsearch)" }
|
||||
page: { type: number, description: "Page number (default 1)" }
|
||||
required: [query]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: domain_subdomains
|
||||
name: binaryedge-domain-subdomains
|
||||
description: "List a domain's known subdomains."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
domain: { type: string, description: "Domain name" }
|
||||
page: { type: number, description: "Page number (default 1)" }
|
||||
required: [domain]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: dataleaks_email
|
||||
name: binaryedge-dataleaks-email
|
||||
description: "Check whether an email appears in known data leaks."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
email: { type: string, description: "Email address" }
|
||||
required: [email]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: binaryedge-test-connection
|
||||
description: "Verify the API key via the subscription endpoint (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,47 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
email = inputs.get("email")
|
||||
if not email:
|
||||
raise Exception("email is required")
|
||||
return request("/query/dataleaks/email/" + q(email), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,48 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
domain = inputs.get("domain")
|
||||
if not domain:
|
||||
raise Exception("domain is required")
|
||||
page = inputs.get("page")
|
||||
return request("/query/domains/subdomain/" + q(domain), cfg, params={"page": int(page or 1)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,47 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
ip = inputs.get("ip")
|
||||
if not ip:
|
||||
raise Exception("ip is required")
|
||||
return request("/query/ip/" + q(ip), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,47 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
ip = inputs.get("ip")
|
||||
if not ip:
|
||||
raise Exception("ip is required")
|
||||
return request("/query/ip/historical/" + q(ip), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,48 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
if not query:
|
||||
raise Exception("query is required")
|
||||
page = inputs.get("page")
|
||||
return request("/query/search", cfg, params={"query": query, "page": int(page or 1)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,45 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.binaryedge.io/v2"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, cfg, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
req = urllib.request.Request(url, headers={"X-Key": str(cfg.get("api_key", "")), "Accept": "application/json"}, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("/user/subscription", cfg)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,74 @@
|
||||
id: box
|
||||
name: Box
|
||||
version: 1.0.0
|
||||
description: "Box (Content API v2) — evidence and file handling: search files, read file/folder metadata, list a folder's items, and create a shared link. Bearer-token authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: search, get file/folder info, list folder items, create shared link."
|
||||
category: productivity
|
||||
|
||||
# Per-instance configuration. Auth header 'Authorization: Bearer <access_token>'.
|
||||
config_schema:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
description: "Box access token (developer token or OAuth2/JWT-issued token)"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- access_token
|
||||
|
||||
commands:
|
||||
- id: search
|
||||
name: box-search
|
||||
description: "Search for files and folders by keyword."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "Search query" }
|
||||
limit: { type: number, description: "Max results (default 30)" }
|
||||
required: [query]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_file_info
|
||||
name: box-get-file-info
|
||||
description: "Get a file's metadata."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
file_id: { type: string, description: "File ID" }
|
||||
required: [file_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_folder_info
|
||||
name: box-get-folder-info
|
||||
description: "Get a folder's metadata."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
folder_id: { type: string, description: "Folder ID (0 = root)" }
|
||||
required: [folder_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_folder_items
|
||||
name: box-list-folder-items
|
||||
description: "List the items inside a folder."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
folder_id: { type: string, description: "Folder ID (0 = root)" }
|
||||
limit: { type: number, description: "Max items (default 100)" }
|
||||
required: [folder_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: create_shared_link
|
||||
name: box-create-shared-link
|
||||
description: "Create a shared link for a file."
|
||||
inputs_schema:
|
||||
properties:
|
||||
file_id: { type: string, description: "File ID" }
|
||||
access: { type: string, description: "open, company, or collaborators (default company)" }
|
||||
required: [file_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: box-test-connection
|
||||
description: "Verify the access token (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,58 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
file_id = inputs.get("file_id")
|
||||
if not file_id:
|
||||
raise Exception("file_id is required")
|
||||
access = inputs.get("access")
|
||||
return request(
|
||||
"PUT",
|
||||
"/files/" + q(file_id),
|
||||
cfg,
|
||||
body={"shared_link": {"access": (access or "company")}},
|
||||
params={"fields": "shared_link"},
|
||||
)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,51 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
file_id = inputs.get("file_id")
|
||||
if not file_id:
|
||||
raise Exception("file_id is required")
|
||||
return request("GET", "/files/" + q(file_id), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,51 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
folder_id = inputs.get("folder_id")
|
||||
if not folder_id:
|
||||
raise Exception("folder_id is required")
|
||||
return request("GET", "/folders/" + q(folder_id), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,52 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
folder_id = inputs.get("folder_id")
|
||||
if not folder_id:
|
||||
raise Exception("folder_id is required")
|
||||
limit = inputs.get("limit")
|
||||
return request("GET", "/folders/" + q(folder_id) + "/items", cfg, params={"limit": int(limit or 100)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,49 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
if not query:
|
||||
raise Exception("query is required")
|
||||
limit = inputs.get("limit")
|
||||
return request("GET", "/search", cfg, params={"query": query, "limit": int(limit or 30)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,46 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://api.box.com/2.0"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = BASE + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {"Authorization": "Bearer " + str(cfg.get("access_token", "")), "Accept": "application/json"}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
resp = request("GET", "/users/me", cfg)
|
||||
return {"ok": True, "login": resp.get("login")}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,79 @@
|
||||
id: cape
|
||||
name: CAPE Sandbox
|
||||
version: 1.0.0
|
||||
description: "CAPE Sandbox (APIv2) — dynamic malware analysis and config extraction: submit files and URLs, read task status and reports, and list tasks. Token authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: submit file/URL, get task, get report, list tasks."
|
||||
category: enrichment
|
||||
|
||||
# Per-instance configuration. The token is sent as 'Authorization: Token <api_token>'.
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "CAPE URL (e.g. https://cape.example.com)"
|
||||
api_token:
|
||||
type: string
|
||||
description: "CAPE API token"
|
||||
x-soar-sensitive: true
|
||||
insecure:
|
||||
type: boolean
|
||||
description: "Trust any TLS certificate (not secure)"
|
||||
default: false
|
||||
required:
|
||||
- base_url
|
||||
- api_token
|
||||
|
||||
commands:
|
||||
- id: submit_file
|
||||
name: cape-submit-file
|
||||
description: "Submit a file (base64) for analysis."
|
||||
inputs_schema:
|
||||
properties:
|
||||
file_name: { type: string, description: "File name" }
|
||||
content_base64: { type: string, description: "File content, base64-encoded" }
|
||||
required: [file_name, content_base64]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: submit_url
|
||||
name: cape-submit-url
|
||||
description: "Submit a URL for analysis."
|
||||
inputs_schema:
|
||||
properties:
|
||||
url: { type: string, description: "URL to detonate" }
|
||||
required: [url]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_task
|
||||
name: cape-get-task
|
||||
description: "Get a task's status and metadata."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
task_id: { type: string, description: "Task ID" }
|
||||
required: [task_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_report
|
||||
name: cape-get-report
|
||||
description: "Get a task's full analysis report (JSON)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
task_id: { type: string, description: "Task ID" }
|
||||
required: [task_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_tasks
|
||||
name: cape-list-tasks
|
||||
description: "List recent tasks."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
limit: { type: number, description: "Max tasks (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: cape-test-connection
|
||||
description: "Verify connectivity and the token (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,83 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
task_id = inputs.get("task_id")
|
||||
if not task_id:
|
||||
raise Exception("task_id is required")
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
return request("GET", "/apiv2/tasks/get/report/" + q(task_id) + "/", cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,83 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
task_id = inputs.get("task_id")
|
||||
if not task_id:
|
||||
raise Exception("task_id is required")
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
return request("GET", "/apiv2/tasks/view/" + q(task_id) + "/", cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,80 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
limit = inputs.get("limit")
|
||||
return request("GET", "/apiv2/tasks/list/" + str(int(limit or 50)) + "/", cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,92 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
file_name = inputs.get("file_name")
|
||||
content_base64 = inputs.get("content_base64")
|
||||
if not file_name:
|
||||
raise Exception("file_name is required")
|
||||
if not content_base64:
|
||||
raise Exception("content_base64 is required")
|
||||
return request_multipart(
|
||||
"/apiv2/tasks/create/file/",
|
||||
cfg,
|
||||
{},
|
||||
"file",
|
||||
file_name,
|
||||
base64.b64decode(content_base64),
|
||||
)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,82 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
url = inputs.get("url")
|
||||
if not url:
|
||||
raise Exception("url is required")
|
||||
return request("POST", "/apiv2/tasks/create/url/", cfg, form={"url": url})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,80 @@
|
||||
import json, os, sys, base64, ssl, uuid, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def _headers(cfg, extra=None):
|
||||
h = {"Accept": "application/json", "Authorization": "Token " + str(cfg.get("api_token", ""))}
|
||||
if extra:
|
||||
h.update(extra)
|
||||
return h
|
||||
|
||||
|
||||
def request(method, path, cfg, form=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data = None
|
||||
extra = {}
|
||||
if form is not None:
|
||||
data = urllib.parse.urlencode(form).encode("utf-8")
|
||||
extra["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, extra), method=method)
|
||||
with urllib.request.urlopen(req, timeout=120, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def multipart(fields, file_field, file_name, file_bytes):
|
||||
boundary = "----riposte" + uuid.uuid4().hex
|
||||
parts = []
|
||||
for k, v in (fields or {}).items():
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + k + '"\r\n\r\n' + str(v) + "\r\n").encode("utf-8"))
|
||||
parts.append(("--" + boundary + "\r\n"
|
||||
'Content-Disposition: form-data; name="' + file_field + '"; filename="' + file_name + '"\r\n'
|
||||
"Content-Type: application/octet-stream\r\n\r\n").encode("utf-8"))
|
||||
parts.append(file_bytes)
|
||||
parts.append(("\r\n--" + boundary + "--\r\n").encode("utf-8"))
|
||||
return b"".join(parts), "multipart/form-data; boundary=" + boundary
|
||||
|
||||
|
||||
def request_multipart(path, cfg, fields, file_field, file_name, file_bytes):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
data, content_type = multipart(fields, file_field, file_name, file_bytes)
|
||||
req = urllib.request.Request(url, data=data, headers=_headers(cfg, {"Content-Type": content_type}), method="POST")
|
||||
with urllib.request.urlopen(req, timeout=180, context=_ctx(cfg)) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("GET", "/apiv2/cuckoo/status/", cfg)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,129 @@
|
||||
id: carbon_black_cloud
|
||||
name: VMware Carbon Black Cloud
|
||||
version: 1.0.0
|
||||
description: "VMware Carbon Black Cloud (Platform API) — endpoint containment: search and read devices, quarantine/unquarantine an endpoint, trigger a background scan, update a device's policy, search alerts, and ban/unban a file hash (reputation override). API-token authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: device search/get, quarantine/unquarantine, background scan, update policy, alert search, ban hash, delete reputation override."
|
||||
category: endpoint
|
||||
|
||||
# Per-instance configuration. The API token is sent as
|
||||
# 'X-Auth-Token: <api_secret_key>/<api_id>'. org_key identifies the org.
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "Carbon Black Cloud URL (e.g. https://defense.conferdeploy.net)"
|
||||
api_id:
|
||||
type: string
|
||||
description: "API key ID"
|
||||
api_secret_key:
|
||||
type: string
|
||||
description: "API secret key"
|
||||
x-soar-sensitive: true
|
||||
org_key:
|
||||
type: string
|
||||
description: "Organization key"
|
||||
required:
|
||||
- base_url
|
||||
- api_id
|
||||
- api_secret_key
|
||||
- org_key
|
||||
|
||||
commands:
|
||||
- id: list_devices
|
||||
name: cbc-list-devices
|
||||
description: "Search devices (optionally by hostname or IP)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "Free-text query (hostname, user, IP)" }
|
||||
rows: { type: number, description: "Max devices (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: get_device
|
||||
name: cbc-get-device
|
||||
description: "Get a single device by ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: quarantine_device
|
||||
name: cbc-quarantine-device
|
||||
description: "Quarantine (network-isolate) a device."
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: unquarantine_device
|
||||
name: cbc-unquarantine-device
|
||||
description: "Remove a device from quarantine."
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: background_scan
|
||||
name: cbc-background-scan
|
||||
description: "Start or stop a background scan on a device."
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
toggle: { type: string, description: "ON or OFF (default ON)" }
|
||||
required: [device_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: update_device_policy
|
||||
name: cbc-update-device-policy
|
||||
description: "Assign a device to a different policy."
|
||||
inputs_schema:
|
||||
properties:
|
||||
device_id: { type: string, description: "Device ID" }
|
||||
policy_id: { type: string, description: "Target policy ID" }
|
||||
required: [device_id, policy_id]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: search_alerts
|
||||
name: cbc-search-alerts
|
||||
description: "Search alerts. Used for ingestion: results path = results."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "Free-text alert query" }
|
||||
rows: { type: number, description: "Max alerts (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
ingest:
|
||||
results_path: results
|
||||
dedup_key: id
|
||||
- id: ban_hash
|
||||
name: cbc-ban-hash
|
||||
description: "Ban a file by SHA-256 hash (reputation deny-list override)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
sha256: { type: string, description: "SHA-256 hash to ban" }
|
||||
filename: { type: string, description: "Optional associated file name" }
|
||||
description: { type: string, description: "Optional reason/description" }
|
||||
required: [sha256]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: delete_reputation_override
|
||||
name: cbc-delete-reputation-override
|
||||
description: "Delete a reputation override (unban) by its ID."
|
||||
inputs_schema:
|
||||
properties:
|
||||
override_id: { type: string, description: "Reputation override ID" }
|
||||
required: [override_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: cbc-test-connection
|
||||
description: "Verify connectivity and the API token (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
ingestion:
|
||||
command: search_alerts
|
||||
mapper: search_alerts
|
||||
default_incident_type: "Carbon Black Cloud Alert"
|
||||
@@ -0,0 +1,52 @@
|
||||
name: "VMware Carbon Black Cloud Alerts → OCSF"
|
||||
description: "Exhaustive map of a Carbon Black Cloud Alert Search v7 alert (POST /api/alerts/v7/orgs/{org_key}/alerts/_search, results_path = results) to OCSF. Field paths validated against the CBC Platform Alerts v7 API. The alerting process = actor (actor.*); the child/target process spawned as a result = process.*; parent lineage under actor.process.parent_process.*."
|
||||
field_mappings:
|
||||
title: "reason"
|
||||
severity: "severity >= 9 ? 5 : (severity >= 7 ? 4 : (severity >= 5 ? 3 : (severity >= 3 ? 2 : 1)))"
|
||||
description: "type"
|
||||
# results_path = results; source_path is JSONata over ONE alert object.
|
||||
# Paths absent from a given alert (e.g. no childproc.* on a non-process alert type) are
|
||||
# skipped at ingestion, so extra entries are safe.
|
||||
ocsf:
|
||||
# ── Finding / alert identity ───────────────────────────────────────
|
||||
- { source_path: "id", ocsf_field: "finding_info.uid" }
|
||||
- { source_path: "reason", ocsf_field: "finding_info.title" }
|
||||
- { source_path: "type", ocsf_field: "finding_info.desc" }
|
||||
- { source_path: "first_event_timestamp", ocsf_field: "finding_info.created_time" }
|
||||
- { source_path: "backend_update_timestamp", ocsf_field: "finding_info.modified_time" }
|
||||
- { source_path: "alert_url", ocsf_field: "finding_info.src_url" }
|
||||
- { source_path: "workflow.status", ocsf_field: "status" }
|
||||
- { source_path: "determination.value", ocsf_field: "status_detail" }
|
||||
# ── Endpoint / device ──────────────────────────────────────────────
|
||||
- { source_path: "device_id", ocsf_field: "device.uid" }
|
||||
- { source_path: "device_os_version", ocsf_field: "device.os.build" }
|
||||
- { source_path: "device_name", ocsf_field: "src_endpoint.hostname" }
|
||||
- { source_path: "device_os", ocsf_field: "src_endpoint.os.name" }
|
||||
- { source_path: "device_internal_ip", ocsf_field: "src_endpoint.ip" }
|
||||
- { source_path: "device_username", ocsf_field: "user.name" }
|
||||
- { source_path: "org_key", ocsf_field: "cloud.account.uid" }
|
||||
# ── Triggering process — the actor ─────────────────────────────────
|
||||
- { source_path: "process_name", ocsf_field: "actor.process.name" }
|
||||
- { source_path: "process_pid", ocsf_field: "actor.process.pid" }
|
||||
- { source_path: "process_cmdline", ocsf_field: "actor.process.cmd_line" }
|
||||
- { source_path: "process_sha256", ocsf_field: "actor.process.file.hashes.sha256" }
|
||||
- { source_path: "process_md5", ocsf_field: "actor.process.file.hashes.md5" }
|
||||
- { source_path: "process_guid", ocsf_field: "metadata.original_event_uid" }
|
||||
- { source_path: "process_username", ocsf_field: "actor.user.name" }
|
||||
# ── Parent process ──────────────────────────────────────────────────
|
||||
- { source_path: "parent_name", ocsf_field: "actor.process.parent_process.name" }
|
||||
- { source_path: "parent_pid", ocsf_field: "actor.process.parent_process.pid" }
|
||||
- { source_path: "parent_cmdline", ocsf_field: "actor.process.parent_process.cmd_line" }
|
||||
- { source_path: "parent_sha256", ocsf_field: "actor.process.parent_process.file.hashes.sha256" }
|
||||
# ── Child / target process ──────────────────────────────────────────
|
||||
- { source_path: "childproc_name", ocsf_field: "process.name" }
|
||||
- { source_path: "childproc_pid", ocsf_field: "process.pid" }
|
||||
- { source_path: "childproc_cmdline", ocsf_field: "process.cmd_line" }
|
||||
- { source_path: "childproc_guid", ocsf_field: "process.uid" }
|
||||
# ── Detection rule / watchlist / report ─────────────────────────────
|
||||
- { source_path: "watchlists[0].name", ocsf_field: "rule.name" }
|
||||
- { source_path: "watchlists[0].id", ocsf_field: "rule.uid" }
|
||||
- { source_path: "report_name", ocsf_field: "rule.desc" }
|
||||
# ── IOC / observable ──────────────────────────────────────────────
|
||||
- { source_path: "ioc_hit", ocsf_field: "observables.value" }
|
||||
- { source_path: "ioc_field", ocsf_field: "observables.type" }
|
||||
@@ -0,0 +1,60 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
toggle = inputs.get("toggle")
|
||||
body = {
|
||||
"action_type": "BACKGROUND_SCAN",
|
||||
"device_id": [int(device_id)],
|
||||
"options": {"toggle": (toggle or "ON")},
|
||||
}
|
||||
request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/device_actions", cfg, body=body)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,62 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
sha256 = inputs.get("sha256")
|
||||
if not sha256:
|
||||
raise Exception("sha256 is required")
|
||||
filename = inputs.get("filename")
|
||||
description = inputs.get("description")
|
||||
body = {
|
||||
"override_list": "BLACK_LIST",
|
||||
"override_type": "SHA256_HASH",
|
||||
"sha256_hash": sha256,
|
||||
"filename": (filename or "unknown"),
|
||||
"description": (description or "Blocked via Riposte"),
|
||||
}
|
||||
return request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/reputations/overrides", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,57 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
override_id = inputs.get("override_id")
|
||||
if not override_id:
|
||||
raise Exception("override_id is required")
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
resp = request("DELETE", "/appservices/v6/orgs/" + _org(cfg) + "/reputations/overrides/" + q(override_id), cfg)
|
||||
if not resp:
|
||||
return {"ok": True, "deleted": override_id}
|
||||
return resp
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,54 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
q = lambda v: urllib.parse.quote(str(v), safe="")
|
||||
return request("GET", "/appservices/v6/orgs/" + _org(cfg) + "/devices/" + q(device_id), cfg)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,55 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
rows = inputs.get("rows")
|
||||
body = {"rows": int(rows or 50)}
|
||||
if query:
|
||||
body["query"] = query
|
||||
return request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/devices/_search", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,61 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
body = {
|
||||
"action_type": "QUARANTINE",
|
||||
"device_id": [int(device_id)],
|
||||
"options": {"toggle": "ON"},
|
||||
}
|
||||
resp = request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/device_actions", cfg, body=body)
|
||||
if not resp:
|
||||
return {"ok": True, "device_id": device_id, "action": "quarantine"}
|
||||
return resp
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,55 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
query = inputs.get("query")
|
||||
rows = inputs.get("rows")
|
||||
body = {"rows": int(rows or 50)}
|
||||
if query:
|
||||
body["query"] = query
|
||||
return request("POST", "/api/alerts/v7/orgs/" + _org(cfg) + "/alerts/_search", cfg, body=body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,51 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/devices/_search", cfg, body={"rows": 1})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,61 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
body = {
|
||||
"action_type": "QUARANTINE",
|
||||
"device_id": [int(device_id)],
|
||||
"options": {"toggle": "OFF"},
|
||||
}
|
||||
resp = request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/device_actions", cfg, body=body)
|
||||
if not resp:
|
||||
return {"ok": True, "device_id": device_id, "action": "unquarantine"}
|
||||
return resp
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,62 @@
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _org(cfg):
|
||||
return str(cfg.get("org_key", ""))
|
||||
|
||||
|
||||
def request(method, path, cfg, body=None, params=None):
|
||||
url = str(cfg.get("base_url", "")).rstrip("/") + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = {
|
||||
"X-Auth-Token": str(cfg.get("api_secret_key", "")) + "/" + str(cfg.get("api_id", "")),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
if data is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
device_id = inputs.get("device_id")
|
||||
if not device_id:
|
||||
raise Exception("device_id is required")
|
||||
policy_id = inputs.get("policy_id")
|
||||
if not policy_id:
|
||||
raise Exception("policy_id is required")
|
||||
body = {
|
||||
"action_type": "UPDATE_POLICY",
|
||||
"device_id": [int(device_id)],
|
||||
"options": {"policy_id": int(policy_id)},
|
||||
}
|
||||
request("POST", "/appservices/v6/orgs/" + _org(cfg) + "/device_actions", cfg, body=body)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,101 @@
|
||||
id: checkpoint
|
||||
name: Check Point
|
||||
version: 1.0.0
|
||||
description: "Check Point Management (Web API) — firewall containment: list and add host objects, view the access rulebase, add a drop rule (block), publish changes, and install policy. Session (login) authentication; stdlib-only, no extra Python dependencies."
|
||||
changelog: "1.0.0 — Initial release: show/add hosts, show access rulebase, add access rule (drop), publish, install policy."
|
||||
category: network
|
||||
|
||||
# Per-instance configuration. Each command logs in to the Management API
|
||||
# (returns a session id used as the X-chkp-sid header), performs the action,
|
||||
# then logs out. Publishing/installing is explicit (separate commands).
|
||||
config_schema:
|
||||
properties:
|
||||
server_url:
|
||||
type: string
|
||||
description: "Management server URL (e.g. https://mgmt.example.com)"
|
||||
username:
|
||||
type: string
|
||||
description: "Management API username"
|
||||
password:
|
||||
type: string
|
||||
description: "Management API password"
|
||||
x-soar-sensitive: true
|
||||
domain:
|
||||
type: string
|
||||
description: "Domain (for Multi-Domain Management; leave empty otherwise)"
|
||||
insecure:
|
||||
type: boolean
|
||||
description: "Trust any TLS certificate (not secure)"
|
||||
default: false
|
||||
required:
|
||||
- server_url
|
||||
- username
|
||||
- password
|
||||
|
||||
commands:
|
||||
- id: show_hosts
|
||||
name: checkpoint-show-hosts
|
||||
description: "List host objects."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
limit: { type: number, description: "Max hosts (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: add_host
|
||||
name: checkpoint-add-host
|
||||
description: "Create a host object."
|
||||
inputs_schema:
|
||||
properties:
|
||||
name: { type: string, description: "Host object name" }
|
||||
ip_address: { type: string, description: "Host IP address" }
|
||||
required: [name, ip_address]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: show_access_rulebase
|
||||
name: checkpoint-show-access-rulebase
|
||||
description: "Show the access rulebase of a policy layer."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
layer: { type: string, description: "Access layer name (e.g. Network)" }
|
||||
limit: { type: number, description: "Max rules (default 50)" }
|
||||
required: [layer]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: add_access_rule
|
||||
name: checkpoint-add-access-rule
|
||||
description: "Add an access rule to a layer (e.g. a Drop rule to block a source)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
layer: { type: string, description: "Access layer name" }
|
||||
name: { type: string, description: "Rule name" }
|
||||
position: { type: string, description: "Position (e.g. top, bottom, or a number; default top)" }
|
||||
source: { type: string, description: "Source object name (e.g. a host)" }
|
||||
destination: { type: string, description: "Destination object name (default Any)" }
|
||||
action: { type: string, description: "Accept, Drop, or Reject (default Drop)" }
|
||||
required: [layer, name]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: publish
|
||||
name: checkpoint-publish
|
||||
description: "Publish the current session's changes."
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
- id: install_policy
|
||||
name: checkpoint-install-policy
|
||||
description: "Install a policy package on gateway targets."
|
||||
inputs_schema:
|
||||
properties:
|
||||
policy_package: { type: string, description: "Policy package name" }
|
||||
targets: { type: string, description: "Comma-separated gateway target names" }
|
||||
required: [policy_package, targets]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: checkpoint-test-connection
|
||||
description: "Verify connectivity and credentials by logging in (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,104 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
layer = inputs.get("layer")
|
||||
if not layer:
|
||||
raise Exception("layer is required")
|
||||
name = inputs.get("name")
|
||||
if not name:
|
||||
raise Exception("name is required")
|
||||
position = inputs.get("position")
|
||||
action = inputs.get("action")
|
||||
source = inputs.get("source")
|
||||
destination = inputs.get("destination")
|
||||
|
||||
body = {
|
||||
"layer": layer,
|
||||
"name": name,
|
||||
"position": (position or "top"),
|
||||
"action": (action or "Drop"),
|
||||
}
|
||||
if source:
|
||||
body["source"] = source
|
||||
if destination:
|
||||
body["destination"] = destination
|
||||
|
||||
return client.call("add-access-rule", body)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,88 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
name = inputs.get("name")
|
||||
if not name:
|
||||
raise Exception("name is required")
|
||||
ip_address = inputs.get("ip_address")
|
||||
if not ip_address:
|
||||
raise Exception("ip_address is required")
|
||||
return client.call("add-host", {"name": name, "ip-address": ip_address})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,92 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
policy_package = inputs.get("policy_package")
|
||||
if not policy_package:
|
||||
raise Exception("policy_package is required")
|
||||
targets = inputs.get("targets")
|
||||
if not targets:
|
||||
raise Exception("targets is required")
|
||||
targets_list = [s.strip() for s in str(targets).split(",") if s.strip()]
|
||||
if not targets_list:
|
||||
raise Exception("targets is required")
|
||||
|
||||
return client.call("install-policy", {"policy-package": policy_package, "targets": targets_list})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,82 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
return client.call("publish", {})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,86 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
layer = inputs.get("layer")
|
||||
if not layer:
|
||||
raise Exception("layer is required")
|
||||
limit = inputs.get("limit")
|
||||
return client.call("show-access-rulebase", {"name": layer, "limit": int(limit or 50)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,83 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
limit = inputs.get("limit")
|
||||
return client.call("show-hosts", {"limit": int(limit or 50)})
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,83 @@
|
||||
import json, os, sys, ssl, urllib.request, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _ctx(cfg):
|
||||
if cfg.get("insecure"):
|
||||
c = ssl.create_default_context()
|
||||
c.check_hostname = False
|
||||
c.verify_mode = ssl.CERT_NONE
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
self.base = str(cfg.get("server_url", "")).rstrip("/") + "/web_api"
|
||||
self.ctx = _ctx(cfg)
|
||||
self.sid = None
|
||||
|
||||
def _post(self, command, body, sid=None):
|
||||
url = self.base + "/" + command
|
||||
data = json.dumps(body if body is not None else {}).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
if sid:
|
||||
headers["X-chkp-sid"] = sid
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90, context=self.ctx) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def login(self):
|
||||
body = {"user": self.cfg.get("username", ""), "password": self.cfg.get("password", "")}
|
||||
if self.cfg.get("domain"):
|
||||
body["domain"] = self.cfg["domain"]
|
||||
resp = self._post("login", body)
|
||||
self.sid = resp.get("sid")
|
||||
if not self.sid:
|
||||
raise Exception("Login failed: " + json.dumps(resp))
|
||||
return resp
|
||||
|
||||
def call(self, command, body=None):
|
||||
return self._post(command, body, sid=self.sid)
|
||||
|
||||
def logout(self):
|
||||
try:
|
||||
self.call("logout", {})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
cfg = _cfg()
|
||||
inputs = _inputs()
|
||||
client = Client(cfg)
|
||||
client.login()
|
||||
try:
|
||||
result = fn(client, inputs)
|
||||
finally:
|
||||
client.logout()
|
||||
print(json.dumps(result))
|
||||
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)
|
||||
|
||||
|
||||
def main(client, inputs):
|
||||
client.call("show-hosts", {"limit": 1})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,76 @@
|
||||
id: chronicle
|
||||
name: Google Chronicle
|
||||
version: 1.0.0
|
||||
description: "Google Chronicle (Backstory API) — SIEM threat context: list IOCs seen in the enterprise, list alerts, list assets, and get IOC details for an artifact. Authenticates with a Google service account (RS256 JWT bearer flow). Runs on a remote engine. Requires the Python 'PyJWT' and 'cryptography' libraries on the engine host (pip install pyjwt cryptography)."
|
||||
changelog: "1.0.0 — Initial release: list IOCs, list alerts, list assets, get IOC details."
|
||||
category: siem
|
||||
|
||||
# Per-instance configuration. The scripts build a signed RS256 assertion from the
|
||||
# service account's private_key/client_email and exchange it for an access token
|
||||
# (scope chronicle-backstory). base_url is the regional Chronicle API host.
|
||||
config_schema:
|
||||
properties:
|
||||
service_account_json:
|
||||
type: string
|
||||
description: "Full Chronicle service account key JSON (must contain client_email and private_key)"
|
||||
x-soar-sensitive: true
|
||||
base_url:
|
||||
type: string
|
||||
description: "Chronicle API base URL (region-specific)"
|
||||
default: "https://backstory.googleapis.com"
|
||||
required:
|
||||
- service_account_json
|
||||
|
||||
commands:
|
||||
- id: list_iocs
|
||||
name: chronicle-list-iocs
|
||||
description: "List IOCs (indicators of compromise) observed in the enterprise since a start time."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
start_time: { type: string, description: "RFC3339 start time (e.g. 2024-01-01T00:00:00Z)" }
|
||||
page_size: { type: number, description: "Max IOCs (default 100)" }
|
||||
required: [start_time]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_alerts
|
||||
name: chronicle-list-alerts
|
||||
description: "List alerts in a time window."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
start_time: { type: string, description: "RFC3339 start time" }
|
||||
end_time: { type: string, description: "RFC3339 end time" }
|
||||
page_size: { type: number, description: "Max alerts (default 100)" }
|
||||
required: [start_time, end_time]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: list_assets
|
||||
name: chronicle-list-assets
|
||||
description: "List assets that accessed an artifact (domain, IP, or hash) in a time window."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
artifact_type: { type: string, description: "domain_name, destination_ip_address, or hash_sha256" }
|
||||
artifact_value: { type: string, description: "The artifact value" }
|
||||
start_time: { type: string, description: "RFC3339 start time" }
|
||||
end_time: { type: string, description: "RFC3339 end time" }
|
||||
required: [artifact_type, artifact_value, start_time, end_time]
|
||||
outputs_schema: { properties: {} }
|
||||
- id: ioc_details
|
||||
name: chronicle-ioc-details
|
||||
description: "Get IOC details for an artifact (domain, IP, or hash)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
artifact_type: { type: string, description: "domain_name, destination_ip_address, or hash_sha256" }
|
||||
artifact_value: { type: string, description: "The artifact value" }
|
||||
required: [artifact_type, artifact_value]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: test_connection
|
||||
name: chronicle-test-connection
|
||||
description: "Verify the service-account token exchange (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
@@ -0,0 +1,82 @@
|
||||
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
import jwt
|
||||
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
SCOPE = "https://www.googleapis.com/auth/chronicle-backstory"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("base_url") or "https://backstory.googleapis.com")).rstrip("/")
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
raw = cfg.get("service_account_json", "")
|
||||
sa = json.loads(raw) if isinstance(raw, str) else raw
|
||||
if not sa.get("client_email") or not sa.get("private_key"):
|
||||
raise Exception("service_account_json must contain client_email and private_key")
|
||||
now = int(time.time())
|
||||
aud = sa.get("token_uri") or TOKEN_URL
|
||||
payload = {"iss": sa["client_email"], "scope": SCOPE, "aud": aud, "iat": now, "exp": now + 3600}
|
||||
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
|
||||
data = urllib.parse.urlencode({
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
"assertion": assertion,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(aud, data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def api(method, path, cfg, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(cfg)}
|
||||
req = urllib.request.Request(url, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def _artifact_params(inputs):
|
||||
at = inputs.get("artifact_type")
|
||||
av = inputs.get("artifact_value")
|
||||
if not at:
|
||||
raise Exception("artifact_type is required")
|
||||
if not av:
|
||||
raise Exception("artifact_value is required")
|
||||
return {"artifact." + at: av}
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
params = _artifact_params(inputs)
|
||||
return api("GET", "/v1/artifact/listiocdetails", cfg, params)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,83 @@
|
||||
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
import jwt
|
||||
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
SCOPE = "https://www.googleapis.com/auth/chronicle-backstory"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("base_url") or "https://backstory.googleapis.com")).rstrip("/")
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
raw = cfg.get("service_account_json", "")
|
||||
sa = json.loads(raw) if isinstance(raw, str) else raw
|
||||
if not sa.get("client_email") or not sa.get("private_key"):
|
||||
raise Exception("service_account_json must contain client_email and private_key")
|
||||
now = int(time.time())
|
||||
aud = sa.get("token_uri") or TOKEN_URL
|
||||
payload = {"iss": sa["client_email"], "scope": SCOPE, "aud": aud, "iat": now, "exp": now + 3600}
|
||||
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
|
||||
data = urllib.parse.urlencode({
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
"assertion": assertion,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(aud, data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def api(method, path, cfg, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(cfg)}
|
||||
req = urllib.request.Request(url, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
start_time = inputs.get("start_time")
|
||||
if not start_time:
|
||||
raise Exception("start_time is required")
|
||||
end_time = inputs.get("end_time")
|
||||
if not end_time:
|
||||
raise Exception("end_time is required")
|
||||
page_size = inputs.get("page_size")
|
||||
params = {
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
"page_size": int(page_size or 100),
|
||||
}
|
||||
return api("GET", "/v1/alert/listalerts", cfg, params)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,90 @@
|
||||
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
import jwt
|
||||
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
SCOPE = "https://www.googleapis.com/auth/chronicle-backstory"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("base_url") or "https://backstory.googleapis.com")).rstrip("/")
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
raw = cfg.get("service_account_json", "")
|
||||
sa = json.loads(raw) if isinstance(raw, str) else raw
|
||||
if not sa.get("client_email") or not sa.get("private_key"):
|
||||
raise Exception("service_account_json must contain client_email and private_key")
|
||||
now = int(time.time())
|
||||
aud = sa.get("token_uri") or TOKEN_URL
|
||||
payload = {"iss": sa["client_email"], "scope": SCOPE, "aud": aud, "iat": now, "exp": now + 3600}
|
||||
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
|
||||
data = urllib.parse.urlencode({
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
"assertion": assertion,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(aud, data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def api(method, path, cfg, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(cfg)}
|
||||
req = urllib.request.Request(url, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def _artifact_params(inputs):
|
||||
at = inputs.get("artifact_type")
|
||||
av = inputs.get("artifact_value")
|
||||
if not at:
|
||||
raise Exception("artifact_type is required")
|
||||
if not av:
|
||||
raise Exception("artifact_value is required")
|
||||
return {"artifact." + at: av}
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
params = _artifact_params(inputs)
|
||||
start_time = inputs.get("start_time")
|
||||
if not start_time:
|
||||
raise Exception("start_time is required")
|
||||
end_time = inputs.get("end_time")
|
||||
if not end_time:
|
||||
raise Exception("end_time is required")
|
||||
params["start_time"] = start_time
|
||||
params["end_time"] = end_time
|
||||
return api("GET", "/v1/artifact/listassets", cfg, params)
|
||||
|
||||
|
||||
_run(main)
|
||||
@@ -0,0 +1,79 @@
|
||||
import json, os, sys, time, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
import jwt
|
||||
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
SCOPE = "https://www.googleapis.com/auth/chronicle-backstory"
|
||||
|
||||
|
||||
def _cfg():
|
||||
return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _base(cfg):
|
||||
return (str(cfg.get("base_url") or "https://backstory.googleapis.com")).rstrip("/")
|
||||
|
||||
|
||||
def _token(cfg):
|
||||
raw = cfg.get("service_account_json", "")
|
||||
sa = json.loads(raw) if isinstance(raw, str) else raw
|
||||
if not sa.get("client_email") or not sa.get("private_key"):
|
||||
raise Exception("service_account_json must contain client_email and private_key")
|
||||
now = int(time.time())
|
||||
aud = sa.get("token_uri") or TOKEN_URL
|
||||
payload = {"iss": sa["client_email"], "scope": SCOPE, "aud": aud, "iat": now, "exp": now + 3600}
|
||||
assertion = jwt.encode(payload, sa["private_key"], algorithm="RS256")
|
||||
data = urllib.parse.urlencode({
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
"assertion": assertion,
|
||||
}).encode("utf-8")
|
||||
req = urllib.request.Request(aud, data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
tok = json.loads(r.read())
|
||||
if not tok.get("access_token"):
|
||||
raise Exception("Token request failed: " + json.dumps(tok))
|
||||
return tok["access_token"]
|
||||
|
||||
|
||||
def api(method, path, cfg, params=None):
|
||||
url = _base(cfg) + path
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean)
|
||||
headers = {"Accept": "application/json", "Authorization": "Bearer " + _token(cfg)}
|
||||
req = urllib.request.Request(url, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _run(fn):
|
||||
try:
|
||||
print(json.dumps(fn(_cfg(), _inputs())))
|
||||
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)
|
||||
|
||||
|
||||
def main(cfg, inputs):
|
||||
start_time = inputs.get("start_time")
|
||||
if not start_time:
|
||||
raise Exception("start_time is required")
|
||||
page_size = inputs.get("page_size")
|
||||
params = {
|
||||
"start_time": start_time,
|
||||
"page_size": int(page_size or 100),
|
||||
}
|
||||
return api("GET", "/v1/ioc/listiocs", cfg, params)
|
||||
|
||||
|
||||
_run(main)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user