@@ -39,6 +39,7 @@ import (
3939 appsv1 "k8s.io/api/apps/v1"
4040 corev1 "k8s.io/api/core/v1"
4141 apierrors "k8s.io/apimachinery/pkg/api/errors"
42+ k8s_errors "k8s.io/apimachinery/pkg/api/errors"
4243 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4344 ctrl "sigs.k8s.io/controller-runtime"
4445
@@ -510,6 +511,19 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
510511 return ctrlResult , err
511512 }
512513
514+ // Check for Application Credentials
515+ ctrlResult , err = r .verifyApplicationCredentials (
516+ ctx ,
517+ r .GetLogger (ctx ),
518+ helper .GetClient (),
519+ instance .Namespace ,
520+ "swift" ,
521+ & envVars ,
522+ )
523+ if (err != nil || ctrlResult != ctrl.Result {}) {
524+ return ctrlResult , err
525+ }
526+
513527 // Get the service password and pass it to the template
514528 sps , _ , err := secret .GetSecret (ctx , helper , instance .Spec .Secret , instance .Namespace )
515529 if err != nil {
@@ -569,6 +583,20 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
569583 return ctrl.Result {}, err
570584 }
571585
586+ // Get Application Credential data if available
587+ useAC := false
588+ acID := ""
589+ acSecret := ""
590+ // Try to get Application Credential for this service (via keystone api helper)
591+ if acData , err := keystonev1 .GetApplicationCredentialFromSecret (ctx , r .Client , instance .Namespace , swift .ServiceName ); err != nil {
592+ Log .Error (err , "Failed to get ApplicationCredential for service" , "service" , swift .ServiceName )
593+ } else if acData != nil {
594+ useAC = true
595+ acID = acData .ID
596+ acSecret = acData .Secret
597+ Log .Info ("Using ApplicationCredentials auth" , "service" , swift .ServiceName )
598+ }
599+
572600 // Create a Secret populated with content from templates/
573601 tpl := swiftproxy .SecretTemplates (
574602 instance ,
@@ -581,7 +609,13 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
581609 secretRef ,
582610 os .GetRegion (),
583611 transportURLString ,
612+ << << << < HEAD
584613 instance .Spec .APITimeout ,
614+ == == == =
615+ useAC ,
616+ acID ,
617+ acSecret ,
618+ >> >> >> > d1ed12b (Application Credential support )
585619 )
586620 err = secret .EnsureSecrets (ctx , helper , instance , tpl , & envVars )
587621 if err != nil {
@@ -835,7 +869,43 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
835869 return nil
836870 }
837871
838- return ctrl .NewControllerManagedBy (mgr ).
872+ // Application Credential secret watching function
873+ acSecretFn := func (_ context.Context , o client.Object ) []reconcile.Request {
874+ name := o .GetName ()
875+ ns := o .GetNamespace ()
876+ result := []reconcile.Request {}
877+
878+ // Only handle Secret objects
879+ if _ , isSecret := o .(* corev1.Secret ); ! isSecret {
880+ return nil
881+ }
882+
883+ // Check if this is a swift AC secret by name pattern (ac-swift-secret)
884+ expectedSecretName := keystonev1 .GetACSecretName ("swift" )
885+ if name == expectedSecretName {
886+ // get all SwiftProxy CRs in this namespace
887+ swiftProxies := & swiftv1beta1.SwiftProxyList {}
888+ listOpts := []client.ListOption {
889+ client .InNamespace (ns ),
890+ }
891+ if err := r .Client .List (context .Background (), swiftProxies , listOpts ... ); err != nil {
892+ return nil
893+ }
894+
895+ // Enqueue reconcile for all swift proxy instances
896+ for _ , cr := range swiftProxies .Items {
897+ objKey := client.ObjectKey {
898+ Namespace : ns ,
899+ Name : cr .Name ,
900+ }
901+ result = append (result , reconcile.Request {NamespacedName : objKey })
902+ }
903+ }
904+
905+ return result
906+ }
907+
908+ b := ctrl .NewControllerManagedBy (mgr ).
839909 For (& swiftv1beta1.SwiftProxy {}).
840910 Owns (& corev1.Secret {}).
841911 Owns (& keystonev1.KeystoneService {}).
@@ -848,15 +918,17 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
848918 handler .EnqueueRequestsFromMapFunc (r .findObjectsForSrc ),
849919 builder .WithPredicates (predicate.ResourceVersionChangedPredicate {}),
850920 ).
921+ Watches (& corev1.Secret {},
922+ handler .EnqueueRequestsFromMapFunc (acSecretFn )).
851923 Watches (& memcachedv1.Memcached {},
852924 handler .EnqueueRequestsFromMapFunc (memcachedFn )).
853925 Watches (& topologyv1.Topology {},
854926 handler .EnqueueRequestsFromMapFunc (r .findObjectsForSrc ),
855927 builder .WithPredicates (predicate.GenerationChangedPredicate {})).
856928 Watches (& keystonev1.KeystoneAPI {},
857929 handler .EnqueueRequestsFromMapFunc (r .findObjectForSrc ),
858- builder .WithPredicates (keystonev1 .KeystoneAPIStatusChangedPredicate )).
859- Complete (r )
930+ builder .WithPredicates (keystonev1 .KeystoneAPIStatusChangedPredicate ))
931+ return b . Complete (r )
860932}
861933
862934func (r * SwiftProxyReconciler ) findObjectsForSrc (ctx context.Context , src client.Object ) []reconcile.Request {
@@ -1020,3 +1092,82 @@ func (r *SwiftProxyReconciler) transportURLCreateOrUpdate(
10201092
10211093 return transportURL , op , err
10221094}
1095+
1096+ // verifyApplicationCredentials handles Application Credentials validation
1097+ // It only uses AC if it's in a complete/ready state, otherwise continues with password auth
1098+ func (r * SwiftProxyReconciler ) verifyApplicationCredentials (
1099+ ctx context.Context ,
1100+ log logr.Logger ,
1101+ client client.Client ,
1102+ namespace string ,
1103+ serviceName string ,
1104+ configVars * map [string ]env.Setter ,
1105+ ) (ctrl.Result , error ) {
1106+ // Check for Application Credential - only use it if it's fully ready
1107+ acName := fmt .Sprintf ("ac-%s" , serviceName )
1108+ ac := & keystonev1.KeystoneApplicationCredential {}
1109+
1110+ if err := client .Get (ctx , types.NamespacedName {Namespace : namespace , Name : acName }, ac ); err == nil {
1111+ // AC CR exists - check if it's in ready state
1112+ if r .isACReady (ctx , log , client , ac ) {
1113+ // AC is ready - add it to configVars for hash tracking
1114+ secretKey := types.NamespacedName {Namespace : namespace , Name : ac .Status .SecretName }
1115+ hash , res , err := secret .VerifySecret (
1116+ ctx ,
1117+ secretKey ,
1118+ []string {"AC_ID" , "AC_SECRET" },
1119+ client ,
1120+ 10 * time .Second ,
1121+ )
1122+ if err != nil {
1123+ log .Info ("ApplicationCredential secret verification failed, continuing with password auth" , "error" , err .Error ())
1124+ } else if res .RequeueAfter > 0 {
1125+ return res , nil
1126+ } else {
1127+ // AC is ready and verified - add to configVars for change tracking
1128+ (* configVars )["secret-" + ac .Status .SecretName ] = env .SetValue (hash )
1129+ log .Info ("Using ApplicationCredential authentication" )
1130+ }
1131+ } else {
1132+ // AC exists but not ready - wait for it
1133+ log .Info ("ApplicationCredential exists but not ready, waiting" )
1134+ return ctrl.Result {RequeueAfter : time .Duration (10 ) * time .Second }, nil
1135+ }
1136+ } else if ! k8s_errors .IsNotFound (err ) {
1137+ return ctrl.Result {}, err
1138+ }
1139+
1140+ return ctrl.Result {}, nil
1141+ }
1142+
1143+ // isACReady checks if ApplicationCredential is in a ready state with all required components
1144+ func (r * SwiftProxyReconciler ) isACReady (ctx context.Context , log logr.Logger , client client.Client , ac * keystonev1.KeystoneApplicationCredential ) bool {
1145+ // Check if AC has completed setup (secret name is populated)
1146+ if ac .Status .SecretName == "" {
1147+ log .V (1 ).Info ("AC not ready: SecretName not populated" , "ac" , ac .Name )
1148+ return false
1149+ }
1150+
1151+ secret := & corev1.Secret {}
1152+ secretKey := types.NamespacedName {Namespace : ac .Namespace , Name : ac .Status .SecretName }
1153+ if err := client .Get (ctx , secretKey , secret ); err != nil {
1154+ log .V (1 ).Info ("AC not ready: Secret not found" , "secret" , secretKey , "error" , err )
1155+ return false
1156+ }
1157+
1158+ acID , acIDExists := secret .Data ["AC_ID" ]
1159+ acSecret , acSecretExists := secret .Data ["AC_SECRET" ]
1160+
1161+ if ! acIDExists || ! acSecretExists {
1162+ log .V (1 ).Info ("AC not ready: Missing required fields" , "secret" , secretKey )
1163+ return false
1164+ }
1165+
1166+ if len (acID ) == 0 || len (acSecret ) == 0 {
1167+ log .V (1 ).Info ("AC not ready: Empty required fields" , "secret" , secretKey )
1168+ return false
1169+ }
1170+
1171+ log .V (1 ).Info ("AC is ready" , "secret" , secretKey )
1172+ return true
1173+ }
0 commit comments