From f8bb85aeb7842106e60569eb8535380f042d5b9e Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 18 Sep 2015 02:59:57 -0700 Subject: [PATCH] Enhance auth JSONRPC, now provides persistent output Implements - Auth.Generate("user") - Auth.Fetch("user") - Auth.Reset("user") This patch also adds testing for each of these cases --- pkg/auth/config.go | 61 ++++++++++++-- pkg/controller/rpc/auth.go | 120 +++++++++++++++++++++++++--- pkg/controller/rpc/version.go | 8 +- pkg/controller/rpc_test.go | 47 ++++++++++- pkg/probe/probe.go | 6 ++ pkg/server/api_signature_v4_test.go | 2 +- 6 files changed, 220 insertions(+), 24 deletions(-) diff --git a/pkg/auth/config.go b/pkg/auth/config.go index 008e26f77..0d37aa46b 100644 --- a/pkg/auth/config.go +++ b/pkg/auth/config.go @@ -17,6 +17,7 @@ package auth import ( + "os" "os/user" "path/filepath" @@ -37,7 +38,7 @@ type Config struct { Users map[string]*User } -// getAuthConfigPath get donut config file path +// getAuthConfigPath get users config path func getAuthConfigPath() (string, *probe.Error) { if customConfigPath != "" { return customConfigPath, nil @@ -46,10 +47,51 @@ func getAuthConfigPath() (string, *probe.Error) { if err != nil { return "", probe.NewError(err) } - authConfigPath := filepath.Join(u.HomeDir, ".minio", "users.json") + authConfigPath := filepath.Join(u.HomeDir, ".minio") return authConfigPath, nil } +// createAuthConfigPath create users config path +func createAuthConfigPath() *probe.Error { + authConfigPath, err := getAuthConfigPath() + if err != nil { + return err.Trace() + } + if err := os.MkdirAll(authConfigPath, 0700); err != nil { + return probe.NewError(err) + } + return nil +} + +// isAuthConfigFileExists is auth config file exists? +func isAuthConfigFileExists() bool { + if _, err := os.Stat(mustGetAuthConfigFile()); err != nil { + if os.IsNotExist(err) { + return false + } + panic(err) + } + return true +} + +// mustGetAuthConfigFile always get users config file, if not panic +func mustGetAuthConfigFile() string { + authConfigFile, err := getAuthConfigFile() + if err != nil { + panic(err) + } + return authConfigFile +} + +// getAuthConfigFile get users config file +func getAuthConfigFile() (string, *probe.Error) { + authConfigPath, err := getAuthConfigPath() + if err != nil { + return "", err.Trace() + } + return filepath.Join(authConfigPath, "users.json"), nil +} + // customConfigPath not accessed from outside only allowed through get/set methods var customConfigPath string @@ -58,9 +100,9 @@ func SetAuthConfigPath(configPath string) { customConfigPath = configPath } -// SaveConfig save donut config +// SaveConfig save auth config func SaveConfig(a *Config) *probe.Error { - authConfigPath, err := getAuthConfigPath() + authConfigFile, err := getAuthConfigFile() if err != nil { return err.Trace() } @@ -68,18 +110,21 @@ func SaveConfig(a *Config) *probe.Error { if err != nil { return err.Trace() } - if err := qc.Save(authConfigPath); err != nil { + if err := qc.Save(authConfigFile); err != nil { return err.Trace() } return nil } -// LoadConfig load donut config +// LoadConfig load auth config func LoadConfig() (*Config, *probe.Error) { - authConfigPath, err := getAuthConfigPath() + authConfigFile, err := getAuthConfigFile() if err != nil { return nil, err.Trace() } + if _, err := os.Stat(authConfigFile); err != nil { + return nil, probe.NewError(err) + } a := &Config{} a.Version = "0.0.1" a.Users = make(map[string]*User) @@ -87,7 +132,7 @@ func LoadConfig() (*Config, *probe.Error) { if err != nil { return nil, err.Trace() } - if err := qc.Load(authConfigPath); err != nil { + if err := qc.Load(authConfigFile); err != nil { return nil, err.Trace() } return qc.Data().(*Config), nil diff --git a/pkg/controller/rpc/auth.go b/pkg/controller/rpc/auth.go index 83ad7d2be..7641bc8c5 100644 --- a/pkg/controller/rpc/auth.go +++ b/pkg/controller/rpc/auth.go @@ -17,7 +17,10 @@ package rpc import ( + "errors" "net/http" + "os" + "strings" "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/probe" @@ -26,29 +29,126 @@ import ( // AuthService auth service type AuthService struct{} +// AuthArgs auth params +type AuthArgs struct { + User string `json:"user"` +} + // AuthReply reply with new access keys and secret ids type AuthReply struct { - AccessKeyID string `json:"accesskey"` - SecretAccessKey string `json:"secretaccesskey"` + Name string `json:"name"` + AccessKeyID string `json:"accessKeyId"` + SecretAccessKey string `json:"secretAccessKey"` } -func getAuth(reply *AuthReply) *probe.Error { - accessID, err := auth.GenerateAccessKeyID() +// generateAuth generate new auth keys for a user +func generateAuth(args *AuthArgs, reply *AuthReply) *probe.Error { + config, err := auth.LoadConfig() + if err != nil { + if os.IsNotExist(err.ToGoError()) { + // Initialize new config, since config file doesn't exist yet + config = &auth.Config{} + config.Version = "0.0.1" + config.Users = make(map[string]*auth.User) + } else { + return err.Trace() + } + } + if _, ok := config.Users[args.User]; ok { + return probe.NewError(errors.New("Credentials already set, if you wish to change this invoke Reset() method")) + } + accessKeyID, err := auth.GenerateAccessKeyID() if err != nil { return err.Trace() } - reply.AccessKeyID = string(accessID) - secretID, err := auth.GenerateSecretAccessKey() + reply.AccessKeyID = string(accessKeyID) + + secretAccessKey, err := auth.GenerateSecretAccessKey() + if err != nil { + return err.Trace() + } + reply.SecretAccessKey = string(secretAccessKey) + config.Users[args.User] = &auth.User{ + Name: args.User, + AccessKeyID: string(accessKeyID), + SecretAccessKey: string(secretAccessKey), + } + if err := auth.SaveConfig(config); err != nil { + return err.Trace() + } + return nil +} + +// fetchAuth fetch auth keys for a user +func fetchAuth(args *AuthArgs, reply *AuthReply) *probe.Error { + config, err := auth.LoadConfig() if err != nil { return err.Trace() } - reply.SecretAccessKey = string(secretID) + if _, ok := config.Users[args.User]; !ok { + return probe.NewError(errors.New("User not found")) + } + reply.AccessKeyID = config.Users[args.User].AccessKeyID + reply.SecretAccessKey = config.Users[args.User].SecretAccessKey return nil } -// Get auth keys -func (s *AuthService) Get(r *http.Request, args *Args, reply *AuthReply) error { - if err := getAuth(reply); err != nil { +// resetAuth reset auth keys for a user +func resetAuth(args *AuthArgs, reply *AuthReply) *probe.Error { + config, err := auth.LoadConfig() + if err != nil { + return err.Trace() + } + if _, ok := config.Users[args.User]; !ok { + return probe.NewError(errors.New("User not found")) + } + accessKeyID, err := auth.GenerateAccessKeyID() + if err != nil { + return err.Trace() + } + reply.AccessKeyID = string(accessKeyID) + secretAccessKey, err := auth.GenerateSecretAccessKey() + if err != nil { + return err.Trace() + } + reply.SecretAccessKey = string(secretAccessKey) + + config.Users[args.User] = &auth.User{ + Name: args.User, + AccessKeyID: string(accessKeyID), + SecretAccessKey: string(secretAccessKey), + } + return auth.SaveConfig(config).Trace() +} + +// Generate auth keys +func (s *AuthService) Generate(r *http.Request, args *AuthArgs, reply *AuthReply) error { + if strings.TrimSpace(args.User) == "" { + return errors.New("Invalid argument") + } + if err := generateAuth(args, reply); err != nil { + return probe.WrapError(err) + } + return nil +} + +// Fetch auth keys +func (s *AuthService) Fetch(r *http.Request, args *AuthArgs, reply *AuthReply) error { + if strings.TrimSpace(args.User) == "" { + return errors.New("Invalid argument") + } + if err := fetchAuth(args, reply); err != nil { + return probe.WrapError(err) + } + return nil +} + +// Reset auth keys, generates new set of auth keys +func (s *AuthService) Reset(r *http.Request, args *AuthArgs, reply *AuthReply) error { + if strings.TrimSpace(args.User) == "" { + return errors.New("Invalid argument") + } + if err := resetAuth(args, reply); err != nil { return probe.WrapError(err) } return nil diff --git a/pkg/controller/rpc/version.go b/pkg/controller/rpc/version.go index 9ea831552..a6f4e41b0 100644 --- a/pkg/controller/rpc/version.go +++ b/pkg/controller/rpc/version.go @@ -18,7 +18,8 @@ package rpc import ( "net/http" - "time" + + "github.com/minio/minio/pkg/version" ) // Args basic json RPC params @@ -29,7 +30,7 @@ type Args struct { // VersionReply version reply type VersionReply struct { Version string `json:"version"` - BuildDate string `json:"build-date"` + BuildDate string `json:"buildDate"` } // VersionService - @@ -38,8 +39,9 @@ type VersionService struct{} func getVersion() string { return "0.0.1" } + func getBuildDate() string { - return time.Now().UTC().Format(http.TimeFormat) + return version.Version } func setVersionReply(reply *VersionReply) { diff --git a/pkg/controller/rpc_test.go b/pkg/controller/rpc_test.go index 3d01e65e6..dbe90763b 100644 --- a/pkg/controller/rpc_test.go +++ b/pkg/controller/rpc_test.go @@ -17,11 +17,14 @@ package controller import ( + "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" jsonrpc "github.com/gorilla/rpc/v2/json" + "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/controller/rpc" . "gopkg.in/check.v1" ) @@ -36,6 +39,10 @@ var _ = Suite(&MySuite{}) var testRPCServer *httptest.Server func (s *MySuite) SetUpSuite(c *C) { + root, err := ioutil.TempDir(os.TempDir(), "api-") + c.Assert(err, IsNil) + auth.SetAuthConfigPath(root) + testRPCServer = httptest.NewServer(getRPCHandler()) } @@ -81,8 +88,8 @@ func (s *MySuite) TestSysInfo(c *C) { func (s *MySuite) TestAuth(c *C) { op := rpc.Operation{ - Method: "Auth.Get", - Request: rpc.Args{Request: ""}, + Method: "Auth.Generate", + Request: rpc.AuthArgs{User: "newuser"}, } req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) @@ -97,4 +104,40 @@ func (s *MySuite) TestAuth(c *C) { c.Assert(reply, Not(DeepEquals), rpc.AuthReply{}) c.Assert(len(reply.AccessKeyID), Equals, 20) c.Assert(len(reply.SecretAccessKey), Equals, 40) + + op = rpc.Operation{ + Method: "Auth.Fetch", + Request: rpc.AuthArgs{User: "newuser"}, + } + req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + c.Assert(err, IsNil) + c.Assert(req.Get("Content-Type"), Equals, "application/json") + resp, err = req.Do() + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + + var newReply rpc.AuthReply + c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &newReply), IsNil) + resp.Body.Close() + c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) + c.Assert(reply.AccessKeyID, Equals, newReply.AccessKeyID) + c.Assert(reply.SecretAccessKey, Equals, newReply.SecretAccessKey) + + op = rpc.Operation{ + Method: "Auth.Reset", + Request: rpc.AuthArgs{User: "newuser"}, + } + req, err = rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + c.Assert(err, IsNil) + c.Assert(req.Get("Content-Type"), Equals, "application/json") + resp, err = req.Do() + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + + var resetReply rpc.AuthReply + c.Assert(jsonrpc.DecodeClientResponse(resp.Body, &resetReply), IsNil) + resp.Body.Close() + c.Assert(newReply, Not(DeepEquals), rpc.AuthReply{}) + c.Assert(reply.AccessKeyID, Not(Equals), resetReply.AccessKeyID) + c.Assert(reply.SecretAccessKey, Not(Equals), resetReply.SecretAccessKey) } diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go index 06c2be2c2..e662e6f84 100644 --- a/pkg/probe/probe.go +++ b/pkg/probe/probe.go @@ -107,6 +107,9 @@ func (e *Error) Trace(fields ...string) *Error { // Internal trace - records the point at which it is invoked. func (e *Error) trace(fields ...string) *Error { + if e == nil { + return nil + } pc, file, line, _ := runtime.Caller(2) function := runtime.FuncForPC(pc).Name() _, function = filepath.Split(function) @@ -139,6 +142,9 @@ func (e *Error) Untrace() *Error { // ToGoError returns original error message. func (e *Error) ToGoError() error { + if e == nil || e.Cause == nil { + return nil + } return e.Cause } diff --git a/pkg/server/api_signature_v4_test.go b/pkg/server/api_signature_v4_test.go index 8e6f72dbb..7c82f27ae 100644 --- a/pkg/server/api_signature_v4_test.go +++ b/pkg/server/api_signature_v4_test.go @@ -75,7 +75,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) { s.accessKeyID = string(accessKeyID) s.secretAccessKey = string(secretAccessKey) - auth.SetAuthConfigPath(filepath.Join(root, "users.json")) + auth.SetAuthConfigPath(root) perr = auth.SaveConfig(authConf) c.Assert(perr, IsNil)