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:
parent
0aa21f302e
commit
462d2f87df
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue