Storage

Default PersistentVolume isolation and additional volume access through namespace rules.

Storage enforcement is configured under spec.rules[].enforce.storage. Persistent Volumes covers default Tenant isolation and additional volume access for PVs without a Tenant label.

Rules use the shared namespace selection, audiences, conditions, and actions and order. See Reference for a complete Tenant example.

Rule Composition

Actions and order

Storage rules use the existing ordered action evaluation, but affect only additional access to PVs without a Tenant label:

ActionEffect on additional access
allowGrants access when the selector, audience, namespace selection, and all conditions match.
denyRejects the exception when it is the last matching allow/deny rule. This is the default action.
auditEmits a Kubernetes event for a match; never grants access.

The last matching allow or deny wins. If none grants access, the existing missing-Tenant-label rejection remains. An audit-only rule therefore does not permit an unlabeled PV. Other applicable admission checks must still pass after a storage rule allows access.

Same-Tenant volume access remains available without these rules. A matching rule cannot authorize another Tenant’s labeled PV or a PV whose Tenant label has an empty value. See Ownership and label protection.

Admission scope and limits

  • Checks apply to PVC creation and updates of an unbound PVC with an explicit spec.volumeName, including the PATCH that sets it during a restore.
  • The referenced PV must exist. A PV being deleted cannot receive an additional grant.
  • PVCs without spec.volumeName do not use these additional rules. They do not change dynamic provisioning or how Kubernetes selects a PV.
  • Already-bound PVC updates keep their existing behavior. These rules do not reauthorize or revoke an established binding.
  • The grant authorizes the PVC admission request. It does not rewrite the PV’s claimRef, restore labels, move data, or complete binding. RBAC, StorageClass restrictions, other admission policies, and Kubernetes binding requirements continue to apply.

PVC selector mutation runs only on creation. All updates preserve the existing selector, including updates while the claim is still Pending. The PV ownership check still applies when an unbound PVC is updated to reference a volume. See PVC selectors and dynamic provisioning.

Persistent Volumes

Capsule isolates PersistentVolumes (PVs) between Tenants by default. Storage rules let administrators grant additional access to selected PVs that do not yet carry Capsule’s Tenant ownership label. Configure these additional grants under spec.rules[].enforce.storage.volumes.

This supports workflows that patch a destination PersistentVolumeClaim (PVC) before changing the restored PV’s claim reference from a temporary claim to the destination claim. The default isolation described below continues to apply.

Default PersistentVolume isolation

Tenant owners can create PVCs using the StorageClasses available to their Tenant. When a PV’s spec.claimRef points to a PVC in a Tenant namespace, Capsule’s PV controller labels the volume with capsule.clastix.io/tenant: <tenant-name>. The controller resolves the Tenant from the claim’s namespace; PVs themselves are cluster-scoped. This behavior does not require any storage.volumes rules.

For example, a PV claimed in solar-production, a namespace belonging to the solar Tenant, has the following relevant fields. This is an excerpt of the existing PV, not a complete volume definition:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: solar-data
  labels:
    capsule.clastix.io/tenant: solar
spec:
  storageClassName: standard
  persistentVolumeReclaimPolicy: Retain
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    namespace: solar-production
    name: data

Reusing retained volumes

What happens after a PVC is deleted depends on the PV’s reclaim policy. With Retain, the volume and its data remain for manual reclamation; it is not automatically ready for another claim. Follow the Kubernetes reclamation procedure before reusing the storage.

Once the PV is available for reuse, Capsule permits claims from namespaces belonging to solar to reference it. Kubernetes must still be able to bind the claim: the requested capacity, access modes, StorageClass, and other binding requirements must be satisfied. Deleting the old PVC does not remove the PV’s Tenant label or authorize a different Tenant to use it.

A claim from green-energy, a namespace belonging to another Tenant named green, cannot reference solar-data:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
  namespace: green-energy
spec:
  storageClassName: standard
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  volumeName: solar-data

Capsule rejects that request with a cross-Tenant mount error:

admission webhook "pvc.validating.projectcapsule.dev" denied the request: Preventing a cross-tenant mount for PersistentVolume solar-data

PVC selectors and dynamic provisioning

On PVC creation, Capsule adds the owning Tenant requirement to an existing spec.selector, or creates a selector when spec.volumeName is set. It preserves unrelated selector requirements and replaces conflicting requirements for capsule.clastix.io/tenant with the correct Tenant value.

A new PVC with neither spec.selector nor spec.volumeName receives no Tenant selector, preserving dynamic provisioning. On update, Capsule leaves the selector unchanged, even while the PVC is Pending: Kubernetes treats the selector as immutable after creation. This allows a restore component to set spec.volumeName without Capsule adding a selector change that Kubernetes would reject.

For an explicit spec.volumeName, Capsule also checks the referenced PV’s Tenant label during PVC admission. A PV labeled for the same Tenant passes this ownership check; another Tenant’s PV is rejected. Without an additional grant, a PV with no Tenant label is also rejected. The rules below extend this last case while retaining the default ownership checks.

Additional volume access

Each entry in storage.volumes has a required Kubernetes label selector and an optional name for admission and audit messages. The selector matches labels on the PV named by the PVC’s spec.volumeName.

