TUN-6774: Validate OriginRequest.Access to add Ingress.Middleware

We take advantage of the JWTValidator middleware and attach it to an
ingress rule based on Access configurations. We attach the Validator
directly to the ingress rules because we want to take advantage of
caching and token revert/handling that comes with go-oidc.
This commit is contained in:
Sudarsan Reddy 2022-09-22 14:04:47 +01:00
parent 0aa21f302e
commit 462d2f87df
5 changed files with 48 additions and 13 deletions

View File

@ -234,14 +234,14 @@ type OriginRequestConfig struct {
}
type AccessConfig struct {
// Enabled when set to true will fail every request that does not arrive through an access authenticated endpoint.
Enabled bool
// Required when set to true will fail every request that does not arrive through an access authenticated endpoint.
Required bool `yaml:"required" json:"required,omitempty"`
// TeamName is the organization team name to get the public key certificates for.
TeamName string `yaml:"teamName" json:"teamName,omitempty"`
TeamName string `yaml:"teamName" json:"teamName"`
// AudTag is the AudTag to verify access JWT against.
AudTag []string `yaml:"audTag" json:"audTag,omitempty"`
AudTag []string `yaml:"audTag" json:"audTag"`
}
type IngressIPRule struct {

View File

@ -13,6 +13,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress/middleware"
"github.com/cloudflare/cloudflared/ipaccess"
)
@ -168,6 +169,28 @@ func (ing Ingress) CatchAll() *Rule {
return &ing.Rules[len(ing.Rules)-1]
}
func validateAccessConfiguration(cfg *config.AccessConfig) error {
if !cfg.Required {
return nil
}
// It is possible to set `required:true` and not have these two configured yet.
// But if one of them is configured, we'd validate for correctness.
if len(cfg.AudTag) == 0 && cfg.TeamName == "" {
return nil
}
if len(cfg.AudTag) == 0 {
return errors.New("access audtag cannot be empty")
}
if cfg.TeamName == "" {
return errors.New("access.TeamName cannot be blank")
}
return nil
}
func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) {
rules := make([]Rule, len(ingress))
for i, r := range ingress {
@ -237,6 +260,17 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}
}
var handlers []middleware.Handler
if access := r.OriginRequest.Access; access != nil {
if err := validateAccessConfiguration(access); err != nil {
return Ingress{}, err
}
if access.Required {
verifier := middleware.NewJWTValidator(access.TeamName, "", access.AudTag)
handlers = append(handlers, verifier)
}
}
if err := validateHostname(r, i, len(ingress)); err != nil {
return Ingress{}, err
}
@ -255,6 +289,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
Hostname: r.Hostname,
Service: service,
Path: pathRegexp,
Handlers: handlers,
Config: cfg,
}
}

View File

@ -42,8 +42,8 @@ func NewJWTValidator(teamName string, certsURL string, audTags []string) *JWTVal
}
}
func (v *JWTValidator) Handle(ctx context.Context, headers http.Header) error {
accessJWT := headers.Get(headerKeyAccessJWTAssertion)
func (v *JWTValidator) Handle(ctx context.Context, r *http.Request) error {
accessJWT := r.Header.Get(headerKeyAccessJWTAssertion)
if accessJWT == "" {
return ErrNoAccessToken
}

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"regexp"
"strings"
"github.com/cloudflare/cloudflared/ingress/middleware"
)
// Rule routes traffic from a hostname/path on the public internet to the
@ -21,9 +23,7 @@ type Rule struct {
Service OriginService `json:"service"`
// Handlers is a list of functions that acts as a middleware during ProxyHTTP
// TODO TUN-6774: Uncomment when we parse ingress to this. This serves as a demonstration on how
// we want to plug in Verifiers.
// Handlers []middleware.Handler
Handlers []middleware.Handler
// Configure the request cloudflared sends to this specific origin.
Config OriginRequestConfig `json:"originRequest"`

View File

@ -182,25 +182,25 @@ func TestMarshalJSON(t *testing.T) {
{
name: "Nil",
path: nil,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"Enabled":false}}}`,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Nil regex",
path: &Regexp{Regexp: nil},
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"Enabled":false}}}`,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Empty",
path: &Regexp{Regexp: regexp.MustCompile("")},
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"Enabled":false}}}`,
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
{
name: "Basic",
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"Enabled":false}}}`,
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true,
},
}