Skip to content

Commit c520a78

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 1da807a commit c520a78

5 files changed

Lines changed: 829 additions & 4 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: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# Protecting OLMv1 API Access
2+
3+
By default, only cluster administrators have permission to read, create, modify, or delete `ClusterExtension` and `ClusterObjectSet` resources. No other users or groups have any access to these APIs unless explicitly granted by a cluster administrator.
4+
5+
Because the operator-controller runs with `cluster-admin` privileges, any user granted write access to the `ClusterExtension` API is effectively being delegated cluster-admin trust. Cluster administrators are generally discouraged from delegating this access to non-admin users. If delegation is necessary, the controls described below can help limit the scope of what delegated users can do.
6+
7+
## RBAC
8+
9+
If you must delegate `ClusterExtension` access to non-admin users, start by assigning narrowly scoped roles:
10+
11+
### ClusterRole
12+
```yaml
13+
apiVersion: rbac.authorization.k8s.io/v1
14+
kind: ClusterRole
15+
metadata:
16+
name: clusterextension-viewer-role
17+
rules:
18+
- apiGroups:
19+
- olm.operatorframework.io
20+
resources:
21+
- clusterextensions
22+
- clusterobjectsets
23+
verbs:
24+
- get
25+
- list
26+
- watch
27+
---
28+
apiVersion: rbac.authorization.k8s.io/v1
29+
kind: ClusterRole
30+
metadata:
31+
name: clusterextension-installer-role
32+
rules:
33+
- apiGroups:
34+
- olm.operatorframework.io
35+
resources:
36+
- clusterextensions
37+
- clusterobjectsets
38+
verbs:
39+
- create
40+
- update
41+
- delete
42+
- get
43+
- list
44+
- watch
45+
```
46+
47+
### ClusterRoleBinding
48+
49+
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:
50+
51+
```yaml
52+
apiVersion: rbac.authorization.k8s.io/v1
53+
kind: ClusterRoleBinding
54+
metadata:
55+
name: clusterextension-installer-rolebinding
56+
roleRef:
57+
apiGroup: rbac.authorization.k8s.io
58+
kind: ClusterRole
59+
name: clusterextension-installer-role
60+
subjects:
61+
- kind: User
62+
name: alice
63+
apiGroup: rbac.authorization.k8s.io
64+
- kind: Group
65+
name: team-monitoring
66+
apiGroup: rbac.authorization.k8s.io
67+
```
68+
69+
## Validating Admission Policy
70+
71+
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/).
72+
73+
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.
74+
75+
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.
76+
77+
```yaml
78+
apiVersion: admissionregistration.k8s.io/v1
79+
kind: ValidatingAdmissionPolicy
80+
metadata:
81+
name: ce-policy-team-monitoring
82+
spec:
83+
failurePolicy: Fail
84+
matchConstraints:
85+
resourceRules:
86+
- apiGroups: ["olm.operatorframework.io"]
87+
apiVersions: ["v1"]
88+
resources: ["clusterextensions"]
89+
operations: ["CREATE", "UPDATE"]
90+
matchConditions:
91+
- name: only-team-monitoring
92+
expression: >-
93+
request.userInfo.groups.exists(g, g == "team-monitoring")
94+
validations:
95+
# Add validation expressions here (see sections below)
96+
---
97+
apiVersion: admissionregistration.k8s.io/v1
98+
kind: ValidatingAdmissionPolicyBinding
99+
metadata:
100+
name: ce-policy-team-monitoring
101+
spec:
102+
policyName: ce-policy-team-monitoring
103+
validationActions: ["Deny"]
104+
```
105+
106+
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.
107+
108+
### Allow a group to manage extensions in specific namespaces
109+
110+
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:
111+
112+
```yaml
113+
validations:
114+
- expression: >-
115+
object.spec.namespace in ["monitoring", "observability"]
116+
messageExpression: >-
117+
"spec.namespace must be one of [\"monitoring\", \"observability\"], got: \"" + object.spec.namespace + "\""
118+
```
119+
120+
### Restrict which ClusterCatalogs a user/group can reference
121+
122+
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.
123+
124+
```yaml
125+
validations:
126+
# Must use a catalog selector that targets team=monitoring catalogs
127+
- expression: >-
128+
has(object.spec.source.catalog) &&
129+
has(object.spec.source.catalog.selector) &&
130+
has(object.spec.source.catalog.selector.matchLabels) &&
131+
"team" in object.spec.source.catalog.selector.matchLabels &&
132+
object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
133+
messageExpression: >-
134+
"spec.source.catalog.selector.matchLabels must include team=monitoring" +
135+
(has(object.spec.source.catalog.selector) &&
136+
has(object.spec.source.catalog.selector.matchLabels) &&
137+
"team" in object.spec.source.catalog.selector.matchLabels
138+
? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
139+
: ", got: <unset>")
140+
```
141+
142+
### Restrict which packages a user/group can install
143+
144+
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:
145+
146+
```yaml
147+
validations:
148+
# Package allowlist with per-package pinned minor versions
149+
- expression: >-
150+
(object.spec.source.catalog.packageName == "prometheus" &&
151+
has(object.spec.source.catalog.version) &&
152+
object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
153+
||
154+
(object.spec.source.catalog.packageName == "alertmanager" &&
155+
has(object.spec.source.catalog.version) &&
156+
object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
157+
messageExpression: >-
158+
"package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
159+
(has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
160+
"\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"
161+
```
162+
163+
### Restrict upgrade constraint policy
164+
165+
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:
166+
167+
```yaml
168+
validations:
169+
- expression: >-
170+
!has(object.spec.source.catalog.upgradeConstraintPolicy) ||
171+
object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
172+
message: >-
173+
team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
174+
Only cluster-admins may bypass catalog-provided upgrade constraints.
175+
```
176+
177+
### Restrict CRD upgrade safety
178+
179+
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:
180+
181+
```yaml
182+
validations:
183+
- expression: >-
184+
!has(object.spec.install) ||
185+
!has(object.spec.install.preflight) ||
186+
!has(object.spec.install.preflight.crdUpgradeSafety) ||
187+
object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
188+
message: >-
189+
team-monitoring may not disable CRD upgrade safety checks.
190+
Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".
191+
```
192+
193+
## Complete example
194+
195+
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.
196+
197+
```yaml
198+
apiVersion: admissionregistration.k8s.io/v1
199+
kind: ValidatingAdmissionPolicy
200+
metadata:
201+
name: ce-policy-team-monitoring
202+
spec:
203+
failurePolicy: Fail
204+
matchConstraints:
205+
resourceRules:
206+
- apiGroups: ["olm.operatorframework.io"]
207+
apiVersions: ["v1"]
208+
resources: ["clusterextensions"]
209+
operations: ["CREATE", "UPDATE"]
210+
matchConditions:
211+
- name: only-team-monitoring
212+
expression: >-
213+
request.userInfo.groups.exists(g, g == "team-monitoring")
214+
validations:
215+
- expression: >-
216+
has(object.spec.source.catalog) &&
217+
has(object.spec.source.catalog.selector) &&
218+
has(object.spec.source.catalog.selector.matchLabels) &&
219+
"team" in object.spec.source.catalog.selector.matchLabels &&
220+
object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
221+
messageExpression: >-
222+
"spec.source.catalog.selector.matchLabels must include team=monitoring" +
223+
(has(object.spec.source.catalog.selector) &&
224+
has(object.spec.source.catalog.selector.matchLabels) &&
225+
"team" in object.spec.source.catalog.selector.matchLabels
226+
? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
227+
: ", got: <unset>")
228+
- expression: >-
229+
(object.spec.source.catalog.packageName == "prometheus" &&
230+
has(object.spec.source.catalog.version) &&
231+
object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
232+
||
233+
(object.spec.source.catalog.packageName == "alertmanager" &&
234+
has(object.spec.source.catalog.version) &&
235+
object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
236+
messageExpression: >-
237+
"package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
238+
(has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
239+
"\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"
240+
- expression: >-
241+
object.spec.namespace == "monitoring"
242+
messageExpression: >-
243+
"spec.namespace must be \"monitoring\", got: \"" + object.spec.namespace + "\""
244+
- expression: >-
245+
!has(object.spec.source.catalog.upgradeConstraintPolicy) ||
246+
object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
247+
message: >-
248+
team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
249+
Only cluster-admins may bypass catalog-provided upgrade constraints.
250+
- expression: >-
251+
!has(object.spec.install) ||
252+
!has(object.spec.install.preflight) ||
253+
!has(object.spec.install.preflight.crdUpgradeSafety) ||
254+
object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
255+
message: >-
256+
team-monitoring may not disable CRD upgrade safety checks.
257+
Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".
258+
---
259+
apiVersion: admissionregistration.k8s.io/v1
260+
kind: ValidatingAdmissionPolicyBinding
261+
metadata:
262+
name: ce-policy-team-monitoring
263+
spec:
264+
policyName: ce-policy-team-monitoring
265+
validationActions: ["Deny"]
266+
```

docs/project/olmv1_design_decisions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ 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
OLMv1 adopts cluster-admin scope directly. Previously, OLMv1 required cluster administrators to create ServiceAccounts and associated RBAC to enable the installation of ClusterExtensions. The current approach provides the following benefits:
182182

@@ -188,7 +188,7 @@ OLMv1 adopts cluster-admin scope directly. Previously, OLMv1 required cluster ad
188188
189189
Because the operator-controller 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`. For concrete examples of both approaches, see [Protecting OLMv1 API Access](../howto/how-to-protect-olmv1-api-access.md).
190190

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

193193
### Simple and predictable semantics for install, upgrade, and delete
194194

0 commit comments

Comments
 (0)