@@ -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
@@ -511,6 +512,19 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
511512 return ctrlResult , err
512513 }
513514
515+ // Check for Application Credentials
516+ ctrlResult , err = r .verifyApplicationCredentials (
517+ ctx ,
518+ r .GetLogger (ctx ),
519+ helper .GetClient (),
520+ instance .Namespace ,
521+ "swift" ,
522+ & envVars ,
523+ )
524+ if (err != nil || ctrlResult != ctrl.Result {}) {
525+ return ctrlResult , err
526+ }
527+
514528 // Get the service password and pass it to the template
515529 sps , _ , err := secret .GetSecret (ctx , helper , instance .Spec .Secret , instance .Namespace )
516530 if err != nil {
@@ -570,6 +584,20 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
570584 return ctrl.Result {}, err
571585 }
572586
587+ // Get Application Credential data if available
588+ acID , acSecret , useAC , err := r .getApplicationCredentialData (
589+ ctx ,
590+ helper .GetClient (),
591+ instance .Namespace ,
592+ "swift" ,
593+ )
594+ if err != nil {
595+ Log .Info ("Failed to get application credentials, continuing with password auth" , "error" , err .Error ())
596+ useAC = false
597+ acID = ""
598+ acSecret = ""
599+ }
600+
573601 // Create a Secret populated with content from templates/
574602 tpl := swiftproxy .SecretTemplates (
575603 instance ,
@@ -582,6 +610,9 @@ func (r *SwiftProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
582610 secretRef ,
583611 os .GetRegion (),
584612 transportURLString ,
613+ useAC ,
614+ acID ,
615+ acSecret ,
585616 )
586617 err = secret .EnsureSecrets (ctx , helper , instance , tpl , & envVars )
587618 if err != nil {
@@ -835,7 +866,7 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
835866 return nil
836867 }
837868
838- return ctrl .NewControllerManagedBy (mgr ).
869+ b := ctrl .NewControllerManagedBy (mgr ).
839870 For (& swiftv1beta1.SwiftProxy {}).
840871 Owns (& corev1.Secret {}).
841872 Owns (& keystonev1.KeystoneService {}).
@@ -855,8 +886,9 @@ func (r *SwiftProxyReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
855886 builder .WithPredicates (predicate.GenerationChangedPredicate {})).
856887 Watches (& keystonev1.KeystoneAPI {},
857888 handler .EnqueueRequestsFromMapFunc (r .findObjectForSrc ),
858- builder .WithPredicates (keystonev1 .KeystoneAPIStatusChangedPredicate )).
859- Complete (r )
889+ builder .WithPredicates (keystonev1 .KeystoneAPIStatusChangedPredicate ))
890+ b = AddACWatches (b )
891+ return b .Complete (r )
860892}
861893
862894func (r * SwiftProxyReconciler ) findObjectsForSrc (ctx context.Context , src client.Object ) []reconcile.Request {
@@ -1020,3 +1052,135 @@ func (r *SwiftProxyReconciler) transportURLCreateOrUpdate(
10201052
10211053 return transportURL , op , err
10221054}
1055+
1056+ // verifyApplicationCredentials handles Application Credentials validation
1057+ // It only uses AC if it's in a complete/ready state, otherwise continues with password auth
1058+ func (r * SwiftProxyReconciler ) verifyApplicationCredentials (
1059+ ctx context.Context ,
1060+ log logr.Logger ,
1061+ client client.Client ,
1062+ namespace string ,
1063+ serviceName string ,
1064+ configVars * map [string ]env.Setter ,
1065+ ) (ctrl.Result , error ) {
1066+ // Check for Application Credential - only use it if it's fully ready
1067+ acName := fmt .Sprintf ("ac-%s" , serviceName )
1068+ ac := & keystonev1.KeystoneApplicationCredential {}
1069+
1070+ if err := client .Get (ctx , types.NamespacedName {Namespace : namespace , Name : acName }, ac ); err == nil {
1071+ // AC CR exists - check if it's in ready state
1072+ if r .isACReady (ctx , log , client , ac ) {
1073+ // AC is ready - add it to configVars for hash tracking
1074+ secretKey := types.NamespacedName {Namespace : namespace , Name : ac .Status .SecretName }
1075+ hash , res , err := secret .VerifySecret (
1076+ ctx ,
1077+ secretKey ,
1078+ []string {"AC_ID" , "AC_SECRET" },
1079+ client ,
1080+ 10 * time .Second ,
1081+ )
1082+ if err != nil {
1083+ log .Info ("ApplicationCredential secret verification failed, continuing with password auth" , "error" , err .Error ())
1084+ } else if res .RequeueAfter > 0 {
1085+ return res , nil
1086+ } else {
1087+ // AC is ready and verified - add to configVars for change tracking
1088+ (* configVars )["secret-" + ac .Status .SecretName ] = env .SetValue (hash )
1089+ log .Info ("Using ApplicationCredential authentication" )
1090+ }
1091+ } else {
1092+ // AC exists but not ready - wait for it
1093+ log .Info ("ApplicationCredential exists but not ready, waiting" )
1094+ return ctrl.Result {RequeueAfter : time .Duration (10 ) * time .Second }, nil
1095+ }
1096+ } else if ! k8s_errors .IsNotFound (err ) {
1097+ return ctrl.Result {}, err
1098+ }
1099+
1100+ return ctrl.Result {}, nil
1101+ }
1102+
1103+ // isACReady checks if ApplicationCredential is in a ready state with all required components
1104+ func (r * SwiftProxyReconciler ) isACReady (ctx context.Context , log logr.Logger , client client.Client , ac * keystonev1.KeystoneApplicationCredential ) bool {
1105+ // Check if AC has completed setup (secret name is populated)
1106+ if ac .Status .SecretName == "" {
1107+ log .V (1 ).Info ("AC not ready: SecretName not populated" , "ac" , ac .Name )
1108+ return false
1109+ }
1110+
1111+ secret := & corev1.Secret {}
1112+ secretKey := types.NamespacedName {Namespace : ac .Namespace , Name : ac .Status .SecretName }
1113+ if err := client .Get (ctx , secretKey , secret ); err != nil {
1114+ log .V (1 ).Info ("AC not ready: Secret not found" , "secret" , secretKey , "error" , err )
1115+ return false
1116+ }
1117+
1118+ acID , acIDExists := secret .Data ["AC_ID" ]
1119+ acSecret , acSecretExists := secret .Data ["AC_SECRET" ]
1120+
1121+ if ! acIDExists || ! acSecretExists {
1122+ log .V (1 ).Info ("AC not ready: Missing required fields" , "secret" , secretKey )
1123+ return false
1124+ }
1125+
1126+ if len (acID ) == 0 || len (acSecret ) == 0 {
1127+ log .V (1 ).Info ("AC not ready: Empty required fields" , "secret" , secretKey )
1128+ return false
1129+ }
1130+
1131+ log .V (1 ).Info ("AC is ready" , "secret" , secretKey )
1132+ return true
1133+ }
1134+
1135+ // getApplicationCredentialData retrieves AC data from secret if available and ready
1136+ func (r * SwiftProxyReconciler ) getApplicationCredentialData (
1137+ ctx context.Context ,
1138+ client client.Client ,
1139+ namespace string ,
1140+ serviceName string ,
1141+ ) (acID string , acSecret string , useAC bool , err error ) {
1142+ // Check for Application Credential
1143+ acName := fmt .Sprintf ("ac-%s" , serviceName )
1144+ ac := & keystonev1.KeystoneApplicationCredential {}
1145+
1146+ if err := client .Get (ctx , types.NamespacedName {Namespace : namespace , Name : acName }, ac ); err != nil {
1147+ if k8s_errors .IsNotFound (err ) {
1148+ return "" , "" , false , nil // AC not found, use password auth
1149+ }
1150+ return "" , "" , false , err
1151+ }
1152+
1153+ // AC exists, check if it's ready
1154+ if ac .Status .SecretName == "" {
1155+ return "" , "" , false , nil // AC not ready, use password auth
1156+ }
1157+
1158+ // Look up the AC secret
1159+ secret := & corev1.Secret {}
1160+ secName := types.NamespacedName {Namespace : ac .Namespace , Name : ac .Status .SecretName }
1161+ if err := client .Get (ctx , secName , secret ); err != nil {
1162+ if k8s_errors .IsNotFound (err ) {
1163+ return "" , "" , false , fmt .Errorf ("%w: ApplicationCredential Secret %s" , util .ErrNotFound , secName )
1164+ }
1165+ return "" , "" , false , err
1166+ }
1167+
1168+ // Validate required AC secret keys exist and are not empty
1169+ acIDData , acIDExists := secret .Data ["AC_ID" ]
1170+ acSecretData , acSecretExists := secret .Data ["AC_SECRET" ]
1171+
1172+ if ! acIDExists {
1173+ return "" , "" , false , fmt .Errorf ("%w: field AC_ID not found in ApplicationCredential Secret %s" , util .ErrFieldNotFound , secName )
1174+ }
1175+ if ! acSecretExists {
1176+ return "" , "" , false , fmt .Errorf ("%w: field AC_SECRET not found in ApplicationCredential Secret %s" , util .ErrFieldNotFound , secName )
1177+ }
1178+ if len (acIDData ) == 0 {
1179+ return "" , "" , false , fmt .Errorf ("%w: field AC_ID is empty in ApplicationCredential Secret %s" , util .ErrFieldNotFound , secName )
1180+ }
1181+ if len (acSecretData ) == 0 {
1182+ return "" , "" , false , fmt .Errorf ("%w: field AC_SECRET is empty in ApplicationCredential Secret %s" , util .ErrFieldNotFound , secName )
1183+ }
1184+
1185+ return string (acIDData ), string (acSecretData ), true , nil
1186+ }
0 commit comments