TUN-6775: Add middleware.Handler verification to ProxyHTTP

ProxyHTTP now processes middleware Handler before executing the request.
A chain of handlers is now executed and appropriate response status
codes are sent.
This commit is contained in:
Sudarsan Reddy 2022-09-22 15:11:59 +01:00
parent 9bb7628fbc
commit 7f487c2651
5 changed files with 54 additions and 26 deletions

View File

@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/pkg/errors"
) )
const ( const (
@ -14,7 +13,6 @@ const (
) )
var ( var (
ErrNoAccessToken = errors.New("no access token provided in request")
cloudflareAccessCertsURL = "https://%s.cloudflareaccess.com" cloudflareAccessCertsURL = "https://%s.cloudflareaccess.com"
) )
@ -39,28 +37,43 @@ func NewJWTValidator(teamName string, certsURL string, audTags []string) *JWTVal
verifier := oidc.NewVerifier(certsURL, keySet, config) verifier := oidc.NewVerifier(certsURL, keySet, config)
return &JWTValidator{ return &JWTValidator{
IDTokenVerifier: verifier, IDTokenVerifier: verifier,
audTags: audTags,
} }
} }
func (v *JWTValidator) Handle(ctx context.Context, r *http.Request) error { func (v *JWTValidator) Name() string {
return "AccessJWTValidator"
}
func (v *JWTValidator) Handle(ctx context.Context, r *http.Request) (*HandleResult, error) {
accessJWT := r.Header.Get(headerKeyAccessJWTAssertion) accessJWT := r.Header.Get(headerKeyAccessJWTAssertion)
if accessJWT == "" { if accessJWT == "" {
return ErrNoAccessToken // log the exact error message here. the message is specific to the handler implementation logic, we don't gain anything
// in passing it upstream. and each handler impl know what logging level to use for each.
return &HandleResult{
ShouldFilterRequest: true,
StatusCode: http.StatusForbidden,
Reason: "no access token in request",
}, nil
} }
token, err := v.IDTokenVerifier.Verify(ctx, accessJWT) token, err := v.IDTokenVerifier.Verify(ctx, accessJWT)
if err != nil { if err != nil {
return fmt.Errorf("Invalid token: %w", err) return nil, err
} }
// We want atleast one audTag to match // We want at least one audTag to match
for _, jwtAudTag := range token.Audience { for _, jwtAudTag := range token.Audience {
for _, acceptedAudTag := range v.audTags { for _, acceptedAudTag := range v.audTags {
if acceptedAudTag == jwtAudTag { if acceptedAudTag == jwtAudTag {
return nil return &HandleResult{ShouldFilterRequest: false}, nil
} }
} }
} }
return fmt.Errorf("Invalid token: %w", err) return &HandleResult{
ShouldFilterRequest: true,
StatusCode: http.StatusForbidden,
Reason: fmt.Sprintf("Invalid token in jwt: %v", token.Audience),
}, nil
} }

View File

@ -1,6 +0,0 @@
package middleware
import "testing"
func TestJWTValidatorHandle(t *testing.T) {
}

View File

@ -5,6 +5,15 @@ import (
"net/http" "net/http"
) )
type Handler interface { type HandleResult struct {
Handle(ctx context.Context, r *http.Request) error // Tells that the request didn't passed the handler and should be filtered
ShouldFilterRequest bool
// The status code to return in case ShouldFilterRequest is true.
StatusCode int
Reason string
}
type Handler interface {
Name() string
Handle(ctx context.Context, r *http.Request) (result *HandleResult, err error)
} }

View File

@ -1,10 +0,0 @@
package middleware
import (
"context"
"net/http"
)
type Handler interface {
Handle(ctx context.Context, r *http.Request) error
}

View File

@ -60,6 +60,21 @@ func NewOriginProxy(
return proxy return proxy
} }
func (p *Proxy) applyIngressMiddleware(rule *ingress.Rule, r *http.Request, w connection.ResponseWriter) (error, bool) {
for _, handler := range rule.Handlers {
result, err := handler.Handle(r.Context(), r)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error while processing middleware handler %s", handler.Name())), false
}
if result.ShouldFilterRequest {
w.WriteRespHeaders(result.StatusCode, nil)
return fmt.Errorf("request filtered by middleware handler (%s) due to: %s", handler.Name(), result.Reason), true
}
}
return nil, true
}
// ProxyHTTP further depends on ingress rules to establish a connection with the origin service. This may be // ProxyHTTP further depends on ingress rules to establish a connection with the origin service. This may be
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup. // a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
func (p *Proxy) ProxyHTTP( func (p *Proxy) ProxyHTTP(
@ -86,6 +101,13 @@ func (p *Proxy) ProxyHTTP(
p.logRequest(req, logFields) p.logRequest(req, logFields)
ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum)) ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum))
ruleSpan.End() ruleSpan.End()
if err, applied := p.applyIngressMiddleware(rule, req, w); err != nil {
if applied {
p.log.Error().Msg(err.Error())
return nil
}
return err
}
switch originProxy := rule.Service.(type) { switch originProxy := rule.Service.(type) {
case ingress.HTTPOriginProxy: case ingress.HTTPOriginProxy: