First pass at multi-user support

This commit is contained in:
Derek Collison
2016-05-13 12:27:57 -07:00
parent 01b635cc8f
commit 46a9e6f0bc
9 changed files with 160 additions and 11 deletions

41
auth/multiuser.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package auth
import (
"golang.org/x/crypto/bcrypt"
"github.com/nats-io/gnatsd/server"
)
// Plain authentication is a basic username and password
type MultiUser struct {
users map[string]string
}
// Create a new multi-user
func NewMultiUser(users []server.User) *MultiUser {
m := &MultiUser{users: make(map[string]string)}
for _, u := range users {
m.users[u.Username] = u.Password
}
return m
}
// Check authenticates the client using a username and password against a list of multiple users.
func (m *MultiUser) Check(c server.ClientAuth) bool {
opts := c.GetOpts()
pass, ok := m.users[opts.Username]
if !ok {
return false
}
// Check to see if the password is a bcrypt hash
if isBcrypt(pass) {
if err := bcrypt.CompareHashAndPassword([]byte(pass), []byte(opts.Password)); err != nil {
return false
}
} else if pass != opts.Password {
return false
}
return true
}

View File

@@ -610,7 +610,7 @@ func lexString(lx *lexer) stateFn {
return lexStringEscape
// Termination of non-quoted strings
case isNL(r) || r == eof || r == optValTerm ||
r == arrayValTerm || r == arrayEnd ||
r == arrayValTerm || r == arrayEnd || r == mapEnd ||
isWhitespace(r):
lx.backup()

View File

@@ -698,3 +698,41 @@ func TestUnquotedIPAddr(t *testing.T) {
lx = lex("listen = [localhost:4222, localhost:4333]")
expect(t, lx, expectedItems)
}
var arrayOfMaps = `
authorization {
users = [
{user: alice, password: foo}
{user: bob, password: bar}
]
timeout: 0.5
}
`
func TestArrayOfMaps(t *testing.T) {
expectedItems := []item{
{itemKey, "authorization", 2},
{itemMapStart, "", 2},
{itemKey, "users", 3},
{itemArrayStart, "", 3},
{itemMapStart, "", 4},
{itemKey, "user", 4},
{itemString, "alice", 4},
{itemKey, "password", 4},
{itemString, "foo", 4},
{itemMapEnd, "", 4},
{itemMapStart, "", 5},
{itemKey, "user", 5},
{itemString, "bob", 5},
{itemKey, "password", 5},
{itemString, "bar", 5},
{itemMapEnd, "", 5},
{itemArrayEnd, "", 6},
{itemKey, "timeout", 7},
{itemFloat, "0.5", 7},
{itemMapEnd, "", 8},
{itemEOF, "", 9},
}
lx := lex(arrayOfMaps)
expect(t, lx, expectedItems)
}

View File

@@ -184,7 +184,11 @@ func main() {
func configureAuth(s *server.Server, opts *server.Options) {
// Client
if opts.Username != "" {
// Check for multiple users first
if opts.Users != nil {
auth := auth.NewMultiUser(opts.Users)
s.SetClientAuthMethod(auth)
} else if opts.Username != "" {
auth := &auth.Plain{
Username: opts.Username,
Password: opts.Password,

View File

@@ -0,0 +1,11 @@
# Copyright 2016 Apcera Inc. All rights reserved.
listen: 127.0.0.1:4222
authorization {
users = [
{user: alice, password: foo}
{user: bob, password: bar}
]
timeout: 0.5
}

View File

@@ -1,10 +1,11 @@
// Copyright 2012-2015 Apcera Inc. All rights reserved.
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
@@ -17,6 +18,12 @@ import (
"github.com/nats-io/gnatsd/conf"
)
// For multiple accounts/users.
type User struct {
Username string `json:"user"`
Password string `json:"password"`
}
// Options block for gnatsd server.
type Options struct {
Host string `json:"addr"`
@@ -27,7 +34,8 @@ type Options struct {
NoSigs bool `json:"-"`
Logtime bool `json:"-"`
MaxConn int `json:"max_connections"`
Username string `json:"user,omitempty"`
Users []User `json:"-"`
Username string `json:"-"`
Password string `json:"-"`
Authorization string `json:"-"`
PingInterval time.Duration `json:"ping_interval"`
@@ -64,8 +72,11 @@ type Options struct {
}
type authorization struct {
user string
pass string
// Singles
user string
pass string
// Multiple Users
users []User
timeout float64
}
@@ -140,10 +151,20 @@ func ProcessConfigFile(configFile string) (*Options, error) {
opts.Logtime = v.(bool)
case "authorization":
am := v.(map[string]interface{})
auth := parseAuthorization(am)
auth, err := parseAuthorization(am)
if err != nil {
return nil, err
}
opts.Username = auth.user
opts.Password = auth.pass
opts.AuthTimeout = auth.timeout
// Check for multiple users defined
if auth.users != nil {
if auth.user != "" {
return nil, fmt.Errorf("Can not have a single user/pass and a users array")
}
opts.Users = auth.users
}
case "http":
hp, err := parseListen(v)
if err != nil {
@@ -244,7 +265,13 @@ func parseCluster(cm map[string]interface{}, opts *Options) error {
opts.ClusterHost = mv.(string)
case "authorization":
am := mv.(map[string]interface{})
auth := parseAuthorization(am)
auth, err := parseAuthorization(am)
if err != nil {
return err
}
if auth.users != nil {
return fmt.Errorf("Cluster authorization does not allow multiple users")
}
opts.ClusterUsername = auth.user
opts.ClusterPassword = auth.pass
opts.ClusterAuthTimeout = auth.timeout
@@ -280,8 +307,8 @@ func parseCluster(cm map[string]interface{}, opts *Options) error {
}
// Helper function to parse Authorization configs.
func parseAuthorization(am map[string]interface{}) authorization {
auth := authorization{}
func parseAuthorization(am map[string]interface{}) (*authorization, error) {
auth := &authorization{}
for mk, mv := range am {
switch strings.ToLower(mk) {
case "user", "username":
@@ -297,9 +324,16 @@ func parseAuthorization(am map[string]interface{}) authorization {
at = mv.(float64)
}
auth.timeout = at
case "users":
b, _ := json.Marshal(mv)
users := []User{}
if err := json.Unmarshal(b, &users); err != nil {
return nil, fmt.Errorf("Could not parse user array properly, %v", err)
}
auth.users = users
}
}
return auth
return auth, nil
}
// PrintTLSHelpAndDie prints TLS usage and exits.

View File

@@ -389,3 +389,11 @@ func TestListenPortWithColonConfig(t *testing.T) {
t.Fatalf("Received incorrect port %v, expected %v\n", opts.Port, port)
}
}
func TestMultipleUsersConfig(t *testing.T) {
opts, err := ProcessConfigFile("./configs/multiple_users.conf")
if err != nil {
t.Fatalf("Received an error reading config file: %v\n", err)
}
processOptions(opts)
}

View File

@@ -0,0 +1,10 @@
# Copyright 2016 Apcera Inc. All rights reserved.
listen: 127.0.0.1:4233
authorization {
users = [
{user: alice, password: foo}
{user: bob, password: bar}
]
}

View File

@@ -72,6 +72,9 @@ func RunServerWithConfig(configFile string) (srv *server.Server, opts *server.Op
if opts.Username != "" {
a = &auth.Plain{Username: opts.Username, Password: opts.Password}
}
if opts.Users != nil {
a = auth.NewMultiUser(opts.Users)
}
srv = RunServerWithAuth(opts, a)
return
}