mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
First pass at multi-user support
This commit is contained in:
41
auth/multiuser.go
Normal file
41
auth/multiuser.go
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
6
main.go
6
main.go
@@ -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,
|
||||
|
||||
11
server/configs/multiple_users.conf
Normal file
11
server/configs/multiple_users.conf
Normal 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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
10
test/configs/multi_user.conf
Normal file
10
test/configs/multi_user.conf
Normal 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}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user