> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-trino-dialect.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Network policies

> How the operator manages Kubernetes NetworkPolicies for ClickHouse and Keeper clusters, how to allow client and monitoring traffic, and how to restrict ingress to the controller manager pod.

The operator manages Kubernetes `NetworkPolicy` resources at two levels, both
off by default:

* **Cluster policies** — per-cluster policies covering the internal traffic of
  `ClickHouseCluster` and `KeeperCluster` resources, enabled through
  `spec.networkPolicy` on each custom resource.
* **Operator pod policies** — chart-shipped policies restricting ingress to the
  controller manager pod itself for the metrics and webhook endpoints.

<Note>
  A `NetworkPolicy` is only enforced when the cluster's CNI plugin implements it
  (for example Calico or Cilium). On a CNI without NetworkPolicy enforcement the
  resources are created but silently have no effect — Kubernetes does not return an
  error. Confirm your CNI enforces policies before relying on them.
</Note>

<h2 id="cluster-network-policies">
  Cluster NetworkPolicies
</h2>

Enable the managed policy per cluster:

```yaml theme={null}
apiVersion: clickhouse.com/v1alpha1
kind: ClickHouseCluster
spec:
  networkPolicy:
    policy: Enabled
---
apiVersion: clickhouse.com/v1alpha1
kind: KeeperCluster
spec:
  networkPolicy:
    policy: Enabled
```

The managed policies cover **cluster-internal traffic only**. Selecting the pods
switches them to default deny for ingress, and the operator allows exactly what
the clusters need to function:

| Cluster    | Allowed source                                                       | Allowed ports                                  |
| ---------- | -------------------------------------------------------------------- | ---------------------------------------------- |
| ClickHouse | The cluster's own pods                                               | `9009` (interserver), `9001` (management)      |
| ClickHouse | Operator pods (label `clickhouse.com/role: operator`, any namespace) | `9001`, `9002` (management)                    |
| Keeper     | The cluster's own pods                                               | `9234` (Raft)                                  |
| Keeper     | Operator pods and every `ClickHouseCluster` referencing this keeper  | `2181`, `2281` (client), `9123` (HTTP control) |

A keeper admits ClickHouse clusters based on their `keeperClusterRef` — adding
or removing a reference updates the keeper's policy automatically, including
references from other namespaces.

<h3 id="allowing-clients">
  Allowing clients and monitoring
</h3>

Client connections and metrics scraping are **not** covered: with the managed
policy enabled, nothing can reach the client ports (`9000`/`8123`, or the TLS
variants) or the metrics port until you allow it. NetworkPolicies are additive,
so grant access with your own policy next to the managed one:

```yaml theme={null}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-clients
  namespace: <cluster-namespace>
spec:
  podSelector:
    matchLabels:
      app: <name>-clickhouse
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: my-app
    ports:
    - protocol: TCP
      port: 9000
```

The same pattern applies to Prometheus scrapes (port `9363` on ClickHouse,
`9090` on Keeper) — allow your monitoring namespace explicitly.

Setting `networkPolicy.policy: Disabled` (the default) removes the managed
policy; user-defined policies are never touched by the operator unless they
carry the cluster's `app` label.

<h3 id="np-cluster-wide-disable">
  Cluster-wide opt-out
</h3>

NetworkPolicy management can also be disabled cluster-wide via the operator's
`ENABLE_NETWORK_POLICY` environment variable. With `ENABLE_NETWORK_POLICY=false`,
the operator skips the NetworkPolicy reconcile step for **every**
ClickHouseCluster and KeeperCluster regardless of their `spec.networkPolicy.policy`,
and **does not watch** `NetworkPolicy` resources at all. The operator's
ServiceAccount therefore does not need RBAC permissions on
`networkpolicies.networking.k8s.io`, which is useful when running the operator
under a restricted ServiceAccount that intentionally omits those permissions.

```yaml theme={null}
# in the operator Deployment spec
env:
- name: ENABLE_NETWORK_POLICY
  value: "false"
```

With Helm, the same switch is exposed as a chart value:

```yaml theme={null}
# values.yaml
controller:
  networkPolicyManagement:
    enabled: false
```

<h2 id="operator-pod-policies">
  Operator pod policies
</h2>

The chart also ships optional policies that restrict which traffic can reach the
**controller manager pod** — the operator process itself. They cover the two
ports the operator exposes to other clients: the metrics endpoint and the
admission webhook.

<h2 id="what-the-helm-chart-creates">
  What the Helm chart creates
