Skip to content

Commit 93f16ba

Browse files
dtfranzpedjakclaude
committed
Add documentation examples for using ValidatingAdmissionPolicy to protect OLMv1 API access.
Signed-off-by: Daniel Franz <dfranz@redhat.com> Co-Authored-By: Predrag Knezevic <pknezevi@redhat.com> Co-Authored-By: Daniel Franz <dfranz@redhat.com> Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 157ceb7 commit 93f16ba

5 files changed

Lines changed: 815 additions & 5 deletions

File tree

docs/getting-started/olmv1_getting_started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ kubectl wait --for=condition=Serving=True clustercatalog/operatorhubio --timeout
5858
### Install a Cluster Extension
5959

6060
For simplicity, the following example manifest includes all necessary resources to install the ArgoCD operator.
61-
The manifest includes installation namespace and the ClusterExtension resource, which specifies the name and
61+
The manifest includes installation namespace and the ClusterExtension resource, which specifies the name and
6262
version of the extension to install. More information on installing extensions can be found [here](../tutorials/install-extension.md).
6363

6464
```bash
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# Protecting OLMv1 API Access
2+
3+
Because the operator-controller uses `cluster-admin` level privileges in its normal operations, it is important to lock down access to its associated APIs in order to prevent unauthorized or potentially malicious package installations. Access to the `ClusterExtension` and `ClusterObjectSet` APIs should ideally be limited to cluster administrators and trusted users.
4+
5+
## RBAC
6+
7+
Access to `ClusterExtension` installation or modification can be restricted by assigning appropriate roles to users or groups as follows:
8+
9+
### ClusterRole
10+
```yaml
11+
apiVersion: rbac.authorization.k8s.io/v1
12+
kind: ClusterRole
13+
metadata:
14+
name: clusterextension-viewer-role
15+
rules:
16+
- apiGroups:
17+
- olm.operatorframework.io
18+
resources:
19+
- clusterextensions
20+
- clusterobjectsets
21+
verbs:
22+
- get
23+
- list
24+
- watch
25+
---
26+
apiVersion: rbac.authorization.k8s.io/v1
27+
kind: ClusterRole
28+
metadata:
29+
name: clusterextension-installer-role
30+
rules:
31+
- apiGroups:
32+
- olm.operatorframework.io
33+
resources:
34+
- clusterextensions
35+
- clusterobjectsets
36+
verbs:
37+
- create
38+
- update
39+
- delete
40+
- get
41+
- list
42+
- watch
43+
```
44+
45+
### ClusterRoleBinding
46+
47+
Bind one of the roles above to a specific user or group. The following example grants the `clusterextension-installer-role` to user `alice` and all members of the `team-monitoring` group:
48+
49+
```yaml
50+
apiVersion: rbac.authorization.k8s.io/v1
51+
kind: ClusterRoleBinding
52+
metadata:
53+
name: clusterextension-installer-rolebinding
54+
roleRef:
55+
apiGroup: rbac.authorization.k8s.io
56+
kind: ClusterRole
57+
name: clusterextension-installer-role
58+
subjects:
59+
- kind: User
60+
name: alice
61+
apiGroup: rbac.authorization.k8s.io
62+
- kind: Group
63+
name: team-monitoring
64+
apiGroup: rbac.authorization.k8s.io
65+
```
66+
67+
## Validating Admission Policy
68+
69+
A `ValidatingAdmissionPolicy` provides much more flexibility than simple RBAC when it comes to managing access to `ClusterExtensions`. For more detailed instructions on the fundamentals of the `ValidatingAdmissionPolicy` API, take a look at the Kubernetes docs [here](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/).
70+
71+
In this example, we'll be creating a `ValidatingAdmissionPolicy` and accompanying `ValidatingAdmissionPolicyBinding` in order to manage `ClusterExtension` access for members of the `team-monitoring` group.
72+
73+
To create a `ValidatingAdmissionPolicy` for `ClusterExtensions`, begin with the following skeleton. The `matchConstraints` field tells Kubernetes which API requests the policy applies to, and `matchConditions` further narrows the scope — here, the policy only activates for users who belong to the `team-monitoring` group. Add your constraints to the `validations` list as shown in the sections below.
74+
75+
```yaml
76+
apiVersion: admissionregistration.k8s.io/v1
77+
kind: ValidatingAdmissionPolicy
78+
metadata:
79+
name: ce-policy-team-monitoring
80+
spec:
81+
failurePolicy: Fail
82+
matchConstraints:
83+
resourceRules:
84+
- apiGroups: ["olm.operatorframework.io"]
85+
apiVersions: ["v1"]
86+
resources: ["clusterextensions"]
87+
operations: ["CREATE", "UPDATE"]
88+
matchConditions:
89+
- name: only-team-monitoring
90+
expression: >-
91+
request.userInfo.groups.exists(g, g == "team-monitoring")
92+
validations:
93+
# Add validation expressions here (see sections below)
94+
---
95+
apiVersion: admissionregistration.k8s.io/v1
96+
kind: ValidatingAdmissionPolicyBinding
97+
metadata:
98+
name: ce-policy-team-monitoring
99+
spec:
100+
policyName: ce-policy-team-monitoring
101+
validationActions: ["Deny"]
102+
```
103+
104+
The following custom validations can be applied through native Kubernetes' `ValidatingAdmissionPolicy` API. Each snippet shows what to add to the `validations:` list in the policy above; combine multiple entries to enforce several constraints in a single policy.
105+
106+
### Allow a user to create/manage only specific ClusterExtensions
107+
108+
To restrict which `ClusterExtension` names a group may manage, add a name-prefix validation. In this example, members of `team-monitoring` may only create or update `ClusterExtension` resources whose names begin with `monitoring-`:
109+
110+
```yaml
111+
validations:
112+
- expression: >-
113+
object.metadata.name.startsWith("monitoring-")
114+
messageExpression: >-
115+
"ClusterExtension name must start with \"monitoring-\", got: \"" + object.metadata.name + "\""
116+
```
117+
118+
### Allow a group to manage extensions in specific namespaces
119+
120+
The `spec.namespace` field on a `ClusterExtension` determines which namespace the operator's resources are installed into. To restrict a group to one or more approved namespaces, add the following validation. In this example, `team-monitoring` may only target the `monitoring` or `observability` namespaces:
121+
122+
```yaml
123+
validations:
124+
- expression: >-
125+
object.spec.namespace in ["monitoring", "observability"]
126+
messageExpression: >-
127+
"spec.namespace must be one of [\"monitoring\", \"observability\"], got: \"" + object.spec.namespace + "\""
128+
```
129+
130+
### Restrict which ClusterCatalogs a user/group can reference
131+
132+
As risk mitigation, users and groups can be restricted to only installing ClusterExtensions which target a particular catalog. In this instance, the catalog labeled as `team=monitoring` may have been vetted to contain only packages which are safe for this team to install.
133+
134+
```yaml
135+
validations:
136+
# Must use a catalog selector that targets team=monitoring catalogs
137+
- expression: >-
138+
has(object.spec.source.catalog) &&
139+
has(object.spec.source.catalog.selector) &&
140+
has(object.spec.source.catalog.selector.matchLabels) &&
141+
"team" in object.spec.source.catalog.selector.matchLabels &&
142+
object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
143+
messageExpression: >-
144+
"spec.source.catalog.selector.matchLabels must include team=monitoring" +
145+
(has(object.spec.source.catalog.selector) &&
146+
has(object.spec.source.catalog.selector.matchLabels) &&
147+
"team" in object.spec.source.catalog.selector.matchLabels
148+
? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
149+
: ", got: <unset>")
150+
```
151+
152+
### Restrict which packages a user/group can install
153+
154+
If a user or group need only have access to a limited set of packages and versions, the following validation can be added to set those limits:
155+
156+
```yaml
157+
validations:
158+
# Package allowlist with per-package pinned minor versions
159+
- expression: >-
160+
(object.spec.source.catalog.packageName == "prometheus" &&
161+
has(object.spec.source.catalog.version) &&
162+
object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
163+
||
164+
(object.spec.source.catalog.packageName == "alertmanager" &&
165+
has(object.spec.source.catalog.version) &&
166+
object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
167+
messageExpression: >-
168+
"package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
169+
(has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
170+
"\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"
171+
```
172+
173+
### Restrict upgrade constraint policy
174+
175+
The `spec.source.catalog.upgradeConstraintPolicy` field controls whether the operator-controller respects version upgrade constraints provided by the catalog. Setting it to `SelfCertified` bypasses those constraints, which can lead to unsafe upgrades. To prevent non-admin users from overriding this:
176+
177+
```yaml
178+
validations:
179+
- expression: >-
180+
!has(object.spec.source.catalog.upgradeConstraintPolicy) ||
181+
object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
182+
message: >-
183+
team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
184+
Only cluster-admins may bypass catalog-provided upgrade constraints.
185+
```
186+
187+
### Restrict CRD upgrade safety
188+
189+
The `spec.install.preflight.crdUpgradeSafety` field controls whether the operator-controller runs pre-flight safety checks before applying CRD changes. Setting `enforcement` to `None` disables these checks, which can result in breaking changes being applied to cluster-wide CRDs. To prevent non-admin users from doing this:
190+
191+
```yaml
192+
validations:
193+
- expression: >-
194+
!has(object.spec.install) ||
195+
!has(object.spec.install.preflight) ||
196+
!has(object.spec.install.preflight.crdUpgradeSafety) ||
197+
object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
198+
message: >-
199+
team-monitoring may not disable CRD upgrade safety checks.
200+
Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".
201+
```
202+
203+
## Complete example
204+
205+
The following combines all of the above constraints into a single `ValidatingAdmissionPolicy` and `ValidatingAdmissionPolicyBinding`. Members of the `team-monitoring` group may only install `prometheus@2.54.x` or `alertmanager@0.27.x` into the `monitoring` namespace from catalogs labeled `team=monitoring`, and may not bypass upgrade constraints or disable CRD safety checks.
206+
207+
```yaml
208+
apiVersion: admissionregistration.k8s.io/v1
209+
kind: ValidatingAdmissionPolicy
210+
metadata:
211+
name: ce-policy-team-monitoring
212+
spec:
213+
failurePolicy: Fail
214+
matchConstraints:
215+
resourceRules:
216+
- apiGroups: ["olm.operatorframework.io"]
217+
apiVersions: ["v1"]
218+
resources: ["clusterextensions"]
219+
operations: ["CREATE", "UPDATE"]
220+
matchConditions:
221+
- name: only-team-monitoring
222+
expression: >-
223+
request.userInfo.groups.exists(g, g == "team-monitoring")
224+
validations:
225+
- expression: >-
226+
has(object.spec.source.catalog) &&
227+
has(object.spec.source.catalog.selector) &&
228+
has(object.spec.source.catalog.selector.matchLabels) &&
229+
"team" in object.spec.source.catalog.selector.matchLabels &&
230+
object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
231+
messageExpression: >-
232+
"spec.source.catalog.selector.matchLabels must include team=monitoring" +
233+
(has(object.spec.source.catalog.selector) &&
234+
has(object.spec.source.catalog.selector.matchLabels) &&
235+
"team" in object.spec.source.catalog.selector.matchLabels
236+
? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
237+
: ", got: <unset>")
238+
- expression: >-
239+
(object.spec.source.catalog.packageName == "prometheus" &&
240+
has(object.spec.source.catalog.version) &&
241+
object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
242+
||
243+
(object.spec.source.catalog.packageName == "alertmanager" &&
244+
has(object.spec.source.catalog.version) &&
245+
object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
246+
messageExpression: >-
247+
"package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
248+
(has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
249+
"\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"
250+
- expression: >-
251+
object.spec.namespace == "monitoring"
252+
messageExpression: >-
253+
"spec.namespace must be \"monitoring\", got: \"" + object.spec.namespace + "\""
254+
- expression: >-
255+
!has(object.spec.source.catalog.upgradeConstraintPolicy) ||
256+
object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
257+
message: >-
258+
team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
259+
Only cluster-admins may bypass catalog-provided upgrade constraints.
260+
- expression: >-
261+
!has(object.spec.install) ||
262+
!has(object.spec.install.preflight) ||
263+
!has(object.spec.install.preflight.crdUpgradeSafety) ||
264+
object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
265+
message: >-
266+
team-monitoring may not disable CRD upgrade safety checks.
267+
Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".
268+
---
269+
apiVersion: admissionregistration.k8s.io/v1
270+
kind: ValidatingAdmissionPolicyBinding
271+
metadata:
272+
name: ce-policy-team-monitoring
273+
spec:
274+
policyName: ce-policy-team-monitoring
275+
validationActions: ["Deny"]
276+
```

docs/project/olmv1_design_decisions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,13 @@ OLM v1 will include primitives (e.g. templating) to make it possible to have mul
176176

177177
However, it should be noted that the purpose of these primitives is not to enable multi-tenancy. It is to enable administrators to provide configuration for the installation of an extension. The fact that operators can be packaged as separate bundles and parameterized in a way that permits multiple controller installations is incidental, and not something that OLM v1 will encourage or promote.
178178

179-
### Make OLM secure by safeguarding ClusterExtension permissions
179+
### Secure access to ClusterExtensions through API access RBAC and ValidatingAdmissionPolicy
180180

181181
Initially, OLMv1 was designed as secure-by-default and required cluster administrators to create ServiceAccounts and associated RBAC to enable the installation of ClusterExtensions. However, any users granted permission to create ClusterExtension resources are effectively being delegated cluster-admin trust. Requiring administrators to create least-privileged RBAC for ClusterExtension installation does not remove this avenue of privilege escalation. Thus, OLMv1 adopts cluster-admin scope directly to eliminate the complexity and UX burden of requiring users to manage ServiceAccounts with difficult-to-determine permissions, and enables simpler internal operations for namespace management and content lifecycle.
182182

183-
To safeguard against potential escalation attacks, cluster administrators should treat the ability to create ClusterExtensions as equivalent to having cluster-admin privileges.
183+
Users who previously set `spec.serviceAccount` in their `ClusterExtension` manifests should remove that field. It is retained in the API for backwards compatibility — the API server will still accept it — but its value is silently ignored and will be removed in a future release. Because the operator-controller now always uses its own cluster-admin service account for all operations, cluster administrators should treat the ability to create or modify `ClusterExtension` resources as equivalent to granting cluster-admin privileges, and guard that access accordingly. To restrict which users may trigger installations, use Kubernetes RBAC on the `ClusterExtension` API; for finer-grained constraints such as limiting which packages, catalogs, namespaces, or upgrade policies a user may specify, pair RBAC with a `ValidatingAdmissionPolicy`. See [Protecting OLMv1 API Access](../howto/how-to-protect-olmv1-api-access.md) for concrete examples of both approaches.
184184

185-
Additionally, OLM v1 will use secure communication protocols between all internal components and between itself and its clients.
185+
OLM v1 also uses secure communication protocols between all internal components and between itself and its clients.
186186

187187
### Simple and predictable semantics for install, upgrade, and delete
188188

0 commit comments

Comments
 (0)