Skip to content

Commit 14e1dad

Browse files
authored
fix(security): delegate dangerous CR controls (#499)
Signed-off-by: Roel de Cort <roel.decort@adfinis.com>
1 parent 9c9f33a commit 14e1dad

39 files changed

Lines changed: 863 additions & 83 deletions

charts/openbao-operator/templates/admission/provisioner-rbac.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ spec:
100100
rule.verbs != null &&
101101
rule.verbs.all(v, v in ['create', 'delete', 'get', 'list', 'patch', 'update', 'watch'])
102102
) ||
103+
(
104+
rule.apiGroups.size() == 1 &&
105+
rule.apiGroups[0] == 'openbao.org' &&
106+
rule.resources != null &&
107+
rule.resources.size() == 1 &&
108+
rule.resources[0] == 'openbaoclusters' &&
109+
rule.verbs != null &&
110+
rule.verbs.all(v, v in ['usecustomexecutables', 'useimagetrustroots'])
111+
) ||
103112
(
104113
rule.apiGroups.size() == 1 &&
105114
rule.apiGroups[0] == 'apps' &&

charts/openbao-operator/templates/admission/validate-openbaocluster.yaml

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ spec:
109109
- name: rolling_upgrade_progress_started
110110
expression: >-
111111
oldObject != null && has(oldObject.status) && has(oldObject.status.upgrade) && (oldObject.status.upgrade.currentPartition < oldObject.spec.replicas || (has(oldObject.status.upgrade.completedPods) && size(oldObject.status.upgrade.completedPods) > 0))
112+
- name: custom_executables_authorized
113+
expression: >-
114+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usecustomexecutables").allowed() ||
115+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usehelperimages").allowed()
116+
- name: image_trust_roots_authorized
117+
expression: >-
118+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("useimagetrustroots").allowed()
119+
- name: has_custom_main_image_trust_roots
120+
expression: >-
121+
has(object.spec.imageVerification) &&
122+
(
123+
(has(object.spec.imageVerification.publicKey) && object.spec.imageVerification.publicKey.trim() != "") ||
124+
(has(object.spec.imageVerification.issuer) && object.spec.imageVerification.issuer.trim() != "") ||
125+
(has(object.spec.imageVerification.subject) && object.spec.imageVerification.subject.trim() != "") ||
126+
(has(object.spec.imageVerification.issuerRegExp) && object.spec.imageVerification.issuerRegExp.trim() != "") ||
127+
(has(object.spec.imageVerification.subjectRegExp) && object.spec.imageVerification.subjectRegExp.trim() != "") ||
128+
(has(object.spec.imageVerification.ignoreTlog) && object.spec.imageVerification.ignoreTlog == true)
129+
)
130+
- name: has_custom_operator_image_trust_roots
131+
expression: >-
132+
has(object.spec.operatorImageVerification) &&
133+
(
134+
(has(object.spec.operatorImageVerification.publicKey) && object.spec.operatorImageVerification.publicKey.trim() != "") ||
135+
(has(object.spec.operatorImageVerification.issuer) && object.spec.operatorImageVerification.issuer.trim() != "") ||
136+
(has(object.spec.operatorImageVerification.subject) && object.spec.operatorImageVerification.subject.trim() != "") ||
137+
(has(object.spec.operatorImageVerification.issuerRegExp) && object.spec.operatorImageVerification.issuerRegExp.trim() != "") ||
138+
(has(object.spec.operatorImageVerification.subjectRegExp) && object.spec.operatorImageVerification.subjectRegExp.trim() != "") ||
139+
(has(object.spec.operatorImageVerification.ignoreTlog) && object.spec.operatorImageVerification.ignoreTlog == true)
140+
)
112141
- name: requested_below_current_version
113142
expression: >-
114143
variables.current_version_valid &&
@@ -374,18 +403,26 @@ spec:
374403
object.spec.profile != "Hardened" ||
375404
object.spec.replicas >= 3
376405
message: "Hardened profile requires at least 3 replicas for Raft quorum HA. Use Profile=Development for non-HA deployments."
377-
# Confused-deputy protection: custom backup helper images run with backup credentials.
406+
# Confused-deputy protection: CR-selected custom executables run with operator-managed identities,
407+
# OpenBao runtime mounts, or both. Existing usehelperimages grants are accepted as a compatibility alias.
378408
- expression: >-
379-
!has(object.spec.backup) ||
380-
!has(object.spec.backup.image) ||
381-
object.spec.backup.image == "" ||
382-
(request.operation == "UPDATE" &&
383-
oldObject != null &&
384-
has(oldObject.spec.backup) &&
385-
has(oldObject.spec.backup.image) &&
386-
oldObject.spec.backup.image == object.spec.backup.image) ||
387-
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usehelperimages").allowed()
388-
message: "Users configuring custom backup helper images must be authorized to use helper image overrides on this OpenBaoCluster."
409+
(
410+
(!has(object.spec.initContainer) || !has(object.spec.initContainer.image) || object.spec.initContainer.image == "") &&
411+
(!has(object.spec.backup) || !has(object.spec.backup.image) || object.spec.backup.image == "") &&
412+
(!has(object.spec.upgrade) || !has(object.spec.upgrade.image) || object.spec.upgrade.image == "") &&
413+
(!has(object.spec.upgrade) || !has(object.spec.upgrade.blueGreen) || !has(object.spec.upgrade.blueGreen.verification) || !has(object.spec.upgrade.blueGreen.verification.prePromotionHook)) &&
414+
(!has(object.spec.plugins) || object.spec.plugins.all(p, (!has(p.image) || p.image == "") && (!has(p.command) || p.command == "")))
415+
) ||
416+
variables.custom_executables_authorized
417+
message: "Users configuring CR-selected custom executables (custom init, backup, upgrade, blue/green hook, or plugin executable configuration) must be authorized to use custom executables on this OpenBaoCluster."
418+
# Supply-chain protection: Hardened image verification may use official defaults without extra RBAC,
419+
# but CR-selected trust roots require a separate delegation.
420+
- expression: >-
421+
!has(object.spec.profile) ||
422+
object.spec.profile != "Hardened" ||
423+
!(variables.has_custom_main_image_trust_roots || variables.has_custom_operator_image_trust_roots) ||
424+
variables.image_trust_roots_authorized
425+
message: "Users configuring custom image verification trust roots in Hardened profile must be authorized to use image trust roots on this OpenBaoCluster."
389426
# Confused-deputy protection: users configuring unseal Secret credentials must be able to read that Secret.
390427
- expression: >-
391428
!has(object.spec.unseal) ||

charts/openbao-operator/templates/admission/validate-openbaorestore.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ spec:
4343
- name: restore_endpoint_is_in_cluster_service
4444
expression: >-
4545
variables.restore_endpoint_hostname_normalized.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.svc(\\.cluster\\.local)?$")
46+
- name: custom_executables_authorized
47+
expression: >-
48+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usecustomexecutables").allowed() ||
49+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usehelperimages").allowed()
4650
validations:
4751
# API contract: restore specs are immutable after creation.
4852
- expression: >-
@@ -86,15 +90,12 @@ spec:
8690
(variables.restore_endpoint_scheme == "http" && variables.restore_endpoint_is_in_cluster_service)))
8791
message: "Restore endpoint must use HTTPS or S3 scheme, unless it targets an in-cluster Service (*.svc)."
8892
# Confused-deputy protection: custom restore helper images run with restore credentials.
93+
# Existing usehelperimages grants are accepted as a compatibility alias.
8994
- expression: >-
9095
!has(object.spec.image) ||
9196
object.spec.image == "" ||
92-
(request.operation == "UPDATE" &&
93-
oldObject != null &&
94-
has(oldObject.spec.image) &&
95-
oldObject.spec.image == object.spec.image) ||
96-
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usehelperimages").allowed()
97-
message: "Users configuring custom restore helper images must be authorized to use helper image overrides on the target OpenBaoCluster."
97+
variables.custom_executables_authorized
98+
message: "Users configuring custom restore helper images must be authorized to use custom executables on the target OpenBaoCluster."
9899
# Confused-deputy protection: users configuring restore Secret credentials must be able to read those Secrets.
99100
- expression: >-
100101
(!has(object.spec.source.target.credentialsSecretRef) ||

charts/openbao-operator/templates/rbac/aggregated-clusterroles.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,26 @@ rules:
6060
- openbaoclusters
6161
verbs:
6262
- get
63+
- usecustomexecutables
6364
- usehelperimages
6465
---
6566

67+
apiVersion: rbac.authorization.k8s.io/v1
68+
kind: ClusterRole
69+
metadata:
70+
labels:
71+
{{- include "openbao-operator.labels" . | nindent 4 }}
72+
name: {{ include "openbao-operator.fullname" . }}-openbaocluster-image-trust-roots
73+
rules:
74+
- apiGroups:
75+
- openbao.org
76+
resources:
77+
- openbaoclusters
78+
verbs:
79+
- get
80+
- useimagetrustroots
81+
---
82+
6683
apiVersion: rbac.authorization.k8s.io/v1
6784
kind: ClusterRole
6885
metadata:

charts/openbao-operator/templates/rbac/single-tenant-clusterrole.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ rules:
2121
- patch
2222
- update
2323
- watch
24+
- apiGroups:
25+
- openbao.org
26+
resources:
27+
- openbaoclusters
28+
verbs:
29+
- usecustomexecutables
30+
- useimagetrustroots
2431
- apiGroups:
2532
- openbao.org
2633
resources:

config/overlays/single-tenant-custom-identity/single_tenant_clusterrole.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ rules:
2424
- delete
2525
- get
2626
- patch
27+
- apiGroups:
28+
- openbao.org
29+
resources:
30+
- openbaoclusters
31+
verbs:
32+
- usecustomexecutables
33+
- useimagetrustroots
2734
- apiGroups:
2835
- openbao.org
2936
resources:

config/overlays/single-tenant/single_tenant_clusterrole.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ rules:
2424
- delete
2525
- get
2626
- patch
27+
- apiGroups:
28+
- openbao.org
29+
resources:
30+
- openbaoclusters
31+
verbs:
32+
- usecustomexecutables
33+
- useimagetrustroots
2734
- apiGroups:
2835
- openbao.org
2936
resources:

config/policy/openbao-restrict-provisioner-rbac.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ spec:
9797
rule.verbs != null &&
9898
rule.verbs.all(v, v in ['create', 'delete', 'get', 'list', 'patch', 'update', 'watch'])
9999
) ||
100+
(
101+
rule.apiGroups.size() == 1 &&
102+
rule.apiGroups[0] == 'openbao.org' &&
103+
rule.resources != null &&
104+
rule.resources.size() == 1 &&
105+
rule.resources[0] == 'openbaoclusters' &&
106+
rule.verbs != null &&
107+
rule.verbs.all(v, v in ['usecustomexecutables', 'useimagetrustroots'])
108+
) ||
100109
(
101110
rule.apiGroups.size() == 1 &&
102111
rule.apiGroups[0] == 'apps' &&

config/policy/openbao-validate-openbaocluster.yaml

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,35 @@ spec:
106106
- name: rolling_upgrade_progress_started
107107
expression: >-
108108
oldObject != null && has(oldObject.status) && has(oldObject.status.upgrade) && (oldObject.status.upgrade.currentPartition < oldObject.spec.replicas || (has(oldObject.status.upgrade.completedPods) && size(oldObject.status.upgrade.completedPods) > 0))
109+
- name: custom_executables_authorized
110+
expression: >-
111+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usecustomexecutables").allowed() ||
112+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usehelperimages").allowed()
113+
- name: image_trust_roots_authorized
114+
expression: >-
115+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("useimagetrustroots").allowed()
116+
- name: has_custom_main_image_trust_roots
117+
expression: >-
118+
has(object.spec.imageVerification) &&
119+
(
120+
(has(object.spec.imageVerification.publicKey) && object.spec.imageVerification.publicKey.trim() != "") ||
121+
(has(object.spec.imageVerification.issuer) && object.spec.imageVerification.issuer.trim() != "") ||
122+
(has(object.spec.imageVerification.subject) && object.spec.imageVerification.subject.trim() != "") ||
123+
(has(object.spec.imageVerification.issuerRegExp) && object.spec.imageVerification.issuerRegExp.trim() != "") ||
124+
(has(object.spec.imageVerification.subjectRegExp) && object.spec.imageVerification.subjectRegExp.trim() != "") ||
125+
(has(object.spec.imageVerification.ignoreTlog) && object.spec.imageVerification.ignoreTlog == true)
126+
)
127+
- name: has_custom_operator_image_trust_roots
128+
expression: >-
129+
has(object.spec.operatorImageVerification) &&
130+
(
131+
(has(object.spec.operatorImageVerification.publicKey) && object.spec.operatorImageVerification.publicKey.trim() != "") ||
132+
(has(object.spec.operatorImageVerification.issuer) && object.spec.operatorImageVerification.issuer.trim() != "") ||
133+
(has(object.spec.operatorImageVerification.subject) && object.spec.operatorImageVerification.subject.trim() != "") ||
134+
(has(object.spec.operatorImageVerification.issuerRegExp) && object.spec.operatorImageVerification.issuerRegExp.trim() != "") ||
135+
(has(object.spec.operatorImageVerification.subjectRegExp) && object.spec.operatorImageVerification.subjectRegExp.trim() != "") ||
136+
(has(object.spec.operatorImageVerification.ignoreTlog) && object.spec.operatorImageVerification.ignoreTlog == true)
137+
)
109138
- name: requested_below_current_version
110139
expression: >-
111140
variables.current_version_valid &&
@@ -371,18 +400,26 @@ spec:
371400
object.spec.profile != "Hardened" ||
372401
object.spec.replicas >= 3
373402
message: "Hardened profile requires at least 3 replicas for Raft quorum HA. Use Profile=Development for non-HA deployments."
374-
# Confused-deputy protection: custom backup helper images run with backup credentials.
403+
# Confused-deputy protection: CR-selected custom executables run with operator-managed identities,
404+
# OpenBao runtime mounts, or both. Existing usehelperimages grants are accepted as a compatibility alias.
375405
- expression: >-
376-
!has(object.spec.backup) ||
377-
!has(object.spec.backup.image) ||
378-
object.spec.backup.image == "" ||
379-
(request.operation == "UPDATE" &&
380-
oldObject != null &&
381-
has(oldObject.spec.backup) &&
382-
has(oldObject.spec.backup.image) &&
383-
oldObject.spec.backup.image == object.spec.backup.image) ||
384-
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.metadata.name).check("usehelperimages").allowed()
385-
message: "Users configuring custom backup helper images must be authorized to use helper image overrides on this OpenBaoCluster."
406+
(
407+
(!has(object.spec.initContainer) || !has(object.spec.initContainer.image) || object.spec.initContainer.image == "") &&
408+
(!has(object.spec.backup) || !has(object.spec.backup.image) || object.spec.backup.image == "") &&
409+
(!has(object.spec.upgrade) || !has(object.spec.upgrade.image) || object.spec.upgrade.image == "") &&
410+
(!has(object.spec.upgrade) || !has(object.spec.upgrade.blueGreen) || !has(object.spec.upgrade.blueGreen.verification) || !has(object.spec.upgrade.blueGreen.verification.prePromotionHook)) &&
411+
(!has(object.spec.plugins) || object.spec.plugins.all(p, (!has(p.image) || p.image == "") && (!has(p.command) || p.command == "")))
412+
) ||
413+
variables.custom_executables_authorized
414+
message: "Users configuring CR-selected custom executables (custom init, backup, upgrade, blue/green hook, or plugin executable configuration) must be authorized to use custom executables on this OpenBaoCluster."
415+
# Supply-chain protection: Hardened image verification may use official defaults without extra RBAC,
416+
# but CR-selected trust roots require a separate delegation.
417+
- expression: >-
418+
!has(object.spec.profile) ||
419+
object.spec.profile != "Hardened" ||
420+
!(variables.has_custom_main_image_trust_roots || variables.has_custom_operator_image_trust_roots) ||
421+
variables.image_trust_roots_authorized
422+
message: "Users configuring custom image verification trust roots in Hardened profile must be authorized to use image trust roots on this OpenBaoCluster."
386423
# Confused-deputy protection: users configuring unseal Secret credentials must be able to read that Secret.
387424
- expression: >-
388425
!has(object.spec.unseal) ||

config/policy/openbao-validate-openbaorestore.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ spec:
4040
- name: restore_endpoint_is_in_cluster_service
4141
expression: >-
4242
variables.restore_endpoint_hostname_normalized.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.svc(\\.cluster\\.local)?$")
43+
- name: custom_executables_authorized
44+
expression: >-
45+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usecustomexecutables").allowed() ||
46+
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usehelperimages").allowed()
4347
validations:
4448
# API contract: restore specs are immutable after creation.
4549
- expression: >-
@@ -83,15 +87,12 @@ spec:
8387
(variables.restore_endpoint_scheme == "http" && variables.restore_endpoint_is_in_cluster_service)))
8488
message: "Restore endpoint must use HTTPS or S3 scheme, unless it targets an in-cluster Service (*.svc)."
8589
# Confused-deputy protection: custom restore helper images run with restore credentials.
90+
# Existing usehelperimages grants are accepted as a compatibility alias.
8691
- expression: >-
8792
!has(object.spec.image) ||
8893
object.spec.image == "" ||
89-
(request.operation == "UPDATE" &&
90-
oldObject != null &&
91-
has(oldObject.spec.image) &&
92-
oldObject.spec.image == object.spec.image) ||
93-
authorizer.group("openbao.org").resource("openbaoclusters").namespace(request.namespace).name(object.spec.cluster).check("usehelperimages").allowed()
94-
message: "Users configuring custom restore helper images must be authorized to use helper image overrides on the target OpenBaoCluster."
94+
variables.custom_executables_authorized
95+
message: "Users configuring custom restore helper images must be authorized to use custom executables on the target OpenBaoCluster."
9596
# Confused-deputy protection: users configuring restore Secret credentials must be able to read those Secrets.
9697
- expression: >-
9798
(!has(object.spec.source.target.credentialsSecretRef) ||

0 commit comments

Comments
 (0)