diff --git a/integrations/fortigate/manifest.yaml b/integrations/fortigate/manifest.yaml new file mode 100644 index 0000000..470c05f --- /dev/null +++ b/integrations/fortigate/manifest.yaml @@ -0,0 +1,137 @@ +id: fortigate +name: FortiGate +version: 1.0.0 +description: "Fortinet FortiGate (FortiOS REST API v2) — network containment: ban/unban IP addresses (quarantine), manage firewall address objects and address groups, and read firewall policies and services. API-token authentication; stdlib-only, no extra Python dependencies. Supports multi-VDOM." +changelog: "1.0.0 — Initial release: ban/unban/list banned IPs, address object create/delete/list, address-group update/list, firewall policy and service listing." +category: network + +# Per-instance configuration. The API token is sent as 'Authorization: Bearer '. +# vdom is an optional default applied to every request (overridable per command). +config_schema: + properties: + server: + type: string + description: "FortiGate URL (e.g. https://192.168.0.1)" + api_key: + type: string + description: "FortiOS REST API token" + x-soar-sensitive: true + vdom: + type: string + description: "Default VDOM (leave empty for the management VDOM)" + insecure: + type: boolean + description: "Trust any TLS certificate (not secure)" + default: false + required: + - server + - api_key + +commands: + - id: ban_ip + name: fortigate-ban-ip + description: "Quarantine one or more source IP addresses (adds them to the FortiGate banned-IP list)." + inputs_schema: + properties: + ip_addresses: { type: string, description: "Comma-separated IPv4/IPv6 addresses to ban" } + expiry_seconds: { type: number, description: "Ban duration in seconds (0 or empty = indefinite)" } + vdom: { type: string, description: "VDOM override" } + required: [ip_addresses] + outputs_schema: { properties: {} } + - id: unban_ip + name: fortigate-unban-ip + description: "Remove one or more IP addresses from the FortiGate banned-IP list." + inputs_schema: + properties: + ip_addresses: { type: string, description: "Comma-separated addresses to unban (empty = clear all)" } + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + - id: list_banned_ips + name: fortigate-list-banned-ips + description: "List the currently banned (quarantined) IP addresses." + risk: read + inputs_schema: + properties: + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + - id: create_address + name: fortigate-create-firewall-address + description: "Create a firewall address object (subnet type)." + inputs_schema: + properties: + name: { type: string, description: "Address object name" } + subnet: { type: string, description: "Subnet as 'IP netmask' or CIDR (e.g. '10.0.0.5 255.255.255.255' or '10.0.0.5/32')" } + comment: { type: string, description: "Optional comment" } + vdom: { type: string, description: "VDOM override" } + required: [name, subnet] + outputs_schema: { properties: {} } + - id: delete_address + name: fortigate-delete-firewall-address + description: "Delete a firewall address object by name." + inputs_schema: + properties: + name: { type: string, description: "Address object name" } + vdom: { type: string, description: "VDOM override" } + required: [name] + outputs_schema: { properties: {} } + - id: list_addresses + name: fortigate-list-firewall-addresses + description: "List firewall address objects (optionally a single one by name)." + risk: read + inputs_schema: + properties: + name: { type: string, description: "Optional address object name to fetch a single entry" } + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + - id: update_address_group + name: fortigate-update-firewall-address-group + description: "Replace the members of a firewall address group (e.g. a blocked-hosts group referenced by a deny policy)." + inputs_schema: + properties: + name: { type: string, description: "Address group name" } + members: { type: string, description: "Comma-separated address object names that become the group's members" } + vdom: { type: string, description: "VDOM override" } + required: [name, members] + outputs_schema: { properties: {} } + - id: list_address_groups + name: fortigate-list-firewall-address-groups + description: "List firewall address groups (optionally a single one by name)." + risk: read + inputs_schema: + properties: + name: { type: string, description: "Optional address group name to fetch a single entry" } + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + - id: list_policies + name: fortigate-list-firewall-policies + description: "List IPv4 firewall policies (optionally a single one by policy id)." + risk: read + inputs_schema: + properties: + policy_id: { type: string, description: "Optional policy id to fetch a single entry" } + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + - id: list_services + name: fortigate-list-firewall-services + description: "List custom firewall services (optionally a single one by name)." + risk: read + inputs_schema: + properties: + name: { type: string, description: "Optional service name to fetch a single entry" } + vdom: { type: string, description: "VDOM override" } + required: [] + outputs_schema: { properties: {} } + + - id: test_connection + name: fortigate-test-connection + description: "Verify connectivity and the API token (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/fortigate/scripts/ban_ip.py b/integrations/fortigate/scripts/ban_ip.py new file mode 100644 index 0000000..5903f8c --- /dev/null +++ b/integrations/fortigate/scripts/ban_ip.py @@ -0,0 +1,74 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + raw_ips = inputs.get("ip_addresses") + if not raw_ips or not str(raw_ips).strip(): + raise Exception("ip_addresses is required") + ip_list = [ip.strip() for ip in str(raw_ips).split(",") if ip.strip()] + if not ip_list: + raise Exception("ip_addresses is required") + + expiry_raw = inputs.get("expiry_seconds") + if expiry_raw is None or str(expiry_raw).strip() == "": + expiry = 0 + else: + expiry = int(expiry_raw) + + body = {"ip_addresses": ip_list, "expiry": expiry} + return request("POST", "/monitor/user/banned/add_users", cfg, inputs, body=body) + + +_run(main) diff --git a/integrations/fortigate/scripts/create_address.py b/integrations/fortigate/scripts/create_address.py new file mode 100644 index 0000000..47ed2e6 --- /dev/null +++ b/integrations/fortigate/scripts/create_address.py @@ -0,0 +1,72 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if not name or not str(name).strip(): + raise Exception("name is required") + subnet = inputs.get("subnet") + if not subnet or not str(subnet).strip(): + raise Exception("subnet is required") + + body = {"name": name, "type": "ipmask", "subnet": subnet} + comment = inputs.get("comment") + if comment and str(comment).strip(): + body["comment"] = comment + + return request("POST", "/cmdb/firewall/address", cfg, inputs, body=body) + + +_run(main) diff --git a/integrations/fortigate/scripts/delete_address.py b/integrations/fortigate/scripts/delete_address.py new file mode 100644 index 0000000..2806049 --- /dev/null +++ b/integrations/fortigate/scripts/delete_address.py @@ -0,0 +1,65 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if not name or not str(name).strip(): + raise Exception("name is required") + + path = "/cmdb/firewall/address/" + urllib.parse.quote(str(name), safe="") + return request("DELETE", path, cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/list_address_groups.py b/integrations/fortigate/scripts/list_address_groups.py new file mode 100644 index 0000000..7887f24 --- /dev/null +++ b/integrations/fortigate/scripts/list_address_groups.py @@ -0,0 +1,64 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if name and str(name).strip(): + path = "/cmdb/firewall/addrgrp/" + urllib.parse.quote(str(name), safe="") + return request("GET", path, cfg, inputs) + return request("GET", "/cmdb/firewall/addrgrp", cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/list_addresses.py b/integrations/fortigate/scripts/list_addresses.py new file mode 100644 index 0000000..aaaa50f --- /dev/null +++ b/integrations/fortigate/scripts/list_addresses.py @@ -0,0 +1,64 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if name and str(name).strip(): + path = "/cmdb/firewall/address/" + urllib.parse.quote(str(name), safe="") + return request("GET", path, cfg, inputs) + return request("GET", "/cmdb/firewall/address", cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/list_banned_ips.py b/integrations/fortigate/scripts/list_banned_ips.py new file mode 100644 index 0000000..aed253a --- /dev/null +++ b/integrations/fortigate/scripts/list_banned_ips.py @@ -0,0 +1,60 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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", "/monitor/user/banned", cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/list_policies.py b/integrations/fortigate/scripts/list_policies.py new file mode 100644 index 0000000..049d784 --- /dev/null +++ b/integrations/fortigate/scripts/list_policies.py @@ -0,0 +1,64 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + policy_id = inputs.get("policy_id") + if policy_id is not None and str(policy_id).strip(): + path = "/cmdb/firewall/policy/" + urllib.parse.quote(str(policy_id), safe="") + return request("GET", path, cfg, inputs) + return request("GET", "/cmdb/firewall/policy", cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/list_services.py b/integrations/fortigate/scripts/list_services.py new file mode 100644 index 0000000..702f580 --- /dev/null +++ b/integrations/fortigate/scripts/list_services.py @@ -0,0 +1,64 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if name and str(name).strip(): + path = "/cmdb/firewall/service/custom/" + urllib.parse.quote(str(name), safe="") + return request("GET", path, cfg, inputs) + return request("GET", "/cmdb/firewall/service/custom", cfg, inputs) + + +_run(main) diff --git a/integrations/fortigate/scripts/test_connection.py b/integrations/fortigate/scripts/test_connection.py new file mode 100644 index 0000000..84703c9 --- /dev/null +++ b/integrations/fortigate/scripts/test_connection.py @@ -0,0 +1,61 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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", "/monitor/system/status", cfg, inputs) + return {"ok": True} + + +_run(main) diff --git a/integrations/fortigate/scripts/unban_ip.py b/integrations/fortigate/scripts/unban_ip.py new file mode 100644 index 0000000..89c68fe --- /dev/null +++ b/integrations/fortigate/scripts/unban_ip.py @@ -0,0 +1,65 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + raw_ips = inputs.get("ip_addresses") + ip_list = [ip.strip() for ip in str(raw_ips).split(",") if ip.strip()] if raw_ips else [] + + if ip_list: + return request("POST", "/monitor/user/banned/clear_users", cfg, inputs, body={"ip_addresses": ip_list}) + return request("POST", "/monitor/user/banned/clear_all", cfg, inputs, body={}) + + +_run(main) diff --git a/integrations/fortigate/scripts/update_address_group.py b/integrations/fortigate/scripts/update_address_group.py new file mode 100644 index 0000000..a958e36 --- /dev/null +++ b/integrations/fortigate/scripts/update_address_group.py @@ -0,0 +1,72 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error, ssl + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _ctx(): + cfg = _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 request(method, path, cfg, inputs, body=None, extra_params=None): + base = str(cfg.get("server", "")).rstrip("/") + "/api/v2" + url = base + path + params = {} + vdom = (inputs.get("vdom") if inputs else None) or cfg.get("vdom") + if vdom: + params["vdom"] = vdom + if extra_params: + params.update(extra_params) + if params: + url += "?" + urllib.parse.urlencode(params) + 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, context=_ctx()) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + cfg = _cfg() + inputs = _inputs() + 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): + name = inputs.get("name") + if not name or not str(name).strip(): + raise Exception("name is required") + raw_members = inputs.get("members") + if not raw_members or not str(raw_members).strip(): + raise Exception("members is required") + members_list = [m.strip() for m in str(raw_members).split(",") if m.strip()] + if not members_list: + raise Exception("members is required") + + body = {"member": [{"name": m} for m in members_list]} + path = "/cmdb/firewall/addrgrp/" + urllib.parse.quote(str(name), safe="") + return request("PUT", path, cfg, inputs, body=body) + + +_run(main)