diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml index 5567a6ff2..a3ef49eb7 100644 --- a/packages/system/cozystack-basics/templates/clusterroles.yaml +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -293,6 +293,15 @@ rules: - update - patch - delete +{{- if and (eq (toString ((.Values.gpu).enabled | default false)) "true") (.Capabilities.APIVersions.Has "admissionregistration.k8s.io/v1/ValidatingAdmissionPolicy") }} +{{/* Rendered under the same conditions as cozystack-gpu-claim-tamper, which + keeps tenants off a claim's finalizers and template mark. Status, which + carries the binding and attachment, is a subresource and stays the + controller's. */}} +- apiGroups: ["gpu.cozystack.io"] + resources: ["gpuclaims"] + verbs: ["create", "update", "patch", "delete"] +{{- end }} --- # == super admin cluster role == # Aggregates admin + all roles labeled for super-admin access diff --git a/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml b/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml new file mode 100644 index 000000000..ba4818239 --- /dev/null +++ b/packages/system/cozystack-basics/tests/clusterroles-gpu-capability-gate_test.yaml @@ -0,0 +1,21 @@ +suite: cozystack-basics GPUClaim write follows the policies that guard it +templates: + - templates/clusterroles.yaml +release: + name: cozystack-basics + namespace: cozy-system +# The GPU policies render only where the API server serves +# ValidatingAdmissionPolicy, so the write they guard does too. +capabilities: + apiVersions: [] +tests: + - it: grants no GPUClaim write without the ValidatingAdmissionPolicy API + set: + gpu: + enabled: true + documentSelector: + path: metadata.name + value: cozy:tenant:admin:base + asserts: + - notExists: + path: rules[?(@.apiGroups[*] == "gpu.cozystack.io")] diff --git a/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml b/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml index f5288ff4d..47a680e50 100644 --- a/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml +++ b/packages/system/cozystack-basics/tests/clusterroles-gpu_test.yaml @@ -7,11 +7,12 @@ release: name: cozystack-basics namespace: cozy-system -# Tenant roles read GPUClaim orders in their namespaces; no tenant role writes -# them yet, since nothing checks an order's budget or entitlement here. -# GPUClass is cluster-scoped, and tenant roles are bound per namespace, so its -# read goes to every authenticated user through a ClusterRoleBinding. GPUNode, -# which names physical devices, is granted to no tenant role. +# Tenants with admin or above manage GPUClaim orders in their namespaces +# where GPUs are on, and lower roles read them. GPUClass is cluster-scoped, +# and tenant roles are bound per namespace, so its read goes to every +# authenticated user through a ClusterRoleBinding. Neither the status of a +# claim, which holds its binding, nor GPUNode, which names physical devices, +# is granted to any tenant role. tests: # The filter returns the rule itself when exactly one rule names the group # and a list otherwise, so equal pins both the rule and that it is the only @@ -28,10 +29,27 @@ tests: resources: ["gpuclaims"] verbs: ["get", "list", "watch"] - - it: cozy:tenant:admin:base grants nothing in gpu.cozystack.io + - it: cozy:tenant:admin:base grants only write on gpuclaims when GPUs are on set: gpu: enabled: true + capabilities: + apiVersions: + - admissionregistration.k8s.io/v1/ValidatingAdmissionPolicy + documentSelector: + path: metadata.name + value: cozy:tenant:admin:base + asserts: + - equal: + path: rules[?(@.apiGroups[*] == "gpu.cozystack.io")] + value: + apiGroups: ["gpu.cozystack.io"] + resources: ["gpuclaims"] + verbs: ["create", "update", "patch", "delete"] + + # The write ships with the GPU policies that guard it, which render only + # where GPUs are on. + - it: cozy:tenant:admin:base grants nothing in gpu.cozystack.io while GPUs are off documentSelector: path: metadata.name value: cozy:tenant:admin:base