Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate HTTPAuthFilter onto HTTPRoute #2836

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
35 changes: 27 additions & 8 deletions charts/consul/templates/crd-routeauthfilters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.11.3
controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: routeauthfilters.consul.hashicorp.com
labels:
Expand Down Expand Up @@ -56,26 +56,40 @@ spec:
description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter.
properties:
jwt:
description: RouteJWTRequirement defines the JWT requirements per
provider.
description: This re-uses the JWT requirement type from Gateway Policy
Types.
properties:
providers:
description: Providers is a list of providers to consider when
verifying a JWT.
items:
description: RouteJWTProvider defines the configuration for
a specific JWT provider.
description: GatewayJWTProvider holds the provider and claim
verification information.
properties:
name:
description: Name is the name of the JWT provider. There
MUST be a corresponding "jwt-provider" config entry with
this name.
type: string
verifyClaims:
description: VerifyClaims is a list of additional claims
to verify in a JWT's payload.
items:
description: RouteJWTClaimVerification defines the specific
claims to be verified.
description: GatewayJWTClaimVerification holds the actual
claim information to be verified.
properties:
path:
description: Path is the path to the claim in the
token JSON.
items:
type: string
type: array
value:
description: "Value is the expected value at the given
path: - If the type at the path is a list then we
verify that this value is contained in the list.
\n - If the type at the path is a string then we
verify that this value matches."
type: string
required:
- path
Expand All @@ -84,7 +98,6 @@ spec:
type: array
required:
- name
- verifyClaims
type: object
type: array
required:
Expand Down Expand Up @@ -134,4 +147,10 @@ spec:
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
{{- end }}
84 changes: 52 additions & 32 deletions control-plane/api-gateway/common/translation.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,32 @@ func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPort
return int(portNumber) + int(mapPrivilegedContainerPorts)
}

func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1.RouteRetryFilter) *api.RetryFilter {
return &api.RetryFilter{
NumRetries: routeRetryFilter.Spec.NumRetries,
RetryOn: routeRetryFilter.Spec.RetryOn,
RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes,
RetryOnConnectFailure: routeRetryFilter.Spec.RetryOnConnectFailure,
}
}

func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter {
return &api.TimeoutFilter{
RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout,
IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout,
}
}

func (t ResourceTranslator) translateRouteJWTFilter(routeJWTFilter *v1alpha1.RouteAuthFilter) *api.JWTFilter {
t-eckert marked this conversation as resolved.
Show resolved Hide resolved
if routeJWTFilter.Spec.JWT == nil {
return nil
}

return &api.JWTFilter{
Providers: ConvertSliceFunc(routeJWTFilter.Spec.JWT.Providers, t.translateJWTProvider),
}
}

func (t ResourceTranslator) translateGatewayPolicy(policy *v1alpha1.GatewayPolicy) (*api.APIGatewayPolicy, *api.APIGatewayPolicy) {
if policy == nil {
return nil, nil
Expand Down Expand Up @@ -204,12 +230,14 @@ func (t ResourceTranslator) translateVerifyClaims(crdClaims *v1alpha1.GatewayJWT
func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry {
namespace := t.Namespace(route.Namespace)

// we don't translate parent refs
// We don't translate parent refs.

hostnames := StringLikeSlice(route.Spec.Hostnames)
rules := ConvertSliceFuncIf(route.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) {
return t.translateHTTPRouteRule(route, rule, resources)
})
rules := ConvertSliceFuncIf(
route.Spec.Rules,
func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) {
return t.translateHTTPRouteRule(route, rule, resources)
})

configEntry := api.HTTPRouteConfigEntry{
Kind: api.HTTPRoute,
Expand All @@ -228,10 +256,11 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re
}

func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) {
services := ConvertSliceFuncIf(rule.BackendRefs, func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) {

return t.translateHTTPBackendRef(route, ref, resources)
})
services := ConvertSliceFuncIf(
t-eckert marked this conversation as resolved.
Show resolved Hide resolved
rule.BackendRefs,
func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) {
return t.translateHTTPBackendRef(route, ref, resources)
})

if len(services) == 0 {
return api.HTTPRouteRule{}, false
Expand Down Expand Up @@ -336,13 +365,18 @@ func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryPar
}

func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) api.HTTPFilters {
var urlRewrite *api.URLRewrite
var (
// Each of these variables will have just one possible value at the end of the function.
urlRewrite *api.URLRewrite
retryFilter *api.RetryFilter
timeoutFilter *api.TimeoutFilter
jwtFilter *api.JWTFilter
)

consulFilter := api.HTTPHeaderFilter{
Add: make(map[string]string),
Set: make(map[string]string),
}
var retryFilter *api.RetryFilter
var timeoutFilter *api.TimeoutFilter

for _, filter := range filters {
if filter.RequestHeaderModifier != nil {
Expand Down Expand Up @@ -371,38 +405,24 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi
// this should never be the case because we only translate a route if it's actually valid, and if we're missing filters during the validation step, then we won't get here
continue
}

switch filter.ExtensionRef.Kind {
case v1alpha1.RouteRetryFilterKind:

retryFilterCRD := crdFilter.(*v1alpha1.RouteRetryFilter)
//new filter that needs to be appended

retryFilter = &api.RetryFilter{
NumRetries: retryFilterCRD.Spec.NumRetries,
RetryOn: retryFilterCRD.Spec.RetryOn,
RetryOnStatusCodes: retryFilterCRD.Spec.RetryOnStatusCodes,
RetryOnConnectFailure: retryFilterCRD.Spec.RetryOnConnectFailure,
}

retryFilter = t.translateRouteRetryFilter(crdFilter.(*v1alpha1.RouteRetryFilter))
case v1alpha1.RouteTimeoutFilterKind:

timeoutFilterCRD := crdFilter.(*v1alpha1.RouteTimeoutFilter)
//new filter that needs to be appended

timeoutFilter = &api.TimeoutFilter{
RequestTimeout: timeoutFilterCRD.Spec.RequestTimeout,
IdleTimeout: timeoutFilterCRD.Spec.IdleTimeout,
}

timeoutFilter = t.translateRouteTimeoutFilter(crdFilter.(*v1alpha1.RouteTimeoutFilter))
case v1alpha1.RouteAuthFilterKind:
jwtFilter = t.translateRouteJWTFilter(crdFilter.(*v1alpha1.RouteAuthFilter))
}
}

}

