mirror of https://github.com/status-im/consul.git
Add the plumbing for APIGW JWT work (#18609)
* Add the plumbing for APIGW JWT work * Remove unneeded import * Add deep equal function for HTTPMatch * Added plumbing for status conditions * Remove unneeded comment * Fix comments * Add calls in xds listener for apigateway to setup listener jwt auth
This commit is contained in:
parent
d45c3c2755
commit
9876923e23
|
@ -63,6 +63,8 @@ func (r *apiGatewayReconciler) Reconcile(ctx context.Context, req controller.Req
|
||||||
return reconcileEntry(r.fsm.State(), r.logger, ctx, req, r.reconcileTCPRoute, r.cleanupRoute)
|
return reconcileEntry(r.fsm.State(), r.logger, ctx, req, r.reconcileTCPRoute, r.cleanupRoute)
|
||||||
case structs.InlineCertificate:
|
case structs.InlineCertificate:
|
||||||
return r.enqueueCertificateReferencedGateways(r.fsm.State(), ctx, req)
|
return r.enqueueCertificateReferencedGateways(r.fsm.State(), ctx, req)
|
||||||
|
case structs.JWTProvider:
|
||||||
|
return r.enqueueJWTProviderReferencedGatewaysAndHTTPRoutes(r.fsm.State(), ctx, req)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -233,6 +235,7 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle
|
||||||
logger.Warn("error retrieving bound api gateway", "error", err)
|
logger.Warn("error retrieving bound api gateway", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := newGatewayMeta(gateway, bound)
|
meta := newGatewayMeta(gateway, bound)
|
||||||
|
|
||||||
certificateErrors, err := meta.checkCertificates(store)
|
certificateErrors, err := meta.checkCertificates(store)
|
||||||
|
@ -241,6 +244,12 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jwtErrors, err := meta.checkJWTProviders(store)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("error checking gateway JWT Providers", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// set each listener as having valid certs, then overwrite that status condition
|
// set each listener as having valid certs, then overwrite that status condition
|
||||||
// if there are any certificate errors
|
// if there are any certificate errors
|
||||||
meta.eachListener(func(listener *structs.APIGatewayListener, bound *structs.BoundAPIGatewayListener) error {
|
meta.eachListener(func(listener *structs.APIGatewayListener, bound *structs.BoundAPIGatewayListener) error {
|
||||||
|
@ -260,7 +269,12 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle
|
||||||
|
|
||||||
if len(certificateErrors) > 0 {
|
if len(certificateErrors) > 0 {
|
||||||
updater.SetCondition(invalidCertificates())
|
updater.SetCondition(invalidCertificates())
|
||||||
} else {
|
}
|
||||||
|
if len(jwtErrors) > 0 {
|
||||||
|
updater.SetCondition(invalidJWTProviders())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certificateErrors) == 0 && len(jwtErrors) == 0 {
|
||||||
updater.SetCondition(gatewayAccepted())
|
updater.SetCondition(gatewayAccepted())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,6 +477,13 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller.
|
||||||
updater.SetCondition(routeNoUpstreams())
|
updater.SetCondition(routeNoUpstreams())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if httpRoute, ok := route.(*structs.HTTPRouteConfigEntry); ok {
|
||||||
|
err := validateJWTForRoute(store, updater, httpRoute)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// the route is valid, attempt to bind it to all gateways
|
// the route is valid, attempt to bind it to all gateways
|
||||||
r.logger.Trace("binding routes to gateway")
|
r.logger.Trace("binding routes to gateway")
|
||||||
modifiedGateways, boundRefs, bindErrors := bindRoutesToGateways(route, meta...)
|
modifiedGateways, boundRefs, bindErrors := bindRoutesToGateways(route, meta...)
|
||||||
|
@ -536,6 +557,11 @@ func NewAPIGatewayController(fsm *fsm.FSM, publisher state.EventPublisher, updat
|
||||||
&stream.SubscribeRequest{
|
&stream.SubscribeRequest{
|
||||||
Topic: state.EventTopicInlineCertificate,
|
Topic: state.EventTopicInlineCertificate,
|
||||||
Subject: stream.SubjectWildcard,
|
Subject: stream.SubjectWildcard,
|
||||||
|
},
|
||||||
|
).Subscribe(
|
||||||
|
&stream.SubscribeRequest{
|
||||||
|
Topic: state.EventTopicJWTProvider,
|
||||||
|
Subject: stream.SubjectWildcard,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -897,6 +923,31 @@ func invalidCertificates() structs.Condition {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invalidJWTProvider returns a condition used when a gateway listener references
|
||||||
|
// a JWTProvider that does not exist. It takes a ref used to scope the condition
|
||||||
|
// to a given APIGateway listener.
|
||||||
|
func invalidJWTProvider(ref structs.ResourceReference, err error) structs.Condition {
|
||||||
|
return structs.NewGatewayCondition(
|
||||||
|
api.GatewayConditionResolvedRefs,
|
||||||
|
api.ConditionStatusFalse,
|
||||||
|
api.GatewayListenerReasonInvalidJWTProviderRef,
|
||||||
|
err.Error(),
|
||||||
|
ref,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidJWTProviders is used to set the overall condition of the APIGateway
|
||||||
|
// to invalid due to missing JWT providers that it references.
|
||||||
|
func invalidJWTProviders() structs.Condition {
|
||||||
|
return structs.NewGatewayCondition(
|
||||||
|
api.GatewayConditionAccepted,
|
||||||
|
api.ConditionStatusFalse,
|
||||||
|
api.GatewayReasonInvalidJWTProviders,
|
||||||
|
"gateway references invalid JWT Providers",
|
||||||
|
structs.ResourceReference{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// gatewayListenerNoConflicts marks an APIGateway listener as having no conflicts within its
|
// gatewayListenerNoConflicts marks an APIGateway listener as having no conflicts within its
|
||||||
// bound routes
|
// bound routes
|
||||||
func gatewayListenerNoConflicts(ref structs.ResourceReference) structs.Condition {
|
func gatewayListenerNoConflicts(ref structs.ResourceReference) structs.Condition {
|
||||||
|
@ -944,6 +995,18 @@ func gatewayNotFound(ref structs.ResourceReference) structs.Condition {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jwtProviderNotFound marks a Route as having failed to bind to a referenced APIGateway due to
|
||||||
|
// one or more of the referenced JWT providers not existing (or having not been reconciled yet)
|
||||||
|
func jwtProviderNotFound(ref structs.ResourceReference, err error) structs.Condition {
|
||||||
|
return structs.NewRouteCondition(
|
||||||
|
api.RouteConditionBound,
|
||||||
|
api.ConditionStatusFalse,
|
||||||
|
api.RouteReasonGatewayNotFound,
|
||||||
|
err.Error(),
|
||||||
|
ref,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// routeUnbound marks the route as having failed to bind to the referenced APIGateway
|
// routeUnbound marks the route as having failed to bind to the referenced APIGateway
|
||||||
func routeUnbound(ref structs.ResourceReference, err error) structs.Condition {
|
func routeUnbound(ref structs.ResourceReference, err error) structs.Condition {
|
||||||
return structs.NewRouteCondition(
|
return structs.NewRouteCondition(
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
//go:build !consulent
|
||||||
|
// +build !consulent
|
||||||
|
|
||||||
|
package gateways
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/consul/controller"
|
||||||
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *apiGatewayReconciler) enqueueJWTProviderReferencedGatewaysAndHTTPRoutes(_ *state.Store, _ context.Context, _ controller.Request) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *gatewayMeta) checkJWTProviders(_ *state.Store) (map[structs.ResourceReference]error, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateJWTForRoute(_ *state.Store, _ *structs.StatusUpdater, _ *structs.HTTPRouteConfigEntry) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -54,6 +54,11 @@ func (h *handlerAPIGateway) initialize(ctx context.Context) (ConfigSnapshot, err
|
||||||
return snap, err
|
return snap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = watchJWTProviders(ctx, h)
|
||||||
|
if err != nil {
|
||||||
|
return snap, err
|
||||||
|
}
|
||||||
|
|
||||||
snap.APIGateway.Listeners = make(map[string]structs.APIGatewayListener)
|
snap.APIGateway.Listeners = make(map[string]structs.APIGatewayListener)
|
||||||
snap.APIGateway.BoundListeners = make(map[string]structs.BoundAPIGatewayListener)
|
snap.APIGateway.BoundListeners = make(map[string]structs.BoundAPIGatewayListener)
|
||||||
snap.APIGateway.HTTPRoutes = watch.NewMap[structs.ResourceReference, *structs.HTTPRouteConfigEntry]()
|
snap.APIGateway.HTTPRoutes = watch.NewMap[structs.ResourceReference, *structs.HTTPRouteConfigEntry]()
|
||||||
|
@ -97,27 +102,33 @@ func (h *handlerAPIGateway) handleUpdate(ctx context.Context, u UpdateEvent, sna
|
||||||
return fmt.Errorf("error filling agent cache: %v", u.Err)
|
return fmt.Errorf("error filling agent cache: %v", u.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch u.CorrelationID {
|
||||||
case u.CorrelationID == rootsWatchID:
|
case rootsWatchID:
|
||||||
// Handle change in the CA roots
|
// Handle change in the CA roots
|
||||||
if err := h.handleRootCAUpdate(u, snap); err != nil {
|
if err := h.handleRootCAUpdate(u, snap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case u.CorrelationID == apiGatewayConfigWatchID || u.CorrelationID == boundGatewayConfigWatchID:
|
case apiGatewayConfigWatchID, boundGatewayConfigWatchID:
|
||||||
// Handle change in the api-gateway or bound-api-gateway config entry
|
// Handle change in the api-gateway or bound-api-gateway config entry
|
||||||
if err := h.handleGatewayConfigUpdate(ctx, u, snap, u.CorrelationID); err != nil {
|
if err := h.handleGatewayConfigUpdate(ctx, u, snap, u.CorrelationID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case u.CorrelationID == inlineCertificateConfigWatchID:
|
case inlineCertificateConfigWatchID:
|
||||||
// Handle change in an attached inline-certificate config entry
|
// Handle change in an attached inline-certificate config entry
|
||||||
if err := h.handleInlineCertConfigUpdate(ctx, u, snap); err != nil {
|
if err := h.handleInlineCertConfigUpdate(ctx, u, snap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case u.CorrelationID == routeConfigWatchID:
|
case routeConfigWatchID:
|
||||||
// Handle change in an attached http-route or tcp-route config entry
|
// Handle change in an attached http-route or tcp-route config entry
|
||||||
if err := h.handleRouteConfigUpdate(ctx, u, snap); err != nil {
|
if err := h.handleRouteConfigUpdate(ctx, u, snap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case jwtProviderID:
|
||||||
|
err := setJWTProvider(u, snap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if err := (*handlerUpstreams)(h).handleUpdateUpstreams(ctx, u, snap); err != nil {
|
if err := (*handlerUpstreams)(h).handleUpdateUpstreams(ctx, u, snap); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -343,6 +343,38 @@ type HTTPMatch struct {
|
||||||
Query []HTTPQueryMatch
|
Query []HTTPQueryMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m HTTPMatch) DeepEqual(other HTTPMatch) bool {
|
||||||
|
if m.Method != other.Method {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Path != other.Path {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Headers) != len(other.Headers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Query) != len(other.Query) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(m.Headers); i++ {
|
||||||
|
if m.Headers[i] != other.Headers[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(m.Query); i++ {
|
||||||
|
if m.Query[i] != other.Query[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPMatchMethod specifies which type of HTTP verb should
|
// HTTPMatchMethod specifies which type of HTTP verb should
|
||||||
// be used for matching a given request.
|
// be used for matching a given request.
|
||||||
type HTTPMatchMethod string
|
type HTTPMatchMethod string
|
||||||
|
|
|
@ -437,3 +437,476 @@ func TestHTTPRoute(t *testing.T) {
|
||||||
}
|
}
|
||||||
testConfigEntryNormalizeAndValidate(t, cases)
|
testConfigEntryNormalizeAndValidate(t, cases)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPMatch_DeepEqual(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Headers []HTTPHeaderMatch
|
||||||
|
Method HTTPMatchMethod
|
||||||
|
Path HTTPPathMatch
|
||||||
|
Query []HTTPQueryMatch
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
other HTTPMatch
|
||||||
|
}
|
||||||
|
tests := map[string]struct {
|
||||||
|
match HTTPMatch
|
||||||
|
other HTTPMatch
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
"all fields equal": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
"differing number of header matches": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
"differing header matches": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h4",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
"different path matching": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/zoidberg",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
"differing methods": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodConnect,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
"differing number of query matches": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
"different query matches": {
|
||||||
|
match: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "another",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: HTTPMatch{
|
||||||
|
Headers: []HTTPHeaderMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchExact,
|
||||||
|
Name: "h1",
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPHeaderMatchPrefix,
|
||||||
|
Name: "h2",
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Method: HTTPMatchMethodGet,
|
||||||
|
Path: HTTPPathMatch{
|
||||||
|
Match: HTTPPathMatchType(HTTPHeaderMatchPrefix),
|
||||||
|
Value: "/bender",
|
||||||
|
},
|
||||||
|
Query: []HTTPQueryMatch{
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchExact,
|
||||||
|
Name: "q",
|
||||||
|
Value: "nibbler",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Match: HTTPQueryMatchPresent,
|
||||||
|
Name: "ship",
|
||||||
|
Value: "planet express",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tt := range tests {
|
||||||
|
name := name
|
||||||
|
tt := tt
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := tt.match.DeepEqual(tt.other); got != tt.want {
|
||||||
|
t.Errorf("HTTPMatch.DeepEqual() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,6 @@ type perRouteFilterBuilder struct {
|
||||||
route *structs.HTTPRouteConfigEntry
|
route *structs.HTTPRouteConfigEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p perRouteFilterBuilder) buildFilter(match *envoy_route_v3.RouteMatch) (map[string]*anypb.Any, error) {
|
func (p perRouteFilterBuilder) buildTypedPerFilterConfig(match *envoy_route_v3.RouteMatch, routeAction *envoy_route_v3.Route_Route) (map[string]*anypb.Any, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import (
|
||||||
|
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
envoy_http_jwt_authn_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
|
||||||
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
envoy_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/xds/naming"
|
"github.com/hashicorp/consul/agent/xds/naming"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
@ -137,6 +140,45 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
|
||||||
logger: s.Logger,
|
logger: s.Logger,
|
||||||
}
|
}
|
||||||
listener := makeListener(listenerOpts)
|
listener := makeListener(listenerOpts)
|
||||||
|
|
||||||
|
route, _ := cfgSnap.APIGateway.HTTPRoutes.Get(readyListener.routeReference)
|
||||||
|
foundJWT := false
|
||||||
|
if listenerCfg.Override != nil && listenerCfg.Override.JWT != nil {
|
||||||
|
foundJWT = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundJWT && listenerCfg.Default != nil && listenerCfg.Default.JWT != nil {
|
||||||
|
foundJWT = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundJWT {
|
||||||
|
for _, rule := range route.Rules {
|
||||||
|
if rule.Filters.JWT != nil {
|
||||||
|
foundJWT = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, svc := range rule.Services {
|
||||||
|
if svc.Filters.JWT != nil {
|
||||||
|
foundJWT = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var authFilters []*envoy_http_v3.HttpFilter
|
||||||
|
if foundJWT {
|
||||||
|
builder := &GatewayAuthFilterBuilder{
|
||||||
|
listener: listenerCfg,
|
||||||
|
route: route,
|
||||||
|
providers: cfgSnap.JWTProviders,
|
||||||
|
envoyProviders: make(map[string]*envoy_http_jwt_authn_v3.JwtProvider, len(cfgSnap.JWTProviders)),
|
||||||
|
}
|
||||||
|
authFilters, err = builder.makeGatewayAuthFilters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
filterOpts := listenerFilterOpts{
|
filterOpts := listenerFilterOpts{
|
||||||
useRDS: true,
|
useRDS: true,
|
||||||
protocol: listenerKey.Protocol,
|
protocol: listenerKey.Protocol,
|
||||||
|
@ -145,7 +187,7 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro
|
||||||
cluster: "",
|
cluster: "",
|
||||||
statPrefix: "ingress_upstream_",
|
statPrefix: "ingress_upstream_",
|
||||||
routePath: "",
|
routePath: "",
|
||||||
httpAuthzFilters: nil,
|
httpAuthzFilters: authFilters,
|
||||||
accessLogs: &cfgSnap.Proxy.AccessLogs,
|
accessLogs: &cfgSnap.Proxy.AccessLogs,
|
||||||
logger: s.Logger,
|
logger: s.Logger,
|
||||||
}
|
}
|
||||||
|
@ -210,7 +252,6 @@ type readyListener struct {
|
||||||
|
|
||||||
// getReadyListeners returns a map containing the list of upstreams for each listener that is ready
|
// getReadyListeners returns a map containing the list of upstreams for each listener that is ready
|
||||||
func getReadyListeners(cfgSnap *proxycfg.ConfigSnapshot) map[string]readyListener {
|
func getReadyListeners(cfgSnap *proxycfg.ConfigSnapshot) map[string]readyListener {
|
||||||
|
|
||||||
ready := map[string]readyListener{}
|
ready := map[string]readyListener{}
|
||||||
for _, l := range cfgSnap.APIGateway.Listeners {
|
for _, l := range cfgSnap.APIGateway.Listeners {
|
||||||
// Only include upstreams for listeners that are ready
|
// Only include upstreams for listeners that are ready
|
||||||
|
@ -278,7 +319,7 @@ func makeDownstreamTLSContextFromSnapshotAPIListenerConfig(cfgSnap *proxycfg.Con
|
||||||
func makeCommonTLSContextFromSnapshotAPIGatewayListenerConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg structs.APIGatewayListener) (*envoy_tls_v3.CommonTlsContext, error) {
|
func makeCommonTLSContextFromSnapshotAPIGatewayListenerConfig(cfgSnap *proxycfg.ConfigSnapshot, listenerCfg structs.APIGatewayListener) (*envoy_tls_v3.CommonTlsContext, error) {
|
||||||
var tlsContext *envoy_tls_v3.CommonTlsContext
|
var tlsContext *envoy_tls_v3.CommonTlsContext
|
||||||
|
|
||||||
//API Gateway TLS config is per listener
|
// API Gateway TLS config is per listener
|
||||||
tlsCfg, err := resolveAPIListenerTLSConfig(listenerCfg.TLS)
|
tlsCfg, err := resolveAPIListenerTLSConfig(listenerCfg.TLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -321,8 +362,8 @@ func makeInlineOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot,
|
||||||
tlsCfg structs.GatewayTLSConfig,
|
tlsCfg structs.GatewayTLSConfig,
|
||||||
protocol string,
|
protocol string,
|
||||||
filterOpts listenerFilterOpts,
|
filterOpts listenerFilterOpts,
|
||||||
certs []structs.InlineCertificateConfigEntry) ([]*envoy_listener_v3.FilterChain, error) {
|
certs []structs.InlineCertificateConfigEntry,
|
||||||
|
) ([]*envoy_listener_v3.FilterChain, error) {
|
||||||
var chains []*envoy_listener_v3.FilterChain
|
var chains []*envoy_listener_v3.FilterChain
|
||||||
|
|
||||||
constructChain := func(name string, hosts []string, tlsContext *envoy_tls_v3.CommonTlsContext) error {
|
constructChain := func(name string, hosts []string, tlsContext *envoy_tls_v3.CommonTlsContext) error {
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, []string{"*"}, false)
|
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, []string{"*"}, false, perRouteFilterBuilder{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -94,12 +94,12 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
|
||||||
addressesMap[routeName] = make(map[string]string)
|
addressesMap[routeName] = make(map[string]string)
|
||||||
}
|
}
|
||||||
// cluster name is unique per address/port so we should not be doing any override here
|
// cluster name is unique per address/port so we should not be doing any override here
|
||||||
|
|
||||||
clusterName := clusterNameForDestination(cfgSnap, svcConfig.Name, address, svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault())
|
clusterName := clusterNameForDestination(cfgSnap, svcConfig.Name, address, svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault())
|
||||||
addressesMap[routeName][clusterName] = address
|
addressesMap[routeName][clusterName] = address
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,6 @@ func (s *ResourceGenerator) routesForConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ResourceGenerator) makeRoutesForAddresses(routeName string, addresses map[string]string) ([]proto.Message, error) {
|
func (s *ResourceGenerator) makeRoutesForAddresses(routeName string, addresses map[string]string) ([]proto.Message, error) {
|
||||||
|
|
||||||
var resources []proto.Message
|
var resources []proto.Message
|
||||||
|
|
||||||
route, err := makeNamedAddressesRoute(routeName, addresses)
|
route, err := makeNamedAddressesRoute(routeName, addresses)
|
||||||
|
@ -201,7 +200,8 @@ func (s *ResourceGenerator) makeRoutes(
|
||||||
cfgSnap *proxycfg.ConfigSnapshot,
|
cfgSnap *proxycfg.ConfigSnapshot,
|
||||||
svc structs.ServiceName,
|
svc structs.ServiceName,
|
||||||
clusterName string,
|
clusterName string,
|
||||||
autoHostRewrite bool) ([]proto.Message, error) {
|
autoHostRewrite bool,
|
||||||
|
) ([]proto.Message, error) {
|
||||||
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
|
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
|
||||||
|
|
||||||
if !hasResolver {
|
if !hasResolver {
|
||||||
|
@ -255,6 +255,7 @@ func (s *ResourceGenerator) routesForMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
|
||||||
chain,
|
chain,
|
||||||
[]string{"*"},
|
[]string{"*"},
|
||||||
true,
|
true,
|
||||||
|
perRouteFilterBuilder{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -378,7 +379,7 @@ func (s *ResourceGenerator) routesForIngressGateway(cfgSnap *proxycfg.ConfigSnap
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := generateUpstreamIngressDomains(listenerKey, u)
|
domains := generateUpstreamIngressDomains(listenerKey, u)
|
||||||
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, domains, false)
|
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, domains, false, perRouteFilterBuilder{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -435,6 +436,7 @@ func (s *ResourceGenerator) routesForAPIGateway(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
readyUpstreamsList := getReadyListeners(cfgSnap)
|
readyUpstreamsList := getReadyListeners(cfgSnap)
|
||||||
|
|
||||||
for _, readyUpstreams := range readyUpstreamsList {
|
for _, readyUpstreams := range readyUpstreamsList {
|
||||||
|
readyUpstreams := readyUpstreams
|
||||||
listenerCfg := readyUpstreams.listenerCfg
|
listenerCfg := readyUpstreams.listenerCfg
|
||||||
// Do not create any route configuration for TCP listeners
|
// Do not create any route configuration for TCP listeners
|
||||||
if listenerCfg.Protocol != structs.ListenerProtocolHTTP {
|
if listenerCfg.Protocol != structs.ListenerProtocolHTTP {
|
||||||
|
@ -461,12 +463,13 @@ func (s *ResourceGenerator) routesForAPIGateway(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
// specific naming convention in discoverychain.consolidateHTTPRoutes. If we don't
|
// specific naming convention in discoverychain.consolidateHTTPRoutes. If we don't
|
||||||
// convert our route to use the same naming convention, we won't find any chains below.
|
// convert our route to use the same naming convention, we won't find any chains below.
|
||||||
reformatedRoutes := discoverychain.ReformatHTTPRoute(route, &listenerCfg, cfgSnap.APIGateway.GatewayConfig)
|
reformatedRoutes := discoverychain.ReformatHTTPRoute(route, &listenerCfg, cfgSnap.APIGateway.GatewayConfig)
|
||||||
|
filterBuilder := perRouteFilterBuilder{providerMap: cfgSnap.JWTProviders, listener: &listenerCfg, route: route}
|
||||||
for _, reformatedRoute := range reformatedRoutes {
|
for _, reformatedRoute := range reformatedRoutes {
|
||||||
reformatedRoute := reformatedRoute
|
reformatedRoute := reformatedRoute
|
||||||
|
|
||||||
upstream := buildHTTPRouteUpstream(reformatedRoute, listenerCfg)
|
upstream := buildHTTPRouteUpstream(reformatedRoute, listenerCfg)
|
||||||
uid := proxycfg.NewUpstreamID(&upstream)
|
uid := proxycfg.NewUpstreamID(&upstream)
|
||||||
|
|
||||||
chain := cfgSnap.APIGateway.DiscoveryChain[uid]
|
chain := cfgSnap.APIGateway.DiscoveryChain[uid]
|
||||||
if chain == nil {
|
if chain == nil {
|
||||||
// Note that if we continue here we must also do this in the cluster generation
|
// Note that if we continue here we must also do this in the cluster generation
|
||||||
|
@ -476,7 +479,7 @@ func (s *ResourceGenerator) routesForAPIGateway(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
|
|
||||||
domains := generateUpstreamAPIsDomains(listenerKey, upstream, reformatedRoute.Hostnames)
|
domains := generateUpstreamAPIsDomains(listenerKey, upstream, reformatedRoute.Hostnames)
|
||||||
|
|
||||||
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, domains, false)
|
virtualHost, err := s.makeUpstreamRouteForDiscoveryChain(cfgSnap, uid, chain, domains, false, filterBuilder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -605,6 +608,7 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain(
|
||||||
chain *structs.CompiledDiscoveryChain,
|
chain *structs.CompiledDiscoveryChain,
|
||||||
serviceDomains []string,
|
serviceDomains []string,
|
||||||
forMeshGateway bool,
|
forMeshGateway bool,
|
||||||
|
filterBuilder perRouteFilterBuilder,
|
||||||
) (*envoy_route_v3.VirtualHost, error) {
|
) (*envoy_route_v3.VirtualHost, error) {
|
||||||
routeName := uid.EnvoyID()
|
routeName := uid.EnvoyID()
|
||||||
var routes []*envoy_route_v3.Route
|
var routes []*envoy_route_v3.Route
|
||||||
|
@ -624,6 +628,7 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain(
|
||||||
routes = make([]*envoy_route_v3.Route, 0, len(startNode.Routes))
|
routes = make([]*envoy_route_v3.Route, 0, len(startNode.Routes))
|
||||||
|
|
||||||
for _, discoveryRoute := range startNode.Routes {
|
for _, discoveryRoute := range startNode.Routes {
|
||||||
|
discoveryRoute := discoveryRoute
|
||||||
routeMatch := makeRouteMatchForDiscoveryRoute(discoveryRoute)
|
routeMatch := makeRouteMatchForDiscoveryRoute(discoveryRoute)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -688,8 +693,13 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter, err := filterBuilder.buildTypedPerFilterConfig(routeMatch, routeAction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
route.Match = routeMatch
|
route.Match = routeMatch
|
||||||
route.Action = routeAction
|
route.Action = routeAction
|
||||||
|
route.TypedPerFilterConfig = filter
|
||||||
|
|
||||||
routes = append(routes, route)
|
routes = append(routes, route)
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,10 @@ const (
|
||||||
// certificates and cannot bind to any routes
|
// certificates and cannot bind to any routes
|
||||||
GatewayReasonInvalidCertificates GatewayConditionReason = "InvalidCertificates"
|
GatewayReasonInvalidCertificates GatewayConditionReason = "InvalidCertificates"
|
||||||
|
|
||||||
|
// This reason is used with the "Accepted" condition when the gateway has multiple invalid
|
||||||
|
// JWT providers and cannot bind to any routes
|
||||||
|
GatewayReasonInvalidJWTProviders GatewayConditionReason = "InvalidJWTProviders"
|
||||||
|
|
||||||
// This condition indicates that the gateway was unable to resolve
|
// This condition indicates that the gateway was unable to resolve
|
||||||
// conflicting specification requirements for this Listener. If a
|
// conflicting specification requirements for this Listener. If a
|
||||||
// Listener is conflicted, its network port should not be configured
|
// Listener is conflicted, its network port should not be configured
|
||||||
|
@ -163,6 +167,14 @@ const (
|
||||||
// If the reference is not allowed, the reason RefNotPermitted must be used
|
// If the reference is not allowed, the reason RefNotPermitted must be used
|
||||||
// instead.
|
// instead.
|
||||||
GatewayListenerReasonInvalidCertificateRef GatewayConditionReason = "InvalidCertificateRef"
|
GatewayListenerReasonInvalidCertificateRef GatewayConditionReason = "InvalidCertificateRef"
|
||||||
|
|
||||||
|
// This reason is used with the "ResolvedRefs" condition when a
|
||||||
|
// Listener has a JWT configuration with at least one JWTProvider
|
||||||
|
// that is invalid or does not exist.
|
||||||
|
// A JWTProvider is considered invalid when it refers to a nonexistent
|
||||||
|
// or unsupported resource or kind, or when the data within that resource
|
||||||
|
// is malformed.
|
||||||
|
GatewayListenerReasonInvalidJWTProviderRef GatewayConditionReason = "InvalidJWTProviderRef"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[ConditionStatus][]GatewayConditionReason{
|
var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[ConditionStatus][]GatewayConditionReason{
|
||||||
|
@ -172,6 +184,7 @@ var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[Condition
|
||||||
},
|
},
|
||||||
ConditionStatusFalse: {
|
ConditionStatusFalse: {
|
||||||
GatewayReasonInvalidCertificates,
|
GatewayReasonInvalidCertificates,
|
||||||
|
GatewayReasonInvalidJWTProviders,
|
||||||
},
|
},
|
||||||
ConditionStatusUnknown: {},
|
ConditionStatusUnknown: {},
|
||||||
},
|
},
|
||||||
|
@ -190,6 +203,7 @@ var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[Condition
|
||||||
},
|
},
|
||||||
ConditionStatusFalse: {
|
ConditionStatusFalse: {
|
||||||
GatewayListenerReasonInvalidCertificateRef,
|
GatewayListenerReasonInvalidCertificateRef,
|
||||||
|
GatewayListenerReasonInvalidJWTProviderRef,
|
||||||
},
|
},
|
||||||
ConditionStatusUnknown: {},
|
ConditionStatusUnknown: {},
|
||||||
},
|
},
|
||||||
|
@ -282,6 +296,10 @@ const (
|
||||||
// This reason is used with the "Bound" condition when the route fails
|
// This reason is used with the "Bound" condition when the route fails
|
||||||
// to find the gateway
|
// to find the gateway
|
||||||
RouteReasonGatewayNotFound RouteConditionReason = "GatewayNotFound"
|
RouteReasonGatewayNotFound RouteConditionReason = "GatewayNotFound"
|
||||||
|
|
||||||
|
// This reason is used with the "Accepted" condition when the route references non-existent
|
||||||
|
// JWTProviders
|
||||||
|
RouteReasonJWTProvidersNotFound RouteConditionReason = "JWTProvidersNotFound"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStatus][]RouteConditionReason{
|
var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStatus][]RouteConditionReason{
|
||||||
|
@ -302,6 +320,7 @@ var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStat
|
||||||
ConditionStatusFalse: {
|
ConditionStatusFalse: {
|
||||||
RouteReasonGatewayNotFound,
|
RouteReasonGatewayNotFound,
|
||||||
RouteReasonFailedToBind,
|
RouteReasonFailedToBind,
|
||||||
|
RouteReasonJWTProvidersNotFound,
|
||||||
},
|
},
|
||||||
ConditionStatusUnknown: {},
|
ConditionStatusUnknown: {},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue