Skip to content

Commit

Permalink
Add the plumbing for APIGW JWT work (#18609)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jm96441n authored Aug 31, 2023
1 parent d45c3c2 commit 9876923
Show file tree
Hide file tree
Showing 9 changed files with 695 additions and 19 deletions.
65 changes: 64 additions & 1 deletion agent/consul/gateways/controller_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
case structs.InlineCertificate:
return r.enqueueCertificateReferencedGateways(r.fsm.State(), ctx, req)
case structs.JWTProvider:
return r.enqueueJWTProviderReferencedGatewaysAndHTTPRoutes(r.fsm.State(), ctx, req)
default:
return nil
}
Expand Down Expand Up @@ -233,6 +235,7 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle
logger.Warn("error retrieving bound api gateway", "error", err)
return err
}

meta := newGatewayMeta(gateway, bound)

certificateErrors, err := meta.checkCertificates(store)
Expand All @@ -241,6 +244,12 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle
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
// if there are any certificate errors
meta.eachListener(func(listener *structs.APIGatewayListener, bound *structs.BoundAPIGatewayListener) error {
Expand All @@ -260,7 +269,12 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle

if len(certificateErrors) > 0 {
updater.SetCondition(invalidCertificates())
} else {
}
if len(jwtErrors) > 0 {
updater.SetCondition(invalidJWTProviders())
}

if len(certificateErrors) == 0 && len(jwtErrors) == 0 {
updater.SetCondition(gatewayAccepted())
}

Expand Down Expand Up @@ -463,6 +477,13 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller.
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
r.logger.Trace("binding routes to gateway")
modifiedGateways, boundRefs, bindErrors := bindRoutesToGateways(route, meta...)
Expand Down Expand Up @@ -536,6 +557,11 @@ func NewAPIGatewayController(fsm *fsm.FSM, publisher state.EventPublisher, updat
&stream.SubscribeRequest{
Topic: state.EventTopicInlineCertificate,
Subject: stream.SubjectWildcard,
},
).Subscribe(
&stream.SubscribeRequest{
Topic: state.EventTopicJWTProvider,
Subject: stream.SubjectWildcard,
})
}

Expand Down Expand Up @@ -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
// bound routes
func gatewayListenerNoConflicts(ref structs.ResourceReference) structs.Condition {
Expand Down Expand Up @@ -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
func routeUnbound(ref structs.ResourceReference, err error) structs.Condition {
return structs.NewRouteCondition(
Expand Down
27 changes: 27 additions & 0 deletions agent/consul/gateways/controller_gateways_ce.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 16 additions & 5 deletions agent/proxycfg/api_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ func (h *handlerAPIGateway) initialize(ctx context.Context) (ConfigSnapshot, err
return snap, err
}

err = watchJWTProviders(ctx, h)
if err != nil {
return snap, err
}

snap.APIGateway.Listeners = make(map[string]structs.APIGatewayListener)
snap.APIGateway.BoundListeners = make(map[string]structs.BoundAPIGatewayListener)
snap.APIGateway.HTTPRoutes = watch.NewMap[structs.ResourceReference, *structs.HTTPRouteConfigEntry]()
Expand Down Expand Up @@ -97,27 +102,33 @@ func (h *handlerAPIGateway) handleUpdate(ctx context.Context, u UpdateEvent, sna
return fmt.Errorf("error filling agent cache: %v", u.Err)
}

switch {
case u.CorrelationID == rootsWatchID:
switch u.CorrelationID {
case rootsWatchID:
// Handle change in the CA roots
if err := h.handleRootCAUpdate(u, snap); err != nil {
return err
}
case u.CorrelationID == apiGatewayConfigWatchID || u.CorrelationID == boundGatewayConfigWatchID:
case apiGatewayConfigWatchID, boundGatewayConfigWatchID:
// Handle change in the api-gateway or bound-api-gateway config entry
if err := h.handleGatewayConfigUpdate(ctx, u, snap, u.CorrelationID); err != nil {
return err
}
case u.CorrelationID == inlineCertificateConfigWatchID:
case inlineCertificateConfigWatchID:
// Handle change in an attached inline-certificate config entry
if err := h.handleInlineCertConfigUpdate(ctx, u, snap); err != nil {
return err
}
case u.CorrelationID == routeConfigWatchID:
case routeConfigWatchID:
// Handle change in an attached http-route or tcp-route config entry
if err := h.handleRouteConfigUpdate(ctx, u, snap); err != nil {
return err
}
case jwtProviderID:
err := setJWTProvider(u, snap)
if err != nil {
return err
}

default:
if err := (*handlerUpstreams)(h).handleUpdateUpstreams(ctx, u, snap); err != nil {
return err
Expand Down
32 changes: 32 additions & 0 deletions agent/structs/config_entry_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,38 @@ type HTTPMatch struct {
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
// be used for matching a given request.
type HTTPMatchMethod string
Expand Down
Loading

0 comments on commit 9876923

Please sign in to comment.