Skip to content

Commit 5bdbaf5

Browse files
committed
ApplicationAuth: handle authentication mode
1 parent d346930 commit 5bdbaf5

2 files changed

Lines changed: 594 additions & 212 deletions

File tree

controllers/capabilities/applicationauth_controller.go

Lines changed: 159 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@ package controllers
1818

1919
import (
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

284331
func (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

Comments
 (0)