return api.HTTPFilters{
Headers: []api.HTTPHeaderFilter{consulFilter},
URLRewrite: urlRewrite,
RetryFilter: retryFilter,
TimeoutFilter: timeoutFilter,
JWT: jwtFilter,
}
}

Expand Down
79 changes: 76 additions & 3 deletions control-plane/api-gateway/common/translation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"k8s.io/utils/pointer"
"math/big"
"sigs.k8s.io/controller-runtime/pkg/client"
"strings"
"testing"
"time"

"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -1267,7 +1268,21 @@ func TestTranslator_ToHTTPRoute(t *testing.T) {
ExtensionRef: &gwv1beta1.LocalObjectReference{
Name: "test",
Kind: v1alpha1.RouteRetryFilterKind,
Group: "consul.hashicorp.com/v1alpha1",
Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group),
},
},
{
ExtensionRef: &gwv1beta1.LocalObjectReference{
Name: "test-timeout-filter",
Kind: v1alpha1.RouteTimeoutFilterKind,
Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group),
},
},
{
ExtensionRef: &gwv1beta1.LocalObjectReference{
Name: "test-jwt-filter",
Kind: v1alpha1.RouteAuthFilterKind,
Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group),
},
},
},
Expand Down Expand Up @@ -1332,6 +1347,47 @@ func TestTranslator_ToHTTPRoute(t *testing.T) {
RetryOnConnectFailure: pointer.Bool(true),
},
},

&v1alpha1.RouteTimeoutFilter{
TypeMeta: metav1.TypeMeta{
Kind: v1alpha1.RouteTimeoutFilterKind,
APIVersion: "consul.hashicorp.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-timeout-filter",
Namespace: "k8s-ns",
},
Spec: v1alpha1.RouteTimeoutFilterSpec{
RequestTimeout: 10,
IdleTimeout: 30,
},
},

&v1alpha1.RouteAuthFilter{
TypeMeta: metav1.TypeMeta{
Kind: v1alpha1.RouteAuthFilterKind,
APIVersion: "consul.hashicorp.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-jwt-filter",
Namespace: "k8s-ns",
},
Spec: v1alpha1.RouteAuthFilterSpec{
JWT: &v1alpha1.GatewayJWTRequirement{
Providers: []*v1alpha1.GatewayJWTProvider{
{
Name: "test-jwt-provider",
VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{
{
Path: []string{"/okta"},
Value: "okta",
},
},
},
},
},
},
},
},
},
want: api.HTTPRouteConfigEntry{
Expand All @@ -1348,6 +1404,23 @@ func TestTranslator_ToHTTPRoute(t *testing.T) {
RetryOnStatusCodes: []uint32{500, 502},
RetryOnConnectFailure: pointer.Bool(false),
},
TimeoutFilter: &api.TimeoutFilter{
RequestTimeout: time.Duration(10 * time.Nanosecond),
IdleTimeout: time.Duration(30 * time.Nanosecond),
},
JWT: &api.JWTFilter{
Providers: []*api.APIGatewayJWTProvider{
{
Name: "test-jwt-provider",
VerifyClaims: []*api.APIGatewayJWTClaimVerification{
{
Path: []string{"/okta"},
Value: "okta",
},
},
},
},
},
},
Matches: []api.HTTPMatch{
{
Expand Down
24 changes: 6 additions & 18 deletions control-plane/api/v1alpha1/routeauthfilter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
RouteAuthFilterKind = "RouteAuthFilter"
)

func init() {
SchemeBuilder.Register(&RouteAuthFilter{}, &RouteAuthFilterList{})
}
Expand Down Expand Up @@ -37,23 +41,7 @@ type RouteAuthFilterList struct {

// RouteAuthFilterSpec defines the desired state of RouteAuthFilter.
type RouteAuthFilterSpec struct {
// This re-uses the JWT requirement type from Gateway Policy Types.
//+kubebuilder:validation:Optional
JWT *RouteJWTRequirement `json:"jwt,omitempty"`
}

// RouteJWTRequirement defines the JWT requirements per provider.
type RouteJWTRequirement struct {
Providers []RouteJWTProvider `json:"providers"`
}

// RouteJWTProvider defines the configuration for a specific JWT provider.
type RouteJWTProvider struct {
Name string `json:"name"`
VerifyClaims []RouteJWTClaimVerification `json:"verifyClaims"`
}

// RouteJWTClaimVerification defines the specific claims to be verified.
type RouteJWTClaimVerification struct {
Path []string `json:"path"`
Value string `json:"value"`
JWT *GatewayJWTRequirement `json:"jwt,omitempty"`
}
Loading