Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions stream/cloudflare-rules-list/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# OpenCTI
OPENCTI_URL=http://localhost:8080

Check failure on line 2 in stream/cloudflare-rules-list/.env.sample

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC102: OPENCTI_URL=http://localhost:8080 — expected http://localhost
OPENCTI_TOKEN=ChangeMe

# Connector
CONNECTOR_ID=ChangeMe
CONNECTOR_TYPE=STREAM
CONNECTOR_NAME=Cloudflare Rules List
CONNECTOR_SCOPE=cloudflare
CONNECTOR_LOG_LEVEL=info
CONNECTOR_LIVE_STREAM_ID=live
CONNECTOR_LIVE_STREAM_LISTEN_DELETE=true
CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES=true
# Minimum interval between snapshot uploads ('30m', '1h', '1h30m', or seconds)
CONNECTOR_SYNC_INTERVAL=1h

# Cloudflare
CLOUDFLARE_ACCOUNT_ID=ChangeMe

Check warning on line 18 in stream/cloudflare-rules-list/.env.sample

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_ACCOUNT_ID has no valid connector prefix
# API token with 'Account > Account Filter Lists > Edit' permission
CLOUDFLARE_API_TOKEN=ChangeMe

Check warning on line 20 in stream/cloudflare-rules-list/.env.sample

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_API_TOKEN has no valid connector prefix
# ID of the existing Cloudflare Rules List (IP kind) to sync into
CLOUDFLARE_LIST_ID=ChangeMe

Check warning on line 22 in stream/cloudflare-rules-list/.env.sample

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_LIST_ID has no valid connector prefix
# Base URL of the Cloudflare API. Override only for testing or a compatible gateway.
# CLOUDFLARE_API_BASE_URL=https://api.cloudflare.com/client/v4

Check warning on line 24 in stream/cloudflare-rules-list/.env.sample

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_API_BASE_URL has no valid connector prefix
19 changes: 19 additions & 0 deletions stream/cloudflare-rules-list/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.12-alpine

ENV CONNECTOR_TYPE=STREAM \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

# connectors-sdk is installed from a git URL, so git/build tooling is needed at build time.
# libmagic is a runtime dependency of pycti (python-magic) and must remain installed.
COPY src /opt/opencti-connector-cloudflare-rules-list
RUN apk --no-cache add git build-base libffi-dev openssl-dev libmagic && \
cd /opt/opencti-connector-cloudflare-rules-list && \
pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir -r requirements.txt && \
apk del git build-base

COPY entrypoint.sh /

Check failure on line 16 in stream/cloudflare-rules-list/Dockerfile

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC402: Dockerfile uses entrypoint.sh at line 16
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
161 changes: 161 additions & 0 deletions stream/cloudflare-rules-list/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# OpenCTI Cloudflare Rules List Connector