</h2>

When enabled, the chart creates up to two ingress-only policies, both selecting
the controller manager pod:

| Policy                  | Allowed source                        | Allowed port                        |
| ----------------------- | ------------------------------------- | ----------------------------------- |
| `allow-metrics-traffic` | Namespaces labeled `metrics: enabled` | `metrics.port` (default `8080`/TCP) |
| `allow-webhook-traffic` | Namespaces labeled `webhook: enabled` | `webhook.port` (default `9443`/TCP) |

Both policies declare only `policyTypes: [Ingress]`. They do not restrict egress
from the operator, and they do not touch ClickHouse server or Keeper pods.

<h2 id="default-deny">
  Default-deny behavior
</h2>

Selecting a pod with an ingress `NetworkPolicy` switches that pod to **default
deny for ingress**: once either policy applies, any inbound traffic to the
controller manager pod that is not explicitly allowed is dropped. After enabling,
the only ingress that reaches the operator is:

* a metrics scrape from a namespace labeled `metrics: enabled`, and
* an admission webhook call from a namespace labeled `webhook: enabled`.

Everything else to the pod is denied. This is the intended hardening, but it
means an unlabeled scraper or webhook caller stops working the moment the
policies take effect.

<h2 id="enabling">
  Enabling the policies
</h2>

With Helm, set the gate in your values:

```yaml theme={null}
# values.yaml
networkPolicy:
  enabled: true
```

```bash theme={null}
helm upgrade --install clickhouse-operator \
  oci://ghcr.io/clickhouse/clickhouse-operator-helm \
  -n clickhouse-operator-system --create-namespace \
  -f values.yaml
```

`allow-webhook-traffic` additionally requires `webhook.enabled: true` (the
default), so disabling the webhook also removes its policy.

With the raw `kubectl` manifests, uncomment the `[NETWORK POLICY]` section as
described in the [kubectl install guide](/products/kubernetes-operator/install/kubectl).
The raw manifests ship the same two policies.

<h2 id="labeling-namespaces">
  Labeling client namespaces
</h2>

Because both policies match the source by `namespaceSelector`, every namespace
that needs to reach the operator must carry the matching label. A scrape or
webhook call from an unlabeled namespace is dropped.

```bash theme={null}
# Allow a Prometheus namespace to scrape the metrics endpoint
kubectl label namespace <prometheus-namespace> metrics=enabled

# Allow webhook callers from a given namespace
kubectl label namespace <caller-namespace> webhook=enabled
```

Pair this with the metrics RBAC described in
[Monitoring → Securing the metrics endpoint](/products/kubernetes-operator/guides/monitoring#securing-the-metrics-endpoint):
the NetworkPolicy controls reachability, while the ClusterRole binding controls
authorization. Both must be in place for a secured scrape to succeed.

<Warning>
  Admission webhook requests originate from the Kubernetes API server, not from an
  ordinary pod. Whether that traffic is subject to a `NetworkPolicy`, and from
  which source it appears, depends on your control-plane topology and CNI —
  managed control planes in particular may reach the webhook from an address that
  no `namespaceSelector` can match. If the API server's traffic is not covered by a
  `webhook: enabled` namespace, enabling `allow-webhook-traffic` can block
  admission and make `ClickHouseCluster`/`KeeperCluster` create and update requests
  time out. Test admission on a non-production cluster after enabling, and add an
  explicit allow rule for the API server if needed.
</Warning>

<h2 id="verifying">
  Verifying
</h2>

```bash theme={null}
NS=clickhouse-operator-system

# The policies exist
kubectl -n $NS get networkpolicy

# Inspect the selectors and allowed sources
kubectl -n $NS describe networkpolicy
```

After enabling, confirm that:

* Prometheus still scrapes the metrics endpoint (its namespace is labeled
  `metrics: enabled` and bound to the metrics-reader ClusterRole).
* Creating or updating a `ClickHouseCluster` still passes admission (the webhook
  is reachable).

If a scrape returns no data or a CR apply hangs, an unlabeled source namespace or
the API server reachability caveat above is the most likely cause.

<h2 id="related-guides">
  Related guides
</h2>

* [Monitoring the operator](/products/kubernetes-operator/guides/monitoring) — the metrics endpoint, its RBAC, and securing scrapes.
* [Install with kubectl](/products/kubernetes-operator/install/kubectl) — where to uncomment the network policy section.
* [Install with Helm](/products/kubernetes-operator/install/helm) — chart values relevant to the operator.
