From 52a966f4e5e1298d1b192ae979f8b2b7d826dbcd Mon Sep 17 00:00:00 2001 From: Guillaume BOURGEOIS Date: Fri, 26 Jun 2026 00:08:13 +0200 Subject: [PATCH] feat(ipinfo): add IPinfo Core API connector IP enrichment (geolocation, ASN/network, privacy flags): lookup_ip, lookup_field, lookup_me, batch (code-first), and test_connection. Token auth via the `token` query parameter. Co-Authored-By: Claude Opus 4.8 --- integrations/ipinfo/manifest.yaml | 96 ++++++++++++++++++++++++++++ integrations/ipinfo/scripts/batch.py | 32 ++++++++++ 2 files changed, 128 insertions(+) create mode 100644 integrations/ipinfo/manifest.yaml create mode 100644 integrations/ipinfo/scripts/batch.py diff --git a/integrations/ipinfo/manifest.yaml b/integrations/ipinfo/manifest.yaml new file mode 100644 index 0000000..27e45fe --- /dev/null +++ b/integrations/ipinfo/manifest.yaml @@ -0,0 +1,96 @@ +id: ipinfo +name: IPinfo +version: 1.0.0 +description: "IPinfo Core API — IP address enrichment: geolocation, ASN / network details, and privacy/network flags (hosting, mobile, anycast, satellite)." +changelog: "1.0.0 — Initial release: IP lookup, single-field lookup, own IP, batch, and connectivity test." +category: enrichment + +config_schema: + properties: + base_url: + type: string + description: IPinfo Core API base URL + default: https://api.ipinfo.io + token: + type: string + description: IPinfo API access token (Account → Token) + x-soar-sensitive: true + required: + - token + +# IPinfo authenticates with the access token as the `token` query parameter. +auth: + - id: apikey + type: api_key + in: query + name: token + value_template: "{{secret}}" + secret_field: token + +commands: + - id: lookup_ip + name: ipinfo-lookup-ip + description: Full enrichment for an IP — geolocation, ASN/network, and privacy/network flags. + risk: read + inputs_schema: + properties: + ip: { type: string, description: "IPv4 or IPv6 address" } + required: [ip] + outputs_schema: { properties: {} } + request: + method: GET + path: /lookup/{ip} + auth_ref: apikey + + - id: lookup_field + name: ipinfo-lookup-field + description: A single field for an IP (e.g. city, country, hostname, as). + risk: read + inputs_schema: + properties: + ip: { type: string, description: "IP address" } + field: { type: string, description: "Field name, e.g. city, country, hostname, as" } + required: [ip, field] + outputs_schema: { properties: {} } + request: + method: GET + path: /lookup/{ip}/{field} + auth_ref: apikey + + - id: lookup_me + name: ipinfo-lookup-me + description: Details for the IP address making the request. + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + request: + method: GET + path: /lookup/me + auth_ref: apikey + + # Code-first: the batch endpoint takes a raw JSON array body, which the + # form-based generator (object body) cannot produce. See scripts/batch.py. + - id: batch + name: ipinfo-batch + description: Look up multiple IPs (or IP/field paths) in a single request. + risk: read + inputs_schema: + properties: + ips: { type: string, description: "Comma-separated IPs or lookup paths, e.g. '8.8.8.8,1.1.1.1,8.8.4.4/country'" } + required: [ips] + outputs_schema: { properties: {} } + + - id: test_connection + name: ipinfo-test-connection + description: "Verify connectivity and credentials (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + request: + method: GET + path: /lookup/me + auth_ref: apikey diff --git a/integrations/ipinfo/scripts/batch.py b/integrations/ipinfo/scripts/batch.py new file mode 100644 index 0000000..1ee03eb --- /dev/null +++ b/integrations/ipinfo/scripts/batch.py @@ -0,0 +1,32 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.ipinfo.io").rstrip("/") + + +def main(): + token = SECRETS.get("token", "") + ips = [x.strip() for x in str(INPUTS.get("ips", "")).split(",") if x.strip()] + if not ips: + print(json.dumps({"error": "ips is required"})) + sys.exit(1) + + # IPinfo's POST /batch expects a raw JSON array of IPs / lookup paths. + url = BASE + "/batch?" + urllib.parse.urlencode({"token": token}) + data = json.dumps(ips).encode("utf-8") + headers = {"Content-Type": "application/json", "Accept": "application/json"} + req = urllib.request.Request(url, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + raw = r.read() + print(json.dumps(json.loads(raw) if raw else {})) + + +try: + main() +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)