-
Notifications
You must be signed in to change notification settings - Fork 0
/
checker.go
121 lines (108 loc) · 3.21 KB
/
checker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package main
import (
"context"
"fmt"
"net/http"
"github.com/coreos/go-oidc"
"go.uber.org/zap"
)
type checker interface {
Check(ctx context.Context, req *Request) (*Response, error)
}
type cloudflareAuthChecker struct {
verifier *oidc.IDTokenVerifier
logger *zap.Logger
// The "Auth Domain" unique to your Cloudflare Access account.
authDomain string
// A list of allowed "Cloudflare Access - Application Audience (AUD)" tags.
allowedApplicationAudiences []string
}
func NewCloudflareAuthChecker(ctx context.Context, authDomain string, allowedApplicationAudiences []string, logger *zap.Logger) *cloudflareAuthChecker {
certsURL := fmt.Sprintf("%s/cdn-cgi/access/certs", authDomain)
config := &oidc.Config{
// We are checking it manually so that we can support multiple client IDs.
SkipClientIDCheck: true,
}
keySet := oidc.NewRemoteKeySet(ctx, certsURL)
verifier := oidc.NewVerifier(authDomain, keySet, config)
return &cloudflareAuthChecker{
verifier: verifier,
logger: logger,
authDomain: authDomain,
allowedApplicationAudiences: allowedApplicationAudiences,
}
}
// Based on https://developers.cloudflare.com/access/advanced-management/validating-json
func (c *cloudflareAuthChecker) Check(ctx context.Context, req *Request) (*Response, error) {
c.logger.Info("Handling request", zap.String("url", req.Request.URL.String()))
accessJWT := req.Request.Header.Get("Cf-Access-Jwt-Assertion")
if accessJWT == "" {
c.logger.Debug(
"No Cloudflare Access header found",
zap.String("authDomain", c.authDomain),
)
return &Response{
Allow: false,
Response: http.Response{
StatusCode: http.StatusUnauthorized,
},
}, nil
}
if len(c.allowedApplicationAudiences) == 0 {
c.logger.Warn(
"No allowed application audiences set, denying all requests",
zap.String("authDomain", c.authDomain),
)
return &Response{
Allow: false,
Response: http.Response{
StatusCode: http.StatusUnauthorized,
},
}, nil
}
c.logger.Debug(
"Verifying token",
zap.String("authDomain", c.authDomain),
zap.Strings("allowedApplicationAudiences", c.allowedApplicationAudiences),
zap.Bool("hasAccessJWT", len(accessJWT) > 0),
)
idToken, err := c.verifier.Verify(ctx, accessJWT)
if err != nil {
c.logger.Debug(
"Failed to verify token",
zap.String("authDomain", c.authDomain),
zap.Strings("allowedApplicationAudiences", c.allowedApplicationAudiences),
zap.Error(err),
)
return &Response{
Allow: false,
Response: http.Response{
StatusCode: http.StatusUnauthorized,
},
}, nil
}
for _, allowedApplicationAudience := range c.allowedApplicationAudiences {
for _, audience := range idToken.Audience {
if allowedApplicationAudience == audience {
return &Response{
Allow: true,
Response: http.Response{
StatusCode: http.StatusOK,
},
}, nil
}
}
}
c.logger.Warn(
"Token's audience(s) is not in allowed list",
zap.String("authDomain", c.authDomain),
zap.Strings("allowedApplicationAudiences", c.allowedApplicationAudiences),
zap.Strings("tokenAudiences", idToken.Audience),
)
return &Response{
Allow: false,
Response: http.Response{
StatusCode: http.StatusUnauthorized,
},
}, nil
}