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
master
Harshavardhana 9 years ago
parent 51652c38cb
commit f8bb85aeb7
  1. 61
      pkg/auth/config.go
  2. 120
      pkg/controller/rpc/auth.go
  3. 8
      pkg/controller/rpc/version.go
  4. 47
      pkg/controller/rpc_test.go
  5. 6
      pkg/probe/probe.go
  6. 2
      pkg/server/api_signature_v4_test.go

@ -17,6 +17,7 @@
package auth package auth
import ( import (
"os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -37,7 +38,7 @@ type Config struct {
Users map[string]*User Users map[string]*User
} }
// getAuthConfigPath get donut config file path // getAuthConfigPath get users config path
func getAuthConfigPath() (string, *probe.Error) { func getAuthConfigPath() (string, *probe.Error) {
if customConfigPath != "" { if customConfigPath != "" {
return customConfigPath, nil return customConfigPath, nil
@ -46,10 +47,51 @@ func getAuthConfigPath() (string, *probe.Error) {
if err != nil { if err != nil {
return "", probe.NewError(err) return "", probe.NewError(err)
} }
authConfigPath := filepath.Join(u.HomeDir, ".minio", "users.json") authConfigPath := filepath.Join(u.HomeDir, ".minio")
return authConfigPath, nil 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 // customConfigPath not accessed from outside only allowed through get/set methods
var customConfigPath string var customConfigPath string
@ -58,9 +100,9 @@ func SetAuthConfigPath(configPath string) {
customConfigPath = configPath customConfigPath = configPath
} }
// SaveConfig save donut config // SaveConfig save auth config
func SaveConfig(a *Config) *probe.Error { func SaveConfig(a *Config) *probe.Error {
authConfigPath, err := getAuthConfigPath() authConfigFile, err := getAuthConfigFile()
if err != nil { if err != nil {
return err.Trace() return err.Trace()
} }
@ -68,18 +110,21 @@ func SaveConfig(a *Config) *probe.Error {
if err != nil { if err != nil {
return err.Trace() return err.Trace()
} }
if err := qc.Save(authConfigPath); err != nil { if err := qc.Save(authConfigFile); err != nil {
return err.Trace() return err.Trace()
} }
return nil return nil
} }
// LoadConfig load donut config // LoadConfig load auth config
func LoadConfig() (*Config, *probe.Error) { func LoadConfig() (*Config, *probe.Error) {
authConfigPath, err := getAuthConfigPath() authConfigFile, err := getAuthConfigFile()
if err != nil { if err != nil {
return nil, err.Trace() return nil, err.Trace()
} }
if _, err := os.Stat(authConfigFile); err != nil {
return nil, probe.NewError(err)
}
a := &Config{} a := &Config{}
a.Version = "0.0.1" a.Version = "0.0.1"
a.Users = make(map[string]*User) a.Users = make(map[string]*User)
@ -87,7 +132,7 @@ func LoadConfig() (*Config, *probe.Error) {
if err != nil { if err != nil {
return nil, err.Trace() return nil, err.Trace()
} }
if err := qc.Load(authConfigPath); err != nil { if err := qc.Load(authConfigFile); err != nil {
return nil, err.Trace() return nil, err.Trace()
} }
return qc.Data().(*Config), nil return qc.Data().(*Config), nil

@ -17,7 +17,10 @@
package rpc package rpc
import ( import (
"errors"
"net/http" "net/http"
"os"
"strings"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/probe" "github.com/minio/minio/pkg/probe"
@ -26,29 +29,126 @@ import (
// AuthService auth service // AuthService auth service
type AuthService struct{} type AuthService struct{}
// AuthArgs auth params
type AuthArgs struct {
User string `json:"user"`
}
// AuthReply reply with new access keys and secret ids // AuthReply reply with new access keys and secret ids
type AuthReply struct { type AuthReply struct {
AccessKeyID string `json:"accesskey"` Name string `json:"name"`
SecretAccessKey string `json:"secretaccesskey"` AccessKeyID string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
} }
func getAuth(reply *AuthReply) *probe.Error { // generateAuth generate new auth keys for a user
accessID, err := auth.GenerateAccessKeyID() 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 { if err != nil {
return err.Trace() return err.Trace()
} }
reply.AccessKeyID = string(accessID) reply.AccessKeyID = string(accessKeyID)
secretID, err := auth.GenerateSecretAccessKey()
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 { if err != nil {
return err.Trace() 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 return nil
} }
// Get auth keys // resetAuth reset auth keys for a user
func (s *AuthService) Get(r *http.Request, args *Args, reply *AuthReply) error { func resetAuth(args *AuthArgs, reply *AuthReply) *probe.Error {
if err := getAuth(reply); err != nil { 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 probe.WrapError(err)
} }
return nil return nil

@ -18,7 +18,8 @@ package rpc
import ( import (
"net/http" "net/http"
"time"
"github.com/minio/minio/pkg/version"
) )
// Args basic json RPC params // Args basic json RPC params
@ -29,7 +30,7 @@ type Args struct {
// VersionReply version reply // VersionReply version reply
type VersionReply struct { type VersionReply struct {
Version string `json:"version"` Version string `json:"version"`
BuildDate string `json:"build-date"` BuildDate string `json:"buildDate"`
} }
// VersionService - // VersionService -
@ -38,8 +39,9 @@ type VersionService struct{}
func getVersion() string { func getVersion() string {
return "0.0.1" return "0.0.1"
} }
func getBuildDate() string { func getBuildDate() string {
return time.Now().UTC().Format(http.TimeFormat) return version.Version
} }
func setVersionReply(reply *VersionReply) { func setVersionReply(reply *VersionReply) {

@ -17,11 +17,14 @@
package controller package controller
import ( import (
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "testing"
jsonrpc "github.com/gorilla/rpc/v2/json" jsonrpc "github.com/gorilla/rpc/v2/json"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/controller/rpc" "github.com/minio/minio/pkg/controller/rpc"
. "gopkg.in/check.v1" . "gopkg.in/check.v1"
) )
@ -36,6 +39,10 @@ var _ = Suite(&MySuite{})
var testRPCServer *httptest.Server var testRPCServer *httptest.Server
func (s *MySuite) SetUpSuite(c *C) { func (s *MySuite) SetUpSuite(c *C) {
root, err := ioutil.TempDir(os.TempDir(), "api-")
c.Assert(err, IsNil)
auth.SetAuthConfigPath(root)
testRPCServer = httptest.NewServer(getRPCHandler()) testRPCServer = httptest.NewServer(getRPCHandler())
} }
@ -81,8 +88,8 @@ func (s *MySuite) TestSysInfo(c *C) {
func (s *MySuite) TestAuth(c *C) { func (s *MySuite) TestAuth(c *C) {
op := rpc.Operation{ op := rpc.Operation{
Method: "Auth.Get", Method: "Auth.Generate",
Request: rpc.Args{Request: ""}, Request: rpc.AuthArgs{User: "newuser"},
} }
req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) req, err := rpc.NewRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport)
c.Assert(err, IsNil) c.Assert(err, IsNil)
@ -97,4 +104,40 @@ func (s *MySuite) TestAuth(c *C) {
c.Assert(reply, Not(DeepEquals), rpc.AuthReply{}) c.Assert(reply, Not(DeepEquals), rpc.AuthReply{})
c.Assert(len(reply.AccessKeyID), Equals, 20) c.Assert(len(reply.AccessKeyID), Equals, 20)
c.Assert(len(reply.SecretAccessKey), Equals, 40) 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)
} }

@ -107,6 +107,9 @@ func (e *Error) Trace(fields ...string) *Error {
// Internal trace - records the point at which it is invoked. // Internal trace - records the point at which it is invoked.
func (e *Error) trace(fields ...string) *Error { func (e *Error) trace(fields ...string) *Error {
if e == nil {
return nil
}
pc, file, line, _ := runtime.Caller(2) pc, file, line, _ := runtime.Caller(2)
function := runtime.FuncForPC(pc).Name() function := runtime.FuncForPC(pc).Name()
_, function = filepath.Split(function) _, function = filepath.Split(function)
@ -139,6 +142,9 @@ func (e *Error) Untrace() *Error {
// ToGoError returns original error message. // ToGoError returns original error message.
func (e *Error) ToGoError() error { func (e *Error) ToGoError() error {
if e == nil || e.Cause == nil {
return nil
}
return e.Cause return e.Cause
} }

@ -75,7 +75,7 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) {
s.accessKeyID = string(accessKeyID) s.accessKeyID = string(accessKeyID)
s.secretAccessKey = string(secretAccessKey) s.secretAccessKey = string(secretAccessKey)
auth.SetAuthConfigPath(filepath.Join(root, "users.json")) auth.SetAuthConfigPath(root)
perr = auth.SaveConfig(authConf) perr = auth.SaveConfig(authConf)
c.Assert(perr, IsNil) c.Assert(perr, IsNil)

Loading…
Cancel
Save