Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions api/v1alpha1/traffic_policy_types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand Down Expand Up @@ -457,7 +456,7 @@ type APIKeyAuthentication struct {
// client2: "k-456"
//
// +optional
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
SecretRef *gwv1.SecretObjectReference `json:"secretRef,omitempty"`

// secretSelector selects multiple secrets containing API Keys. If the same key is defined in multiple secrets, the
// behavior is undefined.
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,44 @@ spec:
client1: "k-123"
client2: "k-456"
properties:
name:
group:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Group is the group of the referent. For example, "gateway.networking.k8s.io".
When unspecified or empty string, core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
default: Secret
description: Kind is kind of the referent. For example "Secret".
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: |-
Namespace is the namespace of the referenced object. When unspecified, the local
namespace is inferred.

Note that when a namespace different than the local namespace is specified,
a ReferenceGrant object is required in the referent namespace to allow that
namespace's owner to accept the reference. See the ReferenceGrant
documentation for details.

Support: Core
maxLength: 63
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
required:
- name
type: object
x-kubernetes-map-type: atomic
secretSelector:
description: |-
secretSelector selects multiple secrets containing API Keys. If the same key is defined in multiple secrets, the
Expand Down
43 changes: 22 additions & 21 deletions internal/kgateway/extensions2/plugins/trafficpolicy/api_key_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/kgateway-dev/kgateway/v2/api/v1alpha1"
"github.com/kgateway-dev/kgateway/v2/internal/kgateway/wellknown"
"github.com/kgateway-dev/kgateway/v2/pkg/krtcollections"
"github.com/kgateway-dev/kgateway/v2/pkg/pluginsdk/collections"
"github.com/kgateway-dev/kgateway/v2/pkg/pluginsdk/ir"
)
Expand Down Expand Up @@ -64,40 +66,39 @@ func constructAPIKeyAuth(

ak := spec.APIKeyAuthentication

// Resolve secrets using SecretIndex
// Resolve secrets using SecretIndex with ReferenceGrant validation
var secrets []ir.Secret
secretGK := schema.GroupKind{Group: "", Kind: "Secret"}
secretCol := commoncol.Secrets.GetSecretCollection(secretGK)
if secretCol == nil {
return fmt.Errorf("secret collection not found")
policyGK := wellknown.TrafficPolicyGVK.GroupKind()
from := krtcollections.From{
GroupKind: policyGK,
Namespace: policy.Namespace,
}

if ak.SecretRef != nil {
// Use ResourceName format: Group/Kind/Namespace/Name
secretObjSource := ir.ObjectSource{
Group: "",
Kind: "Secret",
Namespace: policy.Namespace,
Name: ak.SecretRef.Name,
}
secret := krt.FetchOne(krtctx, secretCol, krt.FilterKey(secretObjSource.ResourceName()))
if secret == nil {
return fmt.Errorf("API key secret %s not found in namespace %s", ak.SecretRef.Name, policy.Namespace)
secret, err := commoncol.Secrets.GetSecret(krtctx, from, *ak.SecretRef)
if err != nil {
return fmt.Errorf("API key secret %s: %w", ak.SecretRef.Name, err)
}
secrets = []ir.Secret{*secret}
} else if ak.SecretSelector != nil {
// Fetch secrets matching labels and namespace
secrets = krt.Fetch(krtctx, secretCol,
krt.FilterLabel(ak.SecretSelector.MatchLabels),
krt.FilterGeneric(func(obj any) bool {
secret := obj.(ir.Secret)
return secret.Namespace == policy.Namespace
}),
// Fetch secrets matching labels and namespace with ReferenceGrant validation
var err error
secrets, err = commoncol.Secrets.GetSecretsBySelector(
krtctx,
from,
secretGK,
policy.Namespace,
ak.SecretSelector.MatchLabels,
)
if err != nil {
return fmt.Errorf("failed to get secrets by selector: %w", err)
}
if len(secrets) == 0 {
return fmt.Errorf("no secrets found matching selector %v in namespace %s", ak.SecretSelector.MatchLabels, policy.Namespace)
}
} else {
// We shouldn't get here because the spec validation should catch this
return fmt.Errorf("either secretRef or secretSelector must be specified")
}

Expand Down
44 changes: 44 additions & 0 deletions internal/kgateway/translator/gateway/gateway_translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,50 @@ func TestBasic(t *testing.T) {
})
})

t.Run("TrafficPolicy API Key Authentication with SecretRef", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/api-key-auth-secretref.yaml",
outputFile: "traffic-policy/api-key-auth-secretref.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})

t.Run("TrafficPolicy API Key Authentication with SecretRef and ReferenceGrant", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/api-key-auth-secretref-with-refgrant.yaml",
outputFile: "traffic-policy/api-key-auth-secretref-with-refgrant.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})

t.Run("TrafficPolicy API Key Authentication with SecretSelector and ReferenceGrant", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/api-key-auth-selector-with-refgrant.yaml",
outputFile: "traffic-policy/api-key-auth-selector-with-refgrant.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})

t.Run("TrafficPolicy API Key Authentication with SecretSelector no matching secrets", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/api-key-auth-selector-no-matching-secret.yaml",
outputFile: "traffic-policy/api-key-auth-selector-no-matching-secret.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})

t.Run("TrafficPolicy with fail open rate limiting", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/fail-open",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
namespace: default
spec:
gatewayClassName: example-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "example.com"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
namespace: default
spec:
parentRefs:
- name: example-gateway
hostnames:
- "example.com"
rules:
- backendRefs:
- name: example-svc
port: 80
matches:
- path:
type: PathPrefix
value: /foo
filters:
- type: ExtensionRef
extensionRef:
group: gateway.kgateway.dev
kind: TrafficPolicy
name: api-key-auth-secretref
---
# API key secret in other namespace
apiVersion: v1
kind: Secret
metadata:
name: api-keys
namespace: other
type: Opaque
data:
client1: ay0xMjM=
client2: ay00NTY=
---
# ReferenceGrant allowing access to the secret in other namespace
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-secret-access
namespace: other
spec:
from:
- group: gateway.kgateway.dev
kind: TrafficPolicy
namespace: default
to:
- group: ""
kind: Secret
---
# TrafficPolicy with API key authentication using SecretRef with ReferenceGrant allowing access to the secret in other namespace
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: api-key-auth-secretref
namespace: default
spec:
targetRefs:
- name: example-route
kind: HTTPRoute
group: gateway.networking.k8s.io
apiKeyAuthentication:
keySources:
- header: "x-api-key"
forwardCredential: false
clientIdHeader: "x-authenticated-client"
secretRef:
name: api-keys
namespace: other
---
# Test service
apiVersion: v1
kind: Service
metadata:
name: example-svc
namespace: default
spec:
selector:
app: example
ports:
- protocol: TCP
port: 80
targetPort: 8080

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
namespace: default
spec:
gatewayClassName: example-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "example.com"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
namespace: default
spec:
parentRefs:
- name: example-gateway
hostnames:
- "example.com"
rules:
- backendRefs:
- name: example-svc
port: 80
matches:
- path:
type: PathPrefix
value: /foo
filters:
- type: ExtensionRef
extensionRef:
group: gateway.kgateway.dev
kind: TrafficPolicy
name: api-key-auth-secretref
---
# API key secret in same namespace
apiVersion: v1
kind: Secret
metadata:
name: api-keys
namespace: default
type: Opaque
data:
client1: ay0xMjM=
client2: ay00NTY=
---
# TrafficPolicy with API key authentication using SecretRef
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: api-key-auth-secretref
namespace: default
spec:
targetRefs:
- name: example-route
kind: HTTPRoute
group: gateway.networking.k8s.io
apiKeyAuthentication:
keySources:
- header: "x-api-key"
forwardCredential: false
clientIdHeader: "x-authenticated-client"
secretRef:
name: api-keys
---
# Test service
apiVersion: v1
kind: Service
metadata:
name: example-svc
namespace: default
spec:
selector:
app: example
ports:
- protocol: TCP
port: 80
targetPort: 8080

Loading