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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Below is an exhaustive enumeration of all configurable parameters available, eac
| 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. |
| OPENCTI_TOKEN | `string` | ✅ | Format: [`password`](https://json-schema.org/understanding-json-schema/reference/string#built-in-formats) | | The API token to connect to OpenCTI. |
| QUALYS_CVE_ENRICHMENT_USERNAME | `string` | ✅ | string | | Qualys API username. |
| QUALYS_CVE_ENRICHMENT_PASSWORD | `string` | ✅ | Format: [`password`](https://json-schema.org/understanding-json-schema/reference/string#built-in-formats) | | Qualys API password. |
| CONNECTOR_NAME | `string` | | string | `"Qualys CVE Enrichment"` | The name of the connector. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
},
"OPENCTI_TOKEN": {
"description": "The API token to connect to OpenCTI.",
"type": "string"
"format": "password",
"type": "string",
"writeOnly": true
},
"CONNECTOR_NAME": {
"default": "Qualys CVE Enrichment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"support_version": ">=7.260529.0",
"subscription_link": null,
"source_code": "https://github.com/OpenCTI-Platform/connectors/tree/master/internal-enrichment/qualys-cve-enrichment",
"manager_supported": false,
"manager_supported": true,
"container_version": "rolling",
"container_image": "opencti/connector-qualys-cve-enrichment",
"container_type": "INTERNAL_ENRICHMENT"
Expand Down
20 changes: 20 additions & 0 deletions internal-enrichment/qualys-cve-enrichment/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Expose ``ConnectorSettings`` to the manager-supported config schema generator.

At runtime the *contents* of ``src/`` are the working directory (see the
Dockerfile), so the connector imports its own modules top-level, e.g.
``from connector.settings import ConnectorSettings``.

The config schema generator instead runs from the connector root and imports
``from src import ConnectorSettings``. Adding this package's own directory to
``sys.path`` lets the connector's existing top-level import style resolve in
that context too, without changing any runtime behaviour.
"""

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from connector.settings import ConnectorSettings # isort: skip

__all__ = ["ConnectorSettings"]
48 changes: 24 additions & 24 deletions internal-enrichment/qualys-cve-enrichment/src/config.yml.sample
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
# OpenCTI Qualys CVE Enrichment Connector Configuration

Check warning on line 1 in internal-enrichment/qualys-cve-enrichment/src/config.yml.sample

View workflow job for this annotation

GitHub Actions / Lint internal-enrichment/qualys-cve-enrichment

VC104: config.yml.sample found at src/config.yml.sample (src/)
# Copy this file to config.yml and update the values
# Copy this file to config.yml and update the values.
#
# Commented-out keys are optional: their defaults are defined in
# `src/connector/settings.py`, which is the single source of truth.
# Uncomment a key only to override the default.

opencti:
# OpenCTI platform URL
url: 'http://localhost:8080'
# OpenCTI API token (from Settings > Security > Users)
token: 'changeme'
# OpenCTI platform URL (required)
url: 'ChangeMe'
# OpenCTI API token, from Settings > Security > Users (required)
token: 'ChangeMe'

connector:
# Unique connector ID (UUID v4 — generate your own, e.g. with `uuidgen`)
id: 'b6e2f0a4-3c5d-4e9a-8f1b-2d7c9a0e5f13'
# Connector type (do not change)
type: 'INTERNAL_ENRICHMENT'
# Unique connector ID, a UUID v4 (generate your own, e.g. with `uuidgen`)
id: 'ChangeMe'
# Display name in OpenCTI
name: 'Qualys CVE Enrichment'
# Entity types to enrich (Vulnerability only)
scope: 'Vulnerability'
# Confidence level for enrichment data (0-100)
confidence_level: 80
# name: 'Qualys CVE Enrichment'
# Entity types to enrich
# scope: 'vulnerability'
# Log level: debug, info, warning, error
log_level: 'info'
# log_level: 'info'
# Auto-enrich on data creation (use with caution - can consume API quota)
auto: false
# auto: false

qualys_cve_enrichment:
# Qualys API base URL
# Qualys API base URL.
# Common platforms:
# US Platform 1: https://qualysapi.qualys.com
# US Platform 2: https://qualysapi.qg2.apps.qualys.com
# EU Platform: https://qualysapi.qg3.apps.qualys.eu
base_url: 'https://qualysapi.qualys.com'
# base_url: 'https://qualysapi.qualys.com'

# Qualys credentials (account with API access)
username: 'changeme'
password: 'changeme'
# Qualys credentials, an account with API access (required)
username: 'ChangeMe'
password: 'ChangeMe'

# SSL certificate verification (set to false for self-signed certs)
ssl_verify: true
# ssl_verify: true

# API mode: 'v2' for legacy XML APIs (/api/2.0/ and /api/3.0/),
# 'modern' for QPS/VMDR JSON APIs (/qps/rest/2.0/ and /rest/2.0/)
api_version: 'v2'
# api_version: 'v2'

# Maximum TLP of an entity the connector will send to the Qualys API.
# One of: clear, white, green, amber, amber+strict, red
max_tlp: 'amber+strict'
# max_tlp: 'amber+strict'
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ pycti==7.260817.0
# OpenCTI connectors SDK
connectors-sdk @ git+https://github.com/OpenCTI-Platform/connectors.git@master#subdirectory=connectors-sdk

# Configuration validation (manager-supported settings)
pydantic >=2.8.2, <3

# HTTP requests library
requests==2.33.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
"""Tests for Qualys CVE Enrichment connector settings."""

from typing import Any
from uuid import UUID

import pytest
from connector.settings import ConnectorSettings
from connectors_sdk import ConfigValidationError

FULL_VALID_SETTINGS_DICT: dict[str, Any] = {
"opencti": {
"url": "http://localhost:8080",
"token": "test-token-00000000-0000-0000-0000-000000000000",
},
"connector": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "Qualys CVE Enrichment",
"scope": "vulnerability",
"log_level": "info",
"auto": False,
},
"qualys_cve_enrichment": {
"base_url": "https://qualysapi.qualys.com",
"username": "test-username",
"password": "test-password",
"ssl_verify": True,
"api_version": "v2",
"max_tlp": "amber+strict",
},
}

MINIMAL_VALID_SETTINGS_DICT: dict[str, Any] = {
"opencti": {
"url": "http://localhost:8080",
"token": "test-token-00000000-0000-0000-0000-000000000000",
},
"connector": {},
"qualys_cve_enrichment": {
"username": "test-username",
"password": "test-password",
},
}


class TestConnectorSettingsInstantiation:
"""Test that ConnectorSettings can be created with valid env vars."""
Expand Down Expand Up @@ -104,3 +141,107 @@ def test_missing_password(self, monkeypatch):
monkeypatch.delenv("QUALYS_CVE_ENRICHMENT_PASSWORD")
with pytest.raises(ConfigValidationError):
ConnectorSettings()


class TestSettingsValidationFromConfigDict:
"""Validate settings from an explicit config dict.

These tests bypass env/config-var loading entirely by overriding
``_load_config_dict``, so they are independent of the ambient environment.
"""

@staticmethod
def _settings_from(config_dict: dict[str, Any]) -> ConnectorSettings:
class FakeConnectorSettings(ConnectorSettings):
@classmethod
def _load_config_dict(cls, _, handler) -> Any:
return handler(config_dict)

return FakeConnectorSettings()

@pytest.mark.parametrize(
"config_dict",
[
pytest.param(FULL_VALID_SETTINGS_DICT, id="full_valid_settings_dict"),
pytest.param(MINIMAL_VALID_SETTINGS_DICT, id="minimal_valid_settings_dict"),
],
)
def test_settings_should_accept_valid_input(self, config_dict):
settings = self._settings_from(config_dict)

assert (
settings.opencti.token.get_secret_value()
== "test-token-00000000-0000-0000-0000-000000000000"
)
assert settings.connector.type == "INTERNAL_ENRICHMENT"
assert settings.qualys_cve_enrichment.username == "test-username"
assert (
settings.qualys_cve_enrichment.password.get_secret_value()
== "test-password"
)
assert isinstance(settings.to_helper_config(), dict)

def test_minimal_settings_should_apply_declared_defaults(self):
"""Omitted optional fields fall back to the defaults in settings.py."""
settings = self._settings_from(MINIMAL_VALID_SETTINGS_DICT)

assert settings.connector.name == "Qualys CVE Enrichment"
assert settings.qualys_cve_enrichment.ssl_verify is True
assert settings.qualys_cve_enrichment.api_version == "v2"
assert settings.qualys_cve_enrichment.max_tlp == "amber+strict"

def test_settings_should_default_connector_id(self):
"""The connector id falls back on its unique default UUID v4."""
settings = self._settings_from(MINIMAL_VALID_SETTINGS_DICT)

assert settings.connector.id == "061407e3-c49c-4c4d-94ef-88da16dbeb52"
assert UUID(settings.connector.id).version == 4

@pytest.mark.parametrize(
"config_dict",
[
pytest.param({}, id="empty_settings_dict"),
pytest.param(
{
"opencti": {"url": "http://localhost:8080"},
"connector": {},
"qualys_cve_enrichment": {
"username": "test-username",
"password": "test-password",
},
},
id="missing_opencti_token",
),
pytest.param(
{
"opencti": {
"url": "http://localhost:8080",
"token": "test-token",
},
"connector": {},
"qualys_cve_enrichment": {
"username": "test-username",
"password": "test-password",
"api_version": "not-a-valid-version",
},
},
id="invalid_api_version",
),
pytest.param(
{
"opencti": {
"url": "http://localhost:8080",
"token": "test-token",
},
"connector": {},
"qualys_cve_enrichment": {"username": "test-username"},
},
id="missing_qualys_password",
),
],
)
def test_settings_should_raise_when_invalid_input(self, config_dict):
with pytest.raises(ConfigValidationError) as exc_info:
self._settings_from(config_dict)

assert "Error validating configuration" in str(exc_info.value)
Loading
Loading