rules:
  - namespaceSelector:
      matchLabels:
        storage-profile: shared-imports
    enforce:
      action: allow
      storage:
        volumes:
          - name: approved-imports
            selector:
              matchLabels:
                storage.example.com/pool: imports
              matchExpressions:
                - key: storage.example.com/quarantined
                  operator: DoesNotExist

Place this rules block under Tenant.spec. It grants additional access to PVs with the imports pool label and no quarantine label, only from selected namespaces. Without an audience, the rule applies to every caller in that scope.

  • All requirements in one selector must match. Entries in volumes are alternatives: any matching entry matches that enforcement rule.
  • selector: {} explicitly matches every eligible PV, including a PV with no labels. Use conditions and an audience to scope a restore exception.
  • Omitting selector, or setting it to null, is invalid. An omitted or empty volumes list grants no additional access.
  • A rule supports at most 64 volume entries. Optional names must be unique within the list and use DNS labels of up to 63 characters.

Ownership and label protection

Capsule checks the PV’s capsule.clastix.io/tenant label before evaluating any additional grant:

PV ownership labelVolume access decision
Matches the destination TenantExisting access is retained, even if no storage rule matches or a storage rule says deny.
Names another TenantDenied; no selector or CEL condition can override ownership.
Key exists with an empty valueDenied; this is not treated as an absent label.
Key is absentRequires an applicable matching allow rule.

Selectors only read PV labels. They never add, remove, or overwrite PV labels, and are never copied into PVC.spec.selector. Labels supplied on the PVC cannot satisfy a PV selector. Capsule’s existing PVC Tenant selector enforcement remains in place.

Labels used for authorization must be controlled by administrators or trusted storage components. Tenant actors must not receive RBAC permissions to edit PVs or the rules granting access. A name prefix or a label alone does not establish Tenant ownership.

Conditions

Use enforce.conditions to inspect both the PVC being admitted and the referenced PV. There is no separate conditions field on a volume entry.

CEL variableValue during additional volume access evaluation
objectThe incoming PVC, including its destination namespace and spec.volumeName.
requestAdmission metadata, including operation and authenticated caller.
volumeA read-only snapshot of the existing PV referenced by spec.volumeName.

Check optional fields with has(...), for example volume != null && has(volume.spec.claimRef). Outside additional volume access evaluation, volume is null, including in metadata conditions on a PVC. Keep conditions that depend on volume in a separate storage enforcement block when other policies need independent conditions.

All conditions must be true. A false condition skips the enforcement block; it does not grant access. Evaluation errors reject admission unless another condition in that block is false, following the normal condition evaluation rules.

Reference

Restore through a temporary namespace

For a restore workflow using temporary claims in openshift-adp, the destination PVC may need to reference a PV whose claimRef still names a temporary claim. PVs are cluster-scoped; the namespace in spec.claimRef belongs to that PVC. If that PV has no Tenant label, the default Capsule ownership check rejects the PVC patch. An additional storage rule can authorize this handoff without relaxing access to another Tenant’s labeled PVs.

The following Tenant grants the exception in namespaces labeled storage-profile: restore. It allows service accounts from openshift-adp to update a PVC only when the PV’s temporary claim is in that namespace and its name starts with the destination namespace followed by -.

apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
  name: solar
spec:
  owners:
    - kind: User
      name: solar-owner
  rules:
    - namespaceSelector:
        matchLabels:
          storage-profile: restore
      audience:
        - kind: Group
          name: system:serviceaccounts:openshift-adp
      enforce:
        action: allow
        conditions:
          - name: restore-handoff
            expression: >-
              request.operation == 'UPDATE' &&
              volume != null &&
              has(volume.spec.claimRef) &&
              volume.spec.claimRef.namespace == 'openshift-adp' &&
              volume.spec.claimRef.name.startsWith(object.metadata.namespace + '-')              
        storage:
          volumes:
            - name: temporary-restore-volume
              selector: {}

Replace the owner, staging namespace, and claim naming condition with values from your restore workflow. This example grants the audience to all service accounts in openshift-adp. To scope it to one restoring component, use an audience with kind: ServiceAccount and its full system:serviceaccount:<namespace>:<name> username. Match the identity making the PVC patch, which may differ from the user who submitted a NonAdminRestore.

The prefix check is a naming convention, not proof of ownership. The trusted restore component remains responsible for handing the correct volume to the destination claim. Use the actual temporary claim naming scheme; Capsule does not derive it from a NonAdminRestore object.

If the temporary PV carries an administrator-controlled pool label, replace selector: {} with a matchLabels selector to narrow the grant further. Capsule does not add that label on your behalf.

After the PVC patch is admitted, the restore component can finish the PV handoff. Capsule’s existing PV labeling behavior still applies when the claim reference points to a Tenant namespace. The rule itself does not change labels or claim references.

Request or PV stateResult with this example
Trusted restore caller, selected namespace, matching temporary claim, no Tenant labelAdditional access is allowed.
Tenant owner without the restore audienceNo additional access.
Namespace without storage-profile: restoreNo additional access.
PVC creation rather than updateNo additional access; the condition requires UPDATE.
Missing claim reference or a different temporary namespace/name prefixNo additional access.
PV labeled for another TenantDenied regardless of the rule.
PV already labeled for solarExisting access is retained; other checks still apply.