mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
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:
parent
9990464cd5
commit
bfe8a9bccc
@ -28,8 +28,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
xjwt "github.com/minio/minio/cmd/jwt"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"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.
|
// Fetch claims in the security token returned by the client.
|
||||||
func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
|
func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
|
||||||
claims := make(map[string]interface{})
|
claims := xjwt.NewMapClaims()
|
||||||
|
|
||||||
token := getSessionToken(r)
|
token := getSessionToken(r)
|
||||||
if token == "" {
|
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
|
// JWT token for x-amz-security-token is signed with admin
|
||||||
// secret key, temporary credentials become invalid if
|
// secret key, temporary credentials become invalid if
|
||||||
// server admin credentials change. This is done to ensure
|
// 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.
|
// on the client side and is treated like an opaque value.
|
||||||
return []byte(globalActiveCred.SecretKey), nil
|
return []byte(globalActiveCred.SecretKey), nil
|
||||||
}
|
}
|
||||||
p := &jwtgo.Parser{
|
|
||||||
ValidMethods: []string{
|
if err := xjwt.ParseWithClaims(token, claims, stsTokenCallback); err != nil {
|
||||||
jwtgo.SigningMethodHS256.Alg(),
|
|
||||||
jwtgo.SigningMethodHS512.Alg(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jtoken, err := p.ParseWithClaims(token, jwtgo.MapClaims(claims), stsTokenCallback)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !jtoken.Valid {
|
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
v, ok := claims["accessKey"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errInvalidAccessKeyID
|
|
||||||
}
|
|
||||||
if _, ok = v.(string); !ok {
|
|
||||||
return nil, errInvalidAccessKeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
if globalPolicyOPA == nil {
|
if globalPolicyOPA == nil {
|
||||||
// If OPA is not set and if ldap claim key is set,
|
// If OPA is not set and if ldap claim key is set, allow the claim.
|
||||||
// allow the claim.
|
if _, ok := claims.Lookup(ldapUser); ok {
|
||||||
if _, ok := claims[ldapUser]; ok {
|
return claims.Map(), nil
|
||||||
return claims, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If OPA is not set, session token should
|
// If OPA is not set, session token should
|
||||||
// have a policy and its mandatory, reject
|
// have a policy and its mandatory, reject
|
||||||
// requests without policy claim.
|
// requests without policy claim.
|
||||||
p, pok := claims[iamPolicyClaimName()]
|
_, pok := claims.Lookup(iamPolicyClaimName())
|
||||||
if !pok {
|
if !pok {
|
||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
if _, pok = p.(string); !pok {
|
|
||||||
return nil, errAuthentication
|
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
||||||
}
|
|
||||||
sp, spok := claims[iampolicy.SessionPolicyName]
|
|
||||||
// Sub policy is optional, if not set return success.
|
|
||||||
if !spok {
|
if !spok {
|
||||||
return claims, nil
|
return claims.Map(), nil
|
||||||
}
|
|
||||||
// Sub policy is set but its not a string, reject such requests
|
|
||||||
spStr, spok := sp.(string)
|
|
||||||
if !spok {
|
|
||||||
return nil, errAuthentication
|
|
||||||
}
|
}
|
||||||
// Looks like subpolicy is set and is a string, if set then its
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
// base64 encoded, decode it. Decoding fails reject such requests.
|
// base64 encoded, decode it. Decoding fails reject such requests.
|
||||||
spBytes, err := base64.StdEncoding.DecodeString(spStr)
|
spBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Base64 decoding fails, we should log to indicate
|
// Base64 decoding fails, we should log to indicate
|
||||||
// something is malforming the request sent by client.
|
// something is malforming the request sent by client.
|
||||||
logger.LogIf(context.Background(), err, logger.Application)
|
logger.LogIf(context.Background(), err, logger.Application)
|
||||||
return nil, errAuthentication
|
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.
|
// Fetch claims in the security token returned by the client and validate the token.
|
||||||
|
@ -201,7 +201,9 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) {
|
|||||||
if owner {
|
if owner {
|
||||||
return globalActiveCred
|
return globalActiveCred
|
||||||
}
|
}
|
||||||
cred, _ = globalIAMSys.GetUser(claims.AccessKey())
|
if claims != nil {
|
||||||
|
cred, _ = globalIAMSys.GetUser(claims.AccessKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cred
|
return cred
|
||||||
}
|
}
|
||||||
|
189
cmd/jwt.go
189
cmd/jwt.go
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,12 +19,12 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
jwtgo "github.com/dgrijalva/jwt-go"
|
||||||
jwtreq "github.com/dgrijalva/jwt-go/request"
|
jwtreq "github.com/dgrijalva/jwt-go/request"
|
||||||
|
xjwt "github.com/minio/minio/cmd/jwt"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"github.com/minio/minio/cmd/logger"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
)
|
)
|
||||||
@ -35,8 +35,8 @@ const (
|
|||||||
// Default JWT token for web handlers is one day.
|
// Default JWT token for web handlers is one day.
|
||||||
defaultJWTExpiry = 24 * time.Hour
|
defaultJWTExpiry = 24 * time.Hour
|
||||||
|
|
||||||
// Inter-node JWT token expiry is 100 years approx.
|
// Inter-node JWT token expiry is 15 minutes.
|
||||||
defaultInterNodeJWTExpiry = 100 * 365 * 24 * time.Hour
|
defaultInterNodeJWTExpiry = 15 * time.Minute
|
||||||
|
|
||||||
// URL JWT token expiry is one minute (might be exposed).
|
// URL JWT token expiry is one minute (might be exposed).
|
||||||
defaultURLJWTExpiry = time.Minute
|
defaultURLJWTExpiry = time.Minute
|
||||||
@ -73,42 +73,23 @@ func authenticateJWTUsersWithCredentials(credentials auth.Credentials, expiresAt
|
|||||||
return "", errAuthentication
|
return "", errAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := jwtgo.MapClaims{}
|
claims := xjwt.NewMapClaims()
|
||||||
claims["exp"] = expiresAt.Unix()
|
claims.SetExpiry(expiresAt)
|
||||||
claims["sub"] = credentials.AccessKey
|
claims.SetAccessKey(credentials.AccessKey)
|
||||||
claims["accessKey"] = credentials.AccessKey
|
|
||||||
|
|
||||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||||
return jwt.SignedString([]byte(serverCred.SecretKey))
|
return jwt.SignedString([]byte(serverCred.SecretKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateJWTAdmin(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
func authenticateNode(accessKey, secretKey, audience string) (string, error) {
|
||||||
passedCredential, err := auth.CreateCredentials(accessKey, secretKey)
|
claims := xjwt.NewStandardClaims()
|
||||||
if err != nil {
|
claims.SetExpiry(UTCNow().Add(defaultInterNodeJWTExpiry))
|
||||||
return "", err
|
claims.SetAccessKey(accessKey)
|
||||||
}
|
claims.SetAudience(audience)
|
||||||
|
claims.SetIssuer(ReleaseTag)
|
||||||
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
|
|
||||||
|
|
||||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||||
return jwt.SignedString([]byte(serverCred.SecretKey))
|
return jwt.SignedString([]byte(secretKey))
|
||||||
}
|
|
||||||
|
|
||||||
func authenticateNode(accessKey, secretKey string) (string, error) {
|
|
||||||
return authenticateJWTAdmin(accessKey, secretKey, defaultInterNodeJWTExpiry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateWeb(accessKey, secretKey string) (string, error) {
|
func authenticateWeb(accessKey, secretKey string) (string, error) {
|
||||||
@ -120,63 +101,29 @@ func authenticateURL(accessKey, secretKey string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Callback function used for parsing
|
// Callback function used for parsing
|
||||||
func webTokenCallback(jwtToken *jwtgo.Token) (interface{}, error) {
|
func webTokenCallback(claims *xjwt.MapClaims) ([]byte, error) {
|
||||||
if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
if claims.AccessKey == globalActiveCred.AccessKey {
|
||||||
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 {
|
|
||||||
return []byte(globalActiveCred.SecretKey), nil
|
|
||||||
}
|
|
||||||
if globalIAMSys == nil {
|
|
||||||
return nil, errInvalidAccessKeyID
|
|
||||||
}
|
|
||||||
if _, ok = claims["aud"].(string); !ok {
|
|
||||||
cred, ok := globalIAMSys.GetUser(accessKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errInvalidAccessKeyID
|
|
||||||
}
|
|
||||||
return []byte(cred.SecretKey), nil
|
|
||||||
}
|
|
||||||
return []byte(globalActiveCred.SecretKey), nil
|
return []byte(globalActiveCred.SecretKey), nil
|
||||||
}
|
}
|
||||||
|
if globalIAMSys == nil {
|
||||||
return nil, errAuthentication
|
return nil, errInvalidAccessKeyID
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
ok, err := globalIAMSys.IsTempUser(claims.AccessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch e := err.(type) {
|
if err == errNoSuchUser {
|
||||||
case *jwtgo.ValidationError:
|
return nil, errInvalidAccessKeyID
|
||||||
if e.Inner == nil {
|
|
||||||
return nil, errAuthentication
|
|
||||||
}
|
|
||||||
return nil, e.Inner
|
|
||||||
}
|
}
|
||||||
return nil, errAuthentication
|
return nil, err
|
||||||
}
|
}
|
||||||
return jwtToken, nil
|
if ok {
|
||||||
|
return []byte(globalActiveCred.SecretKey), nil
|
||||||
|
}
|
||||||
|
cred, ok := globalIAMSys.GetUser(claims.AccessKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errInvalidAccessKeyID
|
||||||
|
}
|
||||||
|
return []byte(cred.SecretKey), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAuthTokenValid(token string) bool {
|
func isAuthTokenValid(token string) bool {
|
||||||
@ -184,78 +131,40 @@ func isAuthTokenValid(token string) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func webTokenAuthenticate(token string) (mapClaims, bool, error) {
|
func webTokenAuthenticate(token string) (*xjwt.MapClaims, bool, error) {
|
||||||
var claims = jwtgo.MapClaims{}
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return mapClaims{claims}, false, errNoAuthToken
|
return nil, false, errNoAuthToken
|
||||||
}
|
}
|
||||||
jwtToken, err := parseJWTWithClaims(token, &claims)
|
claims := xjwt.NewMapClaims()
|
||||||
if err != nil {
|
if err := xjwt.ParseWithClaims(token, claims, webTokenCallback); err != nil {
|
||||||
return mapClaims{claims}, false, err
|
return claims, false, errAuthentication
|
||||||
}
|
}
|
||||||
if !jwtToken.Valid {
|
owner := claims.AccessKey == globalActiveCred.AccessKey
|
||||||
return mapClaims{claims}, false, errAuthentication
|
return claims, owner, nil
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the request is authenticated.
|
// Check if the request is authenticated.
|
||||||
// Returns nil if the request is authenticated. errNoAuthToken if token missing.
|
// Returns nil if the request is authenticated. errNoAuthToken if token missing.
|
||||||
// Returns errAuthentication for all other errors.
|
// Returns errAuthentication for all other errors.
|
||||||
func webRequestAuthenticate(req *http.Request) (mapClaims, bool, error) {
|
func webRequestAuthenticate(req *http.Request) (*xjwt.MapClaims, bool, error) {
|
||||||
var claims = jwtgo.MapClaims{}
|
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req)
|
||||||
tokStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == jwtreq.ErrNoTokenInRequest {
|
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)
|
claims := xjwt.NewMapClaims()
|
||||||
if err != nil {
|
if err := xjwt.ParseWithClaims(token, claims, webTokenCallback); err != nil {
|
||||||
return mapClaims{claims}, false, err
|
return claims, false, errAuthentication
|
||||||
}
|
}
|
||||||
if !jwtToken.Valid {
|
owner := claims.AccessKey == globalActiveCred.AccessKey
|
||||||
return mapClaims{claims}, false, errAuthentication
|
return claims, owner, nil
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAuthToken() string {
|
func newAuthToken(audience string) string {
|
||||||
cred := globalActiveCred
|
cred := globalActiveCred
|
||||||
token, err := authenticateNode(cred.AccessKey, cred.SecretKey)
|
token, err := authenticateNode(cred.AccessKey, cred.SecretKey, audience)
|
||||||
logger.CriticalIf(context.Background(), err)
|
logger.CriticalIf(context.Background(), err)
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
371
cmd/jwt/parser.go
Normal file
371
cmd/jwt/parser.go
Normal 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
214
cmd/jwt/parser_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
xjwt "github.com/minio/minio/cmd/jwt"
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,9 +63,7 @@ func testAuthenticate(authType string, t *testing.T) {
|
|||||||
// Run tests.
|
// Run tests.
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
var err error
|
var err error
|
||||||
if authType == "node" {
|
if authType == "web" {
|
||||||
_, err = authenticateNode(testCase.accessKey, testCase.secretKey)
|
|
||||||
} else if authType == "web" {
|
|
||||||
_, err = authenticateWeb(testCase.accessKey, testCase.secretKey)
|
_, err = authenticateWeb(testCase.accessKey, testCase.secretKey)
|
||||||
} else if authType == "url" {
|
} else if authType == "url" {
|
||||||
_, err = authenticateURL(testCase.accessKey, testCase.secretKey)
|
_, 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) {
|
func TestAuthenticateWeb(t *testing.T) {
|
||||||
testAuthenticate("web", 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) {
|
func BenchmarkAuthenticateNode(b *testing.B) {
|
||||||
obj, fsDir, err := prepareFS()
|
obj, fsDir, err := prepareFS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,7 +217,7 @@ func BenchmarkAuthenticateNode(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
authenticateNode(creds.AccessKey, creds.SecretKey)
|
authenticateNode(creds.AccessKey, creds.SecretKey, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ func createLockTestServer(t *testing.T) (string, *lockRESTServer, string) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
creds := globalActiveCred
|
creds := globalActiveCred
|
||||||
token, err := authenticateNode(creds.AccessKey, creds.SecretKey)
|
token, err := authenticateNode(creds.AccessKey, creds.SecretKey, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ type Client struct {
|
|||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
httpIdleConnsCloser func()
|
httpIdleConnsCloser func()
|
||||||
url *url.URL
|
url *url.URL
|
||||||
newAuthToken func() string
|
newAuthToken func(audience string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL query separator constants
|
// URL query separator constants
|
||||||
@ -62,7 +62,7 @@ func (c *Client) CallWithContext(ctx context.Context, method string, values url.
|
|||||||
return nil, &NetworkError{err}
|
return nil, &NetworkError{err}
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
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))
|
req.Header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
req.ContentLength = length
|
req.ContentLength = length
|
||||||
@ -102,7 +102,7 @@ func (c *Client) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewClient - returns new REST client.
|
// 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
|
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
|
||||||
// except custom DialContext and TLSClientConfig.
|
// except custom DialContext and TLSClientConfig.
|
||||||
tr := newCustomTransport()
|
tr := newCustomTransport()
|
||||||
|
@ -30,9 +30,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
jwtreq "github.com/dgrijalva/jwt-go/request"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio/cmd/config"
|
"github.com/minio/minio/cmd/config"
|
||||||
xhttp "github.com/minio/minio/cmd/http"
|
xhttp "github.com/minio/minio/cmd/http"
|
||||||
|
xjwt "github.com/minio/minio/cmd/jwt"
|
||||||
"github.com/minio/minio/cmd/logger"
|
"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.
|
// Authenticates storage client's requests and validates for skewed time.
|
||||||
func storageServerRequestValidate(r *http.Request) error {
|
func storageServerRequestValidate(r *http.Request) error {
|
||||||
_, owner, err := webRequestAuthenticate(r)
|
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == jwtreq.ErrNoTokenInRequest {
|
||||||
|
return errNoAuthToken
|
||||||
|
}
|
||||||
return err
|
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
|
return errAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,11 +90,12 @@ func storageServerRequestValidate(r *http.Request) error {
|
|||||||
utcNow := UTCNow()
|
utcNow := UTCNow()
|
||||||
delta := requestTime.Sub(utcNow)
|
delta := requestTime.Sub(utcNow)
|
||||||
if delta < 0 {
|
if delta < 0 {
|
||||||
delta = delta * -1
|
delta *= -1
|
||||||
}
|
}
|
||||||
if delta > DefaultSkewTime {
|
if delta > DefaultSkewTime {
|
||||||
return fmt.Errorf("client time %v is too apart with server time %v", requestTime, utcNow)
|
return fmt.Errorf("client time %v is too apart with server time %v", requestTime, utcNow)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !owner {
|
if !owner {
|
||||||
creds, ok := globalIAMSys.GetUser(claims.AccessKey())
|
creds, ok := globalIAMSys.GetUser(claims.AccessKey)
|
||||||
if ok && creds.SessionToken != "" {
|
if ok && creds.SessionToken != "" {
|
||||||
reply.MinioUserInfo["isTempUser"] = true
|
reply.MinioUserInfo["isTempUser"] = true
|
||||||
}
|
}
|
||||||
@ -154,10 +154,10 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
|
|||||||
|
|
||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.CreateBucketAction,
|
Action: iampolicy.CreateBucketAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
}) {
|
}) {
|
||||||
@ -216,10 +216,10 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
|
|||||||
|
|
||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.DeleteBucketAction,
|
Action: iampolicy.DeleteBucketAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
}) {
|
}) {
|
||||||
@ -319,10 +319,10 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: dnsRecord.Key,
|
BucketName: dnsRecord.Key,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: "",
|
ObjectName: "",
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -342,10 +342,10 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
|
|||||||
}
|
}
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucket.Name,
|
BucketName: bucket.Name,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: "",
|
ObjectName: "",
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -495,19 +495,19 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
|
|||||||
r.Header.Set("delimiter", SlashSeparator)
|
r.Header.Set("delimiter", SlashSeparator)
|
||||||
|
|
||||||
readable := globalIAMSys.IsAllowed(iampolicy.Args{
|
readable := globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
})
|
})
|
||||||
|
|
||||||
writable := globalIAMSys.IsAllowed(iampolicy.Args{
|
writable := globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: iampolicy.PutObjectAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: args.Prefix + SlashSeparator,
|
ObjectName: args.Prefix + SlashSeparator,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -675,10 +675,10 @@ next:
|
|||||||
govBypassPerms := ErrAccessDenied
|
govBypassPerms := ErrAccessDenied
|
||||||
if authErr != errNoAuthToken {
|
if authErr != errNoAuthToken {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.DeleteObjectAction,
|
Action: iampolicy.DeleteObjectAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -686,10 +686,10 @@ next:
|
|||||||
return toJSONError(ctx, errAccessDenied)
|
return toJSONError(ctx, errAccessDenied)
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.BypassGovernanceRetentionAction,
|
Action: iampolicy.BypassGovernanceRetentionAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -719,10 +719,10 @@ next:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.DeleteObjectAction,
|
Action: iampolicy.DeleteObjectAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
Claims: claims.Map(),
|
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
|
// for IAM users, access key cannot be updated
|
||||||
// claims.AccessKey() is used instead of accesskey from args
|
// claims.AccessKey is used instead of accesskey from args
|
||||||
prevCred, ok := globalIAMSys.GetUser(claims.AccessKey())
|
prevCred, ok := globalIAMSys.GetUser(claims.AccessKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errInvalidAccessKeyID
|
return errInvalidAccessKeyID
|
||||||
}
|
}
|
||||||
@ -855,7 +855,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
|
|||||||
return errIncorrectCreds
|
return errIncorrectCreds
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, err := auth.CreateCredentials(claims.AccessKey(), args.NewSecretKey)
|
creds, err := auth.CreateCredentials(claims.AccessKey, args.NewSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toJSONError(ctx, err)
|
return toJSONError(ctx, err)
|
||||||
}
|
}
|
||||||
@ -892,7 +892,7 @@ func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs,
|
|||||||
creds := globalActiveCred
|
creds := globalActiveCred
|
||||||
if !owner {
|
if !owner {
|
||||||
var ok bool
|
var ok bool
|
||||||
creds, ok = globalIAMSys.GetUser(claims.AccessKey())
|
creds, ok = globalIAMSys.GetUser(claims.AccessKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return toJSONError(ctx, errInvalidAccessKeyID)
|
return toJSONError(ctx, errInvalidAccessKeyID)
|
||||||
}
|
}
|
||||||
@ -954,10 +954,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if authErr == nil {
|
if authErr == nil {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.PutObjectAction,
|
Action: iampolicy.PutObjectAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -966,10 +966,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.PutObjectRetentionAction,
|
Action: iampolicy.PutObjectRetentionAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -977,10 +977,10 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
retPerms = ErrNone
|
retPerms = ErrNone
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.PutObjectLegalHoldAction,
|
Action: iampolicy.PutObjectLegalHoldAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1188,10 +1188,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if authErr == nil {
|
if authErr == nil {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectAction,
|
Action: iampolicy.GetObjectAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1200,10 +1200,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectRetentionAction,
|
Action: iampolicy.GetObjectRetentionAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1211,10 +1211,10 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
getRetPerms = ErrNone
|
getRetPerms = ErrNone
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectLegalHoldAction,
|
Action: iampolicy.GetObjectLegalHoldAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1390,10 +1390,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
if authErr == nil {
|
if authErr == nil {
|
||||||
for _, object := range args.Objects {
|
for _, object := range args.Objects {
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectAction,
|
Action: iampolicy.GetObjectAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: pathJoin(args.Prefix, object),
|
ObjectName: pathJoin(args.Prefix, object),
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1403,10 +1403,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
retentionPerm := ErrAccessDenied
|
retentionPerm := ErrAccessDenied
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectRetentionAction,
|
Action: iampolicy.GetObjectRetentionAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: pathJoin(args.Prefix, object),
|
ObjectName: pathJoin(args.Prefix, object),
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1417,10 +1417,10 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
legalHoldPerm := ErrAccessDenied
|
legalHoldPerm := ErrAccessDenied
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetObjectLegalHoldAction,
|
Action: iampolicy.GetObjectLegalHoldAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
ObjectName: pathJoin(args.Prefix, object),
|
ObjectName: pathJoin(args.Prefix, object),
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
@ -1570,10 +1570,10 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
|
|||||||
|
|
||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetBucketPolicyAction,
|
Action: iampolicy.GetBucketPolicyAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
}) {
|
}) {
|
||||||
@ -1668,10 +1668,10 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
|
|||||||
|
|
||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.GetBucketPolicyAction,
|
Action: iampolicy.GetBucketPolicyAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
}) {
|
}) {
|
||||||
@ -1759,10 +1759,10 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
|||||||
|
|
||||||
// For authenticated users apply IAM policy.
|
// For authenticated users apply IAM policy.
|
||||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: claims.AccessKey(),
|
AccountName: claims.AccessKey,
|
||||||
Action: iampolicy.PutBucketPolicyAction,
|
Action: iampolicy.PutBucketPolicyAction,
|
||||||
BucketName: args.BucketName,
|
BucketName: args.BucketName,
|
||||||
ConditionValues: getConditionValues(r, "", claims.AccessKey(), claims.Map()),
|
ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims.Map(),
|
Claims: claims.Map(),
|
||||||
}) {
|
}) {
|
||||||
@ -1905,7 +1905,7 @@ func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs,
|
|||||||
var creds auth.Credentials
|
var creds auth.Credentials
|
||||||
if !owner {
|
if !owner {
|
||||||
var ok bool
|
var ok bool
|
||||||
creds, ok = globalIAMSys.GetUser(claims.AccessKey())
|
creds, ok = globalIAMSys.GetUser(claims.AccessKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return toJSONError(ctx, errInvalidAccessKeyID)
|
return toJSONError(ctx, errInvalidAccessKeyID)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
jwtgo "github.com/dgrijalva/jwt-go"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
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/auth"
|
||||||
"github.com/minio/minio/pkg/bucket/policy"
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||||
@ -753,12 +754,10 @@ func TestWebCreateURLToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTokenString(accessKey, secretKey string) (string, error) {
|
func getTokenString(accessKey, secretKey string) (string, error) {
|
||||||
utcNow := UTCNow()
|
claims := xjwt.NewMapClaims()
|
||||||
mapClaims := jwtgo.MapClaims{}
|
claims.SetExpiry(UTCNow().Add(defaultJWTExpiry))
|
||||||
mapClaims["exp"] = utcNow.Add(defaultJWTExpiry).Unix()
|
claims.SetAccessKey(accessKey)
|
||||||
mapClaims["sub"] = accessKey
|
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||||
mapClaims["accessKey"] = accessKey
|
|
||||||
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, mapClaims)
|
|
||||||
return token.SignedString([]byte(secretKey))
|
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()) {
|
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.
|
// Unauthenticated download should fail.
|
||||||
|
Loading…
Reference in New Issue
Block a user