Set a default fsGroup for Kubernetes persistent volumes - #19374
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbea589-4f81-4baf-9e17-4f1af07af5ee
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19374Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19374" |
|
🚀 Deployment tests starting on PR #19374... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Adds default Kubernetes filesystem-group permissions for workloads using first-class persistent volumes.
Changes:
- Emits
fsGroup: 2000withOnRootMismatch. - Documents customization through
PublishAsKubernetesService. - Adds publisher snapshots and AKS deployment coverage for defaults and overrides.
Show a summary per file
| File | Description |
|---|---|
src/Aspire.Hosting.Kubernetes/KubernetesResource.cs |
Applies the default pod security context. |
src/Aspire.Hosting.Kubernetes/KubernetesPersistentVolumeExtensions.cs |
Documents the new behavior. |
tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs |
Tests defaults, overrides, and removal. |
tests/Aspire.Deployment.EndToEnd.Tests/AksPersistentVolumeDeploymentTests.cs |
Verifies deployed AKS volume access. |
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_WithFirstClassPersistentVolume_OnProject_BindsViaMountPathOverload#00.verified.yaml |
Captures the project manifest default. |
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_WithFirstClassPersistentVolume_KubernetesCustomizationOverridesDefaultFsGroup.verified.yaml |
Captures an overridden group. |
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_WithFirstClassPersistentVolume_KubernetesCustomizationCanRemoveDefaultSecurityContext.verified.yaml |
Captures removal of the context. |
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_WithFirstClassPersistentVolume_FallsThroughForUnboundVolumes#00.verified.yaml |
Captures mixed-volume behavior. |
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_WithFirstClassPersistentVolume_BindsByName_PromotesToStatefulSet#00.verified.yaml |
Captures name-based binding behavior. |
Review details
Suppressed comments (1)
src/Aspire.Hosting.Kubernetes/KubernetesPersistentVolumeExtensions.cs:289
- This is a public API reference, so it should use
see crefrather than code formatting; otherwise IntelliSense and generated API documentation cannot link users to the customization method.
/// <c>PublishAsKubernetesService</c> to customize the pod security context when
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Balanced
|
🚀 Deployment tests starting on PR #19374... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbea589-4f81-4baf-9e17-4f1af07af5ee
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/Aspire.Hosting.Kubernetes/KubernetesResource.cs:192
- Using one global GID changes more than volume ownership: Kubernetes defines
fsGroupas a supplemental group for every process in every container, so any image files or other mounts already group-readable/writable by numeric GID 2000 become accessible too. Because GIDs are not namespaced and 2000 is commonly available for image-defined users/groups, this security-sensitive access expansion can silently cross an image's intended permission boundary. Avoid assigning an arbitrary universal group by default; require an explicit/opt-in group (ideally through a first-class binding option) or otherwise obtain a group guaranteed by the target cluster/workload.
securityContext.FsGroup ??= DefaultPersistentVolumeFsGroup;
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
🚀 Deployment tests starting on PR #19374... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
Adam Ratzman (adamint)
left a comment
There was a problem hiding this comment.
I think the fixed fsGroup: 2000 default needs another pass before this is ready. It is rejected by OpenShift’s default restricted SCC because the value is outside the namespace-allocated range, and it causes a one-time recursive permission rewrite for existing PVCs on upgrade. I also left a smaller test comment to assert the workload is actually non-root.
The implementation itself worked in the targeted publisher tests and the live AKS deployment path. That AKS run was at the parent commit, but the only delta to the current head is the XML-doc link fix.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbea589-4f81-4baf-9e17-4f1af07af5ee
|
🚀 Deployment tests starting on PR #19374... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
Tests selector (audit mode)The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement. 4 / 100 test projects · 3 jobs, from 9 changed files. Selected test projects (4 / 100)
Selected jobs (3)
How these were chosen — grouped by what changed🔧 🔧 🧪 🧪 🧪 🧪 🧪 🧪 🧪 Job reasons
Selection computed for commit |
PR Testing ReportPR Information
Artifact Version Verification
The PR CLI and package hive were installed with the published "Dogfood this PR" command. The repo container install selected the matching Linux ARM64 artifact. Manifest scenarios used a separate isolated local install because the minimal container runner does not include the .NET SDK required to compile and publish an AppHost. The subsequent PR commit only strengthened the deployment test assertions; the targeted deployment workflow rebuilt the solution, packages, and CLI at that latest head before running the live test. Changes AnalyzedFiles Changed
Change Categories
Test Scenarios ExecutedScenario 1: Default persistent-volume manifestObjective: Verify that a fresh AppHost using the PR package emits the automatic pod filesystem group for a first-class persistent volume. Coverage Type: Happy path Status: ✅ Passed Steps:
Evidence:
Observations:
Scenario 2: fsGroup customizationObjective: Verify that the existing Kubernetes customization hook overrides Aspire's default group. Coverage Type: Boundary/customization Status: ✅ Passed Steps:
Evidence:
Observations:
Scenario 3: fsGroup opt-outObjective: Verify that users can remove the generated pod security context. Coverage Type: Unhappy path/customization Status: ✅ Passed Steps:
Evidence:
Expected Unhappy-Path Outcome: The persistent volume remains bound, but Aspire emits no pod Observations:
Scenario 4: Unbound legacy-volume boundaryObjective: Verify that the new default does not affect a workload that has a volume but no first-class persistent-volume binding. Coverage Type: Negative/boundary Status: ✅ Passed Steps:
Evidence:
Expected Boundary Outcome: The workload remains a Observations:
Scenario 5: Live AKS persistent-volume redeploymentObjective: Verify real Azure Disk write access and ownership transition using the latest PR artifacts. Coverage Type: Deployment end-to-end Status: ✅ Passed Workflow: https://github.com/microsoft/aspire/actions/runs/31772264851 Evidence:
Observations:
Summary
Overall Result✅ PR VERIFIED |
|
/backport to release/13.5 |
|
Started backporting to |
|
/backport to release/13.5 |
|
Started backporting to |
|
Pull request created: #1502
|
|
📝 Documentation has been drafted in microsoft/aspire.dev#1502 targeting Added a new "Default pod security context" section to Note This draft PR needs human review before merging. |
Description
This is a usability fix targeted for Aspire 13.5.
The problem
A Kubernetes volume access mode such as
ReadWriteOncecontrols how the volume can be attached and mounted; it does not grant the container's Linux process permission to write to the mounted filesystem.That distinction produces a confusing experience with first-class Aspire persistent volumes. The PVC can be
Bound, the pod can beRunning, and the volume can be mounted successfully, while the application still fails on its first write because the filesystem is owned byroot:rootand the container runs as a non-root user. From the application's perspective this surfaces as an I/O permission error even though the deployment otherwise appears healthy.The workaround before this change
Today, users must understand the underlying Kubernetes ownership model and customize every affected workload themselves:
The alternatives are similarly low-level, such as adding a privileged init container to run
chown, changing the image to use a known UID/GID, or configuring storage-driver-specific mount options. Requiring one of these workarounds makes a newly provisioned persistent volume look broken by default and leaks Kubernetes filesystem details into otherwise straightforward AppHost code.How this change fixes it
Workloads bound through either
WithPersistentVolume(...)overload now receive this pod security context automatically:fsGroupadds a supplemental group to the processes in the pod and, for supported volume types, instructs Kubernetes or the CSI driver to make the mounted volume accessible to that group. It does not change the image-defined UID or primary GID, so Aspire does not need to know which identity the image uses.Aspire uses a stable group ID rather than selecting a new value on each deployment because numeric ownership is persisted on the volume.
OnRootMismatchavoids unnecessary recursive ownership changes when the volume already has the expected group.Existing AppHost code therefore requires no extra configuration:
This behavior is limited to first-class
WithPersistentVolume(...)bindings. Ordinary workloads and legacy PVC generation through the Kubernetes environment's default storage type are unchanged. Read-only mounts remain read-only.Using a different fsGroup
The default is applied before existing
PublishAsKubernetesServicecallbacks run, so a workload or cluster that requires a specific group can replace it:The generated
OnRootMismatchpolicy is retained unless the callback also replaces it.Opting out
A workload can remove the generated pod security context through the same customization mechanism:
This is useful when ownership is managed by the image, an admission controller, or storage-specific configuration. Workloads that need other pod security-context settings can instead clear or replace only
FsGroupandFsGroupChangePolicy.Security and compatibility considerations
The group ID
2000is an Aspire-managed default, not a Kubernetes-reserved value. Some storage drivers do not supportfsGroup, CSI drivers may apply the group at mount time themselves, and cluster admission policies can restrict allowed group ranges. The existing customization callback remains the escape hatch for those environments.This change intentionally grants processes in the pod supplemental group access to supported mounted volumes and can update persisted POSIX group ownership. It does not change the image-defined UID or primary GID, and it does not make a read-only mount writable.
Validation
3000or remove the generated security context entirely.DeployAksPersistentVolumeSurvivesRedeploypassed against commit7b565f163e. It verifies that the application runs as non-root UID1654, receives supplemental group2000, sees/srv/dataowned by group2000, and can write. It then redeploys withfsGroup: 3000, confirms the same PVC is reused, verifies UID1654now has supplemental group3000and/srv/datais owned by group3000, reads the persisted data, and creates a new file.Checklist
<remarks />and<code />elements on your triple slash comments?