diff --git a/integrations/automox/manifest.yaml b/integrations/automox/manifest.yaml new file mode 100644 index 0000000..175010d --- /dev/null +++ b/integrations/automox/manifest.yaml @@ -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 '. +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: {} } diff --git a/integrations/automox/scripts/get_device.py b/integrations/automox/scripts/get_device.py new file mode 100644 index 0000000..74b8345 --- /dev/null +++ b/integrations/automox/scripts/get_device.py @@ -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) diff --git a/integrations/automox/scripts/list_devices.py b/integrations/automox/scripts/list_devices.py new file mode 100644 index 0000000..9a3ed93 --- /dev/null +++ b/integrations/automox/scripts/list_devices.py @@ -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) diff --git a/integrations/automox/scripts/list_policies.py b/integrations/automox/scripts/list_policies.py new file mode 100644 index 0000000..e6181b7 --- /dev/null +++ b/integrations/automox/scripts/list_policies.py @@ -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) diff --git a/integrations/automox/scripts/run_command.py b/integrations/automox/scripts/run_command.py new file mode 100644 index 0000000..b76774b --- /dev/null +++ b/integrations/automox/scripts/run_command.py @@ -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) diff --git a/integrations/automox/scripts/test_connection.py b/integrations/automox/scripts/test_connection.py new file mode 100644 index 0000000..a82722d --- /dev/null +++ b/integrations/automox/scripts/test_connection.py @@ -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)