@@ -18,15 +18,13 @@ package controllers
1818
1919import (
2020 "context"
21- "crypto/sha256"
22- "encoding/hex"
2321 "encoding/json"
2422 "fmt"
25- "strconv"
26- "time"
23+ "strings"
2724
2825 capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1"
2926 controllerhelper "github.com/3scale/3scale-operator/pkg/controller/helper"
27+ rand "github.com/3scale/3scale-operator/pkg/crypto/rand"
3028 "github.com/3scale/3scale-operator/pkg/helper"
3129 "github.com/3scale/3scale-operator/pkg/reconcilers"
3230 "github.com/3scale/3scale-operator/version"
@@ -133,6 +131,12 @@ func (r *ApplicationAuthReconciler) Reconcile(ctx context.Context, req ctrl.Requ
133131 return ctrl.Result {}, err
134132 }
135133
134+ authMode := product .Spec .AuthenticationMode ()
135+ if authMode == nil {
136+ err := fmt .Errorf ("unable to identify authentication mode from Product CR" )
137+ return r .reconcileStatus (applicationAuth , err , reqLogger )
138+ }
139+
136140 // Retrieve providerAccountRef
137141 providerAccount , err := controllerhelper .LookupProviderAccount (r .Client (), applicationAuth .GetNamespace (), applicationAuth .Spec .ProviderAccountRef , r .Logger ())
138142 if err != nil {
@@ -158,9 +162,20 @@ func (r *ApplicationAuthReconciler) Reconcile(ctx context.Context, req ctrl.Requ
158162 return ctrl.Result {}, err
159163 }
160164
161- // populate authSecret struct
162- authSecret := authSecretReferenceSource (r .Client (), applicationAuth .Namespace , applicationAuth .Spec .AuthSecretRef , reqLogger )
163- err = r .applicationAuthReconciler (applicationAuth , * developerAccount .Status .ID , * application .Status .ID , product , * authSecret , threescaleAPIClient )
165+ controller , err := GetAuthController (* authMode , reqLogger )
166+ if err != nil {
167+ return ctrl.Result {}, err
168+ }
169+
170+ // populate authSecret struct and make sure required fields are available
171+ shouldGenerateSecret := applicationAuth .Spec .GenerateSecret != nil && * applicationAuth .Spec .GenerateSecret
172+ reqLogger .Info ("LookupAuthSecret" , "ns" , applicationAuth .Namespace , "authSecretRef" , applicationAuth .Spec .AuthSecretRef )
173+ authSecret , err := controller .SecretReferenceSource (r .Client (), applicationAuth .Namespace , applicationAuth .Spec .AuthSecretRef , shouldGenerateSecret )
174+ if err != nil {
175+ return r .reconcileStatus (applicationAuth , err , reqLogger )
176+ }
177+
178+ err = controller .Sync (threescaleAPIClient , * developerAccount .Status .ID , * application .Status .ID , * authSecret )
164179 if err != nil {
165180 return r .reconcileStatus (applicationAuth , err , reqLogger )
166181 }
@@ -176,109 +191,141 @@ func (r *ApplicationAuthReconciler) SetupWithManager(mgr ctrl.Manager) error {
176191 Complete (r )
177192}
178193
179- func (r * ApplicationAuthReconciler ) applicationAuthReconciler (
180- applicationAuth * capabilitiesv1beta1.ApplicationAuth ,
181- developerAccountID int64 ,
182- applicationID int64 ,
183- product * capabilitiesv1beta1.Product ,
184- authSecret AuthSecret ,
185- threescaleClient * threescaleapi.ThreeScaleClient ,
186- ) error {
187- // generate sha base of timestamp
188- timestamp := time .Now ().Unix ()
189- // Write the timestamp string and encode to hash
190- hash := sha256 .New ()
191- hash .Write ([]byte (strconv .FormatInt (timestamp , 10 )))
192- hashedBytes := hash .Sum (nil )
193- hashedString := hex .EncodeToString (hashedBytes )
194-
195- // Check the values if populated or the GenerateSecret field is true and make the api call to update
196- // If UserKey is not populated generate random sha
197- if authSecret .UserKey == "" && * applicationAuth .Spec .GenerateSecret {
198- authSecret .UserKey = hashedString
194+ type AuthController interface {
195+ Sync (threescaleClient * threescaleapi.ThreeScaleClient , developerAccountID int64 , applicationID int64 , authSecret AuthSecret ) error
196+ SecretReferenceSource (cl client.Client , ns string , authSectretRef * corev1.LocalObjectReference , generateSecret bool ) (* AuthSecret , error )
197+ }
198+
199+ func GetAuthController (mode string , logger logr.Logger ) (AuthController , error ) {
200+ switch mode {
201+ case "1" :
202+ return & userKeyAuthMode {logger : logger }, nil
203+ case "2" :
204+ return & appIDAuthMode {logger : logger }, nil
205+ default :
206+ return nil , fmt .Errorf ("unknown authentication mode" )
207+ }
208+ }
209+
210+ type userKeyAuthMode struct {
211+ logger logr.Logger
212+ }
213+
214+ func (u * userKeyAuthMode ) Sync (threescaleClient * threescaleapi.ThreeScaleClient , developerAccountID int64 , applicationID int64 , authSecret AuthSecret ) error {
215+ // get the existing value from the porta
216+ existingApplication , err := threescaleClient .Application (developerAccountID , applicationID )
217+ if err != nil {
218+ return err
199219 }
200- if authSecret .UserKey != "" {
220+ existingKey := existingApplication .UserKey
221+
222+ // user_key mismatch, update
223+ if existingKey != authSecret .UserKey {
201224 params := make (map [string ]string )
202225 params ["user_key" ] = authSecret .UserKey
203- // edge case if the operator is stopped before reconcile finished need to nil check application.Status.ID
204- _ , err := threescaleClient .UpdateApplication (developerAccountID , applicationID , params )
205- if err != nil {
226+ if _ , err := threescaleClient .UpdateApplication (developerAccountID , applicationID , params ); err != nil {
206227 return err
207228 }
208229 }
230+ return nil
231+ }
209232
210- if authSecret .ApplicationKey != "" {
211- foundApplication , err := threescaleClient .CreateApplicationKey (developerAccountID , applicationID , authSecret .ApplicationKey )
212- if err != nil {
213- return err
214- }
215-
216- authSecret .ApplicationID = foundApplication .ApplicationId
233+ func (u * userKeyAuthMode ) SecretReferenceSource (cl client.Client , ns string , authSectretRef * corev1.LocalObjectReference , generateSecret bool ) (* AuthSecret , error ) {
234+ secretSource := helper .NewSecretSource (cl , ns )
235+ userKeyStr , err := secretSource .RequiredFieldValueFromRequiredSecret (authSectretRef .Name , UserKey )
236+ if err != nil {
237+ return nil , err
217238 }
218239
219- if applicationAuth .Spec .GenerateSecret != nil && * applicationAuth .Spec .GenerateSecret {
220- foundApplication , err := threescaleClient .CreateApplicationRandomKey (developerAccountID , applicationID )
221- if err != nil {
222- return err
223- }
224- authSecret .ApplicationID = foundApplication .ApplicationId
225- var foundApplicationKeys []threescaleapi.ApplicationKey
226- foundApplicationKeys , err = threescaleClient .ApplicationKeys (developerAccountID , applicationID )
227- if err != nil {
228- return err
240+ if userKeyStr == "" {
241+ if generateSecret {
242+ userKeyStr = rand .String (16 )
243+
244+ newValues := map [string ][]byte {
245+ UserKey : []byte (userKeyStr ),
246+ }
247+
248+ if err := updateSecret (context .Background (), cl , authSectretRef .Name , ns , newValues ); err != nil {
249+ return nil , err
250+ }
251+ } else {
252+ // Nothing available raise error now
253+ return nil , fmt .Errorf ("no UserKey available in secret and generate secret is set to false" )
229254 }
230- lastKey := len (foundApplicationKeys ) - 1
231- authSecret .ApplicationKey = fmt .Sprint (foundApplicationKeys [lastKey ].Value )
232255 }
256+ return & AuthSecret {UserKey : userKeyStr }, nil
257+ }
233258
234- // get the current values and update the secret
235- ApplicationAuthSecret := & corev1.Secret {}
236- err := r .Client ().Get (r .Context (), types.NamespacedName {
237- Name : applicationAuth .Spec .AuthSecretRef .Name ,
238- Namespace : applicationAuth .Namespace ,
239- }, ApplicationAuthSecret )
259+ type appIDAuthMode struct {
260+ logger logr.Logger
261+ }
262+
263+ func (a * appIDAuthMode ) Sync (threescaleClient * threescaleapi.ThreeScaleClient , developerAccountID int64 , applicationID int64 , authSecret AuthSecret ) error {
264+ desiredKeys := strings .Split (authSecret .ApplicationKey , "," )
265+ if len (desiredKeys ) > 5 {
266+ return fmt .Errorf ("secret contains more than 5 application_key" )
267+ }
268+
269+ // get the existing value from the portal
270+ applicationKeys , err := threescaleClient .ApplicationKeys (developerAccountID , applicationID )
240271 if err != nil {
241- // Handle errors gracefully, e.g., log and return or retry
242- r .Logger ().Error (err , "Failed to get existing ApplicationAuthSecret" )
243272 return err
244273 }
245- newData := ApplicationAuthSecret .Data
246- newValues := map [string ][]byte {
247- UserKey : []byte (authSecret .UserKey ),
248- ApplicationID : []byte (authSecret .ApplicationID ),
249- ApplicationKey : []byte (authSecret .ApplicationKey ),
274+
275+ existingKeys := make ([]string , 0 , len (applicationKeys ))
276+ for _ , key := range applicationKeys {
277+ existingKeys = append (existingKeys , key .Value )
250278 }
251- for key , value := range newValues {
252- newData [key ] = value
279+
280+ // delete existing and not desired
281+ notDesiredExistingKeys := helper .ArrayStringDifference (existingKeys , desiredKeys )
282+ a .logger .V (1 ).Info ("syncApplicationAuth" , "notDesiredExistingKeys" , notDesiredExistingKeys )
283+ for _ , key := range notDesiredExistingKeys {
284+ // key is expected to exist
285+ // notDesiredExistingKeys is a subset of the existingMap key set
286+ if err := threescaleClient .DeleteApplicationKey (developerAccountID , applicationID , key ); err != nil {
287+ return fmt .Errorf ("error sync applicationAuth for developerAccountID: %d, applicationID: %d, error: %w" , developerAccountID , applicationID , err )
288+ }
253289 }
254290
255- ApplicationAuthSecret .Data = newData
256- err = r .Client ().Update (r .Context (), ApplicationAuthSecret )
257- if err != nil {
258- r .Logger ().Error (err , "Failed to update ApplicationAuthSecret" )
259- return err
291+ // Create not existing and desired
292+ desiredNewKeys := helper .ArrayStringDifference (desiredKeys , existingKeys )
293+ a .logger .V (1 ).Info ("syncApplicationPlans" , "desiredNewKeys" , desiredNewKeys )
294+ for _ , key := range desiredNewKeys {
295+ // key is expected to exist
296+ // desiredNewKeys is a subset of the Spec.ApplicationPlans map key set
297+ if _ , err := threescaleClient .CreateApplicationKey (developerAccountID , applicationID , key ); err != nil {
298+ return fmt .Errorf ("error sync applicationAuth for developerAccountID: %d, applicationID: %d, error: %w" , developerAccountID , applicationID , err )
299+ }
260300 }
261301
262302 return nil
263303}
264304
265- func authSecretReferenceSource (cl client.Client , ns string , authSectretRef * corev1.LocalObjectReference , logger logr.Logger ) * AuthSecret {
266- if authSectretRef != nil {
267- logger .Info ("LookupAuthSecret" , "ns" , ns , "authSecretRef" , authSectretRef )
268- secretSource := helper .NewSecretSource (cl , ns )
269- userKeyStr , err := secretSource .RequiredFieldValueFromRequiredSecret (authSectretRef .Name , UserKey )
270- if err != nil {
271- userKeyStr = ""
272- }
273- applicationKeyStr , err := secretSource .RequiredFieldValueFromRequiredSecret (authSectretRef .Name , ApplicationKey )
274- if err != nil {
275- applicationKeyStr = ""
276- }
277-
278- return & AuthSecret {UserKey : userKeyStr , ApplicationKey : applicationKeyStr }
305+ func (a * appIDAuthMode ) SecretReferenceSource (cl client.Client , ns string , authSectretRef * corev1.LocalObjectReference , generateSecret bool ) (* AuthSecret , error ) {
306+ secretSource := helper .NewSecretSource (cl , ns )
307+ applicationKeyStr , err := secretSource .RequiredFieldValueFromRequiredSecret (authSectretRef .Name , ApplicationKey )
308+ if err != nil {
309+ return nil , err
279310 }
280311
281- return nil
312+ if applicationKeyStr == "" {
313+ if generateSecret {
314+ applicationKeyStr = rand .String (16 )
315+
316+ newValues := map [string ][]byte {
317+ ApplicationKey : []byte (applicationKeyStr ),
318+ }
319+
320+ if err := updateSecret (context .Background (), cl , authSectretRef .Name , ns , newValues ); err != nil {
321+ return nil , err
322+ }
323+ } else {
324+ // Nothing available raise error now
325+ return nil , fmt .Errorf ("no ApplicationKey available in secret and generate secret is set to false" )
326+ }
327+ }
328+ return & AuthSecret {ApplicationKey : applicationKeyStr }, nil
282329}
283330
284331func (r * ApplicationAuthReconciler ) reconcileStatus (resource * capabilitiesv1beta1.ApplicationAuth , err error , logger logr.Logger ) (ctrl.Result , error ) {
@@ -319,3 +366,30 @@ func checkApplicationResources(applicationAuthResource *capabilitiesv1beta1.Appl
319366
320367 return nil
321368}
369+
370+ func updateSecret (ctx context.Context , client client.Client , name string , namespace string , values map [string ][]byte ) error {
371+ // get the current values and update the secret
372+ secret := & corev1.Secret {}
373+ err := client .Get (ctx , types.NamespacedName {
374+ Name : name ,
375+ Namespace : namespace ,
376+ }, secret )
377+ if err != nil {
378+ // Handle errors gracefully, e.g., log and return or retry
379+ return err
380+ }
381+
382+ newData := secret .Data
383+
384+ for key , value := range values {
385+ newData [key ] = value
386+ }
387+
388+ secret .Data = newData
389+
390+ if err = client .Update (ctx , secret ); err != nil {
391+ return err
392+ }
393+
394+ return nil
395+ }
0 commit comments