Skip to content

feat: Allow Fileownership change through FSGroup and VOLUME_MOUNT_GROUP - #1841

Open
mytreya-rh wants to merge 1 commit into
kubernetes-sigs:mainfrom
mytreya-rh:allow_file_ownership
Open

feat: Allow Fileownership change through FSGroup and VOLUME_MOUNT_GROUP#1841
mytreya-rh wants to merge 1 commit into
kubernetes-sigs:mainfrom
mytreya-rh:allow_file_ownership

Conversation

@mytreya-rh

@mytreya-rh mytreya-rh commented Jun 9, 2025

Copy link
Copy Markdown

What type of PR is this?
/kind feature

What this PR does / why we need it:
(As of now pls consider it as a draft PR to discuss the solution further.)
Allows the secrets to be mounted with FSGroup as specified in the POD spec.
Thus, A pod with a non-root user should be able to read a secret, and that secret need not be world-readable.

Which issue(s) this PR fixes :
Fixes #858

Is this a chart or deployment yaml update?
There is a yaml update for secrets-store.csi.x-k8s.io_secretproviderclasspodstatuses.yaml (generated through make manifests).
It is added in the manifest_staging/deploy
But if this PR merges after: #1622, the change in SecretProviderClassPodStatusStatus won't be required anymore and we can revert the changes related to reconciler.

Special notes for your reviewer:

Problem:

Solution:

  • outline
  • Do the ownership change from within the driver by advertising the VOLUME_MOUNT_GROUP capability.

Notes:

  • The changes also include secret rotation based on SecretProviderClassPodStatusStatus, but will be reverted if feat: Use RequiresRepublish for secret rotation #1622 merges earlier
  • In addition, pulled up some of common repetitive code from the unit and e2e tests to make them a bit more terse

tests added in e2e-provider:

(leaving in the test status and runtime just for reference)
  • ok 16 Non-root POD with no FSGroup - create in 871ms
  • ok 17 Non-root POD with no FSGroup - Should fail to read non world readable secret in 186ms
  • ok 18 Non-root POD with no FSGroup - unmount succeeds in 10143ms
  • ok 19 Non-root POD with FSGroup - create in 1439ms
  • ok 20 Non-root POD with FSGroup - should read non world readable secret in 202ms
  • ok 21 Non-root POD with FSGroup - rotated secret should also be readable in 37119ms
  • ok 22 Non-root POD with FSGroup - unmount succeeds in 10177ms

unit tests:

  • nodeserver_test
    • TestNodePublishVolume_Errors/Invalid_FSGroup
    • TestNodePublishVolume/volume_mount_with_valid_FSGroup
  • reconciler_test
    • TestReconcileError/failed_to_parse_FSGroup
    • TestReconcileNoError/reconcile_with_FSGroup

TODOs:

  • squashed commits
  • includes documentation
  • adds unit tests

@k8s-ci-robot k8s-ci-robot added the kind/feature Categorizes issue or PR as related to a new feature. label Jun 9, 2025
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 9, 2025

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: mytreya-rh / name: Mytreya Kasturi (b4862da)

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Jun 9, 2025
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

Welcome @mytreya-rh!

It looks like this is your first PR to kubernetes-sigs/secrets-store-csi-driver 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-sigs/secrets-store-csi-driver has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Jun 9, 2025
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

Hi @mytreya-rh. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot
k8s-ci-robot requested review from aramase and ritazh June 9, 2025 17:49
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Jun 9, 2025
@enj enj moved this to Subprojects - Needs Triage in SIG Auth Jun 10, 2025
@enj enj added this to SIG Auth Jun 10, 2025
@dobsonj

dobsonj commented Jun 27, 2025

Copy link
Copy Markdown
Member

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jun 27, 2025
@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch from 898943d to b797f9d Compare June 29, 2025 16:08
@mytreya-rh

Copy link
Copy Markdown
Author

/retest

