diff --git a/config/configuration.go b/config/configuration.go index 94e6467d..34a35612 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -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 { diff --git a/ingress/ingress.go b/ingress/ingress.go index b15eced3..b1d87878 100644 --- a/ingress/ingress.go +++ b/ingress/ingress.go @@ -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, } } diff --git a/ingress/middleware/jwtvalidator.go b/ingress/middleware/jwtvalidator.go index 8bdab7e1..667bc1ce 100644 --- a/ingress/middleware/jwtvalidator.go +++ b/ingress/middleware/jwtvalidator.go @@ -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 } diff --git a/ingress/rule.go b/ingress/rule.go index b3dd0001..c7733254 100644 --- a/ingress/rule.go +++ b/ingress/rule.go @@ -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"` diff --git a/ingress/rule_test.go b/ingress/rule_test.go index d399b1be..8ab86132 100644 --- a/ingress/rule_test.go +++ b/ingress/rule_test.go @@ -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, }, }