Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions api/v1alpha1/authenticationfilter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
)

const (
// AuthenticationFilterKind is the name of the AuthenticationFilter kind.
AuthenticationFilterKind = "AuthenticationFilter"
// KindAuthenticationFilter is the name of the AuthenticationFilter kind.
KindAuthenticationFilter = "AuthenticationFilter"
)

//+kubebuilder:object:root=true
Expand Down
80 changes: 80 additions & 0 deletions api/v1alpha1/validation/authenticationfilter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package validation

import (
"errors"
"fmt"
"net/url"

utilerrors "k8s.io/apimachinery/pkg/util/errors"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
)

// ValidateAuthenticationFilter validates the provided filter. The only supported
// ValidateAuthenticationFilter type is "JWT".
func ValidateAuthenticationFilter(filter *egv1a1.AuthenticationFilter) error {
var errs []error
if filter == nil {
return errors.New("filter is nil")
}
if err := validateAuthenticationFilterSpec(&filter.Spec); err != nil {
errs = append(errs, errors.New("filter is nil"))
}

return utilerrors.NewAggregate(errs)
}

// validateAuthenticationFilterSpec validates the provided spec. The only supported
// ValidateAuthenticationFilter type is "JWT".
func validateAuthenticationFilterSpec(spec *egv1a1.AuthenticationFilterSpec) error {
var errs []error

switch {
case spec == nil:
errs = append(errs, errors.New("spec is nil"))
case spec.Type != egv1a1.JwtAuthenticationFilterProviderType:
errs = append(errs, fmt.Errorf("unsupported authenticationfilter type: %v", spec.Type))
case len(spec.JwtProviders) == 0:
errs = append(errs, fmt.Errorf("at least one provider must be specified for type %v", spec.Type))
}

// Return early if any errors exist.
if len(errs) != 0 {
return utilerrors.NewAggregate(errs)
}

for i := range spec.JwtProviders {
provider := spec.JwtProviders[i]
if err := ValidateJwtProvider(&provider); err != nil {
errs = append(errs, err)
}
}

return utilerrors.NewAggregate(errs)
}

// ValidateJwtProvider validates the provided JWT authentication filter provider.
func ValidateJwtProvider(jwt *egv1a1.JwtAuthenticationFilterProvider) error {
var errs []error

switch {
case len(jwt.Name) == 0:
errs = append(errs, errors.New("name must be set for jwt provider"))
case len(jwt.Issuer) != 0:
if _, err := url.ParseRequestURI(jwt.Issuer); err != nil {
errs = append(errs, fmt.Errorf("invalid issuer URI: %v", err))
}
case len(jwt.RemoteJWKS.URI) == 0:
Comment thread
danehans marked this conversation as resolved.
Outdated
errs = append(errs, fmt.Errorf("uri must be set for remote JWKS provider: %s", jwt.Name))
}
if _, err := url.ParseRequestURI(jwt.RemoteJWKS.URI); err != nil {
errs = append(errs, fmt.Errorf("invalid remote JWKS URI: %v", err))
}

return utilerrors.NewAggregate(errs)
}
227 changes: 227 additions & 0 deletions api/v1alpha1/validation/authenticationfilter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package validation

import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/stretchr/testify/require"
)

func TestValidateAuthenticationFilter(t *testing.T) {
testCases := []struct {
name string
filter *egv1a1.AuthenticationFilter
expected bool
}{
{
name: "nil authentication filter",
filter: nil,
expected: false,
},
{
name: "valid authentication filter",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Issuer: "https://www.test.local",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "https://test.local/jwt/public-key/jwks.json",
},
},
},
},
},
expected: true,
},
{
name: "unspecified provider name",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "",
Issuer: "https://www.test.local",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "https://test.local/jwt/public-key/jwks.json",
},
},
},
},
},
expected: false,
},
{
name: "invalid issuer uri",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Issuer: "http://invalid url.local",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "http://www.test.local",
},
},
},
},
},
expected: false,
},
{
name: "invalid remote jwks uri",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Issuer: "http://www.test.local",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "invalid/local",
},
},
},
},
},
expected: false,
},
{
name: "unspecified remote jwks uri",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "",
},
},
},
},
},
expected: false,
},
{
name: "unspecified issuer",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Audiences: []string{"test.local"},
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "https://test.local/jwt/public-key/jwks.json",
},
},
},
},
},
expected: true,
},
{
name: "unspecified audiences",
filter: &egv1a1.AuthenticationFilter{
TypeMeta: metav1.TypeMeta{
Kind: egv1a1.KindAuthenticationFilter,
APIVersion: egv1a1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egv1a1.AuthenticationFilterSpec{
Type: egv1a1.JwtAuthenticationFilterProviderType,
JwtProviders: []egv1a1.JwtAuthenticationFilterProvider{
{
Name: "test",
Issuer: "https://www.test.local",
RemoteJWKS: egv1a1.RemoteJWKS{
URI: "https://test.local/jwt/public-key/jwks.json",
},
},
},
},
},
expected: true,
},
}

for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
err := ValidateAuthenticationFilter(tc.filter)
if tc.expected {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
4 changes: 2 additions & 2 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ func ValidateHTTPRouteFilter(filter *v1beta1.HTTPRouteFilter) error {
return errors.New("extensionRef field must be specified for an extended filter")
case string(filter.ExtensionRef.Group) != egv1a1.GroupVersion.Group:
return fmt.Errorf("invalid group; must be %s", egv1a1.GroupVersion.Group)
case string(filter.ExtensionRef.Kind) != egv1a1.AuthenticationFilterKind:
return fmt.Errorf("invalid kind; must be %s", egv1a1.AuthenticationFilterKind)
case string(filter.ExtensionRef.Kind) != egv1a1.KindAuthenticationFilter:
return fmt.Errorf("invalid kind; must be %s", egv1a1.KindAuthenticationFilter)
default:
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions internal/gatewayapi/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestValidateAuthenFilterRef(t *testing.T) {
Type: gwapiv1b1.HTTPRouteFilterExtensionRef,
ExtensionRef: &gwapiv1b1.LocalObjectReference{
Group: "UnsupportedGroup",
Kind: egv1a1.AuthenticationFilterKind,
Kind: egv1a1.KindAuthenticationFilter,
Name: "test",
},
},
Expand All @@ -103,7 +103,7 @@ func TestValidateAuthenFilterRef(t *testing.T) {
Type: gwapiv1b1.HTTPRouteFilterExtensionRef,
ExtensionRef: &gwapiv1b1.LocalObjectReference{
Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group),
Kind: egv1a1.AuthenticationFilterKind,
Kind: egv1a1.KindAuthenticationFilter,
Name: "test",
},
},
Expand Down
Loading