@enj enj moved this from Subprojects - Needs Triage to In Review in SIG Auth Jun 30, 2025

@aramase aramase left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The windows job failures are related to this PR.

E0630 16:56:06.282028   10108 atomic_writer.go:419] "unable to change file with owner" err="chown c:\\var\\lib\\kubelet\\pods\\ff425598-c3fa-480d-a6af-814831673629\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount\\..2025_06_30_16_56_06.1168464543\\secretalias: not supported by windows" logContext="secrets-store-csi-driver" fullPath="c:\\var\\lib\\kubelet\\pods\\ff425598-c3fa-480d-a6af-814831673629\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount\\..2025_06_30_16_56_06.1168464543\\secretalias" owner=-1

ref: https://storage.googleapis.com/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_secrets-store-csi-driver/1841/pull-secrets-store-csi-driver-e2e-windows/1939725228552753152/artifacts/2025-06-30T170106/secrets-store.log

@github-project-automation github-project-automation Bot moved this from In Review to Changes Requested in SIG Auth Jun 30, 2025
@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch from b797f9d to cbac857 Compare June 30, 2025 19:43
@mytreya-rh

Copy link
Copy Markdown
Author

The windows job failures are related to this PR.

E0630 16:56:06.282028   10108 atomic_writer.go:419] "unable to change file with owner" err="chown c:\\var\\lib\\kubelet\\pods\\ff425598-c3fa-480d-a6af-814831673629\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount\\..2025_06_30_16_56_06.1168464543\\secretalias: not supported by windows" logContext="secrets-store-csi-driver" fullPath="c:\\var\\lib\\kubelet\\pods\\ff425598-c3fa-480d-a6af-814831673629\\volumes\\kubernetes.io~csi\\secrets-store-inline\\mount\\..2025_06_30_16_56_06.1168464543\\secretalias" owner=-1

ref: https://storage.googleapis.com/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_secrets-store-csi-driver/1841/pull-secrets-store-csi-driver-e2e-windows/1939725228552753152/artifacts/2025-06-30T170106/secrets-store.log

Thanks @aramase !
Pushed a commit to skip Chown on Windows. Guess this is inline with FSGroup behavior as well on Windows nodes.

@mytreya-rh

Copy link
Copy Markdown
Author

/retest

@dobsonj dobsonj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one comment on test/bats/e2e-provider.bats, but otherwise LGTM. It's a useful fix, implementation looks correct, good test coverage, and passing CI tests. Netlify is warning about a line unrelated from your changes.

Comment thread test/bats/e2e-provider.bats Outdated
kubectl wait -n rotation --for=condition=Ready --timeout=60s pod ${curl_pod_name}
local pod_ip=$(kubectl get pod -n kube-system -l app=csi-secrets-store-e2e-provider -o jsonpath="{.items[0].status.podIP}")
run kubectl exec ${curl_pod_name} -n rotation -- curl http://${pod_ip}:8080/rotation?rotated=true
sleep 35 # 30 is poll interval, 5 second grace should be enough

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry that 35 seconds may not be enough to prevent flakes. In @test "Test auto rotation of mount contents and K8s secrets" (line 472) it used to sleep 60 seconds, but now it only sleeps 35 seconds? Is it possible for a reconcile loop to be delayed for some reason that would cause this to take longer than 35?

I would probably not reduce this below 60, we had one similar case in vault.bats waiting on secret rotation where we had to increase it to 120 to improve the pass rate.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dobsonj
Reverted the sleep back to 60s

@mytreya-rh

Copy link
Copy Markdown
Author

/retest

@dobsonj

dobsonj commented Jul 21, 2025

Copy link
Copy Markdown
Member

/lgtm

/sig storage
/triage accepted
/priority important-soon

/assign @aramase
for approval and to decide which PR should merge first between #1841 and #1622

@k8s-ci-robot k8s-ci-robot added the sig/storage Categorizes an issue or PR as relevant to SIG Storage. label Jul 21, 2025
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 22.31%. Comparing base (19f9876) to head (8618850).
⚠️ Report is 36 commits behind head on main.

Files with missing lines Patch % Lines
pkg/secrets-store/nodeserver.go 60.00% 6 Missing ⚠️
pkg/util/fileutil/atomic_writer.go 33.33% 2 Missing ⚠️
pkg/util/fileutil/writer.go 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1841      +/-   ##
==========================================
+ Coverage   21.47%   22.31%   +0.83%     
==========================================
  Files          57       57              
  Lines        3269     3218      -51     
==========================================
+ Hits          702      718      +16     
+ Misses       2476     2407      -69     
- Partials       91       93       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch from 8618850 to 40f47db Compare March 20, 2026 06:27
@mytreya-rh

mytreya-rh commented Mar 20, 2026

Copy link
Copy Markdown
Author

/retest
looks like transient error:

error: Internal error occurred: error sending request: Post "https://10.224.0.33:10250/exec/test-ns/busybox-deployment-78b5c7bdf9-22sf4/busybox?command=cat&command=%!F(MISSING)mnt%!F(MISSING)secrets-store%!F(MISSING)secretalias&error=1&output=1": proxy error from localhost:9443 while dialing 10.224.0.33:10250, code 500: 500 Internal Server Error

@aramase aramase left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is close.

Comment thread pkg/secrets-store/nodeserver.go Outdated
"k8s.io/klog/v2"
mount "k8s.io/mount-utils"
"sigs.k8s.io/controller-runtime/pkg/client"
internalerrors "sigs.k8s.io/secrets-store-csi-driver/pkg/errors"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: group commits

import (
  stdlib

  internal

  external
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, makes it better organized. Thanks

Comment thread pkg/util/fileutil/filesystem.go Outdated
if len(fsGroupStr) == 0 {
return NoGID, nil
}
return strconv.Atoi(fsGroupStr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strconv.Atoi accepts negative values. The test even validates -23 as a valid GID. A negative GID other than -1 passed to os.Chown is undefined behavior on Linux. Kubelet should never send a negative value, but we should still reject it here.

func ParseFSGroup(fsGroupStr string) (int, error) {
      if len(fsGroupStr) == 0 {
              return NoGID, nil
      }
      gid, err := strconv.Atoi(fsGroupStr)
      if err != nil {
              return NoGID, err
      }
      if gid < 0 {
              return NoGID, fmt.Errorf("invalid FSGroup: %d must be non-negative", gid)
      }
      return gid, nil
}

Update the negative gid test case to expect an error.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, and done.
Earlier, my intention was to keep complete type compatibility/extensibility, and let the implementation (os.Chown) handle the full range that it supports, but as fsGroup is validated at API to be in range: 0 to 2147483647, disallowing the negative values like you suggested.

Comment thread test/bats/e2e-provider.bats Outdated
# On Windows, the failed unmount calls from: https://github.com/kubernetes-sigs/secrets-store-csi-driver/pull/545
# do not prevent the pod from being deleted. Search through the driver logs
# for the error.
run bash -c "kubectl -n $NAMESPACE logs -l app=$POD_NAME --tail -1 -c secrets-store | grep '^E.*failed to clean and unmount target path.*$'"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-l app=$POD_NAME doesn't match anything — the test pods don't have that label. This means kubectl logs returns empty, grep always fails, and assert_failure always passes. The unmount error check is effectively a no-op.

Should be the driver DaemonSet pods:

Suggested change
run bash -c "kubectl -n $NAMESPACE logs -l app=$POD_NAME --tail -1 -c secrets-store | grep '^E.*failed to clean and unmount target path.*$'"
run bash -c "kubectl logs -l app=secrets-store-csi-driver --tail -1 -c secrets-store -n kube-system | grep '^E.*failed to clean and unmount target path.*$'"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, that was such bad refactoring. Thanks for catching it, corrected.

csiPodName: "pod1",
csiPodNamespace: "default",
csiPodUID: "poduid1",
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: default VolumeCapability has nil AccessType. Works today because GetMount() on nil returns nil and GetVolumeMountGroup() on nil returns "". Fragile if we ever add a nil guard.

              VolumeCapability: &csi.VolumeCapability{
                      AccessType: &csi.VolumeCapability_Mount{
                              Mount: &csi.VolumeCapability_MountVolume{},
                      },
              },

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread test/bats/e2e-provider.bats Outdated
# enable rotation response in mock server
local curl_pod_name=curl-$(openssl rand -hex 5)
kubectl run ${curl_pod_name} -n rotation --image=curlimages/curl:7.75.0 --labels="test=rotation" -- tail -f /dev/null > /dev/null
kubectl wait -n rotation --for=condition=Ready --timeout=60s pod ${curl_pod_name} > /dev/null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kubectl wait -n rotation --for=condition=Ready --timeout=60s pod ${curl_pod_name} > /dev/null
kubectl wait -n rotation --for=condition=Ready --timeout=60s pod ${curl_pod_name}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we need the redirection to /dev/null so that the function just returns the curl_pod_name right?
ex:

$k wait -n kube-system --for=condition=Ready --timeout=60s pod coredns-6f6b679f8f-6vqqg
pod/coredns-6f6b679f8f-6vqqg condition met
$
$ k wait -n kube-system --for=condition=Ready --timeout=60s pod coredns-6f6b679f8f-6vqqg >/dev/null
$

Comment thread test/bats/e2e-provider.bats Outdated
Comment on lines +108 to +109
kubectl run ${curl_pod_name} -n rotation --image=curlimages/curl:7.75.0 --labels="test=rotation" -- tail -f /dev/null > /dev/null
kubectl wait -n rotation --for=condition=Ready --timeout=60s pod ${curl_pod_name} > /dev/null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If kubectl run or kubectl wait fails, the function silently continues and you get a confusing downstream failure. Add || return 1 after the critical commands.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, now returning 1 on kubectl errors, so that the function call results in error in the caller's scope

FsUser *int64
Data []byte
Mode int32
FsGroup *int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: upstream uses FsUser *int64. This changes both the name and type — both intentional. Add a short comment noting the divergence so future readers don't think it drifted by accident.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually added the comment in the file header. and now added reasoning for type change as well.
Shall i move it to the struct definition instead?

@mytreya-rh

Copy link
Copy Markdown
Author

/retest
looks like infra issue

Error: INSTALLATION FAILED: Kubernetes cluster unreachable: Get "https://sscsi-e2e--sscsi-e2e-86ed-46678f-t4lfap2y.hcp.uksouth.azmk8s.io:443/version": dial tcp: lookup sscsi-e2e--sscsi-e2e-86ed-46678f-t4lfap2y.hcp.uksouth.azmk8s.io on 172.20.0.10:53: no such host

@aramase aramase left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully last set of comments.

Comment thread test/bats/e2e-provider.bats Outdated
# On Windows, the failed unmount calls from: https://github.com/kubernetes-sigs/secrets-store-csi-driver/pull/545
# do not prevent the pod from being deleted. Search through the driver logs
# for the error.
run bash -c "kubectl -n $NAMESPACE logs -l app=secrets-store-csi-driver --tail -1 -c secrets-store | grep '^E.*failed to clean and unmount target path.*$'"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-n $NAMESPACE queries the test namespace (e.g. default, test-v1alpha1), but the driver DaemonSet pods run in kube-system. This means kubectl logs finds no pods in the test namespace, grep fails, and assert_failure always passes — making this
check a no-op.

The original code had -n kube-system:

Suggested change
run bash -c "kubectl -n $NAMESPACE logs -l app=secrets-store-csi-driver --tail -1 -c secrets-store | grep '^E.*failed to clean and unmount target path.*$'"
run bash -c "kubectl logs -l app=secrets-store-csi-driver --tail -1 -c secrets-store -n kube-system | grep '^E.*failed to clean and unmount target path.*$'"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thanks again for catching this oversight.
Also simplified the semantics of passing file permissions to create_spc function in this file.

Comment thread test/e2eprovider/server/server.go Outdated
Comment on lines +183 to +185
if err != nil || mode > 511 {
return nil, fmt.Errorf("invalid filePermission: %s, error: %w for file: %s", mockSecretsStoreObject.FilePermission, err, mockSecretsStoreObject.ObjectName)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When mode > 511 but err == nil, this wraps a nil error with %w which prints <nil> in the message. Split the conditions:

if err != nil {
    return nil, fmt.Errorf("invalid filePermission: %s, error: %w for file: %s", mockSecretsStoreObject.FilePermission, err, mockSecretsStoreObject.ObjectName)
}
if mode > 511 {
    return nil, fmt.Errorf("invalid filePermission: %s exceeds 0777 for file: %s", mockSecretsStoreObject.FilePermission, mockSecretsStoreObject.ObjectName)
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, also changed mode > 511 to mode > 0o777 for better readability

Comment thread pkg/secrets-store/nodeserver.go Outdated
}

klog.V(2).InfoS("node publish volume", "target", targetPath, "volumeId", volumeID, "mount flags", mountFlags)
klog.V(2).InfoS("node publish volume", "target", targetPath, "volumeId", volumeID, "mount flags", mountFlags, "volumeCapabilities", req.VolumeCapability.String())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: req.VolumeCapability.String() dumps the entire proto including mount flags, access mode, etc. If the intent is just to log the FSGroup, consider logging mountVol.GetVolumeMountGroup() after parsing it instead. The full capability proto can be noisy
in production logs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, now only logging VolumeMountGroup.
However, not using the parsed value but the value obtained in the NodePublishVolume arguments, as it could help in better debugging if for some reason the parse function is not working as expected.

// * tag: v1.20.6,
// * commit: 8a62859e515889f07e3e3be6a1080413f17cf2c3
// * link: https://github.com/kubernetes/kubernetes/blob/8a62859e515889f07e3e3be6a1080413f17cf2c3/pkg/volume/util/atomic_writer.go
// In addition, FileProjection::FSUser has been changed to FileProjection::FSGroup

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment is fine but could you also add a one-liner at the struct itself? That's where people will look when they see FsGroup *int and wonder why it doesn't match upstream's FsUser *int64:

// FileProjection contains file Data and access Mode.
// FsGroup diverges from upstream's FsUser (*int64) — see file header for rationale.
type FileProjection struct {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@mytreya-rh

Copy link
Copy Markdown
Author

/retest
looks like an environment issue in the AWS Provider:

E0330 11:11:23.049879 1 nodeserver.go:253] "failed to mount secrets store object content" err="rpc error: code = Unknown desc = Failed to fetch parameters from all regions." pod="kube-system/basic-test-mount" isRemountRequest=false
I0330 11:11:23.049919 1 nodeserver.go:86] "unmounting target path as node publish volume failed" targetPath="/var/lib/kubelet/pods/e021597b-0a10-48cf-811f-c98114044231/volumes/kubernetes.io~csi/secrets-store-inline/mount" pod="kube-system/basic-test-mount"

@mytreya-rh

Copy link
Copy Markdown
Author

/retest
seems to be an infra issue or IRSA configuration error. If this fails, will add some debug in next commit

0330 12:07:02.492849 1 nodeserver.go:253] "failed to mount secrets store object content" err="rpc error: code = Unknown desc = Failed to fetch secret from all regions. Verify secret exists and required permissions are granted for: SecretsManagerRotationTest-secret-d37d9220da61" pod="kube-system/basic-test-mount" isRemountRequest=false

@mytreya-rh

Copy link
Copy Markdown
Author

/hold
debugging the failing [aws e2e job

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Mar 31, 2026
@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch 2 times, most recently from 43f6852 to e08b33d Compare March 31, 2026 17:51
@mytreya-rh

Copy link
Copy Markdown
Author

/unhold
aws test was failing with

E0331 16:58:41.618697 1 nodeserver.go:253] "failed to mount secrets store object content" err="rpc error: code = Unknown desc = IRSA token extraction failed: token for audience "sts.amazonaws.com" not found - ensure tokenRequests includes this audience in CSIDriver" pod="kube-system/basic-test-mount" isRemountRequest=false
included fix in Add sts.amazonaws.com audience to tokenRequests as needed by the newer version of AWS Provider

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 1, 2026
@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch 3 times, most recently from 740df77 to b4862da Compare April 3, 2026 08:12
@aramase
aramase requested a review from Copilot June 11, 2026 04:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds FSGroup-based file ownership support for mounted secret contents by advertising the CSI VOLUME_MOUNT_GROUP capability and applying the provided GID when writing files. It also extends the e2e mock provider and Bats tests to validate non-root pods reading non-world-readable secrets when fsGroup is set.

Changes:

  • Advertise csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP, parse VolumeMountGroup, and propagate it through the mount/write pipeline to os.Chown(..., gid).
  • Extend the e2e provider to accept per-object filePermission and update Bats fixtures/tests to cover non-root + FSGroup scenarios.
  • Add ParseFSGroup/NoGID helpers and update unit tests for the new parameters/signatures.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/secrets-store/nodeserver.go Parses VolumeMountGroup, passes GID through mount flow, and advertises VOLUME_MOUNT_GROUP.
pkg/secrets-store/provider_client.go Threads GID into WritePayloads when writing mounted files.
pkg/util/fileutil/writer.go Extends WritePayloads to accept a GID and attach it to projections for atomic writer.
pkg/util/fileutil/atomic_writer.go Applies group ownership (os.Chown(..., -1, gid)) to written files (non-Windows).
pkg/util/fileutil/filesystem.go / filesystem_test.go Adds NoGID and ParseFSGroup with tests.
pkg/util/fileutil/writer_test.go Updates tests for new WritePayloads(..., gid) signature.
pkg/secrets-store/provider_client_test.go Updates tests for new MountContent(..., gid) signature.
pkg/secrets-store/nodeserver_test.go Refactors setup helpers and adds FSGroup parsing/error test coverage.
test/e2eprovider/types/types.go Adds filePermission to mock object schema for e2e provider.
test/e2eprovider/server/server.go Parses filePermission and sets returned file mode for mounted files.
test/bats/tests/e2e_provider/pod-secrets-store-inline-volume-crd.yaml Parameterizes pod/SPC names and pod/container security contexts for FSGroup scenarios.
test/bats/tests/e2e_provider/e2e_provider_secretproviderclass.yaml Parameterizes SPC name and per-object file modes used by the e2e provider.
test/bats/tests/e2e_provider/e2e_provider_v1alpha1_secretproviderclass.yaml Removed; replaced by parameterized SPC manifest usage.
test/bats/e2e-provider.bats Adds helper functions and new non-root/FSGroup e2e tests; refactors repeated logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +117 to +121
function disable_secret_rotation() {
local curl_pod_name=$1
local pod_ip=$(kubectl get pod -n kube-system -l app=csi-secrets-store-e2e-provider -o jsonpath="{.items[0].status.podIP}")
run kubectl exec ${curl_pod_name} -n rotation -- curl http://${pod_ip}:8080/rotation?rotated=false
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, @copilot
The pod creation is now moved to setup_file and the same POD will be reused across the various rotation tests. The pod will be deleted in teardown when the namespace gets deleted.
This way we wont create and delete the POD multiple times.
Instead of run kubectl, the disable_secret_rotation function just executes kubectl directly to not let the error go unnoticed.

POD_NAME="non-root-with-no-fsgroup" delete_pod
}

@test "Non-root POD with FSGroup - create" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing one case here: mode 0600 with fsGroup should still fail to read. That's the test that proves the chown only touches the group bit and not the user bit — the exact property that would silently break if a future refactor mis-orders the args to os.Chown(fullPath, -1, *fileProjection.FsGroup). Without it, swapping those args goes unnoticed: mode 0640 still works (group readable either way once you own it), and there's no negative test catching the regression.

Suggested addition:

@test "deploy e2e-provider v1 secretproviderclass crd with owner-only permissions" {
  SPC_NAME="e2e-provider-600" SECRET_MODE=0600 KEY_MODE=0600 create_spc
}

@test "Non-root POD with FSGroup - should fail to read owner-only secret" {
  SPC_NAME="e2e-provider-600" POD_NAME="non-root-with-fsgroup-600" POD_SECURITY_CONTEXT='"runAsNonRoot": true, "runAsUser": 1004, "runAsGroup": 1004, "fsGroup": 1004' create_pod
  POD_NAME="non-root-with-fsgroup-600" run read_secret
  assert_failure
}

@test "Non-root POD with FSGroup - owner-only unmount succeeds" {
  POD_NAME="non-root-with-fsgroup-600" delete_pod
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @aramase included the above test and its working as expected.

@aramase aramase left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mytreya-rh just one comment and also PTAL at the copilot review comment.

@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch from b4862da to 4e3ef66 Compare June 11, 2026 06:50
Implements the CSI NodeServiceCapability RPC_VOLUME_MOUNT_GROUP so that
mounted secret files are chown'd to the pod's FSGroup.
This allows secrets to be not world-readable in non-root containers.

1. nodeServer::NodeGetCapabilities()
   advertise VOLUME_MOUNT_GROUP
2. nodeServer::NodePublishVolume()
   get POD's FSGroup if any from: req.VolumeCapability.GetMount().GetVolumeMountGroup()
3. pass the fsgroup onto (writer.go) WritePayloads()
4. include the FSGroup in the FileProjection struct (rename FileProjection::FSUser as FSGroup)
5. change AtomicWriter::writePayloadToDir() to chown the group based on FSGroup
6. Add relevant Unit tests, and e2eprovider tests
7. Bit of refactoring in the unit and e2eprovider tests to make them more terse
@mytreya-rh
mytreya-rh force-pushed the allow_file_ownership branch from 4e3ef66 to a917b86 Compare June 11, 2026 07:22
@Nemric

Nemric commented Aug 1, 2026

Copy link
Copy Markdown

Hi, what's the status of this PR that seems to help a lot for files that need 600 ownership

@mytreya-rh

Copy link
Copy Markdown
Author

Hi, what's the status of this PR that seems to help a lot for files that need 600 ownership

hi @Nemric This PR helps non-root containers mount and use non world-readable secrets.
Thus, making it possible to mount secrets with 440 permissions instead of the default 666.
This in itself improves the security posture of the mounted secrets and also the PODs using them.
The way it works is by respecting the FSGroup field of the POD spec.

But, as of now, the FSUser which is derived from the runAsUser field of the POD/Container spec is NOT passed to CSIDrivers and changing the USER ownership will require changes in kubelet and perhaps in the NodePublishVolume API.
We can discuss this as a follow up as it would be a useful feature. WDYT @aramase ?

@Nemric

Nemric commented Aug 2, 2026

Copy link
Copy Markdown

would that help ? kubernetes/kubernetes#139764
[edit] : kubernetes/enhancements#5936

@mytreya-rh

Copy link
Copy Markdown
Author

would that help ? kubernetes/kubernetes#139764

surely.. thanks for sharing

@ryan0x44

Copy link
Copy Markdown

Would be great to see this merged, it looks ready to go - but let me know if I can help with anything!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. sig/storage Categorizes an issue or PR as relevant to SIG Storage. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow file ownership to be set for secrets