jwt: Simplify JWT parsing (#8802)

JWT parsing is simplified by using a custom claim
data structure such as MapClaims{}, also writes
a custom Unmarshaller for faster unmarshalling.

- Avoid as much reflections as possible
- Provide the right types for functions as much
  as possible
- Avoid strings.Join, strings.Split to reduce
  allocations, rely on indexes directly.
This commit is contained in:
Harshavardhana 2020-01-31 08:29:22 +05:30 committed by GitHub
parent 9990464cd5
commit bfe8a9bccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 799 additions and 252 deletions

View File

@ -28,8 +28,8 @@ import (
"net/http"
"strings"
jwtgo "github.com/dgrijalva/jwt-go"
xhttp "github.com/minio/minio/cmd/http"
xjwt "github.com/minio/minio/cmd/jwt"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bucket/policy"
@ -179,12 +179,14 @@ func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
// Fetch claims in the security token returned by the client.
func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
claims := make(map[string]interface{})
claims := xjwt.NewMapClaims()
token := getSessionToken(r)
if token == "" {
return claims, nil
return claims.Map(), nil
}
stsTokenCallback := func(jwtToken *jwtgo.Token) (interface{}, error) {
stsTokenCallback := func(claims *xjwt.MapClaims) ([]byte, error) {
// JWT token for x-amz-security-token is signed with admin
// secret key, temporary credentials become invalid if
// server admin credentials change. This is done to ensure
@ -195,66 +197,42 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
// on the client side and is treated like an opaque value.
return []byte(globalActiveCred.SecretKey), nil
}
p := &jwtgo.Parser{
ValidMethods: []string{
jwtgo.SigningMethodHS256.Alg(),
jwtgo.SigningMethodHS512.Alg(),
},
}
jtoken, err := p.ParseWithClaims(token, jwtgo.MapClaims(claims), stsTokenCallback)
if err != nil {
return nil, err
}
if !jtoken.Valid {
if err := xjwt.ParseWithClaims(token, claims, stsTokenCallback); err != nil {
return nil, errAuthentication
}
v, ok := claims["accessKey"]
if !ok {
return nil, errInvalidAccessKeyID
}
if _, ok = v.(string); !ok {
return nil, errInvalidAccessKeyID
}
if globalPolicyOPA == nil {
// If OPA is not set and if ldap claim key is set,
// allow the claim.
if _, ok := claims[ldapUser]; ok {
return claims, nil
// If OPA is not set and if ldap claim key is set, allow the claim.
if _, ok := claims.Lookup(ldapUser); ok {
return claims.Map(), nil
}
// If OPA is not set, session token should
// have a policy and its mandatory, reject
// requests without policy claim.
p, pok := claims[iamPolicyClaimName()]
_, pok := claims.Lookup(iamPolicyClaimName())
if !pok {
return nil, errAuthentication
}
if _, pok = p.(string); !pok {
return nil, errAuthentication
}
sp, spok := claims[iampolicy.SessionPolicyName]
// Sub policy is optional, if not set return success.
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
if !spok {
return claims, nil
}
// Sub policy is set but its not a string, reject such requests
spStr, spok := sp.(string)
if !spok {
return nil, errAuthentication
return claims.Map(), nil
}
// Looks like subpolicy is set and is a string, if set then its
// base64 encoded, decode it. Decoding fails reject such requests.
spBytes, err := base64.StdEncoding.DecodeString(spStr)
spBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil {
// Base64 decoding fails, we should log to indicate
// something is malforming the request sent by client.
logger.LogIf(context.Background(), err, logger.Application)
return nil, errAuthentication
}
claims[iampolicy.SessionPolicyName] = string(spBytes)
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
}
return claims, nil
return claims.Map(), nil
}
// Fetch claims in the security token returned by the client and validate the token.

View File

@ -201,7 +201,9 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) {
if owner {
return globalActiveCred
}
cred, _ = globalIAMSys.GetUser(claims.AccessKey())
if claims != nil {
cred, _ = globalIAMSys.GetUser(claims.AccessKey)
}
}
return cred
}

View File

@ -1,5 +1,5 @@
/*
* MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc.
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,12 +19,12 @@ package cmd
import (
"context"
"errors"
"fmt"
"net/http"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
jwtreq "github.com/dgrijalva/jwt-go/request"
xjwt "github.com/minio/minio/cmd/jwt"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
)
@ -35,8 +35,8 @@ const (
// Default JWT token for web handlers is one day.
defaultJWTExpiry = 24 * time.Hour
// Inter-node JWT token expiry is 100 years approx.
defaultInterNodeJWTExpiry = 100 * 365 * 24 * time.Hour
// Inter-node JWT token expiry is 15 minutes.
defaultInterNodeJWTExpiry = 15 * time.Minute
// URL JWT token expiry is one minute (might be exposed).
defaultURLJWTExpiry = time.Minute
@ -73,42 +73,23 @@ func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt
return "", errAuthentication
}
claims := jwtgo.MapClaims{}
claims["exp"] = expiresAt.Unix()
claims["sub"] = credentials.AccessKey
claims["accessKey"] = credentials.AccessKey
claims := xjwt.NewMapClaims()
claims.SetExpiry(expiresAt)
claims.SetAccessKey(credentials.AccessKey)
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
return jwt.SignedString([]byte(serverCred.SecretKey))
}
func authenticateJWTAdmin(accessKey, secretKey string, expiry time.Duration) (string, error) {
passedCredential, err := auth.CreateCredentials(accessKey, secretKey)
if err != nil {
return "", err
}
serverCred := globalActiveCred
if serverCred.AccessKey != passedCredential.AccessKey {
return "", errInvalidAccessKeyID
}
if !serverCred.Equal(passedCredential) {
return "", errAuthentication
}
claims := jwtgo.MapClaims{}
claims["exp"] = UTCNow().Add(expiry).Unix()
claims["sub"] = passedCredential.AccessKey
claims["accessKey"] = passedCredential.AccessKey
func authenticateNode(accessKey, secretKey, audience string) (string, error) {
claims := xjwt.NewStandardClaims()
claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry))
claims.SetAccessKey(accessKey)
claims.SetAudience(audience)
claims.SetIssuer(ReleaseTag)
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
return jwt.SignedString([]byte(serverCred.SecretKey))
}
func authenticateNode(accessKey, secretKey string) (string, error) {
return authenticateJWTAdmin(accessKey, secretKey, defaultInterNodeJWTExpiry)
return jwt.SignedString([]byte(secretKey))
}
func authenticateWeb(accessKey, secretKey string) (string, error) {
@ -120,63 +101,29 @@ func authenticateURL(accessKey, secretKey string) (string, error) {
}
// Callback function used for parsing
func webTokenCallback(jwtToken *jwtgo.Token) (interface{}, error) {
if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
}
if err := jwtToken.Claims.Valid(); err != nil {
return nil, errAuthentication
}
if claimsPtr, ok := jwtToken.Claims.(*jwtgo.MapClaims); ok {
claims := *claimsPtr
accessKey, ok := claims["accessKey"].(string)
if !ok {
accessKey, ok = claims["sub"].(string)
if !ok {
return nil, errInvalidAccessKeyID
}
}
if accessKey == globalActiveCred.AccessKey {
func webTokenCallback(claims *xjwt.MapClaims) ([]byte, error) {
if claims.AccessKey == globalActiveCred.AccessKey {
return []byte(globalActiveCred.SecretKey), nil
}
if globalIAMSys == nil {
return nil, errInvalidAccessKeyID
}
if _, ok = claims["aud"].(string); !ok {
cred, ok := globalIAMSys.GetUser(accessKey)
ok, err := globalIAMSys.IsTempUser(claims.AccessKey)
if err != nil {
if err == errNoSuchUser {
return nil, errInvalidAccessKeyID
}
return nil, err
}
if ok {
return []byte(globalActiveCred.SecretKey), nil
}
cred, ok := globalIAMSys.GetUser(claims.AccessKey)
if !ok {
return nil, errInvalidAccessKeyID
}
return []byte(cred.SecretKey), nil
}
return []byte(globalActiveCred.SecretKey), nil
}
return nil, errAuthentication
}
func parseJWTWithClaims(tokenString string, claims jwtgo.Claims) (*jwtgo.Token, error) {
p := &jwtgo.Parser{
SkipClaimsValidation: true,
ValidMethods: []string{
jwtgo.SigningMethodHS256.Alg(),
jwtgo.SigningMethodHS512.Alg(),
},
}
jwtToken, err := p.ParseWithClaims(tokenString, claims, webTokenCallback)
if err != nil {
switch e := err.(type) {
case *jwtgo.ValidationError:
if e.Inner == nil {
return nil, errAuthentication
}
return nil, e.Inner
}
return nil, errAuthentication
}
return jwtToken, nil
}
func isAuthTokenValid(token string) bool {
@ -184,78 +131,40 @@ func isAuthTokenValid(token string) bool {
return err == nil
}
func webTokenAuthenticate(token string) (mapClaims, bool, error) {
var claims = jwtgo.MapClaims{}
func webTokenAuthenticate(token string) (*xjwt.MapClaims, bool, error) {
if token == "" {
return mapClaims{claims}, false, errNoAuthToken
return nil, false, errNoAuthToken
}
jwtToken, err := parseJWTWithClaims(token, &claims)
if err != nil {
return mapClaims{claims}, false, err
claims := xjwt.NewMapClaims()
if err := xjwt.ParseWithClaims(token, claims, webTokenCallback); err != nil {
return claims, false, errAuthentication
}
if !jwtToken.Valid {
return mapClaims{claims}, false, errAuthentication
}
accessKey, ok := claims["accessKey"].(string)
if !ok {
accessKey, ok = claims["sub"].(string)
if !ok {
return mapClaims{claims}, false, errAuthentication
}
}
owner := accessKey == globalActiveCred.AccessKey
return mapClaims{claims}, owner, nil
}
type mapClaims struct {
jwtgo.MapClaims
}
func (m mapClaims) Map() map[string]interface{} {
return m.MapClaims
}
func (m mapClaims) AccessKey() string {
claimSub, ok := m.MapClaims["accessKey"].(string)
if !ok {
claimSub, _ = m.MapClaims["sub"].(string)
}
return claimSub
owner := claims.AccessKey == globalActiveCred.AccessKey
return claims, owner, nil
}
// Check if the request is authenticated.
// Returns nil if the request is authenticated. errNoAuthToken if token missing.
// Returns errAuthentication for all other errors.
func webRequestAuthenticate(req *http.Request) (mapClaims, bool, error) {
var claims = jwtgo.MapClaims{}
tokStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req)
func webRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, bool, error) {
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req)
if err != nil {
if err == jwtreq.ErrNoTokenInRequest {
return mapClaims{claims}, false, errNoAuthToken
return nil, false, errNoAuthToken
}
return mapClaims{claims}, false, err
return nil, false, err
}
jwtToken, err := parseJWTWithClaims(tokStr, &claims)
if err != nil {
return mapClaims{claims}, false, err
claims := xjwt.NewMapClaims()
if err := xjwt.ParseWithClaims(token, claims, webTokenCallback); err != nil {
return claims, false, errAuthentication
}
if !jwtToken.Valid {
return mapClaims{claims}, false, errAuthentication
}
accessKey, ok := claims["accessKey"].(string)
if !ok {
accessKey, ok = claims["sub"].(string)
if !ok {
return mapClaims{claims}, false, errAuthentication
}
}
owner := accessKey == globalActiveCred.AccessKey
return mapClaims{claims}, owner, nil
owner := claims.AccessKey == globalActiveCred.AccessKey
return claims, owner, nil
}
func newAuthToken() string {
func newAuthToken(audience string) string {
cred := globalActiveCred
token, err := authenticateNode(cred.AccessKey, cred.SecretKey)
token, err := authenticateNode(cred.AccessKey, cred.SecretKey, audience)
logger.CriticalIf(context.Background(), err)
return token
}

371
cmd/jwt/parser.go Normal file
View File

@ -0,0 +1,371 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
// This file is a re-implementation of the original code here with some
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
import (
"crypto"
"crypto/hmac"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
jsoniter "github.com/json-iterator/go"
)
// SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods
// Expects key type of []byte for both signing and validation
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256, HS384, HS512
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
)
var (
base64BufPool sync.Pool
hmacSigners []*SigningMethodHMAC
)
func init() {
base64BufPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 1024)
return &buf
},
}
hmacSigners = []*SigningMethodHMAC{
{"HS256", crypto.SHA256},
{"HS384", crypto.SHA384},
{"HS512", crypto.SHA512},
}
}
// StandardClaims are basically standard claims with "accessKey"
type StandardClaims struct {
AccessKey string `json:"accessKey,omitempty"`
jwtgo.StandardClaims
}
// MapClaims - implements custom unmarshaller
type MapClaims struct {
AccessKey string `json:"accessKey,omitempty"`
jwtgo.MapClaims
}
// NewStandardClaims - initializes standard claims
func NewStandardClaims() *StandardClaims {
return &StandardClaims{}
}
// SetIssuer sets issuer for these claims
func (c *StandardClaims) SetIssuer(issuer string) {
c.Issuer = issuer
}
// SetAudience sets audience for these claims
func (c *StandardClaims) SetAudience(aud string) {
c.Audience = aud
}
// SetExpiry sets expiry in unix epoch secs
func (c *StandardClaims) SetExpiry(t time.Time) {
c.ExpiresAt = t.Unix()
}
// SetAccessKey sets access key as jwt subject and custom
// "accessKey" field.
func (c *StandardClaims) SetAccessKey(accessKey string) {
c.Subject = accessKey
c.AccessKey = accessKey
}
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
// claims interface, additionally validates "accessKey" fields.
func (c *StandardClaims) Valid() error {
if err := c.StandardClaims.Valid(); err != nil {
return err
}
if c.AccessKey == "" && c.Subject == "" {
return jwtgo.NewValidationError("accessKey/sub missing",
jwtgo.ValidationErrorClaimsInvalid)
}
return nil
}
// NewMapClaims - Initializes a new map claims
func NewMapClaims() *MapClaims {
return &MapClaims{MapClaims: jwtgo.MapClaims{}}
}
// Lookup returns the value and if the key is found.
func (c *MapClaims) Lookup(key string) (value string, ok bool) {
var vinterface interface{}
vinterface, ok = c.MapClaims[key]
if ok {
value, ok = vinterface.(string)
}
return
}
// SetExpiry sets expiry in unix epoch secs
func (c *MapClaims) SetExpiry(t time.Time) {
c.MapClaims["exp"] = t.Unix()
}
// SetAccessKey sets access key as jwt subject and custom
// "accessKey" field.
func (c *MapClaims) SetAccessKey(accessKey string) {
c.MapClaims["sub"] = accessKey
c.MapClaims["accessKey"] = accessKey
}
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
// claims interface, additionally validates "accessKey" fields.
func (c *MapClaims) Valid() error {
if err := c.MapClaims.Valid(); err != nil {
return err
}
if c.AccessKey == "" {
return jwtgo.NewValidationError("accessKey/sub missing",
jwtgo.ValidationErrorClaimsInvalid)
}
return nil
}
// Map returns underlying low-level map claims.
func (c *MapClaims) Map() map[string]interface{} {
return c.MapClaims
}
// MarshalJSON marshals the MapClaims struct
func (c *MapClaims) MarshalJSON() ([]byte, error) {
return json.Marshal(c.MapClaims)
}
// ParseWithStandardClaims - parse the token string, valid methods.
func ParseWithStandardClaims(tokenStr string, claims *StandardClaims, key []byte) error {
// Key is not provided.
if key == nil {
// keyFunc was not provided, return error.
return jwtgo.NewValidationError("no key was provided.", jwtgo.ValidationErrorUnverifiable)
}
bufp := base64BufPool.Get().(*[]byte)
defer base64BufPool.Put(bufp)
signer, err := ParseUnverifiedStandardClaims(tokenStr, claims, *bufp)
if err != nil {
return err
}
i := strings.LastIndex(tokenStr, ".")
if i < 0 {
return jwtgo.ErrSignatureInvalid
}
n, err := base64Decode(tokenStr[i+1:], *bufp)
if err != nil {
return err
}
hasher := hmac.New(signer.Hash.New, key)
hasher.Write([]byte(tokenStr[:i]))
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
return jwtgo.ErrSignatureInvalid
}
if claims.AccessKey == "" && claims.Subject == "" {
return jwtgo.NewValidationError("accessKey/sub missing",
jwtgo.ValidationErrorClaimsInvalid)
}
// Signature is valid, lets validate the claims for
// other fields such as expiry etc.
return claims.Valid()
}
// https://tools.ietf.org/html/rfc7519#page-11
type jwtHeader struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
}
// ParseUnverifiedStandardClaims - WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func ParseUnverifiedStandardClaims(tokenString string, claims *StandardClaims, buf []byte) (*SigningMethodHMAC, error) {
if strings.Count(tokenString, ".") != 2 {
return nil, jwtgo.ErrSignatureInvalid
}
i := strings.Index(tokenString, ".")
j := strings.LastIndex(tokenString, ".")
n, err := base64Decode(tokenString[:i], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
var header = jwtHeader{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(buf[:n], &header); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
n, err = base64Decode(tokenString[i+1:j], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
if err = json.Unmarshal(buf[:n], claims); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
for _, signer := range hmacSigners {
if header.Algorithm == signer.Name {
return signer, nil
}
}
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
jwtgo.ValidationErrorUnverifiable)
}
// ParseWithClaims - parse the token string, valid methods.
func ParseWithClaims(tokenStr string, claims *MapClaims, fn func(*MapClaims) ([]byte, error)) error {
// Key lookup function has to be provided.
if fn == nil {
// keyFunc was not provided, return error.
return jwtgo.NewValidationError("no Keyfunc was provided.", jwtgo.ValidationErrorUnverifiable)
}
bufp := base64BufPool.Get().(*[]byte)
defer base64BufPool.Put(bufp)
signer, err := ParseUnverifiedMapClaims(tokenStr, claims, *bufp)
if err != nil {
return err
}
i := strings.LastIndex(tokenStr, ".")
if i < 0 {
return jwtgo.ErrSignatureInvalid
}
n, err := base64Decode(tokenStr[i+1:], *bufp)
if err != nil {
return err
}
var ok bool
claims.AccessKey, ok = claims.Lookup("accessKey")
if !ok {
claims.AccessKey, ok = claims.Lookup("sub")
if !ok {
return jwtgo.NewValidationError("accessKey/sub missing",
jwtgo.ValidationErrorClaimsInvalid)
}
}
// Lookup key from claims, claims may not be valid and may return
// invalid key which is okay as the signature verification will fail.
key, err := fn(claims)
if err != nil {
return err
}
hasher := hmac.New(signer.Hash.New, key)
hasher.Write([]byte(tokenStr[:i]))
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
return jwtgo.ErrSignatureInvalid
}
// Signature is valid, lets validate the claims for
// other fields such as expiry etc.
return claims.Valid()
}
// base64Decode returns the bytes represented by the base64 string s.
func base64Decode(s string, buf []byte) (int, error) {
return base64.RawURLEncoding.Decode(buf, []byte(s))
}
// ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func ParseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) {
if strings.Count(tokenString, ".") != 2 {
return nil, jwtgo.ErrSignatureInvalid
}
i := strings.Index(tokenString, ".")
j := strings.LastIndex(tokenString, ".")
n, err := base64Decode(tokenString[:i], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
var header = jwtHeader{}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(buf[:n], &header); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
n, err = base64Decode(tokenString[i+1:j], buf)
if err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil {
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
}
for _, signer := range hmacSigners {
if header.Algorithm == signer.Name {
return signer, nil
}
}
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
jwtgo.ValidationErrorUnverifiable)
}

214
cmd/jwt/parser_test.go Normal file
View File

@ -0,0 +1,214 @@
/*
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
// This file is a re-implementation of the original code here with some
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
import (
"fmt"
"testing"
"time"
"github.com/dgrijalva/jwt-go"
jwtgo "github.com/dgrijalva/jwt-go"
)
var (
defaultKeyFunc = func(claim *MapClaims) ([]byte, error) { return []byte("HelloSecret"), nil }
emptyKeyFunc = func(claim *MapClaims) ([]byte, error) { return nil, nil }
errorKeyFunc = func(claim *MapClaims) ([]byte, error) { return nil, fmt.Errorf("error loading key") }
)
var jwtTestData = []struct {
name string
tokenString string
keyfunc func(*MapClaims) ([]byte, error)
claims jwt.Claims
valid bool
errors int32
}{
{
"basic",
"",
defaultKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
},
},
true,
0,
},
{
"basic expired",
"", // autogen
defaultKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
"exp": float64(time.Now().Unix() - 100),
},
},
false,
-1,
},
{
"basic nbf",
"", // autogen
defaultKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
"nbf": float64(time.Now().Unix() + 100),
},
},
false,
-1,
},
{
"expired and nbf",
"", // autogen
defaultKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
"nbf": float64(time.Now().Unix() + 100),
"exp": float64(time.Now().Unix() - 100),
},
},
false,
-1,
},
{
"basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
},
},
false,
-1,
},
{
"basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nil,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
},
},
false,
-1,
},
{
"basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
},
},
false,
-1,
},
{
"basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc,
&MapClaims{
MapClaims: jwtgo.MapClaims{
"foo": "bar",
},
},
false,
-1,
},
{
"Standard Claims",
"",
defaultKeyFunc,
&StandardClaims{
StandardClaims: jwtgo.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
},
},
true,
0,
},
}
func mapClaimsToken(claims *MapClaims) string {
claims.SetAccessKey("test")
j := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
tk, _ := j.SignedString([]byte("HelloSecret"))
return tk
}
func standardClaimsToken(claims *StandardClaims) string {
claims.AccessKey = "test"
claims.Subject = "test"
j := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
tk, _ := j.SignedString([]byte("HelloSecret"))
return tk
}
func TestParserParse(t *testing.T) {
// Iterate over test data set and run tests
for _, data := range jwtTestData {
data := data
t.Run(data.name, func(t *testing.T) {
// Parse the token
var err error
// Figure out correct claims type
switch claims := data.claims.(type) {
case *MapClaims:
if data.tokenString == "" {
data.tokenString = mapClaimsToken(claims)
}
err = ParseWithClaims(data.tokenString, &MapClaims{}, data.keyfunc)
case *StandardClaims:
if data.tokenString == "" {
data.tokenString = standardClaimsToken(claims)
}
err = ParseWithStandardClaims(data.tokenString, &StandardClaims{}, []byte("HelloSecret"))
}
if data.valid && err != nil {
t.Errorf("Error while verifying token: %T:%v", err, err)
}
if !data.valid && err == nil {
t.Errorf("Invalid token passed validation")
}
if data.errors != 0 {
_, ok := err.(*jwt.ValidationError)
if !ok {
t.Errorf("Expected *jwt.ValidationError, but got %#v instead", err)
}
}
})
}
}

View File

@ -21,6 +21,7 @@ import (
"os"
"testing"
xjwt "github.com/minio/minio/cmd/jwt"
"github.com/minio/minio/pkg/auth"
)
@ -62,9 +63,7 @@ func testAuthenticate(authType string, t *testing.T) {
// Run tests.
for _, testCase := range testCases {
var err error
if authType == "node" {
_, err = authenticateNode(testCase.accessKey, testCase.secretKey)
} else if authType == "web" {
if authType == "web" {
_, err = authenticateWeb(testCase.accessKey, testCase.secretKey)
} else if authType == "url" {
_, err = authenticateURL(testCase.accessKey, testCase.secretKey)
@ -83,10 +82,6 @@ func testAuthenticate(authType string, t *testing.T) {
}
}
func TestAuthenticateNode(t *testing.T) {
testAuthenticate("node", t)
}
func TestAuthenticateWeb(t *testing.T) {
testAuthenticate("web", t)
}
@ -150,6 +145,64 @@ func TestWebRequestAuthenticate(t *testing.T) {
}
}
func BenchmarkParseJWTStandardClaims(b *testing.B) {
obj, fsDir, err := prepareFS()
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(fsDir)
if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil {
b.Fatal(err)
}
creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
err = xjwt.ParseWithStandardClaims(token, xjwt.NewStandardClaims(), []byte(creds.SecretKey))
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkParseJWTMapClaims(b *testing.B) {
obj, fsDir, err := prepareFS()
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(fsDir)
if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil {
b.Fatal(err)
}
creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
err = xjwt.ParseWithClaims(token, xjwt.NewMapClaims(), func(*xjwt.MapClaims) ([]byte, error) {
return []byte(creds.SecretKey), nil
})
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkAuthenticateNode(b *testing.B) {
obj, fsDir, err := prepareFS()
if err != nil {
@ -164,7 +217,7 @@ func BenchmarkAuthenticateNode(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
authenticateNode(creds.AccessKey, creds.SecretKey)
authenticateNode(creds.AccessKey, creds.SecretKey, "")
}
}

View File

@ -40,7 +40,7 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
},
}
creds := globalActiveCred
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
if err != nil {
t.Fatal(err)
}

View File

@ -47,7 +47,7 @@ type Client struct {
httpClient *http.Client
httpIdleConnsCloser func()
url *url.URL
newAuthToken func() string
newAuthToken func(audience string) string
}
// URL query separator constants
@ -62,7 +62,7 @@ func (c *Client) CallWithContext(ctx context.Context, method string, values url.
return nil, &NetworkError{err}
}
req = req.WithContext(ctx)
req.Header.Set("Authorization", "Bearer "+c.newAuthToken())
req.Header.Set("Authorization", "Bearer "+c.newAuthToken(req.URL.Query().Encode()))
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
if length > 0 {
req.ContentLength = length
@ -102,7 +102,7 @@ func (c *Client) Close() {
}
// NewClient - returns new REST client.
func NewClient(url *url.URL, newCustomTransport func() *http.Transport, newAuthToken func() string) (*Client, error) {
func NewClient(url *url.URL, newCustomTransport func() *http.Transport, newAuthToken func(aud string) string) (*Client, error) {
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
// except custom DialContext and TLSClientConfig.
tr := newCustomTransport()

View File

@ -30,9 +30,11 @@ import (
"strings"
"time"
jwtreq "github.com/dgrijalva/jwt-go/request"
"github.com/gorilla/mux"
"github.com/minio/minio/cmd/config"
xhttp "github.com/minio/minio/cmd/http"
xjwt "github.com/minio/minio/cmd/jwt"
"github.com/minio/minio/cmd/logger"
)
@ -54,11 +56,29 @@ const DefaultSkewTime = 15 * time.Minute
// Authenticates storage client's requests and validates for skewed time.
func storageServerRequestValidate(r *http.Request) error {
_, owner, err := webRequestAuthenticate(r)
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(r)
if err != nil {
if err == jwtreq.ErrNoTokenInRequest {
return errNoAuthToken
}
return err
}
if !owner { // Disable access for non-admin users.
claims := xjwt.NewStandardClaims()
if err = xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
return errAuthentication
}
owner := claims.AccessKey == globalActiveCred.AccessKey || claims.Subject == globalActiveCred.AccessKey
if !owner {
return errAuthentication
}
if claims.Audience != r.URL.Query().Encode() {
return errAuthentication
}
if claims.Issuer != ReleaseTag {
return errAuthentication
}
@ -70,11 +90,12 @@ func storageServerRequestValidate(r *http.Request) error {
utcNow := UTCNow()
delta := requestTime.Sub(utcNow)
if delta < 0 {
delta = delta * -1
delta *= -1
}
if delta > DefaultSkewTime {
return fmt.Errorf("client time %v is too apart with server time %v", requestTime, utcNow)
}
return nil
}

View File

@ -101,7 +101,7 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep
}
if !owner {
creds, ok := globalIAMSys.GetUser(claims.AccessKey())
creds, ok := globalIAMSys.GetUser(claims.AccessKey)
if ok && creds.SessionToken != "" {
reply.MinioUserInfo["isTempUser"] = true
}
@ -154,10 +154,10 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
// For authenticated users apply IAM policy.
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.CreateBucketAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
}) {
@ -216,10 +216,10 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
// For authenticated users apply IAM policy.
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.DeleteBucketAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
}) {
@ -319,10 +319,10 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.ListBucketAction,
BucketName: dnsRecord.Key,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: "",
Claims: claims.Map(),
@ -342,10 +342,10 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
}
for _, bucket := range buckets {
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.ListBucketAction,
BucketName: bucket.Name,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: "",
Claims: claims.Map(),
@ -495,19 +495,19 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
r.Header.Set("delimiter", SlashSeparator)
readable := globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.ListBucketAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
})
writable := globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.PutObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: args.Prefix + SlashSeparator,
Claims: claims.Map(),
@ -675,10 +675,10 @@ next:
govBypassPerms := ErrAccessDenied
if authErr != errNoAuthToken {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.DeleteObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: objectName,
Claims: claims.Map(),
@ -686,10 +686,10 @@ next:
return toJSONError(ctx, errAccessDenied)
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.BypassGovernanceRetentionAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: objectName,
Claims: claims.Map(),
@ -719,10 +719,10 @@ next:
}
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.DeleteObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: objectName,
Claims: claims.Map(),
@ -844,8 +844,8 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
}
// for IAM users, access key cannot be updated
// claims.AccessKey() is used instead of accesskey from args
prevCred, ok := globalIAMSys.GetUser(claims.AccessKey())
// claims.AccessKey is used instead of accesskey from args
prevCred, ok := globalIAMSys.GetUser(claims.AccessKey)
if !ok {
return errInvalidAccessKeyID
}
@ -855,7 +855,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
return errIncorrectCreds
}
creds, err := auth.CreateCredentials(claims.AccessKey(), args.NewSecretKey)
creds, err := auth.CreateCredentials(claims.AccessKey, args.NewSecretKey)
if err != nil {
return toJSONError(ctx, err)
}
@ -892,7 +892,7 @@ func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs,
creds := globalActiveCred
if !owner {
var ok bool
creds, ok = globalIAMSys.GetUser(claims.AccessKey())
creds, ok = globalIAMSys.GetUser(claims.AccessKey)
if !ok {
return toJSONError(ctx, errInvalidAccessKeyID)
}
@ -954,10 +954,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
// For authenticated users apply IAM policy.
if authErr == nil {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.PutObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -966,10 +966,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
return
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.PutObjectRetentionAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -977,10 +977,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
retPerms = ErrNone
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.PutObjectLegalHoldAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -1188,10 +1188,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
// For authenticated users apply IAM policy.
if authErr == nil {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -1200,10 +1200,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
return
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectRetentionAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -1211,10 +1211,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
getRetPerms = ErrNone
}
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectLegalHoldAction,
BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: object,
Claims: claims.Map(),
@ -1390,10 +1390,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
if authErr == nil {
for _, object := range args.Objects {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: pathJoin(args.Prefix, object),
Claims: claims.Map(),
@ -1403,10 +1403,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
}
retentionPerm := ErrAccessDenied
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectRetentionAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: pathJoin(args.Prefix, object),
Claims: claims.Map(),
@ -1417,10 +1417,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
legalHoldPerm := ErrAccessDenied
if globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetObjectLegalHoldAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
ObjectName: pathJoin(args.Prefix, object),
Claims: claims.Map(),
@ -1570,10 +1570,10 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
// For authenticated users apply IAM policy.
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetBucketPolicyAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
}) {
@ -1668,10 +1668,10 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
// For authenticated users apply IAM policy.
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.GetBucketPolicyAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
}) {
@ -1759,10 +1759,10 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
// For authenticated users apply IAM policy.
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: claims.AccessKey(),
AccountName: claims.AccessKey,
Action: iampolicy.PutBucketPolicyAction,
BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
IsOwner: owner,
Claims: claims.Map(),
}) {
@ -1905,7 +1905,7 @@ func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs,
var creds auth.Credentials
if !owner {
var ok bool
creds, ok = globalIAMSys.GetUser(claims.AccessKey())
creds, ok = globalIAMSys.GetUser(claims.AccessKey)
if !ok {
return toJSONError(ctx, errInvalidAccessKeyID)
}

View File

@ -36,6 +36,7 @@ import (
jwtgo "github.com/dgrijalva/jwt-go"
humanize "github.com/dustin/go-humanize"
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
xjwt "github.com/minio/minio/cmd/jwt"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/bucket/policy"
"github.com/minio/minio/pkg/bucket/policy/condition"
@ -753,12 +754,10 @@ func TestWebCreateURLToken(t *testing.T) {
}
func getTokenString(accessKey, secretKey string) (string, error) {
utcNow := UTCNow()
mapClaims := jwtgo.MapClaims{}
mapClaims["exp"] = utcNow.Add(defaultJWTExpiry).Unix()
mapClaims["sub"] = accessKey
mapClaims["accessKey"] = accessKey
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, mapClaims)
claims := xjwt.NewMapClaims()
claims.SetExpiry(UTCNow().Add(defaultJWTExpiry))
claims.SetAccessKey(accessKey)
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
return token.SignedString([]byte(secretKey))
}
@ -1002,7 +1001,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl
}
if !bytes.Equal(bodyContent, bytes.NewBufferString("Authentication failed, check your access credentials").Bytes()) {
t.Fatalf("Expected authentication error message, got %v", bodyContent)
t.Fatalf("Expected authentication error message, got %s", string(bodyContent))
}
// Unauthenticated download should fail.