APIGW: Update HTTPRouteConfigEntry for JWT Auth (#18422)

* Updated httproute config entry for JWT Filters

* Added manual deepcopy method for httproute jwt filter

* Fix test

* Update JWTFilter to be in oss file

* Add changelog

* Add build tags for deepcopy oss file
This commit is contained in:
John Maguire 2023-08-10 17:23:42 -04:00 committed by GitHub
parent 6981658585
commit df11e4e7b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 881 additions and 609 deletions

3
.changelog/_18422.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
config-entry(api-gateway): (Enterprise only) Add JWTFilter to HTTPRoute Filters
```

View File

@ -8,3 +8,6 @@ package structs
// APIGatewayJWTRequirement holds the list of JWT providers to be verified against
type APIGatewayJWTRequirement struct{}
// JWTFilter holds the JWT Filter configuration for an HTTPRoute
type JWTFilter struct{}

View File

@ -422,6 +422,7 @@ type HTTPFilters struct {
URLRewrite *URLRewrite
RetryFilter *RetryFilter
TimeoutFilter *TimeoutFilter
JWT *JWTFilter
}
// HTTPHeaderFilter specifies how HTTP headers should be modified.

View File

@ -421,6 +421,9 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry {
cp.Rules[i2].Filters.TimeoutFilter = new(TimeoutFilter)
*cp.Rules[i2].Filters.TimeoutFilter = *o.Rules[i2].Filters.TimeoutFilter
}
if o.Rules[i2].Filters.JWT != nil {
cp.Rules[i2].Filters.JWT = o.Rules[i2].Filters.JWT.DeepCopy()
}
if o.Rules[i2].Matches != nil {
cp.Rules[i2].Matches = make([]HTTPMatch, len(o.Rules[i2].Matches))
copy(cp.Rules[i2].Matches, o.Rules[i2].Matches)
@ -489,6 +492,9 @@ func (o *HTTPRouteConfigEntry) DeepCopy() *HTTPRouteConfigEntry {
cp.Rules[i2].Services[i4].Filters.TimeoutFilter = new(TimeoutFilter)
*cp.Rules[i2].Services[i4].Filters.TimeoutFilter = *o.Rules[i2].Services[i4].Filters.TimeoutFilter
}
if o.Rules[i2].Services[i4].Filters.JWT != nil {
cp.Rules[i2].Services[i4].Filters.JWT = o.Rules[i2].Services[i4].Filters.JWT.DeepCopy()
}
}
}
}

View File

@ -1,6 +1,17 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build !consulent
// +build !consulent
package structs
// DeepCopy generates a deep copy of *APIGatewayJWTRequirement
func (o *APIGatewayJWTRequirement) DeepCopy() *APIGatewayJWTRequirement {
return new(APIGatewayJWTRequirement)
}
// DeepCopy generates a deep copy of *JWTFilter
func (o *JWTFilter) DeepCopy() *JWTFilter {
return new(JWTFilter)
}

View File

@ -201,6 +201,7 @@ type HTTPFilters struct {
URLRewrite *URLRewrite
RetryFilter *RetryFilter
TimeoutFilter *TimeoutFilter
JWT *JWTFilter
}
// HTTPHeaderFilter specifies how HTTP headers should be modified.
@ -226,6 +227,11 @@ type TimeoutFilter struct {
IdleTimeout time.Duration
}
// JWTFilter specifies the JWT configuration for a route
type JWTFilter struct {
Providers []*APIGatewayJWTProvider `json:",omitempty"`
}
// HTTPRouteRule specifies the routing rules used to determine what upstream
// service an HTTP request is routed to.
type HTTPRouteRule struct {

View File

@ -0,0 +1,134 @@
package api
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAPI_ConfigEntries_HTTPRoute(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
configEntries := c.ConfigEntries()
route1 := &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route1",
}
route2 := &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route2",
}
// set it
_, wm, err := configEntries.Set(route1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// also set the second one
_, wm, err = configEntries.Set(route2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := configEntries.Get(HTTPRoute, "route1", nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readRoute, ok := entry.(*HTTPRouteConfigEntry)
require.True(t, ok)
require.Equal(t, route1.Kind, readRoute.Kind)
require.Equal(t, route1.Name, readRoute.Name)
require.Equal(t, route1.Meta, readRoute.Meta)
require.Equal(t, route1.Meta, readRoute.GetMeta())
// update it
route1.Rules = []HTTPRouteRule{
{
Filters: HTTPFilters{
URLRewrite: &URLRewrite{
Path: "abc",
},
},
},
}
// CAS fail
written, _, err := configEntries.CAS(route1, 0, nil)
require.NoError(t, err)
require.False(t, written)
// CAS success
written, wm, err = configEntries.CAS(route1, readRoute.ModifyIndex, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
require.True(t, written)
// re-setting should not yield an error
_, wm, err = configEntries.Set(route1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
route2.Rules = []HTTPRouteRule{
{
Filters: HTTPFilters{
URLRewrite: &URLRewrite{
Path: "def",
},
},
},
}
_, wm, err = configEntries.Set(route2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// list them
entries, qm, err := configEntries.List(HTTPRoute, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
require.Len(t, entries, 2)
for _, entry = range entries {
switch entry.GetName() {
case "route1":
// this also verifies that the update value was persisted and
// the updated values are seen
readRoute, ok = entry.(*HTTPRouteConfigEntry)
require.True(t, ok)
require.Equal(t, route1.Kind, readRoute.Kind)
require.Equal(t, route1.Name, readRoute.Name)
require.Len(t, readRoute.Rules, 1)
require.Equal(t, route1.Rules, readRoute.Rules)
case "route2":
readRoute, ok = entry.(*HTTPRouteConfigEntry)
require.True(t, ok)
require.Equal(t, route2.Kind, readRoute.Kind)
require.Equal(t, route2.Name, readRoute.Name)
require.Len(t, readRoute.Rules, 1)
require.Equal(t, route2.Rules, readRoute.Rules)
}
}
// delete it
wm, err = configEntries.Delete(HTTPRoute, "route1", nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// verify deletion
_, _, err = configEntries.Get(HTTPRoute, "route1", nil)
require.Error(t, err)
}

View File

@ -411,6 +411,7 @@ func HTTPFiltersToStructs(s *HTTPFilters, t *structs.HTTPFilters) {
TimeoutFilterToStructs(s.TimeoutFilter, &x)
t.TimeoutFilter = &x
}
t.JWT = routeJWTFilterToStructs(s.JWT)
}
func HTTPFiltersFromStructs(t *structs.HTTPFilters, s *HTTPFilters) {
if s == nil {
@ -441,6 +442,7 @@ func HTTPFiltersFromStructs(t *structs.HTTPFilters, s *HTTPFilters) {
TimeoutFilterFromStructs(t.TimeoutFilter, &x)
s.TimeoutFilter = &x
}
s.JWT = routeJWTFilterFromStructs(t.JWT)
}
func HTTPHeaderFilterToStructs(s *HTTPHeaderFilter, t *structs.HTTPHeaderFilter) {
if s == nil {

View File

@ -687,6 +687,16 @@ func (msg *TimeoutFilter) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *JWTFilter) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *JWTFilter) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *HTTPHeaderFilter) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)

File diff suppressed because it is too large Load Diff

View File

@ -912,6 +912,8 @@ message HTTPFilters {
URLRewrite URLRewrite = 2;
RetryFilter RetryFilter = 3;
TimeoutFilter TimeoutFilter = 4;
// mog: func-to=routeJWTFilterToStructs func-from=routeJWTFilterFromStructs
JWTFilter JWT = 5;
}
// mog annotation:
@ -947,6 +949,10 @@ message TimeoutFilter {
google.protobuf.Duration IdleTimeout = 2;
}
message JWTFilter {
repeated APIGatewayJWTProvider Providers = 1;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.HTTPHeaderFilter

View File

@ -15,3 +15,11 @@ func gwJWTRequirementToStructs(m *APIGatewayJWTRequirement) *structs.APIGatewayJ
func gwJWTRequirementFromStructs(*structs.APIGatewayJWTRequirement) *APIGatewayJWTRequirement {
return &APIGatewayJWTRequirement{}
}
func routeJWTFilterToStructs(m *JWTFilter) *structs.JWTFilter {
return &structs.JWTFilter{}
}
func routeJWTFilterFromStructs(*structs.JWTFilter) *JWTFilter {
return &JWTFilter{}
}