mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
Have simpler JWT authentication. (#3501)
This commit is contained in:
parent
69559aa101
commit
ee0172dfe4
@ -32,7 +32,7 @@ type serviceCmd struct {
|
|||||||
|
|
||||||
// Shutdown - Shutdown this instance of minio server.
|
// Shutdown - Shutdown this instance of minio server.
|
||||||
func (s *serviceCmd) Shutdown(args *GenericArgs, reply *GenericReply) error {
|
func (s *serviceCmd) Shutdown(args *GenericArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
globalServiceSignalCh <- serviceStop
|
globalServiceSignalCh <- serviceStop
|
||||||
@ -41,7 +41,7 @@ func (s *serviceCmd) Shutdown(args *GenericArgs, reply *GenericReply) error {
|
|||||||
|
|
||||||
// Restart - Restart this instance of minio server.
|
// Restart - Restart this instance of minio server.
|
||||||
func (s *serviceCmd) Restart(args *GenericArgs, reply *GenericReply) error {
|
func (s *serviceCmd) Restart(args *GenericArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
globalServiceSignalCh <- serviceRestart
|
globalServiceSignalCh <- serviceRestart
|
||||||
|
@ -224,7 +224,7 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
} else if aType == authTypeJWT {
|
} else if aType == authTypeJWT {
|
||||||
// Validate Authorization header if its valid for JWT request.
|
// Validate Authorization header if its valid for JWT request.
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenericReply represents any generic RPC reply.
|
// GenericReply represents any generic RPC reply.
|
||||||
@ -63,27 +60,6 @@ type RPCLoginReply struct {
|
|||||||
ServerVersion string
|
ServerVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates if incoming token is valid.
|
|
||||||
func isRPCTokenValid(tokenStr string) bool {
|
|
||||||
jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential())
|
|
||||||
if err != nil {
|
|
||||||
errorIf(err, "Unable to initialize JWT")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
token, err := jwtgo.Parse(tokenStr, func(token *jwtgo.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
return []byte(jwt.SecretKey), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errorIf(err, "Unable to parse JWT token string")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Return if token is valid.
|
|
||||||
return token.Valid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth config represents authentication credentials and Login method name to be used
|
// Auth config represents authentication credentials and Login method name to be used
|
||||||
// for fetching JWT tokens from the RPC server.
|
// for fetching JWT tokens from the RPC server.
|
||||||
type authConfig struct {
|
type authConfig struct {
|
||||||
|
@ -25,17 +25,11 @@ import (
|
|||||||
// Login handler implements JWT login token generator, which upon login request
|
// Login handler implements JWT login token generator, which upon login request
|
||||||
// along with username and password is generated.
|
// along with username and password is generated.
|
||||||
func (br *browserPeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
func (br *browserPeerAPIHandlers) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
||||||
jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential())
|
token, err := authenticateWeb(args.Username, args.Password)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = jwt.Authenticate(args.Username, args.Password); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
token, err := jwt.GenerateToken(args.Username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token = token
|
reply.Token = token
|
||||||
reply.ServerVersion = Version
|
reply.ServerVersion = Version
|
||||||
reply.Timestamp = time.Now().UTC()
|
reply.Timestamp = time.Now().UTC()
|
||||||
@ -54,13 +48,13 @@ type SetAuthPeerArgs struct {
|
|||||||
// SetAuthPeer - Update to new credentials sent from a peer Minio
|
// SetAuthPeer - Update to new credentials sent from a peer Minio
|
||||||
// server. Since credentials are already validated on the sending
|
// server. Since credentials are already validated on the sending
|
||||||
// peer, here we just persist to file and update in-memory config. All
|
// peer, here we just persist to file and update in-memory config. All
|
||||||
// subsequently running isRPCTokenValid() calls will fail, and clients
|
// subsequently running isAuthTokenValid() calls will fail, and clients
|
||||||
// will be forced to re-establish connections. Connections will be
|
// will be forced to re-establish connections. Connections will be
|
||||||
// re-established only when the sending client has also updated its
|
// re-established only when the sending client has also updated its
|
||||||
// credentials.
|
// credentials.
|
||||||
func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *GenericReply) error {
|
func (br *browserPeerAPIHandlers) SetAuthPeer(args SetAuthPeerArgs, reply *GenericReply) error {
|
||||||
// Check auth
|
// Check auth
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,3 +72,15 @@ type credential struct {
|
|||||||
func newCredential() credential {
|
func newCredential() credential {
|
||||||
return credential{mustGetAccessKey(), mustGetSecretKey()}
|
return credential{mustGetAccessKey(), mustGetSecretKey()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCredential(accessKey, secretKey string) (credential, error) {
|
||||||
|
if !isAccessKeyValid(accessKey) {
|
||||||
|
return credential{}, errInvalidAccessKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isSecretKeyValid(secretKey) {
|
||||||
|
return credential{}, errInvalidSecretKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
return credential{accessKey, secretKey}, nil
|
||||||
|
}
|
||||||
|
116
cmd/jwt.go
Normal file
116
cmd/jwt.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2016 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jwtgo "github.com/dgrijalva/jwt-go"
|
||||||
|
jwtreq "github.com/dgrijalva/jwt-go/request"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jwtAlgorithm = "Bearer"
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length")
|
||||||
|
var errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length")
|
||||||
|
|
||||||
|
var errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records")
|
||||||
|
var errAuthentication = errors.New("Authentication failed, check your access credentials")
|
||||||
|
|
||||||
|
func authenticateJWT(accessKey, secretKey string, expiry time.Duration) (string, error) {
|
||||||
|
// Trim spaces.
|
||||||
|
accessKey = strings.TrimSpace(accessKey)
|
||||||
|
|
||||||
|
if !isAccessKeyValid(accessKey) {
|
||||||
|
return "", errInvalidAccessKeyLength
|
||||||
|
}
|
||||||
|
if !isSecretKeyValid(secretKey) {
|
||||||
|
return "", errInvalidSecretKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
|
|
||||||
|
// Validate access key.
|
||||||
|
if accessKey != serverCred.AccessKey {
|
||||||
|
return "", errInvalidAccessKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate secret key.
|
||||||
|
// Using bcrypt to avoid timing attacks.
|
||||||
|
hashedSecretKey, _ := bcrypt.GenerateFromPassword([]byte(serverCred.SecretKey), bcrypt.DefaultCost)
|
||||||
|
if bcrypt.CompareHashAndPassword(hashedSecretKey, []byte(secretKey)) != nil {
|
||||||
|
return "", errAuthentication
|
||||||
|
}
|
||||||
|
|
||||||
|
utcNow := time.Now().UTC()
|
||||||
|
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims{
|
||||||
|
"exp": utcNow.Add(expiry).Unix(),
|
||||||
|
"iat": utcNow.Unix(),
|
||||||
|
"sub": accessKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
return token.SignedString([]byte(serverCred.SecretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticateNode(accessKey, secretKey string) (string, error) {
|
||||||
|
return authenticateJWT(accessKey, secretKey, defaultInterNodeJWTExpiry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticateWeb(accessKey, secretKey string) (string, error) {
|
||||||
|
return authenticateJWT(accessKey, secretKey, defaultJWTExpiry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyFuncCallback(jwtToken *jwtgo.Token) (interface{}, error) {
|
||||||
|
if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(serverConfig.GetCredential().SecretKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAuthTokenValid(tokenString string) bool {
|
||||||
|
jwtToken, err := jwtgo.Parse(tokenString, keyFuncCallback)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Unable to parse JWT token string")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwtToken.Valid
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHTTPRequestValid(req *http.Request) bool {
|
||||||
|
jwtToken, err := jwtreq.ParseFromRequest(req, jwtreq.AuthorizationHeaderExtractor, keyFuncCallback)
|
||||||
|
if err != nil {
|
||||||
|
errorIf(err, "Unable to parse JWT token string")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwtToken.Valid
|
||||||
|
}
|
84
cmd/jwt_test.go
Normal file
84
cmd/jwt_test.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Minio Cloud Storage, (C) 2016 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 cmd
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func testAuthenticate(authType string, t *testing.T) {
|
||||||
|
testPath, err := newTestConfig("us-east-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
|
}
|
||||||
|
defer removeAll(testPath)
|
||||||
|
|
||||||
|
serverCred := serverConfig.GetCredential()
|
||||||
|
|
||||||
|
// Define test cases.
|
||||||
|
testCases := []struct {
|
||||||
|
accessKey string
|
||||||
|
secretKey string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
// Access key too small.
|
||||||
|
{"user", "pass", errInvalidAccessKeyLength},
|
||||||
|
// Access key too long.
|
||||||
|
{"user12345678901234567", "pass", errInvalidAccessKeyLength},
|
||||||
|
// Access key contains unsuppported characters.
|
||||||
|
{"!@#$", "pass", errInvalidAccessKeyLength},
|
||||||
|
// Secret key too small.
|
||||||
|
{"myuser", "pass", errInvalidSecretKeyLength},
|
||||||
|
// Secret key too long.
|
||||||
|
{"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength},
|
||||||
|
// Authentication error.
|
||||||
|
{"myuser", "mypassword", errInvalidAccessKeyID},
|
||||||
|
// Authentication error.
|
||||||
|
{serverCred.AccessKey, "mypassword", errAuthentication},
|
||||||
|
// Success.
|
||||||
|
{serverCred.AccessKey, serverCred.SecretKey, nil},
|
||||||
|
// Success when access key contains leading/trailing spaces.
|
||||||
|
{" " + serverCred.AccessKey + " ", serverCred.SecretKey, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run tests.
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
var err error
|
||||||
|
if authType == "node" {
|
||||||
|
_, err = authenticateNode(testCase.accessKey, testCase.secretKey)
|
||||||
|
} else if authType == "web" {
|
||||||
|
_, err = authenticateWeb(testCase.accessKey, testCase.secretKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testCase.expectedErr != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr)
|
||||||
|
}
|
||||||
|
if testCase.expectedErr.Error() != err.Error() {
|
||||||
|
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeAuthenticate(t *testing.T) {
|
||||||
|
testAuthenticate("node", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebAuthenticate(t *testing.T) {
|
||||||
|
testAuthenticate("web", t)
|
||||||
|
}
|
@ -65,7 +65,7 @@ func (l *lockServer) validateLockArgs(args *LockArgs) error {
|
|||||||
if curTime.Sub(args.Timestamp) > globalMaxSkewTime || args.Timestamp.Sub(curTime) > globalMaxSkewTime {
|
if curTime.Sub(args.Timestamp) > globalMaxSkewTime || args.Timestamp.Sub(curTime) > globalMaxSkewTime {
|
||||||
return errServerTimeMismatch
|
return errServerTimeMismatch
|
||||||
}
|
}
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -23,17 +23,11 @@ type loginServer struct {
|
|||||||
|
|
||||||
// LoginHandler - Handles JWT based RPC logic.
|
// LoginHandler - Handles JWT based RPC logic.
|
||||||
func (b loginServer) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
func (b loginServer) LoginHandler(args *RPCLoginArgs, reply *RPCLoginReply) error {
|
||||||
jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential())
|
token, err := authenticateNode(args.Username, args.Password)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = jwt.Authenticate(args.Username, args.Password); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
token, err := jwt.GenerateToken(args.Username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token = token
|
reply.Token = token
|
||||||
reply.Timestamp = time.Now().UTC()
|
reply.Timestamp = time.Now().UTC()
|
||||||
reply.ServerVersion = Version
|
reply.ServerVersion = Version
|
||||||
|
@ -545,7 +545,7 @@ func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, c
|
|||||||
// Expire the request five minutes from now.
|
// Expire the request five minutes from now.
|
||||||
expirationTime := t.Add(time.Minute * 5)
|
expirationTime := t.Add(time.Minute * 5)
|
||||||
|
|
||||||
credStr := getCredential(accessKey, serverConfig.GetRegion(), t)
|
credStr := getCredentialString(accessKey, serverConfig.GetRegion(), t)
|
||||||
// Create a new post policy.
|
// Create a new post policy.
|
||||||
policy := newPostPolicyBytesV4(credStr, bucketName, objectName, expirationTime)
|
policy := newPostPolicyBytesV4(credStr, bucketName, objectName, expirationTime)
|
||||||
if contentLengthRange {
|
if contentLengthRange {
|
||||||
@ -557,7 +557,7 @@ func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, c
|
|||||||
func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string,
|
func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string,
|
||||||
t time.Time, policy []byte, addFormData map[string]string, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) {
|
t time.Time, policy []byte, addFormData map[string]string, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) {
|
||||||
// Get the user credential.
|
// Get the user credential.
|
||||||
credStr := getCredential(accessKey, serverConfig.GetRegion(), t)
|
credStr := getCredentialString(accessKey, serverConfig.GetRegion(), t)
|
||||||
|
|
||||||
// Only need the encoding.
|
// Only need the encoding.
|
||||||
encodedPolicy := base64.StdEncoding.EncodeToString(policy)
|
encodedPolicy := base64.StdEncoding.EncodeToString(policy)
|
||||||
|
@ -37,7 +37,7 @@ func (s *SetBucketNotificationPeerArgs) BucketUpdate(client BucketMetaState) err
|
|||||||
|
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketNotificationPeer(args *SetBucketNotificationPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketNotificationPeer(args *SetBucketNotificationPeerArgs, reply *GenericReply) error {
|
||||||
// check auth
|
// check auth
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ func (s *SetBucketListenerPeerArgs) BucketUpdate(client BucketMetaState) error {
|
|||||||
|
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketListenerPeer(args *SetBucketListenerPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketListenerPeer(args *SetBucketListenerPeerArgs, reply *GenericReply) error {
|
||||||
// check auth
|
// check auth
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ type EventArgs struct {
|
|||||||
// submit an event to the receiving server.
|
// submit an event to the receiving server.
|
||||||
func (s3 *s3PeerAPIHandlers) Event(args *EventArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) Event(args *EventArgs, reply *GenericReply) error {
|
||||||
// check auth
|
// check auth
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ func (s *SetBucketPolicyPeerArgs) BucketUpdate(client BucketMetaState) error {
|
|||||||
// tell receiving server to update a bucket policy
|
// tell receiving server to update a bucket policy
|
||||||
func (s3 *s3PeerAPIHandlers) SetBucketPolicyPeer(args *SetBucketPolicyPeerArgs, reply *GenericReply) error {
|
func (s3 *s3PeerAPIHandlers) SetBucketPolicyPeer(args *SetBucketPolicyPeerArgs, reply *GenericReply) error {
|
||||||
// check auth
|
// check auth
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Minio Cloud Storage, (C) 2016 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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const jwtAlgorithm = "Bearer"
|
|
||||||
|
|
||||||
// JWT - jwt auth backend
|
|
||||||
type JWT struct {
|
|
||||||
credential
|
|
||||||
expiry time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Default JWT token for web handlers is one day.
|
|
||||||
defaultJWTExpiry time.Duration = time.Hour * 24
|
|
||||||
|
|
||||||
// Inter-node JWT token expiry is 100 years.
|
|
||||||
defaultInterNodeJWTExpiry time.Duration = time.Hour * 24 * 365 * 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// newJWT - returns new JWT object.
|
|
||||||
func newJWT(expiry time.Duration, cred credential) (*JWT, error) {
|
|
||||||
if !isAccessKeyValid(cred.AccessKey) {
|
|
||||||
return nil, errInvalidAccessKeyLength
|
|
||||||
}
|
|
||||||
if !isSecretKeyValid(cred.SecretKey) {
|
|
||||||
return nil, errInvalidSecretKeyLength
|
|
||||||
}
|
|
||||||
return &JWT{cred, expiry}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidAccessKeyLength = errors.New("Invalid access key, access key should be 5 to 20 characters in length")
|
|
||||||
var errInvalidSecretKeyLength = errors.New("Invalid secret key, secret key should be 8 to 40 characters in length")
|
|
||||||
|
|
||||||
// GenerateToken - generates a new Json Web Token based on the incoming access key.
|
|
||||||
func (jwt *JWT) GenerateToken(accessKey string) (string, error) {
|
|
||||||
// Trim spaces.
|
|
||||||
accessKey = strings.TrimSpace(accessKey)
|
|
||||||
|
|
||||||
if !isAccessKeyValid(accessKey) {
|
|
||||||
return "", errInvalidAccessKeyLength
|
|
||||||
}
|
|
||||||
|
|
||||||
tUTCNow := time.Now().UTC()
|
|
||||||
token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims{
|
|
||||||
// Token expires in 10hrs.
|
|
||||||
"exp": tUTCNow.Add(jwt.expiry).Unix(),
|
|
||||||
"iat": tUTCNow.Unix(),
|
|
||||||
"sub": accessKey,
|
|
||||||
})
|
|
||||||
return token.SignedString([]byte(jwt.SecretKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records")
|
|
||||||
var errAuthentication = errors.New("Authentication failed, check your access credentials")
|
|
||||||
|
|
||||||
// Authenticate - authenticates incoming access key and secret key.
|
|
||||||
func (jwt *JWT) Authenticate(accessKey, secretKey string) error {
|
|
||||||
// Trim spaces.
|
|
||||||
accessKey = strings.TrimSpace(accessKey)
|
|
||||||
|
|
||||||
if !isAccessKeyValid(accessKey) {
|
|
||||||
return errInvalidAccessKeyLength
|
|
||||||
}
|
|
||||||
if !isSecretKeyValid(secretKey) {
|
|
||||||
return errInvalidSecretKeyLength
|
|
||||||
}
|
|
||||||
|
|
||||||
if accessKey != jwt.AccessKey {
|
|
||||||
return errInvalidAccessKeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedSecretKey, _ := bcrypt.GenerateFromPassword([]byte(jwt.SecretKey), bcrypt.DefaultCost)
|
|
||||||
if bcrypt.CompareHashAndPassword(hashedSecretKey, []byte(secretKey)) != nil {
|
|
||||||
return errAuthentication
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success.
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* Minio Cloud Storage, (C) 2016 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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests newJWT()
|
|
||||||
func TestNewJWT(t *testing.T) {
|
|
||||||
savedServerConfig := serverConfig
|
|
||||||
defer func() {
|
|
||||||
serverConfig = savedServerConfig
|
|
||||||
}()
|
|
||||||
serverConfig = nil
|
|
||||||
|
|
||||||
// Test non-existent config directory.
|
|
||||||
path1, err := ioutil.TempDir(globalTestTmpDir, "minio-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(path1)
|
|
||||||
|
|
||||||
// Test empty config directory.
|
|
||||||
path2, err := ioutil.TempDir(globalTestTmpDir, "minio-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(path2)
|
|
||||||
|
|
||||||
// Test empty config file.
|
|
||||||
path3, err := ioutil.TempDir(globalTestTmpDir, "minio-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(path3)
|
|
||||||
|
|
||||||
if err = ioutil.WriteFile(path.Join(path3, "config.json"), []byte{}, os.ModePerm); err != nil {
|
|
||||||
t.Fatalf("unable to create config file, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test initialized config file.
|
|
||||||
path4, err := ioutil.TempDir(globalTestTmpDir, "minio-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create a temporary directory, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(path4)
|
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct {
|
|
||||||
dirPath string
|
|
||||||
init bool
|
|
||||||
cred *credential
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
// Test initialized config file.
|
|
||||||
{path4, true, nil, nil},
|
|
||||||
// Test to read already created config file.
|
|
||||||
{path4, true, nil, nil},
|
|
||||||
// Access key is too small.
|
|
||||||
{path4, false, &credential{"user", "pass"}, errInvalidAccessKeyLength},
|
|
||||||
// Access key is too long.
|
|
||||||
{path4, false, &credential{"user12345678901234567", "pass"}, errInvalidAccessKeyLength},
|
|
||||||
// Secret key is too small.
|
|
||||||
{path4, false, &credential{"myuser", "pass"}, errInvalidSecretKeyLength},
|
|
||||||
// Secret key is too long.
|
|
||||||
{path4, false, &credential{"myuser", "pass1234567890123456789012345678901234567"}, errInvalidSecretKeyLength},
|
|
||||||
// Valid access/secret keys.
|
|
||||||
{path4, false, &credential{"myuser", "mypassword"}, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
setGlobalConfigPath(testCase.dirPath)
|
|
||||||
if testCase.init {
|
|
||||||
if _, err := initConfig(); err != nil {
|
|
||||||
t.Fatalf("unable initialize config file, %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if testCase.cred != nil {
|
|
||||||
serverConfig.SetCredential(*testCase.cred)
|
|
||||||
}
|
|
||||||
_, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential())
|
|
||||||
if testCase.expectedErr != nil {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr)
|
|
||||||
}
|
|
||||||
if testCase.expectedErr.Error() != err.Error() {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests JWT.GenerateToken()
|
|
||||||
func TestGenerateToken(t *testing.T) {
|
|
||||||
testPath, err := newTestConfig("us-east-1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable initialize config file, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(testPath)
|
|
||||||
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable get new JWT, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct {
|
|
||||||
accessKey string
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
// Access key is too small.
|
|
||||||
{"user", errInvalidAccessKeyLength},
|
|
||||||
// Access key is too long.
|
|
||||||
{"user12345678901234567", errInvalidAccessKeyLength},
|
|
||||||
// Access key contains unsupported characters.
|
|
||||||
{"!@#$", errInvalidAccessKeyLength},
|
|
||||||
// Valid access key.
|
|
||||||
{"myuser", nil},
|
|
||||||
// Valid access key with leading/trailing spaces.
|
|
||||||
{" myuser ", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
_, err := jwt.GenerateToken(testCase.accessKey)
|
|
||||||
if testCase.expectedErr != nil {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.expectedErr.Error() != err.Error() {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests JWT.Authenticate()
|
|
||||||
func TestAuthenticate(t *testing.T) {
|
|
||||||
testPath, err := newTestConfig("us-east-1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable initialize config file, %s", err)
|
|
||||||
}
|
|
||||||
defer removeAll(testPath)
|
|
||||||
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable get new JWT, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define test cases.
|
|
||||||
testCases := []struct {
|
|
||||||
accessKey string
|
|
||||||
secretKey string
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
// Access key too small.
|
|
||||||
{"user", "pass", errInvalidAccessKeyLength},
|
|
||||||
// Access key too long.
|
|
||||||
{"user12345678901234567", "pass", errInvalidAccessKeyLength},
|
|
||||||
// Access key contains unsuppported characters.
|
|
||||||
{"!@#$", "pass", errInvalidAccessKeyLength},
|
|
||||||
// Secret key too small.
|
|
||||||
{"myuser", "pass", errInvalidSecretKeyLength},
|
|
||||||
// Secret key too long.
|
|
||||||
{"myuser", "pass1234567890123456789012345678901234567", errInvalidSecretKeyLength},
|
|
||||||
// Authentication error.
|
|
||||||
{"myuser", "mypassword", errInvalidAccessKeyID},
|
|
||||||
// Authentication error.
|
|
||||||
{serverConfig.GetCredential().AccessKey, "mypassword", errAuthentication},
|
|
||||||
// Success.
|
|
||||||
{serverConfig.GetCredential().AccessKey, serverConfig.GetCredential().SecretKey, nil},
|
|
||||||
// Success when access key contains leading/trailing spaces.
|
|
||||||
{" " + serverConfig.GetCredential().AccessKey + " ", serverConfig.GetCredential().SecretKey, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run tests.
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
err := jwt.Authenticate(testCase.accessKey, testCase.secretKey)
|
|
||||||
if testCase.expectedErr != nil {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: <nil>", testCase, testCase.expectedErr)
|
|
||||||
}
|
|
||||||
if testCase.expectedErr.Error() != err.Error() {
|
|
||||||
t.Fatalf("%+v: expected: %s, got: %s", testCase, testCase.expectedErr, err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatalf("%+v: expected: <nil>, got: %s", testCase, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,7 +39,7 @@ type storageServer struct {
|
|||||||
|
|
||||||
// DiskInfoHandler - disk info handler is rpc wrapper for DiskInfo operation.
|
// DiskInfoHandler - disk info handler is rpc wrapper for DiskInfo operation.
|
||||||
func (s *storageServer) DiskInfoHandler(args *GenericArgs, reply *disk.Info) error {
|
func (s *storageServer) DiskInfoHandler(args *GenericArgs, reply *disk.Info) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
info, err := s.storage.DiskInfo()
|
info, err := s.storage.DiskInfo()
|
||||||
@ -51,7 +51,7 @@ func (s *storageServer) DiskInfoHandler(args *GenericArgs, reply *disk.Info) err
|
|||||||
|
|
||||||
// MakeVolHandler - make vol handler is rpc wrapper for MakeVol operation.
|
// MakeVolHandler - make vol handler is rpc wrapper for MakeVol operation.
|
||||||
func (s *storageServer) MakeVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
func (s *storageServer) MakeVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.MakeVol(args.Vol)
|
return s.storage.MakeVol(args.Vol)
|
||||||
@ -59,7 +59,7 @@ func (s *storageServer) MakeVolHandler(args *GenericVolArgs, reply *GenericReply
|
|||||||
|
|
||||||
// ListVolsHandler - list vols handler is rpc wrapper for ListVols operation.
|
// ListVolsHandler - list vols handler is rpc wrapper for ListVols operation.
|
||||||
func (s *storageServer) ListVolsHandler(args *GenericArgs, reply *ListVolsReply) error {
|
func (s *storageServer) ListVolsHandler(args *GenericArgs, reply *ListVolsReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
vols, err := s.storage.ListVols()
|
vols, err := s.storage.ListVols()
|
||||||
@ -72,7 +72,7 @@ func (s *storageServer) ListVolsHandler(args *GenericArgs, reply *ListVolsReply)
|
|||||||
|
|
||||||
// StatVolHandler - stat vol handler is a rpc wrapper for StatVol operation.
|
// StatVolHandler - stat vol handler is a rpc wrapper for StatVol operation.
|
||||||
func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) error {
|
func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
volInfo, err := s.storage.StatVol(args.Vol)
|
volInfo, err := s.storage.StatVol(args.Vol)
|
||||||
@ -86,7 +86,7 @@ func (s *storageServer) StatVolHandler(args *GenericVolArgs, reply *VolInfo) err
|
|||||||
// DeleteVolHandler - delete vol handler is a rpc wrapper for
|
// DeleteVolHandler - delete vol handler is a rpc wrapper for
|
||||||
// DeleteVol operation.
|
// DeleteVol operation.
|
||||||
func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.DeleteVol(args.Vol)
|
return s.storage.DeleteVol(args.Vol)
|
||||||
@ -96,7 +96,7 @@ func (s *storageServer) DeleteVolHandler(args *GenericVolArgs, reply *GenericRep
|
|||||||
|
|
||||||
// StatFileHandler - stat file handler is rpc wrapper to stat file.
|
// StatFileHandler - stat file handler is rpc wrapper to stat file.
|
||||||
func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) error {
|
func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
fileInfo, err := s.storage.StatFile(args.Vol, args.Path)
|
fileInfo, err := s.storage.StatFile(args.Vol, args.Path)
|
||||||
@ -109,7 +109,7 @@ func (s *storageServer) StatFileHandler(args *StatFileArgs, reply *FileInfo) err
|
|||||||
|
|
||||||
// ListDirHandler - list directory handler is rpc wrapper to list dir.
|
// ListDirHandler - list directory handler is rpc wrapper to list dir.
|
||||||
func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error {
|
func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
entries, err := s.storage.ListDir(args.Vol, args.Path)
|
entries, err := s.storage.ListDir(args.Vol, args.Path)
|
||||||
@ -122,7 +122,7 @@ func (s *storageServer) ListDirHandler(args *ListDirArgs, reply *[]string) error
|
|||||||
|
|
||||||
// ReadAllHandler - read all handler is rpc wrapper to read all storage API.
|
// ReadAllHandler - read all handler is rpc wrapper to read all storage API.
|
||||||
func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error {
|
func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
buf, err := s.storage.ReadAll(args.Vol, args.Path)
|
buf, err := s.storage.ReadAll(args.Vol, args.Path)
|
||||||
@ -135,7 +135,7 @@ func (s *storageServer) ReadAllHandler(args *ReadFileArgs, reply *[]byte) error
|
|||||||
|
|
||||||
// ReadFileHandler - read file handler is rpc wrapper to read file.
|
// ReadFileHandler - read file handler is rpc wrapper to read file.
|
||||||
func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err error) {
|
func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err error) {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ func (s *storageServer) ReadFileHandler(args *ReadFileArgs, reply *[]byte) (err
|
|||||||
|
|
||||||
// PrepareFileHandler - prepare file handler is rpc wrapper to prepare file.
|
// PrepareFileHandler - prepare file handler is rpc wrapper to prepare file.
|
||||||
func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *GenericReply) error {
|
func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.PrepareFile(args.Vol, args.Path, args.Size)
|
return s.storage.PrepareFile(args.Vol, args.Path, args.Size)
|
||||||
@ -162,7 +162,7 @@ func (s *storageServer) PrepareFileHandler(args *PrepareFileArgs, reply *Generic
|
|||||||
|
|
||||||
// AppendFileHandler - append file handler is rpc wrapper to append file.
|
// AppendFileHandler - append file handler is rpc wrapper to append file.
|
||||||
func (s *storageServer) AppendFileHandler(args *AppendFileArgs, reply *GenericReply) error {
|
func (s *storageServer) AppendFileHandler(args *AppendFileArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.AppendFile(args.Vol, args.Path, args.Buffer)
|
return s.storage.AppendFile(args.Vol, args.Path, args.Buffer)
|
||||||
@ -170,7 +170,7 @@ func (s *storageServer) AppendFileHandler(args *AppendFileArgs, reply *GenericRe
|
|||||||
|
|
||||||
// DeleteFileHandler - delete file handler is rpc wrapper to delete file.
|
// DeleteFileHandler - delete file handler is rpc wrapper to delete file.
|
||||||
func (s *storageServer) DeleteFileHandler(args *DeleteFileArgs, reply *GenericReply) error {
|
func (s *storageServer) DeleteFileHandler(args *DeleteFileArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.DeleteFile(args.Vol, args.Path)
|
return s.storage.DeleteFile(args.Vol, args.Path)
|
||||||
@ -178,7 +178,7 @@ func (s *storageServer) DeleteFileHandler(args *DeleteFileArgs, reply *GenericRe
|
|||||||
|
|
||||||
// RenameFileHandler - rename file handler is rpc wrapper to rename file.
|
// RenameFileHandler - rename file handler is rpc wrapper to rename file.
|
||||||
func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *GenericReply) error {
|
func (s *storageServer) RenameFileHandler(args *RenameFileArgs, reply *GenericReply) error {
|
||||||
if !isRPCTokenValid(args.Token) {
|
if !isAuthTokenValid(args.Token) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
return s.storage.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath)
|
return s.storage.RenameFile(args.SrcVol, args.SrcPath, args.DstVol, args.DstPath)
|
||||||
|
@ -40,17 +40,8 @@ func createTestStorageServer(t *testing.T) *testStorageRPCServer {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := newJWT(defaultInterNodeJWTExpiry, serverConfig.GetCredential())
|
serverCred := serverConfig.GetCredential()
|
||||||
if err != nil {
|
token, err := authenticateNode(serverCred.AccessKey, serverCred.SecretKey)
|
||||||
t.Fatalf("unable to get new JWT, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = jwt.Authenticate(serverConfig.GetCredential().AccessKey, serverConfig.GetCredential().SecretKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable for JWT to authenticate, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := jwt.GenerateToken(serverConfig.GetCredential().AccessKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable for JWT to generate token, %s", err)
|
t.Fatalf("unable for JWT to generate token, %s", err)
|
||||||
}
|
}
|
||||||
|
@ -1077,8 +1077,8 @@ func signRequestV4(req *http.Request, accessKey, secretKey string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCredential generate a credential string.
|
// getCredentialString generate a credential string.
|
||||||
func getCredential(accessKeyID, location string, t time.Time) string {
|
func getCredentialString(accessKeyID, location string, t time.Time) string {
|
||||||
return accessKeyID + "/" + getScope(t, location)
|
return accessKeyID + "/" + getScope(t, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +29,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwtgo "github.com/dgrijalva/jwt-go"
|
|
||||||
jwtreq "github.com/dgrijalva/jwt-go/request"
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/rpc/v2/json2"
|
"github.com/gorilla/rpc/v2/json2"
|
||||||
@ -38,30 +36,6 @@ import (
|
|||||||
"github.com/minio/miniobrowser"
|
"github.com/minio/miniobrowser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isJWTReqAuthenticated validates if any incoming request to be a
|
|
||||||
// valid JWT authenticated request.
|
|
||||||
func isJWTReqAuthenticated(req *http.Request) bool {
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential())
|
|
||||||
if err != nil {
|
|
||||||
errorIf(err, "unable to initialize a new JWT")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var reqCallback jwtgo.Keyfunc
|
|
||||||
reqCallback = func(token *jwtgo.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
return []byte(jwt.SecretKey), nil
|
|
||||||
}
|
|
||||||
token, err := jwtreq.ParseFromRequest(req, jwtreq.AuthorizationHeaderExtractor, reqCallback)
|
|
||||||
if err != nil {
|
|
||||||
errorIf(err, "token parsing failed")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return token.Valid
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebGenericArgs - empty struct for calls that don't accept arguments
|
// WebGenericArgs - empty struct for calls that don't accept arguments
|
||||||
// for ex. ServerInfo, GenerateAuth
|
// for ex. ServerInfo, GenerateAuth
|
||||||
type WebGenericArgs struct{}
|
type WebGenericArgs struct{}
|
||||||
@ -84,7 +58,7 @@ type ServerInfoRep struct {
|
|||||||
|
|
||||||
// ServerInfo - get server info.
|
// ServerInfo - get server info.
|
||||||
func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
|
func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error {
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
host, err := os.Hostname()
|
host, err := os.Hostname()
|
||||||
@ -125,7 +99,7 @@ func (web *webAPIHandlers) StorageInfo(r *http.Request, args *GenericArgs, reply
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
reply.StorageInfo = objectAPI.StorageInfo()
|
reply.StorageInfo = objectAPI.StorageInfo()
|
||||||
@ -144,7 +118,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
bucketLock := globalNSMutex.NewNSLock(args.BucketName, "")
|
bucketLock := globalNSMutex.NewNSLock(args.BucketName, "")
|
||||||
@ -177,7 +151,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
buckets, err := objectAPI.ListBuckets()
|
buckets, err := objectAPI.ListBuckets()
|
||||||
@ -227,7 +201,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
marker := ""
|
marker := ""
|
||||||
@ -271,7 +245,7 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
|
|||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,19 +292,11 @@ type LoginRep struct {
|
|||||||
|
|
||||||
// Login - user login handler.
|
// Login - user login handler.
|
||||||
func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
|
func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error {
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential())
|
token, err := authenticateWeb(args.Username, args.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toJSONError(err)
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = jwt.Authenticate(args.Username, args.Password); err != nil {
|
|
||||||
return toJSONError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := jwt.GenerateToken(args.Username)
|
|
||||||
if err != nil {
|
|
||||||
return toJSONError(err)
|
|
||||||
}
|
|
||||||
reply.Token = token
|
reply.Token = token
|
||||||
reply.UIVersion = miniobrowser.UIVersion
|
reply.UIVersion = miniobrowser.UIVersion
|
||||||
return nil
|
return nil
|
||||||
@ -344,7 +310,7 @@ type GenerateAuthReply struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
|
func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error {
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
cred := newCredential()
|
cred := newCredential()
|
||||||
@ -369,41 +335,16 @@ type SetAuthReply struct {
|
|||||||
|
|
||||||
// SetAuth - Set accessKey and secretKey credentials.
|
// SetAuth - Set accessKey and secretKey credentials.
|
||||||
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
|
func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error {
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize jwt with the new access keys, fail if not possible.
|
// As we already validated the authentication, we save given access/secret keys.
|
||||||
jwt, err := newJWT(defaultJWTExpiry, credential{
|
cred, err := getCredential(args.AccessKey, args.SecretKey)
|
||||||
AccessKey: args.AccessKey,
|
|
||||||
SecretKey: args.SecretKey,
|
|
||||||
}) // JWT Expiry set to 24Hrs.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toJSONError(err)
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate the secret key properly.
|
|
||||||
if err = jwt.Authenticate(args.AccessKey, args.SecretKey); err != nil {
|
|
||||||
return toJSONError(err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
unexpErrsMsg := "Unexpected error(s) occurred - please check minio server logs."
|
|
||||||
gaveUpMsg := func(errMsg error, moreErrors bool) *json2.Error {
|
|
||||||
msg := fmt.Sprintf(
|
|
||||||
"We gave up due to: '%s', but there were more errors. Please check minio server logs.",
|
|
||||||
errMsg.Error(),
|
|
||||||
)
|
|
||||||
var err *json2.Error
|
|
||||||
if moreErrors {
|
|
||||||
err = toJSONError(errors.New(msg))
|
|
||||||
} else {
|
|
||||||
err = toJSONError(errMsg)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cred := credential{args.AccessKey, args.SecretKey}
|
|
||||||
// Notify all other Minio peers to update credentials
|
// Notify all other Minio peers to update credentials
|
||||||
errsMap := updateCredsOnPeers(cred)
|
errsMap := updateCredsOnPeers(cred)
|
||||||
|
|
||||||
@ -427,19 +368,21 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se
|
|||||||
// Since the error message may be very long to display
|
// Since the error message may be very long to display
|
||||||
// on the browser, we tell the user to check the
|
// on the browser, we tell the user to check the
|
||||||
// server logs.
|
// server logs.
|
||||||
return toJSONError(errors.New(unexpErrsMsg))
|
return toJSONError(errors.New("unexpected error(s) occurred - please check minio server logs"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did we have peer errors?
|
// As we have updated access/secret key, generate new auth token.
|
||||||
var moreErrors bool
|
token, err := authenticateWeb(args.AccessKey, args.SecretKey)
|
||||||
if len(errsMap) > 0 {
|
|
||||||
moreErrors = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a JWT token.
|
|
||||||
token, err := jwt.GenerateToken(args.AccessKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gaveUpMsg(err, moreErrors)
|
// Did we have peer errors?
|
||||||
|
if len(errsMap) > 0 {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"we gave up due to: '%s', but there were more errors. Please check minio server logs",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return toJSONError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Token = token
|
reply.Token = token
|
||||||
@ -456,7 +399,7 @@ type GetAuthReply struct {
|
|||||||
|
|
||||||
// GetAuth - return accessKey and secretKey credentials.
|
// GetAuth - return accessKey and secretKey credentials.
|
||||||
func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
|
func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error {
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
creds := serverConfig.GetCredential()
|
creds := serverConfig.GetCredential()
|
||||||
@ -474,7 +417,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
writeWebErrorResponse(w, errAuthentication)
|
writeWebErrorResponse(w, errAuthentication)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -519,24 +462,13 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := vars["object"]
|
object := vars["object"]
|
||||||
tokenStr := r.URL.Query().Get("token")
|
token := r.URL.Query().Get("token")
|
||||||
|
|
||||||
jwt, err := newJWT(defaultJWTExpiry, serverConfig.GetCredential()) // Expiry set to 24Hrs.
|
if !isAuthTokenValid(token) {
|
||||||
if err != nil {
|
|
||||||
errorIf(err, "error in getting new JWT")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token, e := jwtgo.Parse(tokenStr, func(token *jwtgo.Token) (interface{}, error) {
|
|
||||||
if _, ok := token.Method.(*jwtgo.SigningMethodHMAC); !ok {
|
|
||||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
||||||
}
|
|
||||||
return []byte(jwt.SecretKey), nil
|
|
||||||
})
|
|
||||||
if e != nil || !token.Valid {
|
|
||||||
writeWebErrorResponse(w, errAuthentication)
|
writeWebErrorResponse(w, errAuthentication)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add content disposition.
|
// Add content disposition.
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", path.Base(object)))
|
||||||
|
|
||||||
@ -601,7 +533,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
|
|||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,7 +572,7 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
|
|||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +605,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
|||||||
return toJSONError(errServerNotInitialized)
|
return toJSONError(errServerNotInitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,7 +673,7 @@ type PresignedGetRep struct {
|
|||||||
|
|
||||||
// PresignedGET - returns presigned-Get url.
|
// PresignedGET - returns presigned-Get url.
|
||||||
func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
|
func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error {
|
||||||
if !isJWTReqAuthenticated(r) {
|
if !isHTTPRequestValid(r) {
|
||||||
return toJSONError(errAuthentication)
|
return toJSONError(errAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user