@@ -27,6 +27,7 @@ import (
2727 corev1 "k8s.io/api/core/v1"
2828 policyv1 "k8s.io/api/policy/v1"
2929 rbacv1 "k8s.io/api/rbac/v1"
30+ apierrors "k8s.io/apimachinery/pkg/api/errors"
3031 "k8s.io/apimachinery/pkg/api/meta"
3132 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233 "k8s.io/apimachinery/pkg/labels"
@@ -181,7 +182,6 @@ func NewController(ctx context.Context, registry *typed.Registry, dclient dynami
181182
182183 for _ , gvr := range []schema.GroupVersionResource {
183184 appsv1 .SchemeGroupVersion .WithResource ("deployments" ),
184- corev1 .SchemeGroupVersion .WithResource ("secrets" ),
185185 corev1 .SchemeGroupVersion .WithResource ("serviceaccounts" ),
186186 corev1 .SchemeGroupVersion .WithResource ("services" ),
187187 corev1 .SchemeGroupVersion .WithResource ("pods" ),
@@ -202,6 +202,27 @@ func NewController(ctx context.Context, registry *typed.Registry, dclient dynami
202202 return nil , err
203203 }
204204 }
205+ // Secrets get per-type indexes (one per credential role) so that each
206+ // adoption handler only sees secrets of its own type and never
207+ // misidentifies another role's secret as stale. OwningClusterIndex is
208+ // kept for the legacy SecretRef path and for syncExternalResource, which
209+ // uses it to re-enqueue the owning cluster when any secret changes.
210+ secretsInf := externalInformerFactory .ForResource (corev1 .SchemeGroupVersion .WithResource ("secrets" )).Informer ()
211+ if err := secretsInf .AddIndexers (cache.Indexers {
212+ metadata .OwningClusterIndex : metadata .GetClusterKeyFromMeta ,
213+ metadata .OwningClusterDatastoreURIIndex : metadata .GetClusterKeyFromMetaForType (metadata .CredentialTypeDatastoreURI ),
214+ metadata .OwningClusterPresharedKeyIndex : metadata .GetClusterKeyFromMetaForType (metadata .CredentialTypePresharedKey ),
215+ metadata .OwningClusterMigrationSecretsIndex : metadata .GetClusterKeyFromMetaForType (metadata .CredentialTypeMigrationSecrets ),
216+ }); err != nil {
217+ return nil , err
218+ }
219+ if _ , err := secretsInf .AddEventHandler (cache.ResourceEventHandlerFuncs {
220+ AddFunc : func (obj any ) { c .syncExternalResource (obj ) },
221+ UpdateFunc : func (_ , obj any ) { c .syncExternalResource (obj ) },
222+ DeleteFunc : func (obj any ) { c .syncExternalResource (obj ) },
223+ }); err != nil {
224+ return nil , err
225+ }
205226 externalInformerFactories = append (externalInformerFactories , externalInformerFactory )
206227 }
207228
@@ -414,6 +435,40 @@ func credentialSecretNames(cluster *v1alpha1.SpiceDBCluster) []string {
414435 return names
415436}
416437
438+ type credentialSecret struct {
439+ name string
440+ credType string
441+ }
442+
443+ // credentialSecretsForCluster returns one entry per non-skipped credential ref,
444+ // preserving the credential type for each. Unlike credentialSecretNames, the
445+ // same name may appear more than once when a secret is shared across multiple
446+ // roles — each role runs its own adoption handler with its own type label.
447+ func credentialSecretsForCluster (cluster * v1alpha1.SpiceDBCluster ) []credentialSecret {
448+ creds := cluster .Spec .Credentials
449+ if creds == nil && cluster .Spec .SecretRef != "" {
450+ return []credentialSecret {{name : cluster .Spec .SecretRef , credType : "" }}
451+ }
452+ if creds == nil {
453+ return nil
454+ }
455+ roleSecrets := []struct {
456+ ref * v1alpha1.CredentialRef
457+ credType string
458+ }{
459+ {creds .DatastoreURI , metadata .CredentialTypeDatastoreURI },
460+ {creds .PresharedKey , metadata .CredentialTypePresharedKey },
461+ {creds .MigrationSecrets , metadata .CredentialTypeMigrationSecrets },
462+ }
463+ var result []credentialSecret
464+ for _ , rs := range roleSecrets {
465+ if rs .ref != nil && ! rs .ref .Skip && rs .ref .SecretName != "" {
466+ result = append (result , credentialSecret {name : rs .ref .SecretName , credType : rs .credType })
467+ }
468+ }
469+ return result
470+ }
471+
417472// syncExternalResource is called when a dependent resource is updated:
418473// It queues the owning SpiceDBCluster for reconciliation based on the labels.
419474// No other reconciliation should take place here; we keep a single state
@@ -576,21 +631,35 @@ func (c *Controller) selfPauseCluster(...handler.Handler) handler.Handler {
576631func (c * Controller ) secretAdopter (next ... handler.Handler ) handler.Handler {
577632 secretsGVR := corev1 .SchemeGroupVersion .WithResource ("secrets" )
578633 return handler .NewHandlerFromFunc (func (ctx context.Context ) {
579- names := CtxSecretNames .Value (ctx )
580- if len (names ) == 0 {
634+ cluster := CtxCluster .MustValue (ctx )
635+ pairs := credentialSecretsForCluster (cluster )
636+ if len (pairs ) == 0 {
581637 handler .Handlers (next ).MustOne ().Handle (ctx )
582638 return
583639 }
584- cluster := CtxCluster . MustValue ( ctx )
585- for _ , name := range names {
640+ for _ , cs := range pairs {
641+ credType := cs . credType
586642 secretCtx := CtxSecretNN .WithValue (ctx , types.NamespacedName {
587- Name : name ,
643+ Name : cs . name ,
588644 Namespace : cluster .Namespace ,
589645 })
590646 NewSecretAdoptionHandler (
591647 c .Recorder ,
592648 func (ctx context.Context ) (* corev1.Secret , error ) {
593- return typed .MustListerForKey [* corev1.Secret ](c .Registry , typed .NewRegistryKey (DependentFactoryKey (CtxCacheNamespace .Value (ctx )), secretsGVR )).ByNamespace (CtxSecretNN .MustValue (ctx ).Namespace ).Get (CtxSecretNN .MustValue (ctx ).Name )
649+ secret , err := typed .MustListerForKey [* corev1.Secret ](c .Registry , typed .NewRegistryKey (DependentFactoryKey (CtxCacheNamespace .Value (ctx )), secretsGVR )).ByNamespace (CtxSecretNN .MustValue (ctx ).Namespace ).Get (CtxSecretNN .MustValue (ctx ).Name )
650+ if err != nil {
651+ return nil , err
652+ }
653+ // If the secret lacks this role's per-type label key, return
654+ // NotFound so the adoption handler applies the full label set.
655+ // This migrates secrets from operator versions that did not
656+ // set per-role labels.
657+ if lk := metadata .LabelKeyForCredentialType (credType ); lk != "" {
658+ if _ , ok := secret .Labels [lk ]; ! ok {
659+ return nil , apierrors .NewNotFound (corev1 .SchemeGroupVersion .WithResource ("secrets" ).GroupResource (), CtxSecretNN .MustValue (ctx ).Name )
660+ }
661+ }
662+ return secret , nil
594663 },
595664 func (ctx context.Context , err error ) {
596665 cluster := CtxCluster .MustValue (ctx )
@@ -618,6 +687,7 @@ func (c *Controller) secretAdopter(next ...handler.Handler) handler.Handler {
618687 _ , err := c .kclient .CoreV1 ().Secrets (nn .Namespace ).Get (ctx , nn .Name , metav1.GetOptions {})
619688 return err
620689 },
690+ credType ,
621691 handler .NoopHandler ,
622692 ).Handle (secretCtx )
623693 if errors .Is (secretCtx .Err (), context .Canceled ) {
0 commit comments