-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
kind: KonnectAPIAuthConfiguration | ||
apiVersion: konnect.konghq.com/v1alpha1 | ||
metadata: | ||
name: konnect-api-auth-1 | ||
namespace: default | ||
spec: | ||
type: token | ||
token: kpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ||
serverURL: eu.api.konghq.com | ||
--- | ||
kind: KonnectAPIAuthConfiguration | ||
apiVersion: konnect.konghq.com/v1alpha1 | ||
metadata: | ||
name: konnect-api-auth-2 | ||
namespace: default | ||
spec: | ||
type: secretRef | ||
secretRef: | ||
name: konnect-api-auth-secret | ||
serverURL: eu.api.konghq.com | ||
--- | ||
kind: Secret | ||
apiVersion: v1 | ||
metadata: | ||
name: konnect-api-auth-secret | ||
namespace: default | ||
labels: | ||
konghq.com/credential: konnect | ||
stringData: | ||
token: kpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,14 +6,20 @@ import ( | |
"time" | ||
|
||
sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" | ||
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" | ||
corev1 "k8s.io/api/core/v1" | ||
k8serrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/builder" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/handler" | ||
"sigs.k8s.io/controller-runtime/pkg/predicate" | ||
|
||
"github.com/kong/gateway-operator/controller/pkg/log" | ||
k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" | ||
|
||
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" | ||
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / build
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-provision-dataplane-fail
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-provision-dataplane-fail
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests (WEBHOOK_ENABLED=false)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests (WEBHOOK_ENABLED=false)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests (WEBHOOK_ENABLED=true)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests (WEBHOOK_ENABLED=true)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / conformance-tests (traditional_compatible)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / conformance-tests (traditional_compatible)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-bluegreen (WEBHOOK_ENABLED=false)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-bluegreen (WEBHOOK_ENABLED=false)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-bluegreen (WEBHOOK_ENABLED=true)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / integration-tests-bluegreen (WEBHOOK_ENABLED=true)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / unit-tests
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / unit-tests
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / conformance-tests (expressions)
Check failure on line 22 in controller/konnect/reconciler_konnectapiauth.go GitHub Actions / conformance-tests (expressions)
|
||
) | ||
|
||
// KonnectAPIAuthConfigurationReconciler reconciles a KonnectAPIAuthConfiguration object. | ||
|
@@ -23,6 +29,17 @@ type KonnectAPIAuthConfigurationReconciler struct { | |
Client client.Client | ||
} | ||
|
||
const ( | ||
// SecretTokenKey is the key used to store the token in the Secret. | ||
SecretTokenKey = "token" | ||
// SecretCredentialLabel is the label used to identify Secrets holding | ||
// KonnectAPIAuthConfiguration tokens. | ||
SecretCredentialLabel = "konghq.com/credential" //nolint:gosec | ||
// SecretCredentialLabelValueKonnect is the value of the label used to | ||
// identify Secrets holding KonnectAPIAuthConfiguration tokens. | ||
SecretCredentialLabelValueKonnect = "konnect" | ||
) | ||
|
||
// NewKonnectAPIAuthConfigurationReconciler creates a new KonnectAPIAuthConfigurationReconciler. | ||
func NewKonnectAPIAuthConfigurationReconciler( | ||
sdkFactory SDKFactory, | ||
|
@@ -38,8 +55,26 @@ func NewKonnectAPIAuthConfigurationReconciler( | |
|
||
// SetupWithManager sets up the controller with the Manager. | ||
func (r *KonnectAPIAuthConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
secretLabelPredicate, err := predicate.LabelSelectorPredicate( | ||
metav1.LabelSelector{ | ||
MatchLabels: map[string]string{ | ||
SecretCredentialLabel: SecretCredentialLabelValueKonnect, | ||
}, | ||
}, | ||
) | ||
if err != nil { | ||
return fmt.Errorf("failed to create Secret label selector predicate: %w", err) | ||
} | ||
|
||
b := ctrl.NewControllerManagedBy(mgr). | ||
For(&konnectv1alpha1.KonnectAPIAuthConfiguration{}). | ||
Watches( | ||
&corev1.Secret{}, | ||
handler.EnqueueRequestsFromMapFunc( | ||
listKonnectAPIAuthConfigurationsReferencingSecret(mgr.GetClient()), | ||
), | ||
builder.WithPredicates(secretLabelPredicate), | ||
). | ||
Named("KonnectAPIAuthConfiguration") | ||
|
||
return b.Complete(r) | ||
|
@@ -80,9 +115,30 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( | |
return ctrl.Result{}, nil | ||
} | ||
|
||
token, err := getTokenFromKonnectAPIAuthConfiguration(ctx, r.Client, &apiAuth) | ||
if err != nil { | ||
k8sutils.SetCondition( | ||
k8sutils.NewConditionWithGeneration( | ||
KonnectAPIAuthConfigurationValidConditionType, | ||
metav1.ConditionFalse, | ||
KonnectAPIAuthConfigurationReasonInvalid, | ||
err.Error(), | ||
apiAuth.GetGeneration(), | ||
), | ||
&apiAuth.Status, | ||
) | ||
if err := r.Client.Status().Update(ctx, &apiAuth); err != nil { | ||
if k8serrors.IsConflict(err) { | ||
return ctrl.Result{Requeue: true}, nil | ||
} | ||
return ctrl.Result{}, fmt.Errorf("failed to update status of %s: %w", entityTypeName, err) | ||
} | ||
return ctrl.Result{}, err | ||
} | ||
|
||
sdk := r.SDKFactory.NewKonnectSDK( | ||
"https://"+apiAuth.Spec.ServerURL, | ||
SDKToken(apiAuth.Spec.Token), | ||
SDKToken(token), | ||
) | ||
|
||
// TODO(pmalek): check if api auth config has a valid status condition | ||
|
@@ -157,3 +213,38 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( | |
|
||
return ctrl.Result{}, nil | ||
} | ||
|
||
// getTokenFromKonnectAPIAuthConfiguration returns the token from the secret reference or the token field. | ||
func getTokenFromKonnectAPIAuthConfiguration( | ||
ctx context.Context, cl client.Client, apiAuth *konnectv1alpha1.KonnectAPIAuthConfiguration, | ||
) (string, error) { | ||
switch apiAuth.Spec.Type { | ||
case konnectv1alpha1.KonnectAPIAuthTypeToken: | ||
return apiAuth.Spec.Token, nil | ||
case konnectv1alpha1.KonnectAPIAuthTypeSecretRef: | ||
var secret corev1.Secret | ||
nn := types.NamespacedName{ | ||
Namespace: apiAuth.Spec.SecretRef.Namespace, | ||
Name: apiAuth.Spec.SecretRef.Name, | ||
} | ||
if nn.Namespace == "" { | ||
nn.Namespace = apiAuth.Namespace | ||
} | ||
|
||
if err := cl.Get(ctx, nn, &secret); err != nil { | ||
return "", fmt.Errorf("failed to get Secret %s: %w", nn, err) | ||
} | ||
if secret.Labels == nil || secret.Labels[SecretCredentialLabel] != SecretCredentialLabelValueKonnect { | ||
return "", fmt.Errorf("Secret %s does not have label %s: %s", nn, SecretCredentialLabel, SecretCredentialLabelValueKonnect) | ||
} | ||
if secret.Data == nil { | ||
return "", fmt.Errorf("Secret %s has no data", nn) | ||
} | ||
if _, ok := secret.Data[SecretTokenKey]; !ok { | ||
return "", fmt.Errorf("Secret %s does not have key %s", nn, SecretTokenKey) | ||
} | ||
return string(secret.Data[SecretTokenKey]), nil | ||
} | ||
|
||
return "", fmt.Errorf("unknown KonnectAPIAuthType: %s", apiAuth.Spec.Type) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package konnect | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" | ||
) | ||
|
||
func TestGetTokenFromKonnectAPIAuthConfiguration(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
apiAuth *konnectv1alpha1.KonnectAPIAuthConfiguration | ||
secret *corev1.Secret | ||
expectedToken string | ||
expectedError bool | ||
}{ | ||
{ | ||
name: "valid Token", | ||
apiAuth: &konnectv1alpha1.KonnectAPIAuthConfiguration{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-api-auth", | ||
Namespace: "default", | ||
}, | ||
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{ | ||
Type: konnectv1alpha1.KonnectAPIAuthTypeToken, | ||
Token: "kpat_xxxxxxxxxxxx", | ||
}, | ||
}, | ||
expectedToken: "kpat_xxxxxxxxxxxx", | ||
}, | ||
{ | ||
name: "valid Secret Reference", | ||
apiAuth: &konnectv1alpha1.KonnectAPIAuthConfiguration{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-api-auth", | ||
Namespace: "default", | ||
}, | ||
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{ | ||
Type: konnectv1alpha1.KonnectAPIAuthTypeSecretRef, | ||
SecretRef: &corev1.SecretReference{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
}, | ||
}, | ||
}, | ||
secret: &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
Labels: map[string]string{ | ||
"konghq.com/credential": "konnect", | ||
}, | ||
}, | ||
Data: map[string][]byte{ | ||
"token": []byte("test-token"), | ||
}, | ||
}, | ||
expectedToken: "test-token", | ||
}, | ||
{ | ||
name: "Secret is missing konghq.com/credential=konnect label", | ||
apiAuth: &konnectv1alpha1.KonnectAPIAuthConfiguration{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-api-auth", | ||
Namespace: "default", | ||
}, | ||
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{ | ||
Type: konnectv1alpha1.KonnectAPIAuthTypeSecretRef, | ||
SecretRef: &corev1.SecretReference{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
}, | ||
}, | ||
}, | ||
secret: &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
}, | ||
Data: map[string][]byte{ | ||
"token": []byte("test-token"), | ||
}, | ||
}, | ||
expectedError: true, | ||
}, | ||
{ | ||
name: "missing token from referred Secret", | ||
apiAuth: &konnectv1alpha1.KonnectAPIAuthConfiguration{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-api-auth", | ||
Namespace: "default", | ||
}, | ||
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{ | ||
Type: konnectv1alpha1.KonnectAPIAuthTypeSecretRef, | ||
SecretRef: &corev1.SecretReference{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
}, | ||
}, | ||
}, | ||
secret: &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-secret", | ||
Namespace: "default", | ||
Labels: map[string]string{ | ||
"konghq.com/credential": "konnect", | ||
}, | ||
}, | ||
Data: map[string][]byte{ | ||
"random_key": []byte("dummy"), | ||
}, | ||
}, | ||
expectedToken: "test-token", | ||
expectedError: true, | ||
}, | ||
{ | ||
name: "Invalid Secret Reference", | ||
apiAuth: &konnectv1alpha1.KonnectAPIAuthConfiguration{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-api-auth", | ||
Namespace: "default", | ||
}, | ||
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{ | ||
Type: konnectv1alpha1.KonnectAPIAuthTypeSecretRef, | ||
SecretRef: &corev1.SecretReference{ | ||
Name: "non-existent-secret", | ||
Namespace: "default", | ||
}, | ||
}, | ||
}, | ||
expectedError: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
clientBuilder := fake.NewClientBuilder() | ||
|
||
// Create the secret in the fake client | ||
if tt.secret != nil { | ||
clientBuilder.WithObjects(tt.secret) | ||
} | ||
cl := clientBuilder.Build() | ||
|
||
// Call the function under test | ||
token, err := getTokenFromKonnectAPIAuthConfiguration(context.Background(), cl, tt.apiAuth) | ||
if tt.expectedError { | ||
assert.NotNil(t, err) | ||
return | ||
} | ||
|
||
assert.Equal(t, tt.expectedToken, token) | ||
}) | ||
} | ||
} |