/
auth_token.go
215 lines (180 loc) · 5.4 KB
/
auth_token.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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package auth
import (
"net/http"
"regexp"
"strings"
"time"
jcrypto "github.com/SermoDigital/jose/crypto"
"github.com/SermoDigital/jose/jws"
"github.com/codedellemc/rexray/libstorage/api/context"
"github.com/codedellemc/rexray/libstorage/api/types"
)
var rxBearer = regexp.MustCompile(`Bearer (.+)`)
// GetBearerTokenFromReq retrieves the bearer token from the HTTP request.
func GetBearerTokenFromReq(ctx types.Context, req *http.Request) string {
m := rxBearer.FindStringSubmatch(
req.Header.Get(types.AuthorizationHeader))
if len(m) == 0 {
return ""
}
return m[1]
}
// ValidateAuthTokenWithCtx validates the auth token from the provided context.
func ValidateAuthTokenWithCtx(
ctx types.Context,
config *types.AuthConfig) (*types.AuthToken, error) {
if tok, ok := context.AuthToken(ctx); ok {
logFields := map[string]interface{}{"sub": tok.Subject}
err := validateAuthTokenAllowed(ctx, config, logFields, tok)
if err != nil {
return nil, err
}
ctx.WithFields(logFields).Info("validated security token")
return tok, nil
}
return nil, nil
}
// ValidateAuthTokenWithCtxOrReq validates the auth token from the provided
// context. If not present in the context attempt a validation against the
// provided HTTP request.
func ValidateAuthTokenWithCtxOrReq(
ctx types.Context,
config *types.AuthConfig,
req *http.Request) (*types.AuthToken, error) {
tok, err := ValidateAuthTokenWithCtx(ctx, config)
if err != nil {
return nil, err
}
if tok != nil {
return tok, nil
}
return ValidateAuthTokenWithReq(ctx, config, req)
}
var jwtRX = regexp.MustCompile(`^(?:(.+):)?(.+\..+\..+)$`)
func getSubject(s string) string {
m := jwtRX.FindStringSubmatch(s)
if len(m) == 0 {
return s
}
jwt, err := jws.ParseJWT([]byte(m[2]))
if err != nil {
return s
}
if v, ok := jwt.Claims().Subject(); ok {
return v
}
return s
}
func validateAuthTokenAllowed(
ctx types.Context,
config *types.AuthConfig,
logFields map[string]interface{},
tok *types.AuthToken) error {
for _, v := range config.Deny {
if strings.EqualFold(getSubject(v), tok.Subject) {
ctx.WithFields(logFields).Error("access denied")
return &types.ErrSecTokInvalid{Denied: true}
}
}
for _, v := range config.Allow {
if strings.EqualFold(getSubject(v), tok.Subject) {
return nil
}
}
ctx.WithFields(logFields).Error("access not granted")
return &types.ErrSecTokInvalid{Denied: true}
}
// ValidateAuthTokenWithReq validates the auth token from the provided HTTP req.
func ValidateAuthTokenWithReq(
ctx types.Context,
config *types.AuthConfig,
req *http.Request) (*types.AuthToken, error) {
return ValidateAuthTokenWithJWT(
ctx, config, GetBearerTokenFromReq(ctx, req))
}
// ValidateAuthTokenWithJWT validates the auth token from the provided JWT.
func ValidateAuthTokenWithJWT(
ctx types.Context,
config *types.AuthConfig,
encJWT string) (*types.AuthToken, error) {
lf := map[string]interface{}{"encJWT": encJWT}
ctx.WithFields(lf).Debug("validating jwt")
jwt, err := jws.ParseJWT([]byte(encJWT))
if err != nil {
ctx.WithFields(lf).WithError(err).Error("error parsing jwt")
return nil, &types.ErrSecTokInvalid{InvalidToken: true, InnerError: err}
}
sm := parseSigningMethod(config.Alg)
lf["signingMethod"] = sm.Alg()
ctx.WithFields(lf).Debug("parsed jwt signing method")
if err := jwt.Validate(config.Key, sm); err != nil {
ctx.WithFields(lf).WithError(err).Error("error validating jwt")
return nil, &types.ErrSecTokInvalid{InvalidSig: true, InnerError: err}
}
ctx.WithFields(lf).Debug("validated jwt signature")
if len(jwt.Claims()) == 0 {
ctx.WithFields(lf).Error("jwt missing claims")
return nil, &types.ErrSecTokInvalid{}
}
var (
sub string
iat time.Time
exp time.Time
nbf time.Time
ok bool
)
if sub, ok = jwt.Claims().Subject(); !ok {
ctx.WithFields(lf).Error("jwt missing sub claim")
return nil, &types.ErrSecTokInvalid{MissingClaim: "sub"}
}
if iat, ok = jwt.Claims().IssuedAt(); !ok {
ctx.WithFields(lf).Error("jwt missing iat claim")
return nil, &types.ErrSecTokInvalid{MissingClaim: "iat"}
}
if exp, ok = jwt.Claims().Expiration(); !ok {
ctx.WithFields(lf).Error("jwt missing exp claim")
return nil, &types.ErrSecTokInvalid{MissingClaim: "exp"}
}
if nbf, ok = jwt.Claims().NotBefore(); !ok {
ctx.WithFields(lf).Error("jwt missing nbf claim")
return nil, &types.ErrSecTokInvalid{MissingClaim: "nbf"}
}
tok := &types.AuthToken{
Subject: sub,
IssuedAt: iat.UTC().Unix(),
Expires: exp.UTC().Unix(),
NotBefore: nbf.UTC().Unix(),
}
lf["sub"] = tok.Subject
lf["iat"] = tok.IssuedAt
lf["exp"] = tok.Expires
lf["nbf"] = tok.NotBefore
if err := validateAuthTokenAllowed(ctx, config, lf, tok); err != nil {
return nil, err
}
ctx.WithFields(lf).Info("validated security token")
return tok, nil
}
var signingMethods = []jcrypto.SigningMethod{
jcrypto.SigningMethodES256,
jcrypto.SigningMethodES384,
jcrypto.SigningMethodES512,
jcrypto.SigningMethodHS256,
jcrypto.SigningMethodHS384,
jcrypto.SigningMethodHS512,
jcrypto.SigningMethodPS256,
jcrypto.SigningMethodPS384,
jcrypto.SigningMethodPS512,
jcrypto.SigningMethodRS256,
jcrypto.SigningMethodRS384,
jcrypto.SigningMethodRS512,
}
// parseSigningMethod parses the signing method from the provided method name.
func parseSigningMethod(methodName string) jcrypto.SigningMethod {
for _, v := range signingMethods {
if strings.EqualFold(v.Alg(), methodName) {
return v
}
}
return jcrypto.SigningMethodHS256
}