feat(opencti): new OpenCTI threat-intelligence integration
Add a marketplace integration for the OpenCTI platform (GraphQL API, compatible with OpenCTI 5.x/6.x), built on the Python pycti client and run from a remote engine. 26 commands: observables (list/create/delete/field update/add/remove), indicators (list/create/update/field add/remove, types), incidents (list/create/delete, types) with an OCSF ingestion mapper, relationships (list/create/delete), and reference data (organizations, labels, marking definitions, external references). - Ingestion: get_incidents to an OCSF finding mapper + an OpenCTI Incident type. - Auth: user API key (Bearer) via pycti; requires pip install pycti on the engine host. - Scripts are self-contained (INTEGRATION_SECRETS/INTEGRATION_INPUTS in, JSON out) following the established marketplace pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
name: "OpenCTI Incident"
|
||||||
|
color: "#1f6feb"
|
||||||
|
icon: "alert"
|
||||||
@@ -0,0 +1,378 @@
|
|||||||
|
id: opencti
|
||||||
|
name: OpenCTI
|
||||||
|
version: 1.0.0
|
||||||
|
description: "OpenCTI threat-intelligence platform (GraphQL API, compatible with OpenCTI 5.x/6.x). Query and manage observables, indicators, incidents, relationships, labels, markings, organizations and external references; ingest OpenCTI incidents with an OCSF mapper. Runs on a remote engine. Requires the Python 'pycti' library on the engine host (pip install pycti)."
|
||||||
|
changelog: "1.0.0 — Initial release: observables (list/create/delete/field update/add/remove), indicators (list/create/update/field add/remove, types), incidents (list/create/delete, types) with OCSF ingestion mapper, relationships (list/create/delete), and reference data (organizations, labels, marking definitions, external references)."
|
||||||
|
category: threat_intel
|
||||||
|
|
||||||
|
# Per-instance configuration. The integration talks to the OpenCTI GraphQL API at
|
||||||
|
# <base_url>/graphql using the user API key. Run it on a remote engine that can
|
||||||
|
# reach the OpenCTI platform. The bundled scripts require the Python 'pycti'
|
||||||
|
# library on the engine host: pip install pycti
|
||||||
|
config_schema:
|
||||||
|
properties:
|
||||||
|
base_url:
|
||||||
|
type: string
|
||||||
|
description: "OpenCTI base URL, e.g. https://opencti.company.com (scripts append /graphql)"
|
||||||
|
api_key:
|
||||||
|
type: string
|
||||||
|
description: "OpenCTI API key (user profile → API access → API KEY)"
|
||||||
|
x-soar-sensitive: true
|
||||||
|
insecure:
|
||||||
|
type: boolean
|
||||||
|
description: "Trust any TLS certificate (not secure)"
|
||||||
|
default: false
|
||||||
|
required:
|
||||||
|
- base_url
|
||||||
|
- api_key
|
||||||
|
|
||||||
|
# Documented for reference; pycti builds the Authorization header itself from the
|
||||||
|
# api_key (Bearer token) when the scripts construct the client.
|
||||||
|
auth:
|
||||||
|
- id: apikey
|
||||||
|
type: api_key
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
value_template: "Bearer {{secret}}"
|
||||||
|
secret_field: api_key
|
||||||
|
|
||||||
|
commands:
|
||||||
|
# ── Observables ───────────────────────────────────────────────────────────
|
||||||
|
- id: get_observables
|
||||||
|
name: opencti-get-observables
|
||||||
|
description: "List/search observables, optionally filtered by type, score and free text."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
limit: { type: number, description: "Max observables to return (default 50, max 500)" }
|
||||||
|
score_start: { type: number, description: "Minimum score to filter by (0-100)" }
|
||||||
|
score_end: { type: number, description: "Maximum score to filter by (0-100)" }
|
||||||
|
score: { type: string, description: "A specific score (0-100) or 'Unknown'" }
|
||||||
|
observable_types: { type: string, description: "Comma-separated types: ALL, Account, Domain, Email, File, Host, IP, IPv6, Registry Key, URL (default ALL)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor from a previous call (OpenCTI.Observables.LastRunID)" }
|
||||||
|
search: { type: string, description: "Observable value to filter by (partial allowed)" }
|
||||||
|
all_results: { type: boolean, description: "Fetch all results, ignoring limit (default false)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: observable_create
|
||||||
|
name: opencti-observable-create
|
||||||
|
description: "Create a new observable (optionally creating a related indicator)."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
type: { type: string, description: "Observable type: Account, Domain, Email, File-MD5, File-SHA1, File-SHA256, Host, IP, IPv6, Registry Key, URL" }
|
||||||
|
value: { type: string, description: "Observable value" }
|
||||||
|
created_by: { type: string, description: "Organization ID (see organization_list)" }
|
||||||
|
marking_id: { type: string, description: "Marking definition ID (see marking_definition_list)" }
|
||||||
|
label_id: { type: string, description: "Label ID (see label_list / label_create)" }
|
||||||
|
external_references_id: { type: string, description: "External reference ID (see external_reference_create)" }
|
||||||
|
description: { type: string, description: "Observable description" }
|
||||||
|
score: { type: number, description: "Observable score 0-100 (default 50)" }
|
||||||
|
create_indicator: { type: boolean, description: "Also create a related indicator (default false)" }
|
||||||
|
required: [type, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: observable_delete
|
||||||
|
name: opencti-observable-delete
|
||||||
|
description: "Delete an observable by ID."
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Observable ID" }
|
||||||
|
required: [id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: observable_field_update
|
||||||
|
name: opencti-observable-field-update
|
||||||
|
description: "Update an observable field (score or description)."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Observable ID" }
|
||||||
|
field: { type: string, description: "Field to update: score or description" }
|
||||||
|
value: { type: string, description: "New value" }
|
||||||
|
required: [id, field, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: observable_field_add
|
||||||
|
name: opencti-observable-field-add
|
||||||
|
description: "Add a marking definition or label to an observable."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Observable ID" }
|
||||||
|
field: { type: string, description: "Field to add: marking or label" }
|
||||||
|
value: { type: string, description: "Label ID or marking definition ID to add" }
|
||||||
|
required: [id, field, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: observable_field_remove
|
||||||
|
name: opencti-observable-field-remove
|
||||||
|
description: "Remove a marking definition or label from an observable."
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Observable ID" }
|
||||||
|
field: { type: string, description: "Field to remove: marking or label" }
|
||||||
|
value: { type: string, description: "Label ID or marking definition ID to remove" }
|
||||||
|
required: [id, field, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
# ── Indicators ────────────────────────────────────────────────────────────
|
||||||
|
- id: get_indicators
|
||||||
|
name: opencti-get-indicators
|
||||||
|
description: "List/search indicators with optional filters and pagination."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
search: { type: string, description: "Indicator value to filter by (partial allowed)" }
|
||||||
|
created_by: { type: string, description: "Creator organization ID" }
|
||||||
|
creator: { type: string, description: "Creator user ID" }
|
||||||
|
created_after: { type: string, description: "Created-after date (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
created_before: { type: string, description: "Created-before date (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
valid_until_after: { type: string, description: "Valid-until-after date" }
|
||||||
|
valid_until_before: { type: string, description: "Valid-until-before date" }
|
||||||
|
valid_from_after: { type: string, description: "Valid-from-after date" }
|
||||||
|
valid_from_before: { type: string, description: "Valid-from-before date" }
|
||||||
|
indicator_types: { type: string, description: "Comma-separated indicator types (see indicator_types_list)" }
|
||||||
|
label_id: { type: string, description: "Label ID to filter by" }
|
||||||
|
limit: { type: number, description: "Max indicators to return (default 50, max 500)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.Indicators.LastRunID)" }
|
||||||
|
all_results: { type: boolean, description: "Fetch all results, ignoring limit (default false)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: indicator_create
|
||||||
|
name: opencti-indicator-create
|
||||||
|
description: "Create a new indicator (STIX pattern built from value + observable type)."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
name: { type: string, description: "Indicator name" }
|
||||||
|
indicator: { type: string, description: "Indicator value" }
|
||||||
|
main_observable_type: { type: string, description: "Main observable type: Account, Domain, Email, File-MD5, File-SHA1, File-SHA256, IP, IPv6, Registry Key, URL" }
|
||||||
|
indicator_types: { type: string, description: "Comma-separated indicator types (see indicator_types_list)" }
|
||||||
|
description: { type: string, description: "Indicator description" }
|
||||||
|
confidence: { type: number, description: "Confidence 0-100 (default 50)" }
|
||||||
|
score: { type: number, description: "Score 0-100 (default 50)" }
|
||||||
|
valid_from: { type: string, description: "Valid-from date (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
valid_until: { type: string, description: "Valid-until date (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
created_by: { type: string, description: "Creator organization ID" }
|
||||||
|
label_id: { type: string, description: "Label ID" }
|
||||||
|
marking_id: { type: string, description: "Marking definition ID" }
|
||||||
|
external_references_id: { type: string, description: "External reference ID" }
|
||||||
|
create_observables: { type: boolean, description: "Also create the related observable (default false)" }
|
||||||
|
required: [name, indicator, main_observable_type]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: indicator_update
|
||||||
|
name: opencti-indicator-update
|
||||||
|
description: "Update an existing indicator's fields."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Indicator ID" }
|
||||||
|
name: { type: string, description: "Indicator name" }
|
||||||
|
indicator_types: { type: string, description: "Comma-separated indicator types" }
|
||||||
|
description: { type: string, description: "Indicator description" }
|
||||||
|
confidence: { type: number, description: "Confidence 0-100 (default 50)" }
|
||||||
|
score: { type: number, description: "Score 0-100 (default 50)" }
|
||||||
|
valid_from: { type: string, description: "Valid-from date" }
|
||||||
|
valid_until: { type: string, description: "Valid-until date" }
|
||||||
|
label_id: { type: string, description: "Comma-separated label IDs" }
|
||||||
|
marking_id: { type: string, description: "Comma-separated marking IDs" }
|
||||||
|
external_references_id: { type: string, description: "Comma-separated external reference IDs" }
|
||||||
|
required: [id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: indicator_field_add
|
||||||
|
name: opencti-indicator-field-add
|
||||||
|
description: "Add a marking definition or label to an indicator."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Indicator ID" }
|
||||||
|
field: { type: string, description: "Field to add: marking or label" }
|
||||||
|
value: { type: string, description: "Label ID or marking definition ID to add" }
|
||||||
|
required: [id, field, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: indicator_field_remove
|
||||||
|
name: opencti-indicator-field-remove
|
||||||
|
description: "Remove a marking definition or label from an indicator."
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Indicator ID" }
|
||||||
|
field: { type: string, description: "Field to remove: marking or label" }
|
||||||
|
value: { type: string, description: "Label ID or marking definition ID to remove" }
|
||||||
|
required: [id, field, value]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: indicator_types_list
|
||||||
|
name: opencti-indicator-types-list
|
||||||
|
description: "List all indicator types (vocabulary)."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
# ── Incidents (ingestion source) ──────────────────────────────────────────
|
||||||
|
- id: get_incidents
|
||||||
|
name: opencti-get-incidents
|
||||||
|
description: "Fetch OpenCTI incidents for ingestion or search. Returns the entities list; use it as the alert-rule results path."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
search: { type: string, description: "Incident value to filter by (partial allowed)" }
|
||||||
|
created_by: { type: string, description: "Creator organization ID" }
|
||||||
|
creator: { type: string, description: "Creator user ID" }
|
||||||
|
created_after: { type: string, description: "Created-after date / incremental watermark (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
created_before: { type: string, description: "Created-before date" }
|
||||||
|
incident_types: { type: string, description: "Comma-separated incident types (see incident_types_list)" }
|
||||||
|
label_id: { type: string, description: "Label ID to filter by" }
|
||||||
|
limit: { type: number, description: "Max incidents to return (default 50, max 500)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.Incidents.LastRunID)" }
|
||||||
|
all_results: { type: boolean, description: "Fetch all results, ignoring limit (default false)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
ingest:
|
||||||
|
results_path: entities
|
||||||
|
dedup_key: id
|
||||||
|
incremental_field: created_after
|
||||||
|
- id: incident_create
|
||||||
|
name: opencti-incident-create
|
||||||
|
description: "Create a new incident."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
name: { type: string, description: "Incident name" }
|
||||||
|
incident_type: { type: string, description: "Incident type name (see incident_types_list)" }
|
||||||
|
confidence: { type: number, description: "Confidence 0-100 (default 50)" }
|
||||||
|
severity: { type: string, description: "Severity: low, medium, high, critical" }
|
||||||
|
description: { type: string, description: "Incident description" }
|
||||||
|
source: { type: string, description: "Incident source" }
|
||||||
|
objective: { type: string, description: "Incident objective" }
|
||||||
|
created_by: { type: string, description: "Creator organization ID" }
|
||||||
|
first_seen: { type: string, description: "First seen (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
last_seen: { type: string, description: "Last seen (YYYY-MM-DDThh:mm:ss.sssZ)" }
|
||||||
|
label_id: { type: string, description: "Label ID" }
|
||||||
|
marking_id: { type: string, description: "Marking definition ID" }
|
||||||
|
external_references_id: { type: string, description: "External reference ID" }
|
||||||
|
required: [name]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: incident_delete
|
||||||
|
name: opencti-incident-delete
|
||||||
|
description: "Delete an incident by ID."
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Incident ID" }
|
||||||
|
required: [id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: incident_types_list
|
||||||
|
name: opencti-incident-types-list
|
||||||
|
description: "List all incident types (vocabulary)."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
# ── Relationships ─────────────────────────────────────────────────────────
|
||||||
|
- id: relationship_create
|
||||||
|
name: opencti-relationship-create
|
||||||
|
description: "Create a relationship between two entities."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
from_id: { type: string, description: "Source entity ID" }
|
||||||
|
to_id: { type: string, description: "Target entity ID" }
|
||||||
|
relationship_type: { type: string, description: "Relationship type (e.g. related-to, indicates, targets, uses, mitigates; default related-to)" }
|
||||||
|
description: { type: string, description: "Relationship description" }
|
||||||
|
confidence: { type: number, description: "Confidence 0-100" }
|
||||||
|
required: [from_id, to_id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: relationship_delete
|
||||||
|
name: opencti-relationship-delete
|
||||||
|
description: "Delete a relationship by ID."
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
id: { type: string, description: "Relationship ID" }
|
||||||
|
required: [id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: relationship_list
|
||||||
|
name: opencti-relationship-list
|
||||||
|
description: "List relationships from a given entity."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
from_id: { type: string, description: "Source entity ID" }
|
||||||
|
limit: { type: number, description: "Max relationships to return (default 50, max 200)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.Relationships.relationshipsLastRun)" }
|
||||||
|
required: [from_id]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
# ── Reference data ────────────────────────────────────────────────────────
|
||||||
|
- id: organization_list
|
||||||
|
name: opencti-organization-list
|
||||||
|
description: "List all organizations."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
limit: { type: number, description: "Max organizations to return (default 50, max 200)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.Organizations.organizationsLastRun)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: organization_create
|
||||||
|
name: opencti-organization-create
|
||||||
|
description: "Create a new organization."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
name: { type: string, description: "Organization name" }
|
||||||
|
description: { type: string, description: "Organization description" }
|
||||||
|
reliability: { type: string, description: "Reliability: A, B, C, D, E, F" }
|
||||||
|
required: [name]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: label_list
|
||||||
|
name: opencti-label-list
|
||||||
|
description: "List all labels."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
limit: { type: number, description: "Max labels to return (default 50)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.Labels.labelsLastRun)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: label_create
|
||||||
|
name: opencti-label-create
|
||||||
|
description: "Create a new label."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
name: { type: string, description: "Label name" }
|
||||||
|
required: [name]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: marking_definition_list
|
||||||
|
name: opencti-marking-definition-list
|
||||||
|
description: "List all marking definitions."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
limit: { type: number, description: "Max marking definitions to return (default 50)" }
|
||||||
|
last_run_id: { type: string, description: "Pagination cursor (OpenCTI.MarkingDefinitions.markingsLastRun)" }
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
- id: external_reference_create
|
||||||
|
name: opencti-external-reference-create
|
||||||
|
description: "Create an external reference."
|
||||||
|
risk: safe_write
|
||||||
|
inputs_schema:
|
||||||
|
properties:
|
||||||
|
url: { type: string, description: "External reference URL" }
|
||||||
|
source_name: { type: string, description: "External reference source name" }
|
||||||
|
required: [url, source_name]
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
- id: test_connection
|
||||||
|
name: opencti-test-connection
|
||||||
|
description: "Verify connectivity and credentials (used by the Test button)."
|
||||||
|
risk: read
|
||||||
|
inputs_schema:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
outputs_schema: { properties: {} }
|
||||||
|
|
||||||
|
ingestion:
|
||||||
|
command: get_incidents
|
||||||
|
mapper: get_incidents
|
||||||
|
default_incident_type: "OpenCTI Incident"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: "OpenCTI Incidents → OCSF"
|
||||||
|
description: "Maps an OpenCTI incident (get_incidents, results_path = entities) to OCSF finding fields. Each entity is a normalized incident object emitted by the get_incidents script."
|
||||||
|
field_mappings:
|
||||||
|
title: "name"
|
||||||
|
severity: "severity = 'critical' ? 5 : (severity = 'high' ? 4 : (severity = 'medium' ? 3 : 2))"
|
||||||
|
description: "description"
|
||||||
|
# results_path = entities; source_path is JSONata over ONE incident object.
|
||||||
|
# Paths absent from a given incident are skipped at ingestion, so extra entries are safe.
|
||||||
|
ocsf:
|
||||||
|
# ── Finding ───────────────────────────────────────────────────────
|
||||||
|
- { source_path: "id", ocsf_field: "finding_info.uid" }
|
||||||
|
- { source_path: "name", ocsf_field: "finding_info.title" }
|
||||||
|
- { source_path: "description", ocsf_field: "finding_info.desc" }
|
||||||
|
- { source_path: "created", ocsf_field: "finding_info.created_time" }
|
||||||
|
- { source_path: "updatedAt", ocsf_field: "finding_info.modified_time" }
|
||||||
|
- { source_path: "incidentTypes", ocsf_field: "finding_info.types" }
|
||||||
|
- { source_path: "objective", ocsf_field: "message" }
|
||||||
|
- { source_path: "confidence", ocsf_field: "confidence_score" }
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().external_reference.create(
|
||||||
|
source_name=I.get("source_name"),
|
||||||
|
url=I.get("url"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to create external reference.", detail=str(e))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
fail("Failed to create external reference.")
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "message": f"External reference created. id: {result.get('id')}"})
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
def get_incidents(client, search="", created_by=None, creator=None, created_after=None,
|
||||||
|
created_before=None, incident_types=None, label=None, limit=500,
|
||||||
|
last_run_id=None, get_all=False):
|
||||||
|
filters = {"mode": "and", "filters": [], "filterGroups": []}
|
||||||
|
if label:
|
||||||
|
filters["filters"].append({"key": "objectLabel", "values": [label], "operator": "eq", "mode": "or"})
|
||||||
|
if created_by:
|
||||||
|
filters["filters"].append({"key": "createdBy", "values": [created_by], "operator": "eq", "mode": "or"})
|
||||||
|
if creator:
|
||||||
|
filters["filters"].append({"key": "creator_id", "values": [creator], "operator": "eq"})
|
||||||
|
if incident_types:
|
||||||
|
filters["filters"].append({"key": "incident_types", "values": incident_types, "operator": "eq", "mode": "or"})
|
||||||
|
if created_after:
|
||||||
|
filters["filters"].append({"key": "created_at", "values": [created_after], "operator": "gt"})
|
||||||
|
if created_before:
|
||||||
|
filters["filters"].append({"key": "created_at", "values": [created_before], "operator": "lt"})
|
||||||
|
return client.incident.list(
|
||||||
|
after=last_run_id, first=limit, withPagination=True, getAll=get_all, filters=filters, search=search
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
search = I.get("search", "")
|
||||||
|
created_by = I.get("created_by")
|
||||||
|
creator = I.get("creator")
|
||||||
|
created_after = I.get("created_after")
|
||||||
|
created_before = I.get("created_before")
|
||||||
|
incident_types = as_list(I.get("incident_types"))
|
||||||
|
label = I.get("label_id")
|
||||||
|
limit = as_int(I.get("limit", 500), 500)
|
||||||
|
last_run_id = I.get("last_run_id")
|
||||||
|
get_all = as_bool(I.get("all_results", "false"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw = get_incidents(
|
||||||
|
client(),
|
||||||
|
search=search,
|
||||||
|
created_by=created_by,
|
||||||
|
creator=creator,
|
||||||
|
created_after=created_after,
|
||||||
|
created_before=created_before,
|
||||||
|
incident_types=incident_types,
|
||||||
|
label=label,
|
||||||
|
limit=limit,
|
||||||
|
last_run_id=last_run_id,
|
||||||
|
get_all=get_all,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't list incidents from OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
last_run = None if get_all else raw.get("pagination", {}).get("endCursor")
|
||||||
|
incidents_list = raw if get_all else raw.get("entities", [])
|
||||||
|
|
||||||
|
normalized = []
|
||||||
|
for incident in incidents_list:
|
||||||
|
normalized.append({
|
||||||
|
"id": incident.get("id"),
|
||||||
|
"name": incident.get("name"),
|
||||||
|
"description": incident.get("description"),
|
||||||
|
"source": incident.get("source"),
|
||||||
|
"confidence": incident.get("confidence"),
|
||||||
|
"severity": incident.get("severity"),
|
||||||
|
"objective": incident.get("objective"),
|
||||||
|
"createdBy": (incident.get("createdBy") or {}).get("name") or "",
|
||||||
|
"creators": [c.get("name") for c in incident.get("creators", [])],
|
||||||
|
"labels": [l.get("value") for l in incident.get("objectLabel", [])],
|
||||||
|
"incidentTypes": incident.get("incident_types"),
|
||||||
|
"created": incident.get("created"),
|
||||||
|
"updatedAt": incident.get("updated_at"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out({"entities": normalized, "pagination": {"endCursor": last_run}})
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
def get_indicators(client, search, created_by, creator, created_after, created_before,
|
||||||
|
valid_until_after, valid_until_before, valid_from_after, valid_from_before,
|
||||||
|
indicator_types, label, limit, last_run_id, get_all):
|
||||||
|
filters = {"mode": "and", "filters": [], "filterGroups": []}
|
||||||
|
if label:
|
||||||
|
filters["filters"].append({"key": "objectLabel", "values": [label], "operator": "eq", "mode": "or"})
|
||||||
|
if created_by:
|
||||||
|
filters["filters"].append({"key": "createdBy", "values": [created_by], "operator": "eq", "mode": "or"})
|
||||||
|
if creator:
|
||||||
|
filters["filters"].append({"key": "creator_id", "values": [creator], "operator": "eq"})
|
||||||
|
if indicator_types:
|
||||||
|
filters["filters"].append({"key": "indicator_types", "values": indicator_types, "operator": "eq", "mode": "or"})
|
||||||
|
if created_after:
|
||||||
|
filters["filters"].append({"key": "created_at", "values": [created_after], "operator": "gt"})
|
||||||
|
if created_before:
|
||||||
|
filters["filters"].append({"key": "created_at", "values": [created_before], "operator": "lt"})
|
||||||
|
if valid_from_after:
|
||||||
|
filters["filters"].append({"key": "valid_from", "values": [valid_from_after], "operator": "gt"})
|
||||||
|
if valid_from_before:
|
||||||
|
filters["filters"].append({"key": "valid_from", "values": [valid_from_before], "operator": "lt"})
|
||||||
|
if valid_until_after:
|
||||||
|
filters["filters"].append({"key": "valid_until", "values": [valid_until_after], "operator": "gt"})
|
||||||
|
if valid_until_before:
|
||||||
|
filters["filters"].append({"key": "valid_until", "values": [valid_until_before], "operator": "lt"})
|
||||||
|
return client.indicator.list(
|
||||||
|
after=last_run_id, first=limit, withPagination=True, getAll=get_all, filters=filters, search=search
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
search = I.get("search")
|
||||||
|
created_by = I.get("created_by")
|
||||||
|
creator = I.get("creator")
|
||||||
|
created_after = I.get("created_after")
|
||||||
|
created_before = I.get("created_before")
|
||||||
|
valid_until_after = I.get("valid_until_after")
|
||||||
|
valid_until_before = I.get("valid_until_before")
|
||||||
|
valid_from_after = I.get("valid_from_after")
|
||||||
|
valid_from_before = I.get("valid_from_before")
|
||||||
|
indicator_types = as_list(I.get("indicator_types"))
|
||||||
|
label = I.get("label_id")
|
||||||
|
limit = as_int(I.get("limit", 50), 50)
|
||||||
|
last_run_id = I.get("last_run_id")
|
||||||
|
get_all = as_bool(I.get("all_results", "false"))
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw = get_indicators(
|
||||||
|
client(), search, created_by, creator, created_after, created_before,
|
||||||
|
valid_until_after, valid_until_before, valid_from_after, valid_from_before,
|
||||||
|
indicator_types, label, limit, last_run_id, get_all,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't list indicators from OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
last_run = None if get_all else raw.get("pagination", {}).get("endCursor")
|
||||||
|
indicators_list = raw if get_all else raw.get("entities", [])
|
||||||
|
|
||||||
|
normalized = []
|
||||||
|
for ind in indicators_list:
|
||||||
|
normalized.append({
|
||||||
|
"id": ind.get("id"),
|
||||||
|
"name": ind.get("name"),
|
||||||
|
"description": ind.get("description"),
|
||||||
|
"pattern": ind.get("pattern"),
|
||||||
|
"validFrom": ind.get("valid_from"),
|
||||||
|
"validUntil": ind.get("valid_until"),
|
||||||
|
"score": ind.get("x_opencti_score"),
|
||||||
|
"confidence": ind.get("confidence"),
|
||||||
|
"createdBy": ind.get("createdBy").get("name") if ind.get("createdBy") else "",
|
||||||
|
"creators": [c.get("name") for c in ind.get("creators", [])],
|
||||||
|
"labels": [l.get("value") for l in ind.get("objectLabel", [])],
|
||||||
|
"indicatorTypes": ind.get("indicator_types"),
|
||||||
|
"created": ind.get("created"),
|
||||||
|
"updatedAt": ind.get("updated_at"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out({"indicators": normalized, "lastRunID": last_run})
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
TYPES_TO_OPENCTI = {
|
||||||
|
"account": "User-Account", "domain": "Domain-Name", "email": "Email-Addr",
|
||||||
|
"file-md5": "StixFile", "file-sha1": "StixFile", "file-sha256": "StixFile", "file": "StixFile",
|
||||||
|
"host": "X-OpenCTI-Hostname", "ip": "IPv4-Addr", "ipv6": "IPv6-Addr",
|
||||||
|
"registry key": "Windows-Registry-Key", "url": "Url",
|
||||||
|
}
|
||||||
|
OPENCTI_TO_TYPE = {
|
||||||
|
"User-Account": "Account", "Domain-Name": "Domain", "Email-Addr": "Email", "StixFile": "File",
|
||||||
|
"X-OpenCTI-Hostname": "Host", "IPv4-Addr": "IP", "IPv6-Addr": "IPv6",
|
||||||
|
"Windows-Registry-Key": "Registry Key", "Url": "URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_observable_list(observable_list):
|
||||||
|
result = []
|
||||||
|
if "ALL" in observable_list:
|
||||||
|
result = ["User-Account", "Domain-Name", "Email-Addr", "StixFile", "X-OpenCTI-Hostname",
|
||||||
|
"IPv4-Addr", "IPv6-Addr", "Windows-Registry-Key", "Url"]
|
||||||
|
else:
|
||||||
|
result = [TYPES_TO_OPENCTI.get(o.lower(), o) for o in observable_list]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_observables(client, observable_types, score=None, limit=500, last_run_id=None, search="", get_all=False):
|
||||||
|
observable_type = build_observable_list(observable_types)
|
||||||
|
filters = {
|
||||||
|
"mode": "and",
|
||||||
|
"filters": [{"key": "entity_type", "values": observable_type, "operator": "eq", "mode": "or"}],
|
||||||
|
"filterGroups": [],
|
||||||
|
}
|
||||||
|
if score:
|
||||||
|
filters["filters"].append({"key": "x_opencti_score", "values": score, "operator": "eq", "mode": "or"})
|
||||||
|
return client.stix_cyber_observable.list(
|
||||||
|
after=last_run_id, first=limit, withPagination=True, getAll=get_all, filters=filters, search=search
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
observable_types = as_list(I.get("observable_types", "ALL")) or ["ALL"]
|
||||||
|
last_run_id = I.get("last_run_id")
|
||||||
|
limit = as_int(I.get("limit", 50), 50)
|
||||||
|
start = as_int(I.get("score_start", 0), 0)
|
||||||
|
end = as_int(I.get("score_end", 100), 100)
|
||||||
|
score = I.get("score")
|
||||||
|
search = I.get("search", "")
|
||||||
|
get_all = as_bool(I.get("all_results", "false"))
|
||||||
|
|
||||||
|
scores = None
|
||||||
|
if score:
|
||||||
|
if str(score).lower() == "unknown":
|
||||||
|
scores = [None]
|
||||||
|
elif str(score).isdigit():
|
||||||
|
scores = [score]
|
||||||
|
else:
|
||||||
|
fail("Invalid score was provided.")
|
||||||
|
elif start or end:
|
||||||
|
scores = [str(n) for n in range(start, end + 1)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw = get_observables(
|
||||||
|
client(),
|
||||||
|
observable_types,
|
||||||
|
score=scores,
|
||||||
|
limit=limit,
|
||||||
|
last_run_id=last_run_id,
|
||||||
|
search=search,
|
||||||
|
get_all=get_all,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't list observables from OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
last_run = None if get_all else raw.get("pagination", {}).get("endCursor")
|
||||||
|
observables_list = raw if get_all else raw.get("entities", [])
|
||||||
|
|
||||||
|
normalized = []
|
||||||
|
for o in observables_list:
|
||||||
|
normalized.append({
|
||||||
|
"type": OPENCTI_TO_TYPE.get(o["entity_type"], o["entity_type"]),
|
||||||
|
"value": o.get("observable_value"),
|
||||||
|
"id": o.get("id"),
|
||||||
|
"createdBy": o.get("createdBy").get("id") if o.get("createdBy") else None,
|
||||||
|
"score": o.get("x_opencti_score"),
|
||||||
|
"description": o.get("x_opencti_description"),
|
||||||
|
"labels": [l.get("value") for l in o.get("objectLabel", [])],
|
||||||
|
"marking": [m.get("definition") for m in o.get("objectMarking", [])],
|
||||||
|
"externalReferences": o.get("externalReferences"),
|
||||||
|
})
|
||||||
|
|
||||||
|
out({"observables": normalized, "lastRunID": last_run})
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().incident.create(
|
||||||
|
name=I.get("name"),
|
||||||
|
incident_type=I.get("incident_type"),
|
||||||
|
confidence=as_int(I.get("confidence", 50), 50),
|
||||||
|
severity=I.get("severity"),
|
||||||
|
description=I.get("description"),
|
||||||
|
source=I.get("source"),
|
||||||
|
objective=I.get("objective"),
|
||||||
|
createdBy=I.get("created_by"),
|
||||||
|
first_seen=I.get("first_seen"),
|
||||||
|
last_seen=I.get("last_seen"),
|
||||||
|
objectLabel=I.get("label_id"),
|
||||||
|
objectMarking=I.get("marking_id"),
|
||||||
|
externalReferences=I.get("external_references_id"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't create incident in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
if result.get("id"):
|
||||||
|
out({"id": result.get("id"), "message": f"Incident created. id: {result.get('id')}"})
|
||||||
|
else:
|
||||||
|
fail("Incident creation failed.")
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
client().stix_domain_object.delete(id=I.get("id"))
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't delete incident in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": "Incident deleted."})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
query = """
|
||||||
|
query OpenVocabFieldQuery($category: VocabularyCategory!, $orderBy: VocabularyOrdering, $orderMode: OrderingMode) {
|
||||||
|
vocabularies(category: $category, orderBy: $orderBy, orderMode: $orderMode) {
|
||||||
|
edges { node { id name description } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().query(query, {"category": "incident_type_ov"})
|
||||||
|
edges = result["data"]["vocabularies"]["edges"]
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't list incident types from OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"incidentTypes": [edge["node"] for edge in edges]})
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
TYPES_TO_OPENCTI = {
|
||||||
|
"account": "User-Account", "domain": "Domain-Name", "email": "Email-Addr",
|
||||||
|
"file-md5": "StixFile", "file-sha1": "StixFile", "file-sha256": "StixFile", "file": "StixFile",
|
||||||
|
"host": "X-OpenCTI-Hostname", "ip": "IPv4-Addr", "ipv6": "IPv6-Addr",
|
||||||
|
"registry key": "Windows-Registry-Key", "url": "Url",
|
||||||
|
}
|
||||||
|
OBSERVABLE_TYPE_TO_STIX_PATTERN = {
|
||||||
|
"IPv4-Addr": "[ipv4-addr:value = '{{indicator}}']",
|
||||||
|
"IPv6-Addr": "[ipv6-addr:value = '{{indicator}}']",
|
||||||
|
"Domain-Name": "[domain-name:value = '{{indicator}}']",
|
||||||
|
"Url": "[url:value = '{{indicator}}']",
|
||||||
|
"Email-Addr": "[email-addr:value = '{{indicator}}']",
|
||||||
|
"StixFile": "[file:hashes.'SHA-256' = '{{indicator}}']",
|
||||||
|
"Process": "[process:pid = '{{indicator}}']",
|
||||||
|
"User-Account": "[user-account:user_id = '{{indicator}}']",
|
||||||
|
"Windows-Registry-Key": "[windows-registry-key:key = '{{indicator}}']",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_stix_pattern(indicator, observable_type):
|
||||||
|
if observable_type not in OBSERVABLE_TYPE_TO_STIX_PATTERN:
|
||||||
|
fail(f"Invalid observable type: {observable_type}")
|
||||||
|
return OBSERVABLE_TYPE_TO_STIX_PATTERN[observable_type].replace("{{indicator}}", indicator)
|
||||||
|
|
||||||
|
|
||||||
|
name = I.get("name")
|
||||||
|
indicator = I.get("indicator")
|
||||||
|
main_observable_type = TYPES_TO_OPENCTI.get(str(I.get("main_observable_type", "")).lower(), I.get("main_observable_type"))
|
||||||
|
description = I.get("description")
|
||||||
|
valid_from = I.get("valid_from")
|
||||||
|
valid_until = I.get("valid_until")
|
||||||
|
created_by = I.get("created_by")
|
||||||
|
label_id = I.get("label_id")
|
||||||
|
marking_id = I.get("marking_id")
|
||||||
|
external_references_id = I.get("external_references_id")
|
||||||
|
|
||||||
|
pattern = build_stix_pattern(indicator, main_observable_type)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().indicator.create(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
pattern=pattern,
|
||||||
|
pattern_type="stix",
|
||||||
|
x_opencti_main_observable_type=main_observable_type,
|
||||||
|
indicator_types=as_list(I.get("indicator_types")),
|
||||||
|
confidence=as_int(I.get("confidence"), 50),
|
||||||
|
x_opencti_score=as_int(I.get("score"), 50),
|
||||||
|
valid_from=valid_from,
|
||||||
|
valid_until=valid_until,
|
||||||
|
createdBy=created_by,
|
||||||
|
objectLabel=label_id,
|
||||||
|
objectMarking=marking_id,
|
||||||
|
externalReferences=external_references_id,
|
||||||
|
x_opencti_create_observables=as_bool(I.get("create_observables")),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't create indicator in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"id": result.get("id")})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
field = I.get("field")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if field == "marking":
|
||||||
|
client().stix_domain_object.add_marking_definition(id=I["id"], marking_definition_id=I["value"])
|
||||||
|
elif field == "label":
|
||||||
|
client().stix_domain_object.add_label(id=I["id"], label_id=I["value"])
|
||||||
|
else:
|
||||||
|
fail(f"Invalid field: {field}.")
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
fail(f"Can't add {field} to indicator in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": f"Added {field}."})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
field = I.get("field")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if field == "marking":
|
||||||
|
client().stix_domain_object.remove_marking_definition(id=I["id"], marking_definition_id=I["value"])
|
||||||
|
elif field == "label":
|
||||||
|
client().stix_domain_object.remove_label(id=I["id"], label_id=I["value"])
|
||||||
|
else:
|
||||||
|
fail(f"Invalid field: {field}.")
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
fail(f"Can't remove {field} from indicator in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": f"Removed {field}."})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
query = """
|
||||||
|
query OpenVocabFieldQuery($category: VocabularyCategory!, $orderBy: VocabularyOrdering, $orderMode: OrderingMode) {
|
||||||
|
vocabularies(category: $category, orderBy: $orderBy, orderMode: $orderMode) {
|
||||||
|
edges { node { id name description } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().query(query, {"category": "indicator_type_ov"})
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't list indicator types from OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
edges = result["data"]["vocabularies"]["edges"]
|
||||||
|
out({"indicatorTypes": [edge["node"] for edge in edges]})
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
indicator_id = I.get("id")
|
||||||
|
name = I.get("name")
|
||||||
|
description = I.get("description")
|
||||||
|
confidence = I.get("confidence")
|
||||||
|
score = I.get("score")
|
||||||
|
valid_from = I.get("valid_from")
|
||||||
|
valid_until = I.get("valid_until")
|
||||||
|
indicator_types = I.get("indicator_types")
|
||||||
|
label_id = I.get("label_id")
|
||||||
|
marking_id = I.get("marking_id")
|
||||||
|
external_references_id = I.get("external_references_id")
|
||||||
|
|
||||||
|
update_fields = []
|
||||||
|
if name:
|
||||||
|
update_fields.append({"key": "name", "value": name})
|
||||||
|
if description:
|
||||||
|
update_fields.append({"key": "description", "value": description})
|
||||||
|
if confidence:
|
||||||
|
update_fields.append({"key": "confidence", "value": as_int(confidence)})
|
||||||
|
if score:
|
||||||
|
update_fields.append({"key": "x_opencti_score", "value": as_int(score)})
|
||||||
|
if valid_from:
|
||||||
|
update_fields.append({"key": "valid_from", "value": valid_from})
|
||||||
|
if valid_until:
|
||||||
|
update_fields.append({"key": "valid_until", "value": valid_until})
|
||||||
|
if indicator_types:
|
||||||
|
update_fields.append({"key": "indicator_types", "value": indicator_types.split(",")})
|
||||||
|
if label_id:
|
||||||
|
update_fields.append({"key": "objectLabel", "value": label_id.split(",")})
|
||||||
|
if marking_id:
|
||||||
|
update_fields.append({"key": "objectMarking", "value": marking_id.split(",")})
|
||||||
|
if external_references_id:
|
||||||
|
update_fields.append({"key": "externalReferences", "value": external_references_id.split(",")})
|
||||||
|
|
||||||
|
mutation = """
|
||||||
|
mutation IndicatorEditionOverviewFieldPatchMutation($id: ID!, $input: [EditInput!]!, $commitMessage: String, $references: [String]) {
|
||||||
|
indicatorFieldPatch(id: $id, input: $input, commitMessage: $commitMessage, references: $references) {
|
||||||
|
id name confidence description valid_from valid_until x_opencti_score indicator_types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"id": indicator_id,
|
||||||
|
"input": update_fields,
|
||||||
|
"commitMessage": None,
|
||||||
|
"references": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().query(mutation, variables)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't update indicator in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
patched = result.get("data", {}).get("indicatorFieldPatch")
|
||||||
|
if patched:
|
||||||
|
out({
|
||||||
|
"id": patched.get("id"),
|
||||||
|
"name": patched.get("name"),
|
||||||
|
"validFrom": valid_from,
|
||||||
|
"validUntil": valid_until,
|
||||||
|
"message": "Indicator updated.",
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
fail("Can't update indicator in OpenCTI.")
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().label.create(value=I.get("name"))
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to create label.", detail=str(e))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
fail("Failed to create label.")
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "message": f"Label created. id: {result.get('id')}"})
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
labels = client().label.list(
|
||||||
|
first=as_int(I.get("limit", 50), 50),
|
||||||
|
after=I.get("last_run_id"),
|
||||||
|
withPagination=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to list labels.", detail=str(e))
|
||||||
|
|
||||||
|
out({
|
||||||
|
"labels": [{"value": l.get("value"), "id": l.get("id")} for l in labels.get("entities", [])],
|
||||||
|
"labelsLastRun": labels.get("pagination", {}).get("endCursor"),
|
||||||
|
})
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
marks = client().marking_definition.list(
|
||||||
|
first=as_int(I.get("limit", 50), 50),
|
||||||
|
after=I.get("last_run_id"),
|
||||||
|
withPagination=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to list marking definitions.", detail=str(e))
|
||||||
|
|
||||||
|
out({
|
||||||
|
"markingDefinitions": [{"value": m.get("definition"), "id": m.get("id")} for m in marks.get("entities", [])],
|
||||||
|
"markingsLastRun": marks.get("pagination", {}).get("endCursor"),
|
||||||
|
})
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import json, os, sys
|
||||||
|
import io
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
TYPES_TO_OPENCTI = {
|
||||||
|
"account": "User-Account", "domain": "Domain-Name", "email": "Email-Addr",
|
||||||
|
"file-md5": "StixFile", "file-sha1": "StixFile", "file-sha256": "StixFile", "file": "StixFile",
|
||||||
|
"host": "X-OpenCTI-Hostname", "ip": "IPv4-Addr", "ipv6": "IPv6-Addr",
|
||||||
|
"registry key": "Windows-Registry-Key", "url": "Url",
|
||||||
|
}
|
||||||
|
FILE_TYPES = {
|
||||||
|
"file-md5": "file.hashes.md5", "file-sha1": "file.hashes.sha-1", "file-sha256": "file.hashes.sha-256",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
observable_type = I.get("type")
|
||||||
|
data = {"type": TYPES_TO_OPENCTI.get(observable_type.lower(), observable_type), "value": I.get("value")}
|
||||||
|
if observable_type == "Registry Key":
|
||||||
|
data["key"] = I.get("value")
|
||||||
|
if observable_type == "Account":
|
||||||
|
data["account_login"] = I.get("value")
|
||||||
|
|
||||||
|
simple_observable_key = None
|
||||||
|
simple_observable_value = None
|
||||||
|
if "file" in observable_type.lower():
|
||||||
|
simple_observable_key = FILE_TYPES.get(observable_type.lower(), observable_type)
|
||||||
|
simple_observable_value = I.get("value")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdout = io.StringIO()
|
||||||
|
try:
|
||||||
|
result = client().stix_cyber_observable.create(
|
||||||
|
simple_observable_key=simple_observable_key,
|
||||||
|
simple_observable_value=simple_observable_value,
|
||||||
|
type=observable_type,
|
||||||
|
createdBy=I.get("created_by"),
|
||||||
|
objectMarking=I.get("marking_id"),
|
||||||
|
objectLabel=I.get("label_id"),
|
||||||
|
externalReferences=I.get("external_references_id"),
|
||||||
|
simple_observable_description=I.get("description"),
|
||||||
|
x_opencti_score=as_int(I.get("score", "50"), 50),
|
||||||
|
observableData=data,
|
||||||
|
createIndicator=as_bool(I.get("create_indicator", "false")),
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
except Exception as e:
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
fail("Can't create observable in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "value": I.get("value"), "type": observable_type})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
client().stix_cyber_observable.delete(id=I.get("id"))
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't delete observable in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": "Observable deleted."})
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
field = I.get("field")
|
||||||
|
observable_id = I.get("id")
|
||||||
|
value = I.get("value")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if field == "marking":
|
||||||
|
result = client().stix_cyber_observable.add_marking_definition(
|
||||||
|
id=observable_id, marking_definition_id=value
|
||||||
|
)
|
||||||
|
elif field == "label":
|
||||||
|
result = client().stix_cyber_observable.add_label(id=observable_id, label_id=value)
|
||||||
|
else:
|
||||||
|
fail("Invalid field was provided.")
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't add field to observable in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
out({"success": True, "message": "Added {0}.".format(field)})
|
||||||
|
else:
|
||||||
|
fail("Can't add {0} to observable in OpenCTI.".format(field))
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
field = I.get("field")
|
||||||
|
observable_id = I.get("id")
|
||||||
|
value = I.get("value")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if field == "marking":
|
||||||
|
result = client().stix_cyber_observable.remove_marking_definition(
|
||||||
|
id=observable_id, marking_definition_id=value
|
||||||
|
)
|
||||||
|
elif field == "label":
|
||||||
|
result = client().stix_cyber_observable.remove_label(id=observable_id, label_id=value)
|
||||||
|
else:
|
||||||
|
fail("Invalid field was provided.")
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't remove field from observable in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": "Removed {0}.".format(field)})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
KEY_TO_CTI_NAME = {"description": "x_opencti_description", "score": "x_opencti_score"}
|
||||||
|
|
||||||
|
|
||||||
|
field = I.get("field")
|
||||||
|
if field not in KEY_TO_CTI_NAME:
|
||||||
|
fail("Invalid field was provided.")
|
||||||
|
key = KEY_TO_CTI_NAME[field]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().stix_cyber_observable.update_field(id=I.get("id"), key=key, value=I.get("value"))
|
||||||
|
except Exception as e:
|
||||||
|
fail("Can't update observable field in OpenCTI.", detail=str(e))
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "message": "Observable updated."})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient, Identity
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
identity = Identity(client())
|
||||||
|
result = identity.create(
|
||||||
|
name=I.get("name"),
|
||||||
|
type="Organization",
|
||||||
|
x_opencti_reliability=I.get("reliability"),
|
||||||
|
description=I.get("description"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to create organization.", detail=str(e))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
fail("Failed to create organization.")
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "message": f"Organization created. id: {result.get('id')}"})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
orgs = client().identity.list(
|
||||||
|
types="Organization",
|
||||||
|
first=as_int(I.get("limit", 50), 50),
|
||||||
|
after=I.get("last_run_id"),
|
||||||
|
withPagination=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to list organizations.", detail=str(e))
|
||||||
|
|
||||||
|
out({
|
||||||
|
"organizations": [{"name": o.get("name"), "id": o.get("id")} for o in orgs.get("entities", [])],
|
||||||
|
"organizationsLastRun": orgs.get("pagination", {}).get("endCursor"),
|
||||||
|
})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = client().stix_core_relationship.create(
|
||||||
|
fromId=I.get("from_id"),
|
||||||
|
toId=I.get("to_id"),
|
||||||
|
relationship_type=I.get("relationship_type") or "related-to",
|
||||||
|
description=I.get("description"),
|
||||||
|
confidence=as_int(I.get("confidence")) or None,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to create relationship.", detail=str(e))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
fail("Failed to create relationship.")
|
||||||
|
|
||||||
|
out({"id": result.get("id"), "relationshipType": result.get("relationship_type")})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
client().stix_core_relationship.delete(id=I.get("id"))
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to delete relationship.", detail=str(e))
|
||||||
|
|
||||||
|
out({"success": True, "message": "Relationship deleted."})
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
rels = client().stix_core_relationship.list(
|
||||||
|
fromOrToId=I.get("from_id"),
|
||||||
|
first=as_int(I.get("limit", 50), 50),
|
||||||
|
after=I.get("last_run_id"),
|
||||||
|
withPagination=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Failed to list relationships.", detail=str(e))
|
||||||
|
|
||||||
|
cursor = rels.get("pagination", {}).get("endCursor")
|
||||||
|
|
||||||
|
relationships = []
|
||||||
|
for rel in rels.get("entities", []):
|
||||||
|
relationships.append({
|
||||||
|
"id": rel.get("id"),
|
||||||
|
"relationshipType": rel.get("relationship_type"),
|
||||||
|
"fromId": rel["from"]["id"],
|
||||||
|
"toId": rel["to"]["id"],
|
||||||
|
"toEntityType": rel["to"]["entity_type"],
|
||||||
|
})
|
||||||
|
|
||||||
|
out({"relationships": relationships, "relationshipsLastRun": cursor})
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import json, os, sys
|
||||||
|
|
||||||
|
S = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||||
|
I = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||||
|
|
||||||
|
|
||||||
|
def out(value):
|
||||||
|
print(json.dumps(value, default=str))
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message, **extra):
|
||||||
|
payload = {"error": message}
|
||||||
|
payload.update(extra)
|
||||||
|
print(json.dumps(payload, default=str))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def as_bool(v):
|
||||||
|
return v if isinstance(v, bool) else str(v).lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
|
|
||||||
|
def as_int(v, default=None):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(v):
|
||||||
|
if isinstance(v, list):
|
||||||
|
return v
|
||||||
|
if v in (None, ""):
|
||||||
|
return []
|
||||||
|
return [x.strip() for x in str(v).split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pycti import OpenCTIApiClient
|
||||||
|
except ImportError as e:
|
||||||
|
fail("The 'pycti' Python library is required for the OpenCTI integration. "
|
||||||
|
"Install it on the execution host (engine): pip install pycti", detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def client():
|
||||||
|
base = str(S.get("base_url", "")).strip().rstrip("/")
|
||||||
|
api_key = S.get("api_key") or (S.get("credentials") or {}).get("password")
|
||||||
|
return OpenCTIApiClient(base, api_key, ssl_verify=not as_bool(S.get("insecure")), log_level="error")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
client().label.list(first=1, withPagination=True)
|
||||||
|
except Exception as e:
|
||||||
|
fail("Connection failed.", detail=str(e))
|
||||||
|
|
||||||
|
out({"status": "ok"})
|
||||||
Reference in New Issue
Block a user