Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs/providers/documentation/cilium-provider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ The Cilium provider leverages Hubble's network flow data to automatically discov
| Parameter | Description | Example |
|-----------|-------------|----------|
| `cilium_base_endpoint` | The base endpoint of the Cilium Hubble relay | `localhost:4245` |
| `use_tls` | Connect to the Hubble relay over TLS | `true` |
| `ca_certificate` | CA certificate (PEM) used to verify the Hubble relay server | `-----BEGIN CERTIFICATE-----` |
| `client_certificate` | Client certificate (PEM) for mutual TLS | `-----BEGIN CERTIFICATE-----` |
| `client_key` | Client private key (PEM) for mutual TLS | `-----BEGIN PRIVATE KEY-----` |

## Outputs

Expand Down
10 changes: 7 additions & 3 deletions docs/snippets/providers/cilium-snippet-autogenerated.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
Do not edit it manually, as it will be overwritten */}

## Authentication
This provider requires authentication.
- **cilium_base_endpoint**: The base endpoint of the cilium hubble relay (required: True, sensitive: False)
- **use_tls**: Connect to the hubble relay over TLS (required: False, sensitive: False)
- **ca_certificate**: CA certificate (PEM) used to verify the hubble relay server (required: False, sensitive: True)
- **client_certificate**: Client certificate (PEM) for mutual TLS (required: False, sensitive: True)
- **client_key**: Client private key (PEM) for mutual TLS (required: False, sensitive: True)


## In workflows
Expand All @@ -14,6 +18,6 @@ This provider can't be used as a "step" or "action" in workflows. If you want to


## Topology
This provider pulls [topology](/overview/servicetopology) to Keep. It could be used in [correlations](/overview/correlation-topology)
and [mapping](/overview/enrichment/mapping#mapping-with-topology-data), and as a context
This provider pulls [topology](/overview/servicetopology) to Keep. It could be used in [correlations](/overview/correlation-topology)
and [mapping](/overview/enrichment/mapping#mapping-with-topology-data), and as a context
for [alerts](/alerts/sidebar#7-alert-topology-view) and [incidents](/overview#17-incident-topology).
54 changes: 52 additions & 2 deletions keep/providers/cilium_provider/cilium_provider.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import dataclasses
from collections import defaultdict

import grpc
import pydantic

from keep.api.models.db.topology import TopologyServiceInDto
from keep.contextmanager.contextmanager import ContextManager
from keep.providers.base.base_provider import BaseTopologyProvider
from keep.providers.cilium_provider.secure_channel import build_cilium_channel
from keep.providers.models.provider_config import ProviderConfig
from keep.validation.fields import NoSchemeUrl

Expand All @@ -25,6 +25,50 @@ class CiliumProviderAuthConfig:
}
)

use_tls: bool = dataclasses.field(
default=False,
metadata={
"name": "use_tls",
"description": "Connect to the hubble relay over TLS",
"required": False,
"sensitive": False,
"type": "switch",
},
)

ca_certificate: str = dataclasses.field(
default="",
metadata={
"name": "ca_certificate",
"description": "CA certificate (PEM) used to verify the hubble relay server",
"required": False,
"sensitive": True,
"type": "file",
},
)

client_certificate: str = dataclasses.field(
default="",
metadata={
"name": "client_certificate",
"description": "Client certificate (PEM) for mutual TLS",
"required": False,
"sensitive": True,
"type": "file",
},
)

client_key: str = dataclasses.field(
default="",
metadata={
"name": "client_key",
"description": "Client private key (PEM) for mutual TLS",
"required": False,
"sensitive": True,
"type": "file",
},
)


class CiliumProvider(BaseTopologyProvider):
"""Manage Cilium provider."""
Expand Down Expand Up @@ -88,7 +132,13 @@ def pull_topology(self) -> list[TopologyServiceInDto]:
ObserverStub,
)

channel = grpc.insecure_channel(self.authentication_config.cilium_base_endpoint)
channel = build_cilium_channel(
self.authentication_config.cilium_base_endpoint,
use_tls=self.authentication_config.use_tls,
ca_certificate=self.authentication_config.ca_certificate,
client_certificate=self.authentication_config.client_certificate,
client_key=self.authentication_config.client_key,
)
stub = ObserverStub(channel)

# Create a request for the last 1000 flows
Expand Down
24 changes: 24 additions & 0 deletions keep/providers/cilium_provider/secure_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import grpc


def build_cilium_channel(
endpoint: str,
use_tls: bool = False,
ca_certificate: str = "",
client_certificate: str = "",
client_key: str = "",
) -> grpc.Channel:
"""Build a gRPC channel to the Hubble relay, secured with TLS when enabled.

When use_tls is set, the connection uses TLS; supplying client_certificate and
client_key enables mutual TLS, and ca_certificate verifies the server (falling
back to the system trust store when omitted).
"""
if not use_tls:
return grpc.insecure_channel(endpoint)
credentials = grpc.ssl_channel_credentials(
root_certificates=ca_certificate.encode() if ca_certificate else None,
certificate_chain=client_certificate.encode() if client_certificate else None,
private_key=client_key.encode() if client_key else None,
)
return grpc.secure_channel(endpoint, credentials)
54 changes: 54 additions & 0 deletions tests/test_cilium_secure_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from unittest.mock import patch

from keep.providers.cilium_provider.secure_channel import build_cilium_channel

PATCH_GRPC = "keep.providers.cilium_provider.secure_channel.grpc"


def test_insecure_channel_when_tls_disabled():
with patch(PATCH_GRPC) as mock_grpc:
channel = build_cilium_channel("localhost:4245", use_tls=False)
mock_grpc.insecure_channel.assert_called_once_with("localhost:4245")
mock_grpc.secure_channel.assert_not_called()
assert channel is mock_grpc.insecure_channel.return_value


def test_mutual_tls_passes_encoded_pem():
with patch(PATCH_GRPC) as mock_grpc:
channel = build_cilium_channel(
"relay:4245",
use_tls=True,
ca_certificate="CA_PEM",
client_certificate="CERT_PEM",
client_key="KEY_PEM",
)
mock_grpc.ssl_channel_credentials.assert_called_once_with(
root_certificates=b"CA_PEM",
certificate_chain=b"CERT_PEM",
private_key=b"KEY_PEM",
)
mock_grpc.secure_channel.assert_called_once_with(
"relay:4245", mock_grpc.ssl_channel_credentials.return_value
)
mock_grpc.insecure_channel.assert_not_called()
assert channel is mock_grpc.secure_channel.return_value


def test_server_only_tls_omits_client_material():
with patch(PATCH_GRPC) as mock_grpc:
build_cilium_channel("relay:4245", use_tls=True, ca_certificate="CA_PEM")
mock_grpc.ssl_channel_credentials.assert_called_once_with(
root_certificates=b"CA_PEM",
certificate_chain=None,
private_key=None,
)


def test_tls_without_ca_uses_system_trust():
with patch(PATCH_GRPC) as mock_grpc:
build_cilium_channel("relay:4245", use_tls=True)
mock_grpc.ssl_channel_credentials.assert_called_once_with(
root_certificates=None,
certificate_chain=None,
private_key=None,
)
Loading