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
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*** xref:installation/advanced/jdk-version.adoc[JDK version]
*** xref:installation/advanced/platform-architecture.adoc[Platform architecture]
*** xref:installation/advanced/resources.adoc[Resource management]
*** xref:installation/advanced/multi-namespace.adoc[Watching multiple namespaces]
*** xref:installation/advanced/multi.adoc[Multiple Operators]
*** xref:installation/advanced/http-proxy.adoc[HTTP Proxy]
*** xref:installation/advanced/offline.adoc[Offline]
Expand Down
8 changes: 7 additions & 1 deletion docs/modules/ROOT/pages/installation/advanced/advanced.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ You can install one or more Camel K operators (which must share CRDs). In such c

Camel K operator can work in **global** (descoped) mode by watching all namespaces or **namespaced** (scoped). This behavior is controlled by the WATCH_NAMESPACE environment variable. If set to an empty value, then the operator will watch for resources in all namespaces. If it set to a given namespace, then it will only watch for resources in such a namespace.

`WATCH_NAMESPACE` also accepts a **comma-separated list** of namespaces (e.g. `team-a,team-b`), in which case the operator watches exactly those namespaces (plus its own). For a multi-tenant cluster where the set of namespaces changes over time, the operator can instead **discover namespaces dynamically by label** through the `WATCH_NAMESPACE_SELECTOR` environment variable. Both modes keep the operator's RBAC scoped to the watched namespaces instead of granting cluster-wide access. See xref:installation/advanced/multi-namespace.adoc[Watching multiple namespaces] for the full setup.

== Resource management

We provide certain configuration to better "operationalize" the Camel K Operator. More detailed information on the xref:installation/advanced/resources.adoc[resource management] page.
Expand Down Expand Up @@ -43,7 +45,11 @@ The following environment variables can be configured on the operator Deployment

|`WATCH_NAMESPACE`
|_empty_
|The namespace(s) the operator watches for Integration resources. When not set or empty, the operator watches all namespaces.
|The namespace(s) the operator watches for Integration resources. When not set or empty, the operator watches all namespaces (global mode). A single namespace or a comma-separated list (e.g. `team-a,team-b`) scopes the operator to those namespaces (plus its own). See xref:installation/advanced/multi-namespace.adoc[Watching multiple namespaces].

|`WATCH_NAMESPACE_SELECTOR`
|_empty_
|A Kubernetes label selector (e.g. `camel-k-enabled=true`). When set, the operator dynamically discovers and watches every namespace whose labels match, in addition to those listed in `WATCH_NAMESPACE`, and restarts itself when that set changes. Requires read-only access to namespace metadata. See xref:installation/advanced/multi-namespace.adoc[Watching multiple namespaces].

|`NAMESPACE`
|_auto-detected_
Expand Down
117 changes: 117 additions & 0 deletions docs/modules/ROOT/pages/installation/advanced/multi-namespace.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
[[advanced-installation-multi-namespace]]
= Watching multiple namespaces

The Camel K operator traditionally runs in one of two modes: **global** (it watches every namespace
through cluster-wide RBAC) or **single-namespace** (it watches only its own namespace). On a large
multi-tenant cluster neither is ideal: global mode gives the operator cluster-wide access to every
namespace's secrets and workloads, while single-namespace mode cannot cover many teams.

The operator can instead watch an explicit, opt-in **set** of namespaces — either listed statically
or discovered dynamically by a namespace label — while keeping its RBAC scoped to only those
namespaces. This page describes both modes.

NOTE: Multi-namespace mode is a form of the namespaced (non-global) operator: keep `global` set to
`false`. It is fully backward compatible — leaving the new settings empty preserves the default
single-namespace behavior.

[[multi-namespace-static]]
== Static list of namespaces

Set `WATCH_NAMESPACE` to a comma-separated list of namespaces. The operator watches exactly those
namespaces, plus its own (where the `IntegrationPlatform` and leader-election lease live).

With Helm:

[source,shell]
----
helm install camel-k ./helm/camel-k -n camel-k-system \
--set operator.global=false \
--set "operator.watchNamespaces={team-a,team-b}"
----

This mode needs **no** cluster-scoped RBAC. Install the namespaced operator RBAC into each watched
namespace (see <<multi-namespace-rbac>>). Adding or removing a namespace from the list requires a
`helm upgrade` (the operator restarts to pick up the new list).

[[multi-namespace-dynamic]]
== Dynamic discovery by label

Set `WATCH_NAMESPACE_SELECTOR` to a Kubernetes label selector. The operator discovers every
namespace whose labels match, watches them (in addition to anything listed in `WATCH_NAMESPACE`),
and **restarts itself automatically** whenever that set changes — for example when a namespace gains
or loses the label, or is deleted.

[source,shell]
----
helm install camel-k ./helm/camel-k -n camel-k-system \
--set operator.global=false \
--set operator.watchNamespaceSelector="camel-k-enabled=true"
----

To onboard a team namespace, **install the RBAC first, then label it** (the label is the signal that
the namespace is ready):

[source,shell]
----
# 1. Install the namespaced operator RBAC, bound to the operator's ServiceAccount.
./script/install_namespace_rbac.sh team-a camel-k-system

# 2. Label the namespace so the operator starts watching it.
kubectl label namespace team-a camel-k-enabled=true
----

Removing the label (or deleting the namespace) makes the operator stop watching it. Integrations
already running in a namespace whose label was removed keep running; the operator simply stops
managing them until the label is restored.

=== How dynamic discovery works (and its limits)

controller-runtime does not allow adding or removing namespaces from a running cache. To get a
dynamic set, the operator watches `Namespace` objects matching the selector and, when the effective
set changes, **gracefully restarts** (it releases the leader lease, exits, and Kubernetes restarts
it), recomputing the watched set at startup. Reconciliation is level-based, so the brief restart
loses no work.

* The restart is automatic; you do not need to do anything beyond labelling the namespace.
* A short re-evaluation runs periodically as a safety net, so installing the RBAC *after* labelling
still self-heals without re-labelling.
* If a namespace is labelled but lacks the operator RBAC, it is skipped with a warning instead of
blocking the operator.

[[multi-namespace-security]]
== Security model

Dynamic discovery requires the operator to read namespace metadata cluster-wide. This is the **only**
cluster-scoped permission it gets: a `ClusterRole` granting `get`, `list`, `watch` on `namespaces`
and nothing else. Namespace objects contain no secrets, so this does not expose any tenant data.

All access to Integrations, Pods, Deployments, secrets, etc. remains **namespaced**: it is granted
only by the `Role`/`RoleBinding` you install in each watched namespace. A secret read in any
namespace where you have not installed that RBAC is denied. Removing a namespace's RBaC (or never
adding it) means the operator simply cannot act there.

The static-list mode needs no cluster-scoped permission at all.

[[multi-namespace-rbac]]
== Installing the per-namespace RBAC

The operator needs the namespaced operator `Role` and a `RoleBinding` (bound to the operator's
ServiceAccount in the operator namespace) in every watched namespace. The helper script renders the
canonical namespaced RBAC and applies it to a target namespace:

[source,shell]
----
./script/install_namespace_rbac.sh <watched-namespace> [operator-namespace] [operator-sa]
# e.g.
./script/install_namespace_rbac.sh team-a camel-k-system camel-k-operator
----

For the dynamic-discovery cluster role (granting read-only access to namespace metadata), the Helm
chart renders it automatically when `operator.watchNamespaceSelector` is set. For a Kustomize-based
install, apply `pkg/resources/config/rbac/namespace-watcher` and bind it to your operator
ServiceAccount.

NOTE: The operator never creates RBAC in other namespaces itself — doing so would require it to hold
cluster-wide rights to manage roles and bindings, which is exactly the cross-namespace escalation the
project threat model guards against. Installing the per-namespace RBAC is a deliberate, opt-in
administrative action.
14 changes: 11 additions & 3 deletions helm/camel-k/templates/operator-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,21 @@ spec:
- operator
env:
- name: WATCH_NAMESPACE
{{- if eq .Values.operator.global "false" }}
{{- if eq .Values.operator.global "true" }}
value: ""
{{- else if .Values.operator.watchNamespaces }}
value: {{ join "," .Values.operator.watchNamespaces | quote }}
{{- else if .Values.operator.watchNamespaceSelector }}
value: ""
{{- else }}
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- else }}
value: ""
{{- end }}
{{- if and (eq .Values.operator.global "false") .Values.operator.watchNamespaceSelector }}
- name: WATCH_NAMESPACE_SELECTOR
value: {{ .Values.operator.watchNamespaceSelector | quote }}
{{- end }}
- name: LOG_LEVEL
value: {{ .Values.operator.logLevel }}
- name: OPERATOR_NAME
Expand Down
6 changes: 3 additions & 3 deletions helm/camel-k/templates/rbacs-common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ kind: ClusterRole
metadata:
labels:
app: camel-k
name: registry-reader
name: {{ include "camel-k.fullname" . }}-registry-reader
rules:
- apiGroups:
- ""
Expand Down Expand Up @@ -179,11 +179,11 @@ kind: ClusterRoleBinding
metadata:
labels:
app: camel-k
name: registry-reader-binding
name: {{ include "camel-k.fullname" . }}-registry-reader-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: registry-reader
name: {{ include "camel-k.fullname" . }}-registry-reader
subjects:
- kind: ServiceAccount
name: {{ include "camel-k.fullname" . }}-operator
Expand Down
57 changes: 57 additions & 0 deletions helm/camel-k/templates/rbacs-namespace-watcher.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ---------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ---------------------------------------------------------------------------

# Rendered only for a non-global operator that uses dynamic namespace discovery
# (operator.watchNamespaceSelector). It grants the operator ServiceAccount read-only access to
# namespace metadata cluster-wide so it can discover which namespaces carry the watch label. This
# is the ONLY cluster-scoped permission the operator needs in this mode; it does NOT grant access
# to secrets, pods, or any workload in any namespace. Per-namespace access stays governed by the
# namespaced Role/RoleBinding installed in each watched namespace.
{{- if and (eq .Values.operator.global "false") .Values.operator.watchNamespaceSelector }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "camel-k.fullname" . }}-operator-namespace-watcher
labels:
app: camel-k
{{- include "camel-k.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "camel-k.fullname" . }}-operator-namespace-watcher
labels:
app: camel-k
{{- include "camel-k.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ include "camel-k.fullname" . }}-operator
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "camel-k.fullname" . }}-operator-namespace-watcher
{{- end }}
17 changes: 17 additions & 0 deletions helm/camel-k/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ fullnameOverride: ""
operator:
image: docker.io/apache/camel-k:2.11.0-SNAPSHOT
global: "false"
# watchNamespaces lets a non-global ("global: false") operator watch an explicit set of
# namespaces instead of just its own. List the namespaces here, e.g.:
# watchNamespaces:
# - team-a
# - team-b
# The operator always also watches its own (release) namespace. Each listed namespace must have
# the namespaced operator RBAC installed in it (see the multi-namespace installation docs).
# Leave empty for the default single-namespace behavior. Ignored when global is "true".
watchNamespaces: []
# watchNamespaceSelector enables dynamic, label-based namespace discovery for a non-global
# operator. When set (e.g. "camel-k-enabled=true"), the operator watches every namespace whose
# labels match the selector and restarts itself whenever that set changes. This renders a
# minimal cluster-scoped ClusterRole granting read-only access to namespace metadata only
# (get/list/watch on namespaces) bound to the operator ServiceAccount — no cluster-wide access
# to secrets or workloads. Each matched namespace must have the namespaced operator RBAC
# installed in it. Leave empty to disable dynamic discovery. Ignored when global is "true".
watchNamespaceSelector: ""
resources: {}
securityContext: {}
tolerations: []
Expand Down
Loading