Merge pull request #844 from harshavardhana/enhance-auth
Enhance auth JSONRPC, now provides persistent output
This commit is contained in:
commit
6803b64768
6 changed files with 222 additions and 26 deletions
|
@ -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
|
||||
|
|
|
@ -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{}
|
||||
|
||||
// AuthReply reply with new access keys and secret ids
|
||||
type AuthReply struct {
|
||||
AccessKeyID string `json:"accesskey"`
|
||||
SecretAccessKey string `json:"secretaccesskey"`
|
||||
// AuthArgs auth params
|
||||
type AuthArgs struct {
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
func getAuth(reply *AuthReply) *probe.Error {
|
||||
accessID, err := auth.GenerateAccessKeyID()
|
||||
// AuthReply reply with new access keys and secret ids
|
||||
type AuthReply struct {
|
||||
Name string `json:"name"`
|
||||
AccessKeyID string `json:"accessKeyId"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
}
|
||||
|
||||
// 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(secretID)
|
||||
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
|
||||
}
|
||||
|
||||
// Get auth keys
|
||||
func (s *AuthService) Get(r *http.Request, args *Args, reply *AuthReply) error {
|
||||
if err := getAuth(reply); err != 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()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue