Skip to content

Commit c869dcb

Browse files
fix(local-storage): isolate helper permissions and make installation opt-in
Hostpath storage must coexist with existing storage classes without claiming their names or silently changing default selection. Keep helper Pod operations in the installation namespace and retain only the cluster permissions needed for volume reconciliation. The package can be selected independently while the future container cluster lane remains a separate integration. Assisted-By: LLM Signed-off-by: Myasnikov Daniil <[email protected]>
1 parent c17a81a commit c869dcb

11 files changed

Lines changed: 506 additions & 236 deletions

File tree

‎docs/proposals/e2e-lightweight-container-lane.md‎

Lines changed: 9 additions & 198 deletions
Large diffs are not rendered by default.

‎packages/core/platform/templates/bundles/system.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ cozystack-platform HelmRelease never becomes Ready. */}}
321321

322322
# Optional System Packages (controlled via bundles.enabledPackages)
323323
{{include "cozystack.platform.package.optional.default" (list "cozystack.nfs-driver" $) }}
324+
{{include "cozystack.platform.package.optional.default" (list "cozystack.local-storage" $) }}
324325
{{include "cozystack.platform.package.optional.default" (list "cozystack.telepresence" $) }}
325326
{{include "cozystack.platform.package.optional.default" (list "cozystack.external-dns" $) }}
326327
{{include "cozystack.platform.package.optional.default" (list "cozystack.external-dns-application" $) }}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
suite: standalone local storage registration
2+
templates:
3+
- templates/sources.yaml
4+
- templates/bundles/system.yaml
5+
release:
6+
name: cozystack
7+
namespace: cozy-system
8+
tests:
9+
- it: registers the source before optional application APIs exist
10+
template: templates/sources.yaml
11+
documentSelector:
12+
path: metadata.name
13+
value: cozystack.local-storage
14+
asserts:
15+
- equal:
16+
path: spec.variants[0].name
17+
value: default
18+
- contains:
19+
path: spec.variants[0].dependsOn
20+
content: cozystack.networking
21+
- contains:
22+
path: spec.variants[0].components
23+
content:
24+
name: local-storage
25+
path: system/local-storage
26+
install:
27+
privileged: true
28+
namespace: cozy-local-storage
29+
releaseName: local-storage
30+
- it: does not install local storage with ordinary system defaults
31+
template: templates/bundles/system.yaml
32+
set:
33+
bundles.system.enabled: true
34+
asserts:
35+
- containsDocument:
36+
apiVersion: cozystack.io/v1alpha1
37+
kind: Package
38+
name: cozystack.local-storage
39+
any: true
40+
not: true
41+
- it: emits a Package only for explicit system bundle selection
42+
template: templates/bundles/system.yaml
43+
capabilities:
44+
apiVersions:
45+
- apps.cozystack.io/v1alpha1
46+
set:
47+
bundles.system.enabled: true
48+
bundles.enabledPackages: [cozystack.local-storage]
49+
documentSelector:
50+
path: metadata.name
51+
value: cozystack.local-storage
52+
asserts:
53+
- equal:
54+
path: kind
55+
value: Package
56+
- equal:
57+
path: spec.variant
58+
value: default
59+
- it: lets disabledPackages override opt in
60+
template: templates/bundles/system.yaml
61+
set:
62+
bundles.system.enabled: true
63+
bundles.enabledPackages: [cozystack.local-storage]
64+
bundles.disabledPackages: [cozystack.local-storage]
65+
asserts:
66+
- containsDocument:
67+
apiVersion: cozystack.io/v1alpha1
68+
kind: Package
69+
name: cozystack.local-storage
70+
any: true
71+
not: true
72+
- it: leaves a bundle-free platform available for standalone Package installation
73+
template: templates/bundles/system.yaml
74+
set:
75+
bundles.enabledPackages: [cozystack.local-storage]
76+
asserts:
77+
- hasDocuments:
78+
count: 0
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
apiVersion: v2
22
name: cozy-local-storage
33
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
4+
dependencies:
5+
- name: localpv-provisioner
6+
version: 4.5.1
7+
repository: https://openebs.github.io/dynamic-localpv-provisioner

‎packages/system/local-storage/Makefile‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@ export NAMESPACE=cozy-$(NAME)
44
include ../../../hack/common-envs.mk
55
include ../../../hack/package.mk
66

7+
LOCALPV_CHART_VERSION = 4.5.1
8+
HELPER_IMAGE_VERSION = 4.5.0
9+
10+
.PHONY: update update-image-pins test
11+
12+
test:
13+
helm unittest .
14+
python3 tests/rbac_test.py
15+
716
update:
17+
@test "$$(yq '.dependencies[] | select(.name == "localpv-provisioner") | .version' Chart.yaml)" = "$(LOCALPV_CHART_VERSION)"
818
rm -rf charts
9-
helm repo add openebs-localpv https://openebs.github.io/dynamic-localpv-provisioner
10-
helm repo update openebs-localpv
11-
helm pull openebs-localpv/localpv-provisioner --untar --untardir charts --version 4.5.1
19+
helm pull localpv-provisioner --repo https://openebs.github.io/dynamic-localpv-provisioner --untar --untardir charts --version $(LOCALPV_CHART_VERSION)
20+
patch -p1 < patches/rbac.diff
21+
$(MAKE) update-image-pins
22+
23+
# Keep third-party pins visible in umbrella values, without rebuilding or mirroring them.
24+
update-image-pins:
25+
@digest=$$(skopeo inspect --format '{{.Digest}}' docker://docker.io/openebs/provisioner-localpv:$(LOCALPV_CHART_VERSION)) && \
26+
IMAGE_TAG="$(LOCALPV_CHART_VERSION)@$$digest" yq -i '.localpv-provisioner.localpv.image.tag = strenv(IMAGE_TAG)' values.yaml
27+
@digest=$$(skopeo inspect --format '{{.Digest}}' docker://docker.io/openebs/linux-utils:$(HELPER_IMAGE_VERSION)) && \
28+
IMAGE_TAG="$(HELPER_IMAGE_VERSION)@$$digest" yq -i '.localpv-provisioner.helperPod.image.tag = strenv(IMAGE_TAG)' values.yaml
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# OpenEBS LocalPV Hostpath
2+
3+
This optional system package provisions node-local hostpath volumes through OpenEBS Dynamic LocalPV 4.5.1. Its StorageClass is `cozystack-local-hostpath`, uses `WaitForFirstConsumer`, and is not the cluster default. Volumes are bound to one node; the package does not provide replication, CSI snapshots or automatic recovery from node/disk loss. The default `Delete` reclaim policy removes the volume directory after the PVC is deleted.
4+
5+
## Installation and configuration
6+
7+
The platform registers `cozystack.local-storage` as a PackageSource even when its bundles are disabled. Install its `default` variant explicitly with `cozypkg add cozystack.local-storage` and acknowledge privileged components when prompted. The networking Package dependency must be available; current cozypkg resolves dependencies, while applying a Package manifest directly requires preparing those dependencies separately. Alternatively, a system bundle can include `cozystack.local-storage` in `bundles.enabledPackages`; `bundles.disabledPackages` takes precedence. No built-in variant installs it automatically.
8+
9+
For explicit configuration, use a Package resource:
10+
11+
```yaml
12+
apiVersion: cozystack.io/v1alpha1
13+
kind: Package
14+
metadata:
15+
name: cozystack.local-storage
16+
spec:
17+
variant: default
18+
components:
19+
local-storage:
20+
values:
21+
localpv-provisioner:
22+
hostpathClass:
23+
name: cozystack-local-hostpath
24+
isDefaultClass: false
25+
reclaimPolicy: Delete
26+
```
27+
28+
Use `storageClassName: cozystack-local-hostpath` in PVCs. Setting `hostpathClass.isDefaultClass: true` is an administrator opt-in: first inspect and explicitly resolve any existing default StorageClass. The chart does not demote or adopt other classes. A PVC with `storageClassName: ""` explicitly requests no class; omitting the field allows Kubernetes to select the default. Choose a different name before installation if `cozystack-local-hostpath` already exists under another owner; changing a live StorageClass name creates a new class rather than migrating existing volumes.
29+
30+
Only one OpenEBS LocalPV controller using the provisioner identity `openebs.io/local` may manage the cluster. Do not install this package beside a separately managed controller with that identity: a distinct StorageClass name does not isolate their reconciliation. This does not conflict with the ordinary Cozystack LINSTOR provisioner.
31+
32+
The upstream Deployment creates privileged helper Pods in the release namespace (normally `cozy-local-storage`) to create and remove directories under `/var/openebs/local` on the selected node. That directory must be writable and backed by the intended persistent filesystem on every eligible node. RBAC limits helper Pod, leader-election Lease and optional analytics ConfigMap operations to the release namespace. PV/PVC discovery and reconciliation, node discovery, StorageClass reads and PVC events remain cluster-scoped. A privileged storage controller still has host filesystem access; namespace-scoped Pod permissions are not a tenant filesystem isolation boundary.
33+
34+
Drain or remove applications and verify PVC/PV reclamation before uninstalling the controller. `Retain` leaves data and PV reclamation to the administrator. Hostpath volumes do not survive losing their backing node or directory. This package adds no container-cluster E2E workflow or storage migration.
35+
36+
## Maintenance
37+
38+
`make update` downloads chart 4.5.1, reapplies `patches/rbac.diff`, refreshes the provisioner/helper image index pins. `make test` renders the chart and checks storage, configuration and RBAC boundaries locally. Image pins are third-party pass-through references in the umbrella values; they are not rebuilt or mirrored by Cozystack. The pinned indices include Linux amd64, arm64, arm/v7 and ppc64le; actual provisioning remains subject to node filesystem and security prerequisites.
39+
40+
The RBAC patch follows [LocalPV helper calls](https://github.com/openebs/dynamic-localpv-provisioner/blob/v4.5.1/cmd/provisioner-localpv/app/helper_hostpath.go), [startup and analytics](https://github.com/openebs/dynamic-localpv-provisioner/blob/v4.5.1/cmd/provisioner-localpv/app/start.go), and [external provisioner controller v13](https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner/blob/v13.0.0/controller/controller.go). Recheck these calls when updating upstream.
41+
42+
## Parameters
43+
44+
The upstream values root is `localpv-provisioner`. This system chart follows the vendored chart values convention; it does not expose an ApplicationDefinition or generated API schema.
45+
46+
| Value under `localpv-provisioner` | Default | Effect |
47+
| --- | --- | --- |
48+
| `hostpathClass.name` | `cozystack-local-hostpath` | Name of the managed StorageClass. |
49+
| `hostpathClass.isDefaultClass` | `false` | Explicit administrator opt-in to default-class selection. |
50+
| `hostpathClass.enabled` | `true` | Whether to create the StorageClass. |
51+
| `hostpathClass.reclaimPolicy` | `Delete` | Use `Retain` for manual reclamation. |
52+
| `hostpathClass.basePath` | empty | Override the directory inherited from `localpv.basePath`. |
53+
| `localpv.basePath` | `/var/openebs/local` | Writable host directory used by helper Pods. |
54+
| `analytics.enabled` | `false` | Send upstream usage telemetry. |
55+
| `localpv.image` | upstream 4.5.1, pinned index | Provisioner image, refreshed by `make update-image-pins`. |
56+
| `helperPod.image` | upstream 4.5.0, pinned index | Privileged filesystem helper image. |
57+
| `serviceAccount.name` | generated from the release | Optional account name used by the controller and helpers. |
58+
| `rbac.create` | `true` | Create the package RBAC; when false the administrator must provide permissions. |
59+
60+
Other supported upstream values, including image pull secrets, node selectors and helper timeouts, are documented in the [vendored chart](charts/localpv-provisioner/README.md).

‎packages/system/local-storage/charts/localpv-provisioner/templates/rbac.yaml‎

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,21 @@ metadata:
2929
{{- toYaml .Values.extraLabels | nindent 4 }}
3030
{{- end }}
3131
rules:
32-
- apiGroups: ["*"]
32+
- apiGroups: [""]
3333
resources: ["nodes"]
34+
verbs: ["get", "list"]
35+
- apiGroups: [""]
36+
resources: ["persistentvolumeclaims"]
37+
verbs: ["get", "list", "watch", "update"]
38+
- apiGroups: [""]
39+
resources: ["persistentvolumes"]
40+
verbs: ["get", "list", "watch", "create", "delete", "patch"]
41+
- apiGroups: ["storage.k8s.io"]
42+
resources: ["storageclasses"]
3443
verbs: ["get", "list", "watch"]
35-
- apiGroups: ["*"]
36-
resources: ["namespaces", "pods", "events", "endpoints"]
37-
verbs: ["*"]
38-
- apiGroups: ["*"]
39-
resources: ["resourcequotas", "limitranges"]
40-
verbs: ["list", "watch"]
41-
- apiGroups: ["*"]
42-
resources: ["storageclasses", "persistentvolumeclaims", "persistentvolumes"]
43-
verbs: ["*"]
44-
- apiGroups: ["apiextensions.k8s.io"]
45-
resources: ["customresourcedefinitions"]
46-
verbs: [ "get", "list", "create", "update", "delete", "patch"]
47-
- apiGroups: ["openebs.io"]
48-
resources: [ "*"]
49-
verbs: ["*" ]
50-
- apiGroups: ["coordination.k8s.io"]
51-
resources: ["leases"]
52-
verbs: ["get", "create", "update"]
53-
# Analytics state ConfigMap: the provisioner creates and updates this
54-
# ConfigMap itself to persist install/ping/heartbeat timestamps.
5544
- apiGroups: [""]
56-
resources: ["configmaps"]
57-
verbs: ["get", "create", "update", "patch"]
58-
- nonResourceURLs: ["/metrics"]
59-
verbs: ["get"]
45+
resources: ["events"]
46+
verbs: ["create", "update", "patch"]
6047
---
6148
apiVersion: rbac.authorization.k8s.io/v1
6249
kind: ClusterRoleBinding
@@ -75,6 +62,41 @@ roleRef:
7562
kind: ClusterRole
7663
name: {{ template "localpv.fullname" . }}
7764
subjects:
65+
- kind: ServiceAccount
66+
name: {{ template "localpv.serviceAccountName" . }}
67+
namespace: {{ .Release.Namespace }}
68+
---
69+
apiVersion: rbac.authorization.k8s.io/v1
70+
kind: Role
71+
metadata:
72+
name: {{ template "localpv.fullname" . }}
73+
namespace: {{ .Release.Namespace }}
74+
labels:
75+
{{- include "localpv.labels" . | nindent 4 }}
76+
# Helpers operate on host paths but their API objects stay in the release namespace.
77+
rules:
78+
- apiGroups: [""]
79+
resources: ["pods"]
80+
verbs: ["get", "create", "delete"]
81+
- apiGroups: ["coordination.k8s.io"]
82+
resources: ["leases"]
83+
verbs: ["get", "create", "update"]
84+
- apiGroups: [""]
85+
resources: ["configmaps"]
86+
verbs: ["get", "create", "patch"]
87+
---
88+
apiVersion: rbac.authorization.k8s.io/v1
89+
kind: RoleBinding
90+
metadata:
91+
name: {{ template "localpv.fullname" . }}
92+
namespace: {{ .Release.Namespace }}
93+
labels:
94+
{{- include "localpv.labels" . | nindent 4 }}
95+
roleRef:
96+
apiGroup: rbac.authorization.k8s.io
97+
kind: Role
98+
name: {{ template "localpv.fullname" . }}
99+
subjects:
78100
- kind: ServiceAccount
79101
name: {{ template "localpv.serviceAccountName" . }}
80102
namespace: {{ .Release.Namespace }}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--- a/charts/localpv-provisioner/templates/rbac.yaml
2+
+++ b/charts/localpv-provisioner/templates/rbac.yaml
3+
@@ -29,34 +29,21 @@
4+
{{- toYaml .Values.extraLabels | nindent 4 }}
5+
{{- end }}
6+
rules:
7+
-- apiGroups: ["*"]
8+
+- apiGroups: [""]
9+
resources: ["nodes"]
10+
+ verbs: ["get", "list"]
11+
+- apiGroups: [""]
12+
+ resources: ["persistentvolumeclaims"]
13+
+ verbs: ["get", "list", "watch", "update"]
14+
+- apiGroups: [""]
15+
+ resources: ["persistentvolumes"]
16+
+ verbs: ["get", "list", "watch", "create", "delete", "patch"]
17+
+- apiGroups: ["storage.k8s.io"]
18+
+ resources: ["storageclasses"]
19+
verbs: ["get", "list", "watch"]
20+
-- apiGroups: ["*"]
21+
- resources: ["namespaces", "pods", "events", "endpoints"]
22+
- verbs: ["*"]
23+
-- apiGroups: ["*"]
24+
- resources: ["resourcequotas", "limitranges"]
25+
- verbs: ["list", "watch"]
26+
-- apiGroups: ["*"]
27+
- resources: ["storageclasses", "persistentvolumeclaims", "persistentvolumes"]
28+
- verbs: ["*"]
29+
-- apiGroups: ["apiextensions.k8s.io"]
30+
- resources: ["customresourcedefinitions"]
31+
- verbs: [ "get", "list", "create", "update", "delete", "patch"]
32+
-- apiGroups: ["openebs.io"]
33+
- resources: [ "*"]
34+
- verbs: ["*" ]
35+
-- apiGroups: ["coordination.k8s.io"]
36+
- resources: ["leases"]
37+
- verbs: ["get", "create", "update"]
38+
- # Analytics state ConfigMap: the provisioner creates and updates this
39+
- # ConfigMap itself to persist install/ping/heartbeat timestamps.
40+
- apiGroups: [""]
41+
- resources: ["configmaps"]
42+
- verbs: ["get", "create", "update", "patch"]
43+
-- nonResourceURLs: ["/metrics"]
44+
- verbs: ["get"]
45+
+ resources: ["events"]
46+
+ verbs: ["create", "update", "patch"]
47+
---
48+
apiVersion: rbac.authorization.k8s.io/v1
49+
kind: ClusterRoleBinding
50+
@@ -73,6 +60,41 @@
51+
roleRef:
52+
apiGroup: rbac.authorization.k8s.io
53+
kind: ClusterRole
54+
+ name: {{ template "localpv.fullname" . }}
55+
+subjects:
56+
+- kind: ServiceAccount
57+
+ name: {{ template "localpv.serviceAccountName" . }}
58+
+ namespace: {{ .Release.Namespace }}
59+
+---
60+
+apiVersion: rbac.authorization.k8s.io/v1
61+
+kind: Role
62+
+metadata:
63+
+ name: {{ template "localpv.fullname" . }}
64+
+ namespace: {{ .Release.Namespace }}
65+
+ labels:
66+
+ {{- include "localpv.labels" . | nindent 4 }}
67+
+# Helpers operate on host paths but their API objects stay in the release namespace.
68+
+rules:
69+
+- apiGroups: [""]
70+
+ resources: ["pods"]
71+
+ verbs: ["get", "create", "delete"]
72+
+- apiGroups: ["coordination.k8s.io"]
73+
+ resources: ["leases"]
74+
+ verbs: ["get", "create", "update"]
75+
+- apiGroups: [""]
76+
+ resources: ["configmaps"]
77+
+ verbs: ["get", "create", "patch"]
78+
+---
79+
+apiVersion: rbac.authorization.k8s.io/v1
80+
+kind: RoleBinding
81+
+metadata:
82+
+ name: {{ template "localpv.fullname" . }}
83+
+ namespace: {{ .Release.Namespace }}
84+
+ labels:
85+
+ {{- include "localpv.labels" . | nindent 4 }}
86+
+roleRef:
87+
+ apiGroup: rbac.authorization.k8s.io
88+
+ kind: Role
89+
name: {{ template "localpv.fullname" . }}
90+
subjects:
91+
- kind: ServiceAccount

0 commit comments

Comments
 (0)