From f34363b450c80e3c4b161646d2051460a683c5ee Mon Sep 17 00:00:00 2001 From: Guillaume BOURGEOIS Date: Sat, 11 Jul 2026 23:59:39 +0200 Subject: [PATCH] feat(rapid7-insightvm): new Rapid7 InsightVM vulnerability integration InsightVM/Nexpose Console API v3, 9 commands: list/get assets, asset vulnerabilities, list/get vulnerabilities, list sites, start site scan, get scan. HTTP Basic auth, stdlib-only. Co-Authored-By: Claude Opus 4.8 (1M context) --- integrations/rapid7-insightvm/manifest.yaml | 115 ++++++++++++++++++ .../scripts/asset_vulnerabilities.py | 62 ++++++++++ .../rapid7-insightvm/scripts/get_asset.py | 62 ++++++++++ .../rapid7-insightvm/scripts/get_scan.py | 62 ++++++++++ .../scripts/get_vulnerability.py | 62 ++++++++++ .../rapid7-insightvm/scripts/list_assets.py | 61 ++++++++++ .../rapid7-insightvm/scripts/list_sites.py | 59 +++++++++ .../scripts/list_vulnerabilities.py | 61 ++++++++++ .../scripts/start_site_scan.py | 74 +++++++++++ .../scripts/test_connection.py | 59 +++++++++ 10 files changed, 677 insertions(+) create mode 100644 integrations/rapid7-insightvm/manifest.yaml create mode 100644 integrations/rapid7-insightvm/scripts/asset_vulnerabilities.py create mode 100644 integrations/rapid7-insightvm/scripts/get_asset.py create mode 100644 integrations/rapid7-insightvm/scripts/get_scan.py create mode 100644 integrations/rapid7-insightvm/scripts/get_vulnerability.py create mode 100644 integrations/rapid7-insightvm/scripts/list_assets.py create mode 100644 integrations/rapid7-insightvm/scripts/list_sites.py create mode 100644 integrations/rapid7-insightvm/scripts/list_vulnerabilities.py create mode 100644 integrations/rapid7-insightvm/scripts/start_site_scan.py create mode 100644 integrations/rapid7-insightvm/scripts/test_connection.py diff --git a/integrations/rapid7-insightvm/manifest.yaml b/integrations/rapid7-insightvm/manifest.yaml new file mode 100644 index 0000000..5e87820 --- /dev/null +++ b/integrations/rapid7-insightvm/manifest.yaml @@ -0,0 +1,115 @@ +id: rapid7_insightvm +name: Rapid7 InsightVM +version: 1.0.0 +description: "Rapid7 InsightVM / Nexpose (Console API v3) — asset and vulnerability context: list and read assets and their vulnerabilities, list and read vulnerability definitions, list sites, start a site scan, and read scan status. HTTP Basic authentication; stdlib-only, no extra Python dependencies." +changelog: "1.0.0 — Initial release: list/get assets, asset vulnerabilities, list/get vulnerabilities, list sites, start site scan, get scan." +category: vulnerability + +# Per-instance configuration. HTTP Basic auth against the InsightVM/Nexpose +# console API v3 (typically port 3780). Set insecure for the console's default +# self-signed certificate. +config_schema: + properties: + base_url: + type: string + description: "Console base URL (e.g. https://console.example.com:3780)" + username: + type: string + description: "Console username" + password: + type: string + description: "Console password" + x-soar-sensitive: true + insecure: + type: boolean + description: "Trust any TLS certificate (not secure)" + default: false + required: + - base_url + - username + - password + +commands: + - id: list_assets + name: insightvm-list-assets + description: "List assets." + risk: read + inputs_schema: + properties: + page: { type: number, description: "Page number (default 0)" } + size: { type: number, description: "Page size (default 100)" } + required: [] + outputs_schema: { properties: {} } + - id: get_asset + name: insightvm-get-asset + description: "Get a single asset by ID." + risk: read + inputs_schema: + properties: + asset_id: { type: string, description: "Asset ID" } + required: [asset_id] + outputs_schema: { properties: {} } + - id: asset_vulnerabilities + name: insightvm-asset-vulnerabilities + description: "List the vulnerabilities found on an asset." + risk: read + inputs_schema: + properties: + asset_id: { type: string, description: "Asset ID" } + required: [asset_id] + outputs_schema: { properties: {} } + - id: list_vulnerabilities + name: insightvm-list-vulnerabilities + description: "List vulnerability definitions." + risk: read + inputs_schema: + properties: + page: { type: number, description: "Page number (default 0)" } + size: { type: number, description: "Page size (default 100)" } + required: [] + outputs_schema: { properties: {} } + - id: get_vulnerability + name: insightvm-get-vulnerability + description: "Get a vulnerability definition by ID (e.g. a CVE-linked vuln)." + risk: read + inputs_schema: + properties: + vulnerability_id: { type: string, description: "Vulnerability ID" } + required: [vulnerability_id] + outputs_schema: { properties: {} } + - id: list_sites + name: insightvm-list-sites + description: "List sites." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + - id: start_site_scan + name: insightvm-start-site-scan + description: "Start a scan of a site." + inputs_schema: + properties: + site_id: { type: string, description: "Site ID" } + scan_name: { type: string, description: "Optional scan name" } + hosts: { type: string, description: "Optional comma-separated hosts to scan (subset of the site)" } + required: [site_id] + outputs_schema: { properties: {} } + - id: get_scan + name: insightvm-get-scan + description: "Get the status and details of a scan." + risk: read + inputs_schema: + properties: + scan_id: { type: string, description: "Scan ID" } + required: [scan_id] + outputs_schema: { properties: {} } + + - id: test_connection + name: insightvm-test-connection + description: "Verify connectivity and credentials (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/rapid7-insightvm/scripts/asset_vulnerabilities.py b/integrations/rapid7-insightvm/scripts/asset_vulnerabilities.py new file mode 100644 index 0000000..bfd7daa --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/asset_vulnerabilities.py @@ -0,0 +1,62 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + asset_id = inputs.get("asset_id") + if not asset_id: + raise Exception("asset_id is required") + q = lambda v: urllib.parse.quote(str(v), safe="") + return request("GET", "/assets/" + q(asset_id) + "/vulnerabilities", cfg) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/get_asset.py b/integrations/rapid7-insightvm/scripts/get_asset.py new file mode 100644 index 0000000..c849c69 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/get_asset.py @@ -0,0 +1,62 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + asset_id = inputs.get("asset_id") + if not asset_id: + raise Exception("asset_id is required") + q = lambda v: urllib.parse.quote(str(v), safe="") + return request("GET", "/assets/" + q(asset_id), cfg) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/get_scan.py b/integrations/rapid7-insightvm/scripts/get_scan.py new file mode 100644 index 0000000..f6619fc --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/get_scan.py @@ -0,0 +1,62 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + scan_id = inputs.get("scan_id") + if not scan_id: + raise Exception("scan_id is required") + q = lambda v: urllib.parse.quote(str(v), safe="") + return request("GET", "/scans/" + q(scan_id), cfg) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/get_vulnerability.py b/integrations/rapid7-insightvm/scripts/get_vulnerability.py new file mode 100644 index 0000000..ac371b8 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/get_vulnerability.py @@ -0,0 +1,62 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + vulnerability_id = inputs.get("vulnerability_id") + if not vulnerability_id: + raise Exception("vulnerability_id is required") + q = lambda v: urllib.parse.quote(str(v), safe="") + return request("GET", "/vulnerabilities/" + q(vulnerability_id), cfg) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/list_assets.py b/integrations/rapid7-insightvm/scripts/list_assets.py new file mode 100644 index 0000000..f5f98e9 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/list_assets.py @@ -0,0 +1,61 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + page = inputs.get("page") + size = inputs.get("size") + params = {"page": int(page or 0), "size": int(size or 100)} + return request("GET", "/assets", cfg, params=params) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/list_sites.py b/integrations/rapid7-insightvm/scripts/list_sites.py new file mode 100644 index 0000000..e892272 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/list_sites.py @@ -0,0 +1,59 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + params = {"size": 100} + return request("GET", "/sites", cfg, params=params) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/list_vulnerabilities.py b/integrations/rapid7-insightvm/scripts/list_vulnerabilities.py new file mode 100644 index 0000000..3c6dd16 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/list_vulnerabilities.py @@ -0,0 +1,61 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + page = inputs.get("page") + size = inputs.get("size") + params = {"page": int(page or 0), "size": int(size or 100)} + return request("GET", "/vulnerabilities", cfg, params=params) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/start_site_scan.py b/integrations/rapid7-insightvm/scripts/start_site_scan.py new file mode 100644 index 0000000..f399fc0 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/start_site_scan.py @@ -0,0 +1,74 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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): + site_id = inputs.get("site_id") + if not site_id: + raise Exception("site_id is required") + q = lambda v: urllib.parse.quote(str(v), safe="") + + scan_name = inputs.get("scan_name") + hosts = inputs.get("hosts") + + body = {} + if scan_name not in (None, ""): + body["name"] = scan_name + if hosts not in (None, ""): + hosts_list = [s.strip() for s in str(hosts).split(",") if s.strip()] + if hosts_list: + body["hosts"] = hosts_list + + return request("POST", "/sites/" + q(site_id) + "/scans", cfg, body=body) + + +_run(main) diff --git a/integrations/rapid7-insightvm/scripts/test_connection.py b/integrations/rapid7-insightvm/scripts/test_connection.py new file mode 100644 index 0000000..17e2765 --- /dev/null +++ b/integrations/rapid7-insightvm/scripts/test_connection.py @@ -0,0 +1,59 @@ +import json, os, sys, base64, ssl, 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 _auth(cfg): + raw = str(cfg.get("username", "")) + ":" + str(cfg.get("password", "")) + return "Basic " + base64.b64encode(raw.encode("utf-8")).decode("utf-8") + + +def request(method, path, cfg, body=None, params=None): + base = str(cfg.get("base_url", "")).rstrip("/") + "/api/3" + 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": _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=90, 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", "/administration/info", cfg) + return {"ok": True} + + +_run(main)