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
2 changes: 1 addition & 1 deletion cognite/client/_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def get_global_async_httpx_client() -> httpx.AsyncClient:

client = _global_async_httpx_clients[loop] = httpx.AsyncClient(
proxy=global_config.proxy,
verify=not global_config.disable_ssl,
verify=False if global_config.disable_ssl else (global_config.ssl_context or True),
Comment thread
haakonvt marked this conversation as resolved.
limits=httpx.Limits(
max_connections=global_config.max_connection_pool_size,
max_keepalive_connections=None,
Expand Down
12 changes: 12 additions & 0 deletions cognite/client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import getpass
import pprint
import re
import ssl
import warnings
from typing import Any, ClassVar, NoReturn, overload

Expand Down Expand Up @@ -30,6 +31,9 @@ class GlobalConfig:
max_connection_pool_size (int): The maximum number of connections which will be kept in the SDKs connection pool.
Defaults to 20.
disable_ssl (bool): Whether or not to disable SSL. Defaults to False
ssl_context (ssl.SSLContext | None): Custom SSL context for certificate verification. Overrides the
default certifi bundle. Ignored when ``disable_ssl`` is True. Must be set before the first API
request. Defaults to None. See https://cognite-sdk-python.readthedocs-hosted.com/en/latest/settings.html#ssl-certificate-configuration

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local render of docs:
Image

proxy (str | None): Route all traffic (HTTP and HTTPS) via this proxy, e.g. ``http://localhost:8030``.
For proxy authentication, embed credentials in the URL: ``http://user:pass@localhost:8030``.
Defaults to None (no proxy).
Expand Down Expand Up @@ -70,6 +74,7 @@ def __init__(self) -> None:
self.max_retry_backoff: int = 60
self.max_connection_pool_size: int = 20
self.disable_ssl: bool = False
self.ssl_context: ssl.SSLContext | None = None
self.proxy: str | None = None
self._max_workers: int = 5
self._concurrency_settings: ConcurrencySettings = ConcurrencySettings()
Expand All @@ -92,6 +97,9 @@ def __setattr__(self, name: str, val: Any) -> None:
case "file_download_chunk_size" | "file_upload_chunk_size" if val is not None and not is_positive_int(val):
raise ValueError(f"{name} must be a positive integer or None, got {val!r}")

case "ssl_context" if val is not None and not isinstance(val, ssl.SSLContext):
raise TypeError(f"ssl_context must be an ssl.SSLContext or None, got {type(val)!r}")

super().__setattr__(name, val)

@property
Expand Down Expand Up @@ -156,6 +164,10 @@ def apply_settings(self, settings: dict[str, Any] | str) -> None:
"Cannot apply 'concurrency_settings' via apply_settings. Modify the individual attributes on "
"'global_config.concurrency_settings' instead."
)
if "ssl_context" in loaded:
raise ValueError(
"Cannot apply 'ssl_context' via apply_settings. Set 'global_config.ssl_context' directly instead."
)
Comment thread
haakonvt marked this conversation as resolved.
if "default_client_config" in loaded:
if not isinstance(loaded["default_client_config"], ClientConfig):
loaded["default_client_config"] = ClientConfig.load(loaded["default_client_config"])
Expand Down
6 changes: 6 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@

python_maximum_signature_line_length = 80

# Anchors for newly added sections don't exist on readthedocs until after the PR is merged.
# List them here to unblock merging — this list can be emptied once the docs are published.
linkcheck_anchors_ignore = [
"ssl-certificate-configuration",
]

# Patch Sphinx to hide @overload signatures in docs
# Sphinx's autodoc uses ModuleAnalyzer.overloads via the parser
# We patch the analyze method to clear overloads after parsing
Expand Down
50 changes: 50 additions & 0 deletions docs/source/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,56 @@ You can set global configuration options like this:

You should **assume that these must be set prior to instantiating** an ``AsyncCogniteClient`` or ``CogniteClient`` in order for them to *take effect*.

SSL / Certificate configuration
--------------------------------
By default the SDK verifies TLS certificates against the `certifi <https://pypi.org/project/certifi/>`_ CA bundle (Mozilla's curated root list).
If your environment uses a corporate or custom root CA — for example an internal PKI — you have a few options:

**Option 1: Environment variables (no code change required)**

Set ``SSL_CERT_FILE`` or ``SSL_CERT_DIR`` before starting your process. The SDK's underlying ``httpx`` transport picks these
up automatically when no custom ``ssl_context`` is configured:

.. code:: bash

export SSL_CERT_FILE=/etc/ssl/certs/my-corp-bundle.pem

**Option 2: OS trust store**

Use Python's built-in ``ssl`` module to create a context that reads from the operating system's trust store
(macOS Keychain, Windows Certificate Store, or the system ``/etc/ssl/certs`` directory on Linux).
This is useful when your IT department pushes corporate root CAs via OS-level policy:

.. code:: python

import ssl
from cognite.client import global_config

global_config.ssl_context = ssl.create_default_context()

**Option 3: Custom CA bundle file**

Load a specific PEM file directly:

.. code:: python

import ssl
from cognite.client import global_config

global_config.ssl_context = ssl.create_default_context(cafile="/path/to/ca-bundle.pem")

**Disabling SSL verification entirely** (not recommended outside development):

.. code:: python

from cognite.client import global_config

global_config.disable_ssl = True

.. note::
``ssl_context`` and ``SSL_CERT_FILE``/``SSL_CERT_DIR`` must be configured **before the first API request** is made.
``disable_ssl = True`` takes precedence over any ``ssl_context`` that has been set.

Concurrency Settings
--------------------
The SDK allows you to control how many concurrent API requests are made for different categories of APIs
Expand Down
7 changes: 4 additions & 3 deletions tests/tests_integration/test_api/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,9 +656,10 @@ def test_list_workflow_executions(
listed = cognite_client.workflows.executions.list(
workflow_version_ids=workflow_execution_list[0].as_workflow_id()
)
# Compare by ID: cancel() can return before fields like end_time are
# finalized server-side, so full-object equality is flaky.
assert {e.id for e in listed} == {e.id for e in workflow_execution_list}
# Subset (not equality) check by ID: other tests in this class create additional
# executions against the same shared, session-scoped workflow version, and
# cancel() can return before fields like end_time are finalized server-side.
assert {e.id for e in workflow_execution_list} <= {e.id for e in listed}

def test_list_workflow_executions_by_status(
self,
Expand Down
Loading