A **STREAM** connector that pushes **IPv4** threat-intelligence indicators and
observables from OpenCTI into a
[Cloudflare Rules List](https://developers.cloudflare.com/waf/tools/lists/),
where they can be referenced from WAF custom rules, firewall rules, and other
Cloudflare security configurations.

## Table of Contents

- [Introduction](#introduction)
- [Requirements](#requirements)
- [Configuration variables](#configuration-variables)
- [Deployment](#deployment)
- [Behavior](#behavior)
- [Capabilities and limitations](#capabilities-and-limitations)
- [Development](#development)

## Introduction

Cloudflare Rules Lists are reusable collections of IP addresses scoped to a
Cloudflare account. This connector subscribes to an OpenCTI live stream and
keeps a target Rules List (of **IP** kind) in sync with the IPv4 indicators and
`IPv4-Addr` observables known to OpenCTI.

Cloudflare's list-items API follows a **replace/snapshot** model: each upload
replaces the full contents of the list. The connector therefore maintains an
in-memory snapshot of all known IPv4 values and pushes the complete snapshot at
most once per `CONNECTOR_SYNC_INTERVAL`.

## Requirements

- OpenCTI Platform 7.x. The connector pins `pycti==7.260715.0`; because pycti
follows the platform's CalVer scheme, its major version must match your
OpenCTI platform. Align the pin with your platform version if it differs.
- A Cloudflare account and an existing Rules List of **IP** kind
- A Cloudflare API token with the `Account > Account Filter Lists > Edit`
permission
- Python 3.11 or 3.12 (for local runs; the SDK requires `< 3.13`)

## Configuration variables

Configuration is read (in precedence order) from environment variables, then a
`config.yml` or `.env` file next to the connector, then field defaults.

A full, generated reference is available in
[`__metadata__/CONNECTOR_CONFIG_DOC.md`](__metadata__/CONNECTOR_CONFIG_DOC.md).

### OpenCTI configuration

| Parameter | config.yml | Docker env var | Mandatory | Description |
| ------------- | --------------- | ---------------- | --------- | ---------------------------------------- |
| OpenCTI URL | `opencti.url` | `OPENCTI_URL` | Yes | The base URL of the OpenCTI instance. |
| OpenCTI Token | `opencti.token` | `OPENCTI_TOKEN` | Yes | The API token to connect to OpenCTI. |

### Base connector configuration

| Parameter | config.yml | Docker env var | Default | Mandatory | Description |
| ------------------ | ---------------------------- | ---------------------------- | --------------------- | --------- | --------------------------------------------------------------------------- |
| Connector ID | `connector.id` | `CONNECTOR_ID` | / | Yes | A unique `UUIDv4` identifier for this connector instance. |
| Connector Name | `connector.name` | `CONNECTOR_NAME` | `Cloudflare Rules List` | No | Name of the connector as shown in OpenCTI. |
| Connector Scope | `connector.scope` | `CONNECTOR_SCOPE` | `cloudflare` | No | The scope of the stream connector (comma-separated). |
| Log Level | `connector.log_level` | `CONNECTOR_LOG_LEVEL` | `error` | No | `debug`, `info`, `warn`, `warning`, or `error`. |
| Live Stream ID | `connector.live_stream_id` | `CONNECTOR_LIVE_STREAM_ID` | / | Yes | The ID of the OpenCTI live stream to connect to (e.g. `live`). |
| Listen Delete | `connector.live_stream_listen_delete` | `CONNECTOR_LIVE_STREAM_LISTEN_DELETE` | `true` | No | Whether to receive delete events from the live stream. Must be `true` for the connector to drop removed IPs from the list. |
| No Dependencies | `connector.live_stream_no_dependencies` | `CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES` | `true` | No | Whether to ignore object dependencies when processing live-stream events. |
| Sync Interval | `connector.sync_interval` | `CONNECTOR_SYNC_INTERVAL` | `1h` | No | Minimum interval between snapshot uploads (`30m`, `1h`, `1h30m`, seconds). |

### Cloudflare configuration

| Parameter | config.yml | Docker env var | Default | Mandatory | Description |
| -------------------- | ---------------------- | ----------------------- | ------- | --------- | --------------------------------------------------------------------- |
| Account ID | `cloudflare.account_id`| `CLOUDFLARE_ACCOUNT_ID` | / | Yes | Cloudflare account ID that owns the Rules List. |
| API Token | `cloudflare.api_token` | `CLOUDFLARE_API_TOKEN` | / | Yes | API token with `Account > Account Filter Lists > Edit` permission. |
| List ID | `cloudflare.list_id` | `CLOUDFLARE_LIST_ID` | / | Yes | ID of the existing Rules List (IP kind) to sync into. |
| API Base URL | `cloudflare.api_base_url` | `CLOUDFLARE_API_BASE_URL` | `https://api.cloudflare.com/client/v4` | No | Base URL of the Cloudflare API. Override only for testing or a compatible gateway. |

> The Rules List must already exist and be of **IP** kind. Create it under
> Cloudflare → Manage Account → Configurations → Lists, then copy its ID.

## Deployment

### Docker Deployment

Build the image and start the container:

```shell
docker compose up -d
# -or, if you've made local changes-
docker compose up -d --build
```

### Manual Deployment

Create a `config.yml` from the sample, fill in your values, then:

```shell
cd src
pip install -r requirements.txt
python main.py
```

`git` must be available when installing requirements because `connectors-sdk` is
fetched from its repository.

## Behavior

1. **Verify** the configured Cloudflare Rules List exists (and log its kind).
2. **Full sync** on startup: load all IPv4 indicators and `IPv4-Addr`
observables from OpenCTI into an in-memory snapshot, then immediately push
that snapshot to Cloudflare.
3. **Listen** to the OpenCTI live stream — cache IPv4 values on create/update,
drop them on delete.
4. **Replace** the entire Cloudflare list with the current snapshot, then poll
the resulting bulk operation to completion. This push is triggered by
live-stream events and throttled to **at most once per
`CONNECTOR_SYNC_INTERVAL`** — an idle stream produces no uploads even after
the interval elapses.

IPv4 values are extracted from three shapes: STIX indicators with an
`[ipv4-addr:value = '...']` pattern, STIX SCOs with `type: "ipv4-addr"`, and
OpenCTI observables with `entity_type: "IPv4-Addr"`.

Each entry written to the Cloudflare list is tagged with a comment of the form
`OpenCTI: <id>`, recording the source OpenCTI object ID.

## Capabilities and limitations

- **IPv4 only.** IPv6 addresses, domains, URLs, file hashes, and every other
indicator/observable type are ignored. Indicators match only when their STIX
pattern is `[ipv4-addr:value = '...']` (a single address, or a CIDR inside the
quotes); compound patterns contribute only their first IPv4 value.
- **No built-in score, label, confidence, or marking filtering.** Every IPv4 the
connector sees is pushed to the list. To restrict *which* entities reach the
connector, scope the OpenCTI **live stream definition** (e.g. by score or
labels) — the connector itself applies no filtering.
- **The startup full sync is not stream-filtered.** It loads *all* IPv4
indicators and `IPv4-Addr` observables from the platform via the OpenCTI API,
regardless of the live stream's filters. Only the incremental live updates
honor the stream definition, so a filtered stream and the full sync can
disagree on what belongs in the list.
- **State is in-memory.** The snapshot is rebuilt by a full sync on every
restart; nothing is persisted locally.
- **Removals happen on delete events only.** A revoked or expired indicator is
dropped from the list when its delete event arrives, which requires
`CONNECTOR_LIVE_STREAM_LISTEN_DELETE=true`.
- **The Cloudflare list is owned by the connector.** Because each sync *replaces*
the entire list, any items added to it outside the connector are overwritten on
the next push. Use a dedicated list.

## Development

Run the test suite (requires Python 3.11/3.12):

```shell
pip install -r tests/test-requirements.txt pytest-cov
pytest tests/ --cov=cloudflare_rules_list --cov-report=term-missing
```

`pytest-cov` is installed explicitly above (it is intentionally not listed in
`tests/test-requirements.txt`).
22 changes: 22 additions & 0 deletions stream/cloudflare-rules-list/__metadata__/CONNECTOR_CONFIG_DOC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Connector Configurations

Below is an exhaustive enumeration of all configurable parameters available, each accompanied by detailed explanations of their purposes, default behaviors, and usage guidelines to help you understand and utilize them effectively.

### Type: `object`

| Property | Type | Required | Possible values | Default | Description |
| -------- | ---- | -------- | --------------- | ------- | ----------- |
| OPENCTI_URL | `string` | ✅ | Format: [`uri`](https://json-schema.org/understanding-json-schema/reference/string#built-in-formats) | | The base URL of the OpenCTI instance. |
| OPENCTI_TOKEN | `string` | ✅ | string | | The API token to connect to OpenCTI. |
| CONNECTOR_LIVE_STREAM_ID | `string` | ✅ | string | | The ID of the live stream to connect to. |
| CLOUDFLARE_ACCOUNT_ID | `string` | ✅ | string | | Cloudflare account ID that owns the Rules List. |
| CLOUDFLARE_API_TOKEN | `string` | ✅ | Format: [`password`](https://json-schema.org/understanding-json-schema/reference/string#built-in-formats) | | Cloudflare API token with the 'Account > Account Filter Lists > Edit' permission. |
| CLOUDFLARE_LIST_ID | `string` | ✅ | string | | ID of the existing Cloudflare Rules List (IP kind) to sync into. |
| CONNECTOR_NAME | `string` | | string | `"Cloudflare Rules List"` | The name of the connector. |
| CONNECTOR_SCOPE | `array` | | string | `["cloudflare"]` | The scope of the stream connector. |
| CONNECTOR_LOG_LEVEL | `string` | | `debug` `info` `warn` `warning` `error` | `"error"` | The minimum level of logs to display. |
| CONNECTOR_TYPE | `const` | | `STREAM` | `"STREAM"` | |
| CONNECTOR_LIVE_STREAM_LISTEN_DELETE | `boolean` | | boolean | `true` | Whether to listen for delete events on the live stream. |
| CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES | `boolean` | | boolean | `true` | Whether to ignore dependencies when processing events from the live stream. |
| CONNECTOR_SYNC_INTERVAL | `string` | | string | `"1h"` | Minimum interval between snapshot uploads to Cloudflare. Accepts a duration like '30m', '1h', '1h30m', or a bare number of seconds. |
| CLOUDFLARE_API_BASE_URL | `string` | | string | `"https://api.cloudflare.com/client/v4"` | Base URL of the Cloudflare API. Override only for testing against a mock server or a Cloudflare-compatible gateway. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://www.filigran.io/connectors/cloudflare-rules-list_config.schema.json",
"type": "object",
"properties": {
"OPENCTI_URL": {
"description": "The base URL of the OpenCTI instance.",
"format": "uri",
"maxLength": 2083,
"minLength": 1,
"type": "string"
},
"OPENCTI_TOKEN": {
"description": "The API token to connect to OpenCTI.",
"type": "string"
},
"CONNECTOR_NAME": {
"default": "Cloudflare Rules List",
"description": "The name of the connector.",
"type": "string"
},
"CONNECTOR_SCOPE": {
"default": [
"cloudflare"
],
"description": "The scope of the stream connector.",
"items": {
"type": "string"
},
"type": "array"
},
"CONNECTOR_LOG_LEVEL": {
"default": "error",
"description": "The minimum level of logs to display.",
"enum": [
"debug",
"info",
"warn",
"warning",
"error"
],
"type": "string"
},
"CONNECTOR_TYPE": {
"const": "STREAM",
"default": "STREAM",
"type": "string"
},
"CONNECTOR_LIVE_STREAM_ID": {
"description": "The ID of the live stream to connect to.",
"type": "string"
},
"CONNECTOR_LIVE_STREAM_LISTEN_DELETE": {
"default": true,
"description": "Whether to listen for delete events on the live stream.",
"type": "boolean"
},
"CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES": {
"default": true,
"description": "Whether to ignore dependencies when processing events from the live stream.",
"type": "boolean"
},
"CONNECTOR_SYNC_INTERVAL": {
"default": "1h",
"description": "Minimum interval between snapshot uploads to Cloudflare. Accepts a duration like '30m', '1h', '1h30m', or a bare number of seconds.",
"type": "string"
},
"CLOUDFLARE_ACCOUNT_ID": {
"description": "Cloudflare account ID that owns the Rules List.",
"type": "string"
},
"CLOUDFLARE_API_TOKEN": {
"description": "Cloudflare API token with the 'Account > Account Filter Lists > Edit' permission.",
"format": "password",
"type": "string",
"writeOnly": true
},
"CLOUDFLARE_LIST_ID": {
"description": "ID of the existing Cloudflare Rules List (IP kind) to sync into.",
"type": "string"
},
"CLOUDFLARE_API_BASE_URL": {
"default": "https://api.cloudflare.com/client/v4",
"description": "Base URL of the Cloudflare API. Override only for testing against a mock server or a Cloudflare-compatible gateway.",
"type": "string"
}
},
"required": [
"OPENCTI_URL",
"OPENCTI_TOKEN",
"CONNECTOR_LIVE_STREAM_ID",
"CLOUDFLARE_ACCOUNT_ID",
"CLOUDFLARE_API_TOKEN",
"CLOUDFLARE_LIST_ID"
],
"additionalProperties": true
}
21 changes: 21 additions & 0 deletions stream/cloudflare-rules-list/__metadata__/connector_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"title": "Cloudflare Rules List",
"slug": "cloudflare-rules-list",
"description": "Cloudflare Rules Lists are reusable collections of IP addresses that can be referenced from WAF custom rules, firewall rules, and other security configurations across a Cloudflare account.\n\nThe integration of Cloudflare with OpenCTI enables the automatic dissemination of IPv4 threat-intelligence indicators and observables into a Cloudflare Rules List. The connector consumes IPv4 indicators from an OpenCTI live stream, maintains an in-memory snapshot, and periodically replaces the contents of a target Cloudflare Rules List (IP kind) using Cloudflare's bulk list-items API documented at https://developers.cloudflare.com/api/resources/rules/subresources/lists/.",
"short_description": "Push IPv4 threat-intelligence indicators from OpenCTI into a Cloudflare Rules List for use in WAF and firewall rules.",
"logo": "stream/cloudflare-rules-list/__metadata__/logo.png",
"use_cases": [
"Threat Intelligence"
],
"verified": false,
"last_verified_date": null,
"playbook_supported": false,
"max_confidence_level": 50,
"support_version": ">= 6.7.5",
"subscription_link": "https://developers.cloudflare.com/waf/tools/lists/",
"source_code": "https://github.com/OpenCTI-Platform/connectors/tree/master/stream/cloudflare-rules-list",
"manager_supported": true,
"container_version": "rolling",
"container_image": "opencti/connector-cloudflare-rules-list",
"container_type": "STREAM"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions stream/cloudflare-rules-list/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
services:
connector-cloudflare-rules-list:
image: opencti/connector-cloudflare-rules-list:rolling

Check failure on line 3 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC401: Image tag is :rolling — must be :latest
container_name: opencti-connector-cloudflare-rules-list
restart: unless-stopped
environment:
# OpenCTI
- OPENCTI_URL=${OPENCTI_URL}
- OPENCTI_TOKEN=${OPENCTI_TOKEN}

# Connector
- CONNECTOR_ID=${CONNECTOR_ID}
- CONNECTOR_TYPE=STREAM

Check warning on line 13 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC104: docker-compose.yml:13: CONNECTOR_TYPE=STREAM has a default — should be commented out
- CONNECTOR_NAME=Cloudflare Rules List

Check warning on line 14 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC104: docker-compose.yml:14: CONNECTOR_NAME=Cloudflare Rules List has a default — should be commented out
- CONNECTOR_SCOPE=cloudflare
- CONNECTOR_LOG_LEVEL=${CONNECTOR_LOG_LEVEL:-info}
- CONNECTOR_LIVE_STREAM_ID=${CONNECTOR_LIVE_STREAM_ID:-live}
- CONNECTOR_LIVE_STREAM_LISTEN_DELETE=true
- CONNECTOR_LIVE_STREAM_NO_DEPENDENCIES=true
- CONNECTOR_SYNC_INTERVAL=${CONNECTOR_SYNC_INTERVAL:-1h}

# Cloudflare
- CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}

Check warning on line 23 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_ACCOUNT_ID has no valid connector prefix
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}

Check warning on line 24 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_API_TOKEN has no valid connector prefix
- CLOUDFLARE_LIST_ID=${CLOUDFLARE_LIST_ID}

Check warning on line 25 in stream/cloudflare-rules-list/docker-compose.yml

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC103: CLOUDFLARE_LIST_ID has no valid connector prefix
3 changes: 3 additions & 0 deletions stream/cloudflare-rules-list/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
cd /opt/opencti-connector-cloudflare-rules-list
python3 main.py
11 changes: 11 additions & 0 deletions stream/cloudflare-rules-list/src/cloudflare_rules_list/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""OpenCTI -> Cloudflare Rules List stream connector (v2).

Pushes IPv4 threat-intelligence indicators from OpenCTI into a Cloudflare
Rules List, built on the modern OpenCTI ``connectors-sdk`` + Pydantic-settings
architecture (OpenCTI 7.x / pycti 7.x).
"""

from .connector import Connector

Check failure on line 8 in stream/cloudflare-rules-list/src/cloudflare_rules_list/__init__.py

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC309: Relative import: from .connector import Connector
from .settings import ConnectorSettings

Check failure on line 9 in stream/cloudflare-rules-list/src/cloudflare_rules_list/__init__.py

View workflow job for this annotation

GitHub Actions / Lint stream/cloudflare-rules-list

VC309: Relative import: from .settings import ConnectorSettings

__all__ = ["Connector", "ConnectorSettings"]
Loading
Loading