@@ -528,6 +528,19 @@ func (r *DesignateAPIReconciler) reconcileDelete(ctx context.Context, instance *
528528 ); err != nil {
529529 return ctrlResult , err
530530 }
531+ // Remove consumer finalizer from AC secrets DesignateAPI was consuming.
532+ // Check both status and spec to handle the edge case where the reconciler
533+ // crashed after adding the finalizer but before updating the status.
534+ for _ , secretName := range []string {
535+ instance .Status .ApplicationCredentialSecret ,
536+ instance .Spec .Auth .ApplicationCredentialSecret ,
537+ } {
538+ if err := keystonev1 .RemoveACSecretConsumerFinalizer (ctx , helper , instance .Namespace ,
539+ secretName , designate .ACConsumerFinalizer ); err != nil {
540+ return ctrl.Result {}, err
541+ }
542+ }
543+
531544 // We did all the cleanup on the objects we created so we can remove the
532545 // finalizer from ourselves to allow the deletion
533546 controllerutil .RemoveFinalizer (instance , helper .GetFinalizer ())
@@ -905,6 +918,24 @@ func (r *DesignateAPIReconciler) reconcileNormal(ctx context.Context, instance *
905918 return ctrl.Result {}, nil
906919 }
907920
921+ // Add consumer finalizer to the new AC secret early, before deployment.
922+ // The old secret's finalizer is removed later (after all services deploy)
923+ // so that rapid rotations don't revoke a credential still in use by pods.
924+ if instance .Spec .Auth .ApplicationCredentialSecret != "" {
925+ if err := keystonev1 .ManageACSecretFinalizer (ctx , helper , instance .Namespace ,
926+ instance .Spec .Auth .ApplicationCredentialSecret ,
927+ "" ,
928+ designate .ACConsumerFinalizer ); err != nil {
929+ instance .Status .Conditions .Set (condition .FalseCondition (
930+ condition .ServiceConfigReadyCondition ,
931+ condition .ErrorReason ,
932+ condition .SeverityWarning ,
933+ condition .ServiceConfigReadyErrorMessage ,
934+ err .Error ()))
935+ return ctrl.Result {}, err
936+ }
937+ }
938+
908939 instance .Status .Conditions .MarkTrue (condition .ServiceConfigReadyCondition , condition .ServiceConfigReadyMessage )
909940
910941 // Create ConfigMaps and Secrets - end
@@ -1088,6 +1119,25 @@ func (r *DesignateAPIReconciler) reconcileNormal(ctx context.Context, instance *
10881119 }
10891120 // create Deployment - end
10901121
1122+ // Manage the old AC secret's finalizer and status tracking.
1123+ // On rotation (old != new), only remove the old secret's finalizer after
1124+ // all sub-services are ready with the new credentials. This prevents
1125+ // premature revocation during rapid rotations.
1126+ isRotation := instance .Status .ApplicationCredentialSecret != "" && instance .Status .ApplicationCredentialSecret != instance .Spec .Auth .ApplicationCredentialSecret
1127+
1128+ if isRotation {
1129+ allServicesReady := instance .Status .Conditions .AllSubConditionIsTrue ()
1130+ if allServicesReady {
1131+ if err := keystonev1 .RemoveACSecretConsumerFinalizer (ctx , helper , instance .Namespace ,
1132+ instance .Status .ApplicationCredentialSecret , designate .ACConsumerFinalizer ); err != nil {
1133+ return ctrl.Result {}, err
1134+ }
1135+ instance .Status .ApplicationCredentialSecret = instance .Spec .Auth .ApplicationCredentialSecret
1136+ }
1137+ } else {
1138+ instance .Status .ApplicationCredentialSecret = instance .Spec .Auth .ApplicationCredentialSecret
1139+ }
1140+
10911141 // We reached the end of the Reconcile, update the Ready condition based on
10921142 // the sub conditions
10931143 if instance .Status .Conditions .AllSubConditionIsTrue () {
0 commit comments