mirror of
https://github.com/minio/minio.git
synced 2025-01-27 06:33:18 -05:00
Merge pull request #844 from harshavardhana/enhance-auth
Enhance auth JSONRPC, now provides persistent output
This commit is contained in:
commit
6803b64768
@ -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{}
|
||||||
|
|
||||||
// AuthReply reply with new access keys and secret ids
|
// AuthArgs auth params
|
||||||
type AuthReply struct {
|
type AuthArgs struct {
|
||||||
AccessKeyID string `json:"accesskey"`
|
User string `json:"user"`
|
||||||
SecretAccessKey string `json:"secretaccesskey"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuth(reply *AuthReply) *probe.Error {
|
// AuthReply reply with new access keys and secret ids
|
||||||
accessID, err := auth.GenerateAccessKeyID()
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err.Trace()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get auth keys
|
// fetchAuth fetch auth keys for a user
|
||||||
func (s *AuthService) Get(r *http.Request, args *Args, reply *AuthReply) error {
|
func fetchAuth(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"))
|
||||||
|
}
|
||||||
|
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 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…
x
Reference in New Issue
Block a user