Skip to content

Commit 462d2f8

Browse files
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.
1 parent 0aa21f3 commit 462d2f8

File tree

5 files changed

+48
-13
lines changed

5 files changed

+48
-13
lines changed

config/configuration.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,14 @@ type OriginRequestConfig struct {
234234
}
235235

236236
type AccessConfig struct {
237-
// Enabled when set to true will fail every request that does not arrive through an access authenticated endpoint.
238-
Enabled bool
237+
// Required when set to true will fail every request that does not arrive through an access authenticated endpoint.
238+
Required bool `yaml:"required" json:"required,omitempty"`
239239

240240
// TeamName is the organization team name to get the public key certificates for.
241-
TeamName string `yaml:"teamName" json:"teamName,omitempty"`
241+
TeamName string `yaml:"teamName" json:"teamName"`
242242

243243
// AudTag is the AudTag to verify access JWT against.
244-
AudTag []string `yaml:"audTag" json:"audTag,omitempty"`
244+
AudTag []string `yaml:"audTag" json:"audTag"`
245245
}
246246

247247
type IngressIPRule struct {

ingress/ingress.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/urfave/cli/v2"
1414

1515
"github.com/cloudflare/cloudflared/config"
16+
"github.com/cloudflare/cloudflared/ingress/middleware"
1617
"github.com/cloudflare/cloudflared/ipaccess"
1718
)
1819

@@ -168,6 +169,28 @@ func (ing Ingress) CatchAll() *Rule {
168169
return &ing.Rules[len(ing.Rules)-1]
169170
}
170171

172+
func validateAccessConfiguration(cfg *config.AccessConfig) error {
173+
if !cfg.Required {
174+
return nil
175+
}
176+
177+
// It is possible to set `required:true` and not have these two configured yet.
178+
// But if one of them is configured, we'd validate for correctness.
179+
if len(cfg.AudTag) == 0 && cfg.TeamName == "" {
180+
return nil
181+
}
182+
183+
if len(cfg.AudTag) == 0 {
184+
return errors.New("access audtag cannot be empty")
185+
}
186+
187+
if cfg.TeamName == "" {
188+
return errors.New("access.TeamName cannot be blank")
189+
}
190+
191+
return nil
192+
}
193+
171194
func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginRequestConfig) (Ingress, error) {
172195
rules := make([]Rule, len(ingress))
173196
for i, r := range ingress {
@@ -237,6 +260,17 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
237260
}
238261
}
239262

263+
var handlers []middleware.Handler
264+
if access := r.OriginRequest.Access; access != nil {
265+
if err := validateAccessConfiguration(access); err != nil {
266+
return Ingress{}, err
267+
}
268+
if access.Required {
269+
verifier := middleware.NewJWTValidator(access.TeamName, "", access.AudTag)
270+
handlers = append(handlers, verifier)
271+
}
272+
}
273+
240274
if err := validateHostname(r, i, len(ingress)); err != nil {
241275
return Ingress{}, err
242276
}
@@ -255,6 +289,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
255289
Hostname: r.Hostname,
256290
Service: service,
257291
Path: pathRegexp,
292+
Handlers: handlers,
258293
Config: cfg,
259294
}
260295
}

ingress/middleware/jwtvalidator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ func NewJWTValidator(teamName string, certsURL string, audTags []string) *JWTVal
4242
}
4343
}
4444

45-
func (v *JWTValidator) Handle(ctx context.Context, headers http.Header) error {
46-
accessJWT := headers.Get(headerKeyAccessJWTAssertion)
45+
func (v *JWTValidator) Handle(ctx context.Context, r *http.Request) error {
46+
accessJWT := r.Header.Get(headerKeyAccessJWTAssertion)
4747
if accessJWT == "" {
4848
return ErrNoAccessToken
4949
}

ingress/rule.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"encoding/json"
55
"regexp"
66
"strings"
7+
8+
"github.com/cloudflare/cloudflared/ingress/middleware"
79
)
810

911
// Rule routes traffic from a hostname/path on the public internet to the
@@ -21,9 +23,7 @@ type Rule struct {
2123
Service OriginService `json:"service"`
2224

2325
// Handlers is a list of functions that acts as a middleware during ProxyHTTP
24-
// TODO TUN-6774: Uncomment when we parse ingress to this. This serves as a demonstration on how
25-
// we want to plug in Verifiers.
26-
// Handlers []middleware.Handler
26+
Handlers []middleware.Handler
2727

2828
// Configure the request cloudflared sends to this specific origin.
2929
Config OriginRequestConfig `json:"originRequest"`

ingress/rule_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,25 @@ func TestMarshalJSON(t *testing.T) {
182182
{
183183
name: "Nil",
184184
path: nil,
185-
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}}}`,
185+
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}}}`,
186186
want: true,
187187
},
188188
{
189189
name: "Nil regex",
190190
path: &Regexp{Regexp: nil},
191-
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}}}`,
191+
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}}}`,
192192
want: true,
193193
},
194194
{
195195
name: "Empty",
196196
path: &Regexp{Regexp: regexp.MustCompile("")},
197-
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}}}`,
197+
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}}}`,
198198
want: true,
199199
},
200200
{
201201
name: "Basic",
202202
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
203-
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}}}`,
203+
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}}}`,
204204
want: true,
205205
},
206206
}

0 commit comments

Comments
 (0)