Skip to content

Commit 028cace

Browse files
Add tests to use rotation config.
1 parent 54efb52 commit 028cace

File tree

13 files changed

+151
-121
lines changed

13 files changed

+151
-121
lines changed

controllers/secretproviderclasspodstatus_controller.go

Lines changed: 40 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -295,58 +295,44 @@ func (r *SecretProviderClassPodStatusReconciler) Reconcile(ctx context.Context,
295295
errs = append(errs, fmt.Errorf("failed to validate secret object in spc %s/%s, err: %w", spc.Namespace, spc.Name, err))
296296
continue
297297
}
298-
exists, err := r.secretExists(ctx, secretName, req.Namespace)
299-
if err != nil {
300-
klog.ErrorS(err, "failed to check if secret exists", "secret", klog.ObjectRef{Namespace: req.Namespace, Name: secretName}, "spc", klog.KObj(spc), "pod", klog.KObj(pod), "spcps", klog.KObj(spcPodStatus))
301-
// syncSecret.enabled is set to false by default in the helm chart for installing the driver in v0.0.23+
302-
// that would result in a forbidden error, so generate a warning that can be helpful for debugging
303-
if apierrors.IsForbidden(err) {
304-
klog.Warning(SyncSecretForbiddenWarning)
305-
}
306-
errs = append(errs, fmt.Errorf("failed to check if secret %s exists, err: %w", secretName, err))
307-
continue
308-
}
309298

310299
var funcs []func() (bool, error)
300+
secretType := secretutil.GetSecretType(strings.TrimSpace(secretObj.Type))
311301

312-
if !exists {
313-
secretType := secretutil.GetSecretType(strings.TrimSpace(secretObj.Type))
314-
315-
var datamap map[string][]byte
316-
if datamap, err = secretutil.GetSecretData(secretObj.Data, secretType, files); err != nil {
317-
r.generateEvent(pod, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err))
318-
klog.ErrorS(err, "failed to get data in spc for secret", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "secret", klog.ObjectRef{Namespace: req.Namespace, Name: secretName}, "spcps", klog.KObj(spcPodStatus))
319-
errs = append(errs, fmt.Errorf("failed to get data in spc %s/%s for secret %s, err: %w", req.Namespace, spcName, secretName, err))
320-
continue
321-
}
302+
var datamap map[string][]byte
303+
if datamap, err = secretutil.GetSecretData(secretObj.Data, secretType, files); err != nil {
304+
r.generateEvent(pod, corev1.EventTypeWarning, secretCreationFailedReason, fmt.Sprintf("failed to get data in spc %s/%s for secret %s, err: %+v", req.Namespace, spcName, secretName, err))
305+
klog.ErrorS(err, "failed to get data in spc for secret", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "secret", klog.ObjectRef{Namespace: req.Namespace, Name: secretName}, "spcps", klog.KObj(spcPodStatus))
306+
errs = append(errs, fmt.Errorf("failed to get data in spc %s/%s for secret %s, err: %w", req.Namespace, spcName, secretName, err))
307+
continue
308+
}
322309

323-
labelsMap := make(map[string]string)
324-
if secretObj.Labels != nil {
325-
labelsMap = secretObj.Labels
326-
}
327-
annotationsMap := make(map[string]string)
328-
if secretObj.Annotations != nil {
329-
annotationsMap = secretObj.Annotations
330-
}
331-
// Set secrets-store.csi.k8s.io/managed=true label on the secret that's created and managed
332-
// by the secrets-store-csi-driver. This label will be used to perform a filtered list watch
333-
// only on secrets created and managed by the driver
334-
labelsMap[SecretManagedLabel] = "true"
335-
336-
createFn := func() (bool, error) {
337-
if err := r.createK8sSecret(ctx, secretName, req.Namespace, datamap, labelsMap, annotationsMap, secretType); err != nil {
338-
klog.ErrorS(err, "failed to create Kubernetes secret", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "secret", klog.ObjectRef{Namespace: req.Namespace, Name: secretName}, "spcps", klog.KObj(spcPodStatus))
339-
// syncSecret.enabled is set to false by default in the helm chart for installing the driver in v0.0.23+
340-
// that would result in a forbidden error, so generate a warning that can be helpful for debugging
341-
if apierrors.IsForbidden(err) {
342-
klog.Warning(SyncSecretForbiddenWarning)
343-
}
344-
return false, nil
310+
labelsMap := make(map[string]string)
311+
if secretObj.Labels != nil {
312+
labelsMap = secretObj.Labels
313+
}
314+
annotationsMap := make(map[string]string)
315+
if secretObj.Annotations != nil {
316+
annotationsMap = secretObj.Annotations
317+
}
318+
// Set secrets-store.csi.k8s.io/managed=true label on the secret that's created and managed
319+
// by the secrets-store-csi-driver. This label will be used to perform a filtered list watch
320+
// only on secrets created and managed by the driver
321+
labelsMap[SecretManagedLabel] = "true"
322+
323+
createFn := func() (bool, error) {
324+
if err := r.createOrUpdateK8sSecret(ctx, secretName, req.Namespace, datamap, labelsMap, annotationsMap, secretType); err != nil {
325+
klog.ErrorS(err, "failed to create Kubernetes secret", "spc", klog.KObj(spc), "pod", klog.KObj(pod), "secret", klog.ObjectRef{Namespace: req.Namespace, Name: secretName}, "spcps", klog.KObj(spcPodStatus))
326+
// syncSecret.enabled is set to false by default in the helm chart for installing the driver in v0.0.23+
327+
// that would result in a forbidden error, so generate a warning that can be helpful for debugging
328+
if apierrors.IsForbidden(err) {
329+
klog.Warning(SyncSecretForbiddenWarning)
345330
}
346-
return true, nil
331+
return false, nil
347332
}
348-
funcs = append(funcs, createFn)
333+
return true, nil
349334
}
335+
funcs = append(funcs, createFn)
350336

351337
for _, f := range funcs {
352338
if err := wait.ExponentialBackoff(wait.Backoff{
@@ -410,9 +396,9 @@ func (r *SecretProviderClassPodStatusReconciler) processIfBelongsToNode(objMeta
410396
return true
411397
}
412398

413-
// createK8sSecret creates K8s secret with data from mounted files
399+
// createOrUpdateK8sSecret creates K8s secret with data from mounted files
414400
// If a secret with the same name already exists in the namespace of the pod, the error is nil.
415-
func (r *SecretProviderClassPodStatusReconciler) createK8sSecret(ctx context.Context, name, namespace string, datamap map[string][]byte, labelsmap map[string]string, annotationsmap map[string]string, secretType corev1.SecretType) error {
401+
func (r *SecretProviderClassPodStatusReconciler) createOrUpdateK8sSecret(ctx context.Context, name, namespace string, datamap map[string][]byte, labelsmap map[string]string, annotationsmap map[string]string, secretType corev1.SecretType) error {
416402
secret := &corev1.Secret{
417403
ObjectMeta: metav1.ObjectMeta{
418404
Namespace: namespace,
@@ -430,6 +416,13 @@ func (r *SecretProviderClassPodStatusReconciler) createK8sSecret(ctx context.Con
430416
return nil
431417
}
432418
if apierrors.IsAlreadyExists(err) {
419+
klog.InfoS("Kubernetes secret is already created", "secret", klog.ObjectRef{Namespace: namespace, Name: name})
420+
err := r.writer.Update(ctx, secret)
421+
if err != nil {
422+
klog.Errorf("Unable to update kubernetes secret", "secret", klog.ObjectRef{Namespace: namespace, Name: name})
423+
return err
424+
}
425+
klog.InfoS("successfully updated Kubernetes secret", "secret", klog.ObjectRef{Namespace: namespace, Name: name})
433426
return nil
434427
}
435428
return err
@@ -477,23 +470,6 @@ func (r *SecretProviderClassPodStatusReconciler) patchSecretWithOwnerRef(ctx con
477470
return nil
478471
}
479472

480-
// secretExists checks if the secret with name and namespace already exists
481-
func (r *SecretProviderClassPodStatusReconciler) secretExists(ctx context.Context, name, namespace string) (bool, error) {
482-
o := &corev1.Secret{}
483-
secretKey := types.NamespacedName{
484-
Namespace: namespace,
485-
Name: name,
486-
}
487-
err := r.Client.Get(ctx, secretKey, o)
488-
if err == nil {
489-
return true, nil
490-
}
491-
if apierrors.IsNotFound(err) {
492-
return false, nil
493-
}
494-
return false, err
495-
}
496-
497473
// generateEvent generates an event
498474
func (r *SecretProviderClassPodStatusReconciler) generateEvent(obj apiruntime.Object, eventType, reason, message string) {
499475
if obj != nil {

controllers/secretproviderclasspodstatus_controller_test.go

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,6 @@ func newReconciler(client client.Client, scheme *runtime.Scheme, nodeID string)
121121
}
122122
}
123123

124-
func TestSecretExists(t *testing.T) {
125-
g := NewWithT(t)
126-
127-
scheme, err := setupScheme()
128-
g.Expect(err).NotTo(HaveOccurred())
129-
130-
labels := map[string]string{"environment": "test"}
131-
annotations := map[string]string{"kubed.appscode.com/sync": "app=test"}
132-
133-
initObjects := []client.Object{
134-
newSecret("my-secret", "default", labels, annotations),
135-
}
136-
137-
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjects...).Build()
138-
reconciler := newReconciler(client, scheme, "node1")
139-
140-
exists, err := reconciler.secretExists(context.TODO(), "my-secret", "default")
141-
g.Expect(exists).To(Equal(true))
142-
g.Expect(err).NotTo(HaveOccurred())
143-
144-
exists, err = reconciler.secretExists(context.TODO(), "my-secret2", "default")
145-
g.Expect(exists).To(Equal(false))
146-
g.Expect(err).NotTo(HaveOccurred())
147-
}
148-
149124
func TestPatchSecretWithOwnerRef(t *testing.T) {
150125
g := NewWithT(t)
151126

@@ -183,7 +158,7 @@ func TestPatchSecretWithOwnerRef(t *testing.T) {
183158
g.Expect(secret.GetOwnerReferences()).To(HaveLen(1))
184159
}
185160

186-
func TestCreateK8sSecret(t *testing.T) {
161+
func TestCreateOrUpdateK8sSecret(t *testing.T) {
187162
g := NewWithT(t)
188163

189164
scheme, err := setupScheme()
@@ -198,11 +173,11 @@ func TestCreateK8sSecret(t *testing.T) {
198173
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjects...).Build()
199174
reconciler := newReconciler(client, scheme, "node1")
200175

201-
// secret already exists
202-
err = reconciler.createK8sSecret(context.TODO(), "my-secret", "default", nil, labels, annotations, corev1.SecretTypeOpaque)
176+
// secret already exists, just update it.
177+
err = reconciler.createOrUpdateK8sSecret(context.TODO(), "my-secret", "default", nil, labels, annotations, corev1.SecretTypeOpaque)
203178
g.Expect(err).NotTo(HaveOccurred())
204179

205-
err = reconciler.createK8sSecret(context.TODO(), "my-secret2", "default", nil, labels, annotations, corev1.SecretTypeOpaque)
180+
err = reconciler.createOrUpdateK8sSecret(context.TODO(), "my-secret2", "default", nil, labels, annotations, corev1.SecretTypeOpaque)
206181
g.Expect(err).NotTo(HaveOccurred())
207182
secret := &corev1.Secret{}
208183
err = client.Get(context.TODO(), types.NamespacedName{Name: "my-secret2", Namespace: "default"}, secret)

deploy/csidriver.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ spec:
77
attachRequired: false
88
volumeLifecycleModes:
99
- Ephemeral
10-
requiresRepublish: true

manifest_staging/charts/secrets-store-csi-driver/templates/csidriver.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ spec:
1111
volumeLifecycleModes:
1212
- Ephemeral
1313
{{- if and (semverCompare ">=1.20-0" .Capabilities.KubeVersion.Version) .Values.tokenRequests }}
14+
requiresRepublish: true
1415
tokenRequests:
1516
{{- toYaml .Values.tokenRequests | nindent 2 }}
1617
{{- end }}

manifest_staging/deploy/csidriver.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ spec:
77
attachRequired: false
88
volumeLifecycleModes:
99
- Ephemeral
10+
requiresRepublish: true

pkg/secrets-store/nodeserver.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,6 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
7676
errorReason := internalerrors.FailedToMount
7777
rotationEnabled := ns.rotationConfig.enabled
7878

79-
if ns.rotationConfig.enabled {
80-
if ns.rotationConfig.nextRotationTime.After(startTime) {
81-
klog.InfoS("Too soon !!!!, will rotate secret after", ns.rotationConfig.nextRotationTime)
82-
return &csi.NodePublishVolumeResponse{}, nil
83-
}
84-
ns.rotationConfig.nextRotationTime = ns.rotationConfig.nextRotationTime.Add(ns.rotationConfig.interval)
85-
}
86-
8779
defer func() {
8880
if err != nil {
8981
// if there is an error at any stage during node publish volume and if the path
@@ -127,6 +119,20 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
127119
podNamespace = attrib[CSIPodNamespace]
128120
podUID = attrib[CSIPodUID]
129121

122+
klog.InfoS("Checking object", "targetPath", targetPath, "pod", klog.ObjectRef{Namespace: podNamespace, Name: podName})
123+
124+
if ns.rotationConfig.enabled {
125+
lastModificationTime, err := ns.getLastUpdateTime(targetPath)
126+
if err != nil {
127+
klog.Infof("could not find last modification time for %s, error: %v\n", targetPath, err)
128+
} else if startTime.Before(lastModificationTime.Add(ns.rotationConfig.rotationPollInterval)) {
129+
// if next rotation is not yet due, then skip the mount operation
130+
return &csi.NodePublishVolumeResponse{}, nil
131+
}
132+
}
133+
134+
klog.InfoS("Processing object", "targetPath", targetPath, "pod", klog.ObjectRef{Namespace: podNamespace, Name: podName})
135+
130136
mounted, err = ns.ensureMountPoint(targetPath)
131137
if err != nil {
132138
// kubelet will not create the CSI NodePublishVolume target directory in 1.20+, in accordance with the CSI specification.
@@ -140,6 +146,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
140146
return nil, status.Errorf(codes.Internal, "failed to check if target path %s is mount point, err: %v", targetPath, err)
141147
}
142148
}
149+
// If rotation is not enabled, don't remount the already mounted secrets.
143150
if !rotationEnabled && mounted {
144151
klog.InfoS("target path is already mounted", "targetPath", targetPath, "pod", klog.ObjectRef{Namespace: podNamespace, Name: podName})
145152
return &csi.NodePublishVolumeResponse{}, nil
@@ -196,7 +203,6 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
196203
if parameters[CSIPodServiceAccountTokens] == "" {
197204
// Inject pod service account token into volume attributes
198205
klog.Error("csi.storage.k8s.io/serviceAccount.tokens is not populated, set RequiresRepublish")
199-
200206
}
201207

202208
// ensure it's read-only

pkg/secrets-store/nodeserver_test.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os"
2222
"path/filepath"
2323
"testing"
24+
"time"
2425

2526
secretsstorev1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
2627
"sigs.k8s.io/secrets-store-csi-driver/pkg/secrets-store/mocks"
@@ -267,6 +268,7 @@ func TestNodePublishVolume(t *testing.T) {
267268
name string
268269
nodePublishVolReq *csi.NodePublishVolumeRequest
269270
initObjects []client.Object
271+
rotationConfig *RotationConfig
270272
}{
271273
{
272274
name: "volume mount",
@@ -294,9 +296,13 @@ func TestNodePublishVolume(t *testing.T) {
294296
},
295297
},
296298
},
299+
rotationConfig: &RotationConfig{
300+
enabled: false,
301+
rotationPollInterval: time.Minute,
302+
},
297303
},
298304
{
299-
name: "volume mount with refresh token",
305+
name: "volume mount with refresh token ",
300306
nodePublishVolReq: &csi.NodePublishVolumeRequest{
301307
VolumeCapability: &csi.VolumeCapability{},
302308
VolumeId: "testvolid1",
@@ -324,6 +330,41 @@ func TestNodePublishVolume(t *testing.T) {
324330
},
325331
},
326332
},
333+
rotationConfig: &RotationConfig{
334+
enabled: true,
335+
rotationPollInterval: -1 * time.Minute, // Using negative interval to pass the rotation interval check in unit tests
336+
},
337+
},
338+
{
339+
name: "volume mount with rotation but skipped",
340+
nodePublishVolReq: &csi.NodePublishVolumeRequest{
341+
VolumeCapability: &csi.VolumeCapability{},
342+
VolumeId: "testvolid1",
343+
TargetPath: targetPath(t),
344+
VolumeContext: map[string]string{
345+
"secretProviderClass": "provider1",
346+
CSIPodName: "pod1",
347+
CSIPodNamespace: "default",
348+
CSIPodUID: "poduid1",
349+
},
350+
Readonly: true,
351+
},
352+
initObjects: []client.Object{
353+
&secretsstorev1.SecretProviderClass{
354+
ObjectMeta: metav1.ObjectMeta{
355+
Name: "provider1",
356+
Namespace: "default",
357+
},
358+
Spec: secretsstorev1.SecretProviderClassSpec{
359+
Provider: "provider1",
360+
Parameters: map[string]string{"parameter1": "value1"},
361+
},
362+
},
363+
},
364+
rotationConfig: &RotationConfig{
365+
enabled: true,
366+
rotationPollInterval: time.Minute,
367+
},
327368
},
328369
}
329370

@@ -338,7 +379,7 @@ func TestNodePublishVolume(t *testing.T) {
338379
t.Run(test.name, func(t *testing.T) {
339380
r := mocks.NewFakeReporter()
340381

341-
ns, err := testNodeServer(t, fake.NewClientBuilder().WithScheme(s).WithObjects(test.initObjects...).Build(), r, &RotationConfig{})
382+
ns, err := testNodeServer(t, fake.NewClientBuilder().WithScheme(s).WithObjects(test.initObjects...).Build(), r, test.rotationConfig)
342383
if err != nil {
343384
t.Fatalf("expected error to be nil, got: %+v", err)
344385
}
@@ -365,8 +406,13 @@ func TestNodePublishVolume(t *testing.T) {
365406
if err != nil {
366407
t.Fatalf("expected err to be nil, got: %v", err)
367408
}
368-
if len(mnts) == 0 {
369-
t.Errorf("expected mounts...: %v", mnts)
409+
expectedMounts := 1
410+
if ns.rotationConfig.enabled && ns.rotationConfig.rotationPollInterval > 0 {
411+
// If due to rotation interval, NodePublishVolume has skipped, there should not be any mount operation
412+
expectedMounts = 0
413+
}
414+
if len(mnts) != expectedMounts {
415+
t.Errorf("[Number of mounts] want : %d, got mount: %d", expectedMounts, len(mnts))
370416
}
371417
}
372418
})

0 commit comments

Comments
 (0)