2016-08-22 14:01:21 -04:00
|
|
|
/*
|
|
|
|
* 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 (
|
2016-08-23 19:26:42 -04:00
|
|
|
"net/rpc"
|
2016-11-11 03:14:32 -05:00
|
|
|
"sync"
|
2016-08-22 14:01:21 -04:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2016-08-24 13:14:14 -04:00
|
|
|
// GenericReply represents any generic RPC reply.
|
|
|
|
type GenericReply struct{}
|
|
|
|
|
|
|
|
// GenericArgs represents any generic RPC arguments.
|
|
|
|
type GenericArgs struct {
|
2016-10-11 03:50:27 -04:00
|
|
|
Token string // Used to authenticate every RPC call.
|
|
|
|
// Used to verify if the RPC call was issued between
|
|
|
|
// the same Login() and disconnect event pair.
|
|
|
|
Timestamp time.Time
|
|
|
|
|
|
|
|
// Indicates if args should be sent to remote peers as well.
|
|
|
|
Remote bool
|
2016-08-24 13:14:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetToken - sets the token to the supplied value.
|
|
|
|
func (ga *GenericArgs) SetToken(token string) {
|
|
|
|
ga.Token = token
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetTimestamp - sets the timestamp to the supplied value.
|
|
|
|
func (ga *GenericArgs) SetTimestamp(tstamp time.Time) {
|
|
|
|
ga.Timestamp = tstamp
|
|
|
|
}
|
|
|
|
|
|
|
|
// RPCLoginArgs - login username and password for RPC.
|
|
|
|
type RPCLoginArgs struct {
|
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
}
|
|
|
|
|
|
|
|
// RPCLoginReply - login reply provides generated token to be used
|
|
|
|
// with subsequent requests.
|
|
|
|
type RPCLoginReply struct {
|
|
|
|
Token string
|
|
|
|
Timestamp time.Time
|
2016-09-24 06:34:45 -04:00
|
|
|
ServerVersion string
|
2016-08-24 13:14:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Auth config represents authentication credentials and Login method name to be used
|
|
|
|
// for fetching JWT tokens from the RPC server.
|
|
|
|
type authConfig struct {
|
|
|
|
accessKey string // Username for the server.
|
|
|
|
secretKey string // Password for the server.
|
2016-09-30 02:42:37 -04:00
|
|
|
secureConn bool // Ask for a secured connection
|
2016-08-24 13:14:14 -04:00
|
|
|
address string // Network address path of RPC server.
|
|
|
|
path string // Network path for HTTP dial.
|
|
|
|
loginMethod string // RPC service name for authenticating using JWT
|
|
|
|
}
|
|
|
|
|
2016-08-22 14:01:21 -04:00
|
|
|
// AuthRPCClient is a wrapper type for RPCClient which provides JWT based authentication across reconnects.
|
|
|
|
type AuthRPCClient struct {
|
2016-11-23 18:48:10 -05:00
|
|
|
mu sync.Mutex
|
|
|
|
config *authConfig
|
|
|
|
rpc *RPCClient // reconnect'able rpc client built on top of net/rpc Client
|
|
|
|
serverToken string // Disk rpc JWT based token.
|
|
|
|
serverVersion string // Server version exchanged by the RPC.
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// newAuthClient - returns a jwt based authenticated (go) rpc client, which does automatic reconnect.
|
2016-08-24 13:14:14 -04:00
|
|
|
func newAuthClient(cfg *authConfig) *AuthRPCClient {
|
2016-08-22 14:01:21 -04:00
|
|
|
return &AuthRPCClient{
|
2016-08-24 13:14:14 -04:00
|
|
|
// Save the config.
|
|
|
|
config: cfg,
|
|
|
|
// Initialize a new reconnectable rpc client.
|
2016-12-17 21:17:40 -05:00
|
|
|
rpc: newRPCClient(cfg.address, cfg.path, cfg.secureConn),
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close - closes underlying rpc connection.
|
|
|
|
func (authClient *AuthRPCClient) Close() error {
|
2016-11-11 03:14:32 -05:00
|
|
|
authClient.mu.Lock()
|
2016-08-22 14:01:21 -04:00
|
|
|
// reset token on closing a connection
|
2016-12-29 22:42:02 -05:00
|
|
|
authClient.serverToken = ""
|
2016-11-11 03:14:32 -05:00
|
|
|
authClient.mu.Unlock()
|
2016-08-22 14:01:21 -04:00
|
|
|
return authClient.rpc.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Login - a jwt based authentication is performed with rpc server.
|
2016-11-20 19:57:12 -05:00
|
|
|
func (authClient *AuthRPCClient) Login() (err error) {
|
2016-11-11 03:14:32 -05:00
|
|
|
authClient.mu.Lock()
|
2016-11-20 19:57:12 -05:00
|
|
|
// As soon as the function returns unlock,
|
2016-11-11 03:14:32 -05:00
|
|
|
defer authClient.mu.Unlock()
|
2016-11-20 19:57:12 -05:00
|
|
|
|
2016-08-23 22:19:24 -04:00
|
|
|
// Return if already logged in.
|
2016-12-29 22:42:02 -05:00
|
|
|
if authClient.serverToken != "" {
|
2016-08-23 22:19:24 -04:00
|
|
|
return nil
|
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
|
2016-08-22 14:01:21 -04:00
|
|
|
reply := RPCLoginReply{}
|
2016-11-20 19:57:12 -05:00
|
|
|
if err = authClient.rpc.Call(authClient.config.loginMethod, RPCLoginArgs{
|
2016-08-24 13:14:14 -04:00
|
|
|
Username: authClient.config.accessKey,
|
|
|
|
Password: authClient.config.secretKey,
|
2016-08-22 14:01:21 -04:00
|
|
|
}, &reply); err != nil {
|
2016-08-23 22:19:24 -04:00
|
|
|
return err
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
|
2016-09-24 06:34:45 -04:00
|
|
|
// Validate if version do indeed match.
|
|
|
|
if reply.ServerVersion != Version {
|
|
|
|
return errServerVersionMismatch
|
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
|
|
|
|
// Validate if server timestamp is skewed.
|
2016-10-07 14:15:55 -04:00
|
|
|
curTime := time.Now().UTC()
|
|
|
|
if curTime.Sub(reply.Timestamp) > globalMaxSkewTime {
|
|
|
|
return errServerTimeMismatch
|
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
|
2016-08-23 22:19:24 -04:00
|
|
|
// Set token, time stamp as received from a successful login call.
|
2016-11-20 19:57:12 -05:00
|
|
|
authClient.serverToken = reply.Token
|
2016-10-10 13:11:56 -04:00
|
|
|
authClient.serverVersion = reply.ServerVersion
|
2016-08-23 22:19:24 -04:00
|
|
|
return nil
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Call - If rpc connection isn't established yet since previous disconnect,
|
|
|
|
// connection is established, a jwt authenticated login is performed and then
|
|
|
|
// the call is performed.
|
2016-08-24 13:14:14 -04:00
|
|
|
func (authClient *AuthRPCClient) Call(serviceMethod string, args interface {
|
|
|
|
SetToken(token string)
|
|
|
|
SetTimestamp(tstamp time.Time)
|
|
|
|
}, reply interface{}) (err error) {
|
2016-12-29 22:42:02 -05:00
|
|
|
loginAndCallFn := func() error {
|
|
|
|
// On successful login, proceed to attempt the requested service method.
|
|
|
|
if err = authClient.Login(); err == nil {
|
|
|
|
// Set token and timestamp before the rpc call.
|
|
|
|
args.SetToken(authClient.serverToken)
|
|
|
|
args.SetTimestamp(time.Now().UTC())
|
|
|
|
|
|
|
|
// Finally make the network call using net/rpc client.
|
|
|
|
err = authClient.rpc.Call(serviceMethod, args, reply)
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
2016-12-29 22:42:02 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
doneCh := make(chan struct{})
|
|
|
|
defer close(doneCh)
|
|
|
|
for i := range newRetryTimer(time.Second, time.Second*30, MaxJitter, doneCh) {
|
|
|
|
// Invalidate token, and mark it for re-login and
|
|
|
|
// reconnect upon rpc shutdown.
|
|
|
|
if err = loginAndCallFn(); err == rpc.ErrShutdown {
|
|
|
|
// Close the underlying connection, and proceed to reconnect
|
|
|
|
// if we haven't reached the retry threshold.
|
|
|
|
authClient.Close()
|
|
|
|
|
|
|
|
// No need to return error until the retry count threshold has reached.
|
|
|
|
if i < globalMaxAuthRPCRetryThreshold {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
2016-08-23 19:26:42 -04:00
|
|
|
}
|
|
|
|
return err
|
2016-08-22 14:01:21 -04:00
|
|
|
}
|
2016-09-16 03:30:55 -04:00
|
|
|
|
|
|
|
// Node returns the node (network address) of the connection
|
2016-11-20 19:57:12 -05:00
|
|
|
func (authClient *AuthRPCClient) Node() (node string) {
|
2016-09-16 03:30:55 -04:00
|
|
|
if authClient.rpc != nil {
|
2016-11-20 19:57:12 -05:00
|
|
|
node = authClient.rpc.node
|
2016-09-16 03:30:55 -04:00
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
return node
|
2016-09-16 03:30:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// RPCPath returns the RPC path of the connection
|
2016-11-20 19:57:12 -05:00
|
|
|
func (authClient *AuthRPCClient) RPCPath() (rpcPath string) {
|
2016-09-16 03:30:55 -04:00
|
|
|
if authClient.rpc != nil {
|
2016-11-20 19:57:12 -05:00
|
|
|
rpcPath = authClient.rpc.rpcPath
|
2016-09-16 03:30:55 -04:00
|
|
|
}
|
2016-11-20 19:57:12 -05:00
|
|
|
return rpcPath
|
2016-09-16 03:30:55 -04:00
|
|
|
}
|