diff --git a/integrations/ovhcloud/manifest.yaml b/integrations/ovhcloud/manifest.yaml new file mode 100644 index 0000000..e84ce8b --- /dev/null +++ b/integrations/ovhcloud/manifest.yaml @@ -0,0 +1,74 @@ +id: ovhcloud +name: OVHcloud +version: 1.0.0 +description: "OVHcloud (API v1) — cloud account and infrastructure visibility: read the account, list public cloud projects, and list/read dedicated servers. Signed application-key authentication; stdlib-only, no extra Python dependencies. (French vendor.)" +changelog: "1.0.0 — Initial release: get account, list cloud projects, list/get dedicated servers." +category: cloud + +# Per-instance configuration. Requests are signed (SHA-1) with the application +# key/secret + consumer key. endpoint selects the region (eu/ca/us). +config_schema: + properties: + endpoint: + type: string + description: "OVH API endpoint" + default: "https://eu.api.ovh.com/1.0" + application_key: + type: string + description: "Application key" + application_secret: + type: string + description: "Application secret" + x-soar-sensitive: true + consumer_key: + type: string + description: "Consumer key" + x-soar-sensitive: true + required: + - application_key + - application_secret + - consumer_key + +commands: + - id: get_account + name: ovh-get-account + description: "Get the current account (nichandle)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + - id: list_cloud_projects + name: ovh-list-cloud-projects + description: "List Public Cloud project IDs." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + - id: list_dedicated_servers + name: ovh-list-dedicated-servers + description: "List dedicated server names." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + - id: get_dedicated_server + name: ovh-get-dedicated-server + description: "Get a dedicated server's details." + risk: read + inputs_schema: + properties: + server_name: { type: string, description: "Dedicated server name" } + required: [server_name] + outputs_schema: { properties: {} } + + - id: test_connection + name: ovh-test-connection + description: "Verify the signed credentials via /me (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/ovhcloud/scripts/get_account.py b/integrations/ovhcloud/scripts/get_account.py new file mode 100644 index 0000000..d17793b --- /dev/null +++ b/integrations/ovhcloud/scripts/get_account.py @@ -0,0 +1,63 @@ +import json, os, sys, hashlib, 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 _endpoint(cfg): + return (str(cfg.get("endpoint") or "https://eu.api.ovh.com/1.0")).rstrip("/") + + +def _server_time(cfg): + req = urllib.request.Request(_endpoint(cfg) + "/auth/time", method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return r.read().decode("utf-8", "replace").strip() + + +def request(method, path, cfg, body=None): + url = _endpoint(cfg) + path + app_key = str(cfg.get("application_key", "")) + app_secret = str(cfg.get("application_secret", "")) + consumer = str(cfg.get("consumer_key", "")) + ts = _server_time(cfg) + body_str = json.dumps(body) if body is not None else "" + to_sign = app_secret + "+" + consumer + "+" + method.upper() + "+" + url + "+" + body_str + "+" + ts + signature = "$1$" + hashlib.sha1(to_sign.encode("utf-8")).hexdigest() + headers = { + "X-Ovh-Application": app_key, + "X-Ovh-Consumer": consumer, + "X-Ovh-Timestamp": ts, + "X-Ovh-Signature": signature, + "Accept": "application/json", + } + data = None + if body is not None: + data = body_str.encode("utf-8") + 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): + return request("GET", "/me", cfg) + + +_run(main) diff --git a/integrations/ovhcloud/scripts/get_dedicated_server.py b/integrations/ovhcloud/scripts/get_dedicated_server.py new file mode 100644 index 0000000..42f2af0 --- /dev/null +++ b/integrations/ovhcloud/scripts/get_dedicated_server.py @@ -0,0 +1,69 @@ +import json, os, sys, hashlib, 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 _endpoint(cfg): + return (str(cfg.get("endpoint") or "https://eu.api.ovh.com/1.0")).rstrip("/") + + +def _server_time(cfg): + req = urllib.request.Request(_endpoint(cfg) + "/auth/time", method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return r.read().decode("utf-8", "replace").strip() + + +def request(method, path, cfg, body=None): + url = _endpoint(cfg) + path + app_key = str(cfg.get("application_key", "")) + app_secret = str(cfg.get("application_secret", "")) + consumer = str(cfg.get("consumer_key", "")) + ts = _server_time(cfg) + body_str = json.dumps(body) if body is not None else "" + to_sign = app_secret + "+" + consumer + "+" + method.upper() + "+" + url + "+" + body_str + "+" + ts + signature = "$1$" + hashlib.sha1(to_sign.encode("utf-8")).hexdigest() + headers = { + "X-Ovh-Application": app_key, + "X-Ovh-Consumer": consumer, + "X-Ovh-Timestamp": ts, + "X-Ovh-Signature": signature, + "Accept": "application/json", + } + data = None + if body is not None: + data = body_str.encode("utf-8") + 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): + server_name = inputs.get("server_name") + if not server_name: + raise Exception("server_name is required") + return request("GET", "/dedicated/server/" + q(server_name), cfg) + + +_run(main) diff --git a/integrations/ovhcloud/scripts/list_cloud_projects.py b/integrations/ovhcloud/scripts/list_cloud_projects.py new file mode 100644 index 0000000..7c607bc --- /dev/null +++ b/integrations/ovhcloud/scripts/list_cloud_projects.py @@ -0,0 +1,64 @@ +import json, os, sys, hashlib, 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 _endpoint(cfg): + return (str(cfg.get("endpoint") or "https://eu.api.ovh.com/1.0")).rstrip("/") + + +def _server_time(cfg): + req = urllib.request.Request(_endpoint(cfg) + "/auth/time", method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return r.read().decode("utf-8", "replace").strip() + + +def request(method, path, cfg, body=None): + url = _endpoint(cfg) + path + app_key = str(cfg.get("application_key", "")) + app_secret = str(cfg.get("application_secret", "")) + consumer = str(cfg.get("consumer_key", "")) + ts = _server_time(cfg) + body_str = json.dumps(body) if body is not None else "" + to_sign = app_secret + "+" + consumer + "+" + method.upper() + "+" + url + "+" + body_str + "+" + ts + signature = "$1$" + hashlib.sha1(to_sign.encode("utf-8")).hexdigest() + headers = { + "X-Ovh-Application": app_key, + "X-Ovh-Consumer": consumer, + "X-Ovh-Timestamp": ts, + "X-Ovh-Signature": signature, + "Accept": "application/json", + } + data = None + if body is not None: + data = body_str.encode("utf-8") + 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", "/cloud/project", cfg) + return {"projects": resp} + + +_run(main) diff --git a/integrations/ovhcloud/scripts/list_dedicated_servers.py b/integrations/ovhcloud/scripts/list_dedicated_servers.py new file mode 100644 index 0000000..472bde7 --- /dev/null +++ b/integrations/ovhcloud/scripts/list_dedicated_servers.py @@ -0,0 +1,64 @@ +import json, os, sys, hashlib, 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 _endpoint(cfg): + return (str(cfg.get("endpoint") or "https://eu.api.ovh.com/1.0")).rstrip("/") + + +def _server_time(cfg): + req = urllib.request.Request(_endpoint(cfg) + "/auth/time", method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return r.read().decode("utf-8", "replace").strip() + + +def request(method, path, cfg, body=None): + url = _endpoint(cfg) + path + app_key = str(cfg.get("application_key", "")) + app_secret = str(cfg.get("application_secret", "")) + consumer = str(cfg.get("consumer_key", "")) + ts = _server_time(cfg) + body_str = json.dumps(body) if body is not None else "" + to_sign = app_secret + "+" + consumer + "+" + method.upper() + "+" + url + "+" + body_str + "+" + ts + signature = "$1$" + hashlib.sha1(to_sign.encode("utf-8")).hexdigest() + headers = { + "X-Ovh-Application": app_key, + "X-Ovh-Consumer": consumer, + "X-Ovh-Timestamp": ts, + "X-Ovh-Signature": signature, + "Accept": "application/json", + } + data = None + if body is not None: + data = body_str.encode("utf-8") + 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", "/dedicated/server", cfg) + return {"servers": resp} + + +_run(main) diff --git a/integrations/ovhcloud/scripts/test_connection.py b/integrations/ovhcloud/scripts/test_connection.py new file mode 100644 index 0000000..a873004 --- /dev/null +++ b/integrations/ovhcloud/scripts/test_connection.py @@ -0,0 +1,64 @@ +import json, os, sys, hashlib, 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 _endpoint(cfg): + return (str(cfg.get("endpoint") or "https://eu.api.ovh.com/1.0")).rstrip("/") + + +def _server_time(cfg): + req = urllib.request.Request(_endpoint(cfg) + "/auth/time", method="GET") + with urllib.request.urlopen(req, timeout=30) as r: + return r.read().decode("utf-8", "replace").strip() + + +def request(method, path, cfg, body=None): + url = _endpoint(cfg) + path + app_key = str(cfg.get("application_key", "")) + app_secret = str(cfg.get("application_secret", "")) + consumer = str(cfg.get("consumer_key", "")) + ts = _server_time(cfg) + body_str = json.dumps(body) if body is not None else "" + to_sign = app_secret + "+" + consumer + "+" + method.upper() + "+" + url + "+" + body_str + "+" + ts + signature = "$1$" + hashlib.sha1(to_sign.encode("utf-8")).hexdigest() + headers = { + "X-Ovh-Application": app_key, + "X-Ovh-Consumer": consumer, + "X-Ovh-Timestamp": ts, + "X-Ovh-Signature": signature, + "Accept": "application/json", + } + data = None + if body is not None: + data = body_str.encode("utf-8") + 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", "/me", cfg) + return {"ok": True, "nichandle": resp.get("nichandle")} + + +_run(main)