-
Notifications
You must be signed in to change notification settings - Fork 8
/
jwt.go
167 lines (143 loc) · 5.44 KB
/
jwt.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// SPDX-FileCopyrightText: 2022 Marshall Wace <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only
package main
import (
"crypto/rsa"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
log "github.com/sirupsen/logrus"
)
var (
jwtRsaPubKey *rsa.PublicKey
jwtIssuerFlag string
jwtAudienceFlag string
)
type JwtClaims struct {
Action string `json:"action"`
Bucket string `json:"bucket"`
Prefix string `json:"prefix"`
jwt.StandardClaims
}
type AuthHeader struct {
Authorization string `header:"Authorization"`
}
func getRequestParam(c *gin.Context, paramKey string) string {
if c.Param(paramKey) != "" {
return c.Param(paramKey)
} else if c.Query(paramKey) != "" {
return c.Query(paramKey)
} else {
return ""
}
}
func jwtMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if strings.HasPrefix(c.Request.RequestURI, "/healthz") {
return
}
h := AuthHeader{}
if err := c.ShouldBindHeader(&h); err != nil || h.Authorization == "" {
log.Debugf("No Authorization header but JWT checking is enabled, returning 401")
jwtRequestsMetric.WithLabelValues("false", "No Authorization header").Inc()
c.JSON(401, gin.H{"error": "Missing authorization header"})
c.Abort()
return
}
jwtToken := strings.TrimSpace(strings.Replace(h.Authorization, "Bearer", "", 1))
token, err := jwt.ParseWithClaims(jwtToken, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtRsaPubKey, nil
})
if err != nil {
log.Debugf("JWT token couldn't be parsed: %v", err)
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
jwtRequestsMetric.WithLabelValues("false", "JWT expired").Inc()
c.JSON(401, gin.H{"error": "JWT token expired"})
c.Abort()
return
} else if ve.Errors&(jwt.ValidationErrorMalformed) != 0 {
jwtRequestsMetric.WithLabelValues("false", "JWT malformed").Inc()
c.JSON(401, gin.H{"error": "JWT token malformed"})
c.Abort()
return
} else if ve.Errors&(jwt.ValidationErrorUnverifiable|jwt.ValidationErrorSignatureInvalid) != 0 {
jwtRequestsMetric.WithLabelValues("false", "JWT signature incorrect").Inc()
c.JSON(401, gin.H{"error": "JWT token signature incorrect"})
c.Abort()
return
}
}
}
if token == nil {
log.Debugf("JWT token failed to be parsed with given claims")
jwtRequestsMetric.WithLabelValues("false", "JWT claims invalid").Inc()
c.JSON(401, gin.H{"error": "JWT token invalid"})
c.Abort()
return
}
if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
if jwtIssuerFlag != "" && strings.TrimSpace(claims.StandardClaims.Issuer) != jwtIssuerFlag {
log.Debugf("JWT token issuer claim doesn't match provided -jwt-issuer value")
jwtRequestsMetric.WithLabelValues("false", "JWT token issuer claim does not match").Inc()
c.JSON(401, gin.H{"error": "JWT token issuer is not valid"})
c.Abort()
return
}
if jwtAudienceFlag != "" && strings.TrimSpace(claims.StandardClaims.Audience) != jwtAudienceFlag {
log.Debugf("JWT token audience claim doesn't match provided -jwt-audience value")
jwtRequestsMetric.WithLabelValues("false", "JWT token audience claim does not match").Inc()
c.JSON(401, gin.H{"error": "JWT token audience is not valid"})
c.Abort()
return
}
if !validActionForHttpMethod(claims.Action, c.Request.Method) {
log.Debugf("Got valid JWT token, but action allow doesn't match request (action %s != method %s)", claims.Action, c.Request.Method)
jwtRequestsMetric.WithLabelValues("false", "JWT action does not match method").Inc()
c.JSON(401, gin.H{"error": "JWT token action allow doesn't match request method"})
c.Abort()
return
}
keyParam := getRequestParam(c, "key")
bucketParam := getRequestParam(c, "bucket")
if keyParam == "" {
keyParam = getRequestParam(c, "prefix")
}
if keyParam == "" {
keyParam = getRequestParam(c, "path")
}
// Compare request bucket and key params with JWT bucket and key params
if keyParam != "" && strings.TrimSpace(claims.Prefix) != "" && !strings.HasPrefix(keyParam, strings.TrimSpace(claims.Prefix)) {
log.Debugf("JWT token prefix does not match URL object (prefix %s != object %s)", claims.Prefix, strings.TrimSpace(keyParam))
jwtRequestsMetric.WithLabelValues("false", "JWT token prefix does not match URL object").Inc()
c.JSON(401, gin.H{"error": "JWT token prefix does not match URL object"})
c.Abort()
return
}
if bucketParam != "" && strings.TrimSpace(claims.Bucket) != "" && strings.TrimSpace(claims.Bucket) != bucketParam {
log.Debugf("JWT token bucket does not match URL bucket (jwt bucket %s != URL bucket %s", claims.Bucket, strings.TrimSpace(bucketParam))
jwtRequestsMetric.WithLabelValues("false", "JWT token bucket does not match URL bucket").Inc()
c.JSON(401, gin.H{"error": "JWT token bucket does not match URL bucket"})
c.Abort()
return
}
log.Debugf("Got valid JWT token, exiting JWT middleware")
jwtRequestsMetric.WithLabelValues("true", "").Inc()
} else {
jwtRequestsMetric.WithLabelValues("false", "JWT claims invalid").Inc()
c.JSON(401, gin.H{"error": "JWT token invalid"})
c.Abort()
}
}
}
func validActionForHttpMethod(action string, method string) bool {
switch action {
case "READ":
return (method == "GET" || method == "HEAD")
case "WRITE":
return (method == "POST" || method == "PUT")
case "DELETE":
return method == "DELETE"
}
return false
}