Basic nkey support and nonce handling

Signed-off-by: Derek Collison <derek@nats.io>
This commit is contained in:
Derek Collison
2018-09-06 18:36:02 -07:00
parent 5bfbc8bb52
commit 3d2cb0e7d1
8 changed files with 607 additions and 57 deletions

View File

@@ -5,6 +5,7 @@ go:
- 1.11
install:
- go get github.com/nats-io/go-nats
- go get github.com/nats-io/nkeys
- go get github.com/mattn/goveralls
- go get github.com/wadey/gocovmerge
- go get -u honnef.co/go/tools/cmd/megacheck

View File

@@ -15,9 +15,11 @@ package server
import (
"crypto/tls"
"encoding/base64"
"fmt"
"strings"
"github.com/nats-io/nkeys"
"golang.org/x/crypto/bcrypt"
)
@@ -37,6 +39,12 @@ type ClientAuthentication interface {
RegisterUser(*User)
}
// Nkey is for multiple nkey based users
type NkeyUser struct {
Nkey string `json:"user"`
Permissions *Permissions `json:"permissions"`
}
// User is for multiple accounts/users.
type User struct {
Username string `json:"user"`
@@ -56,6 +64,18 @@ func (u *User) clone() *User {
return clone
}
// clone performs a deep copy of the NkeyUser struct, returning a new clone with
// all values copied.
func (n *NkeyUser) clone() *NkeyUser {
if n == nil {
return nil
}
clone := &NkeyUser{}
*clone = *n
clone.Permissions = n.Permissions.clone()
return clone
}
// SubjectPermission is an individual allow and deny struct for publish
// and subscribe authorizations.
type SubjectPermission struct {
@@ -125,10 +145,19 @@ func (s *Server) configureAuthorization() {
// This just checks and sets up the user map if we have multiple users.
if opts.CustomClientAuthentication != nil {
s.info.AuthRequired = true
} else if opts.Users != nil {
s.users = make(map[string]*User)
for _, u := range opts.Users {
s.users[u.Username] = u
} else if opts.Nkeys != nil || opts.Users != nil {
// Support both at the same time.
if opts.Nkeys != nil {
s.nkeys = make(map[string]*NkeyUser)
for _, u := range opts.Nkeys {
s.nkeys[u.Nkey] = u
}
}
if opts.Users != nil {
s.users = make(map[string]*User)
for _, u := range opts.Users {
s.users[u.Username] = u
}
}
s.info.AuthRequired = true
} else if opts.Username != "" || opts.Authorization != "" {
@@ -152,31 +181,72 @@ func (s *Server) checkAuthorization(c *client) bool {
}
}
// hasUsers leyt's us know if we have a users array.
func (s *Server) hasUsers() bool {
s.mu.Lock()
hu := s.users != nil
s.mu.Unlock()
return hu
}
// isClientAuthorized will check the client against the proper authorization method and data.
// This could be token or username/password based.
// This could be nkey, token, or username/password based.
func (s *Server) isClientAuthorized(c *client) bool {
// Snapshot server options.
opts := s.getOpts()
// Snapshot server options by hand and only grab what we really need.
s.optsMu.RLock()
customClientAuthentication := s.opts.CustomClientAuthentication
authorization := s.opts.Authorization
username := s.opts.Username
password := s.opts.Password
s.optsMu.RUnlock()
// Check custom auth first, then multiple users, then token, then single user/pass.
if opts.CustomClientAuthentication != nil {
return opts.CustomClientAuthentication.Check(c)
} else if s.hasUsers() {
s.mu.Lock()
user, ok := s.users[c.opts.Username]
// Check custom auth first, then nkeys, then multiple users, then token, then single user/pass.
if customClientAuthentication != nil {
return customClientAuthentication.Check(c)
}
var nkey *NkeyUser
var user *User
var ok bool
s.mu.Lock()
authRequired := s.info.AuthRequired
if !authRequired {
// TODO(dlc) - If they send us credentials should we fail?
s.mu.Unlock()
return true
}
// Check if we have nkeys or users for client.
hasNkeys := s.nkeys != nil
hasUsers := s.users != nil
if hasNkeys && c.opts.Nkey != "" {
nkey, ok = s.nkeys[c.opts.Nkey]
if !ok {
s.mu.Unlock()
return false
}
} else if hasUsers && c.opts.Username != "" {
user, ok = s.users[c.opts.Username]
if !ok {
s.mu.Unlock()
return false
}
}
s.mu.Unlock()
// Verify the signature against the nonce.
if nkey != nil {
if c.opts.Sig == "" {
return false
}
sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig)
if err != nil {
return false
}
pub, err := nkeys.FromPublicKey(c.opts.Nkey)
if err != nil {
return false
}
if err := pub.Verify(c.nonce, sig); err != nil {
return false
}
return true
}
if user != nil {
ok = comparePasswords(user.Password, c.opts.Password)
// If we are authorized, register the user which will properly setup any permissions
// for pub/sub authorizations.
@@ -184,18 +254,18 @@ func (s *Server) isClientAuthorized(c *client) bool {
c.RegisterUser(user)
}
return ok
} else if opts.Authorization != "" {
return comparePasswords(opts.Authorization, c.opts.Authorization)
} else if opts.Username != "" {
if opts.Username != c.opts.Username {
return false
}
return comparePasswords(opts.Password, c.opts.Password)
}
return true
if authorization != "" {
return comparePasswords(authorization, c.opts.Authorization)
} else if username != "" {
if username != c.opts.Username {
return false
}
return comparePasswords(password, c.opts.Password)
}
return false
}
// checkRouterAuth checks optional router authorization which can be nil or username/password.

View File

@@ -137,6 +137,7 @@ type client struct {
cid uint64
opts clientOpts
start time.Time
nonce []byte
nc net.Conn
ncs string
out outbound
@@ -246,6 +247,8 @@ type clientOpts struct {
Verbose bool `json:"verbose"`
Pedantic bool `json:"pedantic"`
TLSRequired bool `json:"tls_required"`
Nkey string `json:"nkey"`
Sig string `json:"sig"`
Authorization string `json:"auth_token"`
Username string `json:"user"`
Password string `json:"pass"`

39
server/nkey.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/base64"
)
// Raw length of the nonce challenge
const (
nonceRawLen = 16
nonceLen = 22 // base64.RawURLEncoding.EncodedLen(nonceRawLen)
)
// nonceRequired tells us if we should send a nonce.
// Assumes server lock is held
func (s *Server) nonceRequired() bool {
return len(s.opts.Nkeys) > 0
}
// Generate a nonce for INFO challenge.
// Assumes server lock is held
func (s *Server) generateNonce(n []byte) {
var raw [nonceRawLen]byte
data := raw[:]
s.prand.Read(data)
base64.RawURLEncoding.Encode(n, data)
}

266
server/nkey_test.go Normal file
View File

@@ -0,0 +1,266 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"bufio"
crand "crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
mrand "math/rand"
"net"
"strings"
"testing"
"time"
"github.com/nats-io/nkeys"
)
// Nonce has to be a string since we used different encoding by default than json.Unmarshal.
type nonceInfo struct {
Id string `json:"server_id"`
CID uint64 `json:"client_id,omitempty"`
Nonce string `json:"nonce,omitempty"`
}
// This is a seed for a user. We can extract public and private keys from this for testing.
const seed = "SUAOB32VQGYIOM622XYNXN4Q6GQR5I6DFZPPYZMNI5MMNVAQZDAL3OLH554ISOUTJM4EC6NWS5UHMS4CMONVVRW3VXXEQULMR6MDLPOEUVFPU"
func nkeyBasicSetup() (*Server, *client, *bufio.Reader, string) {
kp, _ := nkeys.FromSeed(seed)
pub, _ := kp.PublicKey()
opts := defaultServerOptions
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
return rawSetup(opts)
}
func mixedSetup() (*Server, *client, *bufio.Reader, string) {
kp, _ := nkeys.FromSeed(seed)
pub, _ := kp.PublicKey()
opts := defaultServerOptions
opts.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
opts.Users = []*User{&User{Username: "derek", Password: "foo"}}
return rawSetup(opts)
}
func newClientForServer(s *Server) (*client, *bufio.Reader, string) {
cli, srv := net.Pipe()
cr := bufio.NewReaderSize(cli, maxBufSize)
ch := make(chan *client)
createClientAsync(ch, s, srv)
l, _ := cr.ReadString('\n')
// Grab client
c := <-ch
return c, cr, l
}
func TestServerInfoNonce(t *testing.T) {
_, l := setUpClientWithResponse()
if !strings.HasPrefix(l, "INFO ") {
t.Fatalf("INFO response incorrect: %s\n", l)
}
// Make sure payload is proper json
var info nonceInfo
err := json.Unmarshal([]byte(l[5:]), &info)
if err != nil {
t.Fatalf("Could not parse INFO json: %v\n", err)
}
if info.Nonce != "" {
t.Fatalf("Expected an empty nonce with no nkeys defined")
}
// Now setup server with auth and nkeys to trigger nonce generation
s, _, _, l := nkeyBasicSetup()
if !strings.HasPrefix(l, "INFO ") {
t.Fatalf("INFO response incorrect: %s\n", l)
}
// Make sure payload is proper json
err = json.Unmarshal([]byte(l[5:]), &info)
if err != nil {
t.Fatalf("Could not parse INFO json: %v\n", err)
}
if info.Nonce == "" {
t.Fatalf("Expected a non-empty nonce with nkeys defined")
}
// Make sure new clients get new nonces
oldNonce := info.Nonce
_, _, l = newClientForServer(s)
err = json.Unmarshal([]byte(l[5:]), &info)
if err != nil {
t.Fatalf("Could not parse INFO json: %v\n", err)
}
if info.Nonce == "" {
t.Fatalf("Expected a non-empty nonce")
}
if strings.Compare(oldNonce, info.Nonce) == 0 {
t.Fatalf("Expected subsequent nonces to be different\n")
}
}
func TestNkeyClientConnect(t *testing.T) {
s, c, cr, _ := nkeyBasicSetup()
// Send CONNECT with no signature or nkey, should fail.
connectOp := []byte("CONNECT {\"verbose\":true,\"pedantic\":true}\r\n")
go c.parse(connectOp)
l, _ := cr.ReadString('\n')
if !strings.HasPrefix(l, "-ERR ") {
t.Fatalf("Expected an error")
}
kp, _ := nkeys.FromSeed(seed)
pubKey, _ := kp.PublicKey()
// Send nkey but no signature
c, cr, _ = newClientForServer(s)
cs := fmt.Sprintf("CONNECT {\"nkey\":%q, \"verbose\":true,\"pedantic\":true}\r\n", pubKey)
connectOp = []byte(cs)
go c.parse(connectOp)
l, _ = cr.ReadString('\n')
if !strings.HasPrefix(l, "-ERR ") {
t.Fatalf("Expected an error")
}
// Now improperly sign etc.
c, cr, _ = newClientForServer(s)
cs = fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":%q,\"verbose\":true,\"pedantic\":true}\r\n", pubKey, "bad_sig")
go c.parse([]byte(cs))
l, _ = cr.ReadString('\n')
if !strings.HasPrefix(l, "-ERR ") {
t.Fatalf("Expected an error")
}
// Now properly sign the nonce
c, cr, l = newClientForServer(s)
// Check for Nonce
var info nonceInfo
err := json.Unmarshal([]byte(l[5:]), &info)
if err != nil {
t.Fatalf("Could not parse INFO json: %v\n", err)
}
if info.Nonce == "" {
t.Fatalf("Expected a non-empty nonce with nkeys defined")
}
sigraw, err := kp.Sign([]byte(info.Nonce))
if err != nil {
t.Fatalf("Failed signing nonce: %v", err)
}
sig := base64.RawURLEncoding.EncodeToString(sigraw)
// PING needed to flush the +OK to us.
cs = fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", pubKey, sig)
go c.parse([]byte(cs))
l, _ = cr.ReadString('\n')
if !strings.HasPrefix(l, "+OK") {
t.Fatalf("Expected an OK, got: %v", l)
}
}
func TestMixedClientConnect(t *testing.T) {
s, c, cr, _ := mixedSetup()
// Normal user/pass
// PING needed to flush the +OK to us.
go c.parse([]byte("CONNECT {\"user\":\"derek\",\"pass\":\"foo\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n"))
l, _ := cr.ReadString('\n')
if !strings.HasPrefix(l, "+OK") {
t.Fatalf("Expected an OK, got: %v", l)
}
kp, _ := nkeys.FromSeed(seed)
pubKey, _ := kp.PublicKey()
c, cr, l = newClientForServer(s)
// Check for Nonce
var info nonceInfo
err := json.Unmarshal([]byte(l[5:]), &info)
if err != nil {
t.Fatalf("Could not parse INFO json: %v\n", err)
}
if info.Nonce == "" {
t.Fatalf("Expected a non-empty nonce with nkeys defined")
}
sigraw, err := kp.Sign([]byte(info.Nonce))
if err != nil {
t.Fatalf("Failed signing nonce: %v", err)
}
sig := base64.RawURLEncoding.EncodeToString(sigraw)
// PING needed to flush the +OK to us.
cs := fmt.Sprintf("CONNECT {\"nkey\":%q,\"sig\":\"%s\",\"verbose\":true,\"pedantic\":true}\r\nPING\r\n", pubKey, sig)
go c.parse([]byte(cs))
l, _ = cr.ReadString('\n')
if !strings.HasPrefix(l, "+OK") {
t.Fatalf("Expected an OK, got: %v", l)
}
}
func BenchmarkCryptoRandGeneration(b *testing.B) {
data := make([]byte, 16)
for i := 0; i < b.N; i++ {
crand.Read(data)
}
}
func BenchmarkMathRandGeneration(b *testing.B) {
data := make([]byte, 16)
prng := mrand.New(mrand.NewSource(time.Now().UnixNano()))
for i := 0; i < b.N; i++ {
prng.Read(data)
}
}
func BenchmarkNonceGeneration(b *testing.B) {
data := make([]byte, nonceRawLen)
b64 := make([]byte, nonceLen)
prand := mrand.New(mrand.NewSource(time.Now().UnixNano()))
for i := 0; i < b.N; i++ {
prand.Read(data)
base64.RawURLEncoding.Encode(b64, data)
}
}
func BenchmarkPublicVerify(b *testing.B) {
data := make([]byte, nonceRawLen)
nonce := make([]byte, nonceLen)
mrand.Read(data)
base64.RawURLEncoding.Encode(nonce, data)
user, err := nkeys.CreateUser(nil)
if err != nil {
b.Fatalf("Error creating User Nkey: %v", err)
}
sig, err := user.Sign(nonce)
if err != nil {
b.Fatalf("Error sigining nonce: %v", err)
}
pk, err := user.PublicKey()
if err != nil {
b.Fatalf("Could not extract public key from user: %v", err)
}
pub, err := nkeys.FromPublicKey(pk)
if err != nil {
b.Fatalf("Could not create public key pair from public key string: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := pub.Verify(nonce, sig); err != nil {
b.Fatalf("Error verifying nonce: %v", err)
}
}
}

View File

@@ -28,6 +28,7 @@ import (
"time"
"github.com/nats-io/gnatsd/conf"
"github.com/nats-io/nkeys"
)
// ClusterOpts are options for clusters.
@@ -59,6 +60,7 @@ type Options struct {
Logtime bool `json:"-"`
MaxConn int `json:"max_connections"`
MaxSubs int `json:"max_subscriptions,omitempty"`
Nkeys []*NkeyUser `json:"-"`
Users []*User `json:"-"`
Username string `json:"-"`
Password string `json:"-"`
@@ -110,6 +112,13 @@ func (o *Options) Clone() *Options {
clone.Users[i] = user.clone()
}
}
if o.Nkeys != nil {
clone.Nkeys = make([]*NkeyUser, len(o.Nkeys))
for i, nkey := range o.Nkeys {
clone.Nkeys[i] = nkey.clone()
}
}
if o.Routes != nil {
clone.Routes = make([]*url.URL, len(o.Routes))
for i, route := range o.Routes {
@@ -133,7 +142,8 @@ type authorization struct {
user string
pass string
token string
// Multiple Users
// Multiple Nkeys/Users
nkeys []*NkeyUser
users []*User
timeout float64
defaultPermissions *Permissions
@@ -255,6 +265,14 @@ func (o *Options) ProcessConfigFile(configFile string) error {
}
o.Users = auth.users
}
// Check for nkeys
if auth.nkeys != nil {
if o.Users != nil {
return fmt.Errorf("Can not have users when nkeys are also defined.")
}
o.Nkeys = auth.nkeys
}
case "http":
hp, err := parseListen(v)
if err != nil {
@@ -460,7 +478,12 @@ func parseAuthorization(am map[string]interface{}) (*authorization, error) {
if err != nil {
return nil, err
}
auth.users = users
switch users.(type) {
case []*User:
auth.users = users.([]*User)
case []*NkeyUser:
auth.nkeys = users.([]*NkeyUser)
}
case "default_permission", "default_permissions", "permissions":
pm, ok := mv.(map[string]interface{})
if !ok {
@@ -487,22 +510,28 @@ func parseAuthorization(am map[string]interface{}) (*authorization, error) {
}
// Helper function to parse multiple users array with optional permissions.
func parseUsers(mv interface{}) ([]*User, error) {
func parseUsers(mv interface{}) (interface{}, error) {
// Make sure we have an array
uv, ok := mv.([]interface{})
if !ok {
return nil, fmt.Errorf("Expected users field to be an array, got %v", mv)
}
users := []*User{}
var users []*User
var keys []*NkeyUser
for _, u := range uv {
// Check its a map/struct
um, ok := u.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Expected user entry to be a map/struct, got %v", u)
}
var perms *Permissions
var err error
user := &User{}
nkey := &NkeyUser{}
for k, v := range um {
switch strings.ToLower(k) {
case "nkey":
nkey.Nkey = v.(string)
case "user", "username":
user.Username = v.(string)
case "pass", "password":
@@ -512,18 +541,41 @@ func parseUsers(mv interface{}) ([]*User, error) {
if !ok {
return nil, fmt.Errorf("Expected user permissions to be a map/struct, got %+v", v)
}
permissions, err := parseUserPermissions(pm)
perms, err = parseUserPermissions(pm)
if err != nil {
return nil, err
}
user.Permissions = permissions
}
}
// Check to make sure we have at least username and password
if user.Username == "" || user.Password == "" {
return nil, fmt.Errorf("User entry requires a user and a password")
// Place perms if we have them.
if perms != nil {
// nkey takes precedent.
if nkey.Nkey != "" {
nkey.Permissions = perms
} else {
user.Permissions = perms
}
}
users = append(users, user)
// Check to make sure we have at least username and password if defined.
if nkey.Nkey == "" && (user.Username == "" || user.Password == "") {
return nil, fmt.Errorf("User entry requires a user and a password")
} else if nkey.Nkey != "" {
// Make sure the nkey is legit.
if !nkeys.IsValidPublicUserKey(nkey.Nkey) {
return nil, fmt.Errorf("Not a valid public nkey for a user")
}
// If we have user or password defined here that is an error.
if user.Username != "" || user.Password != "" {
return nil, fmt.Errorf("Nkey users do not take usernames or passwords")
}
keys = append(keys, nkey)
} else {
users = append(users, user)
}
}
if len(keys) > 0 {
return keys, nil
}
return users, nil
}

View File

@@ -17,6 +17,7 @@ import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"net/url"
"os"
@@ -761,6 +762,111 @@ func TestNewStyleAuthorizationConfig(t *testing.T) {
}
}
// Test new nkey users
func TestNkeyUsersConfig(t *testing.T) {
confFileName := "nkeys.conf"
defer os.Remove(confFileName)
content := `
authorization {
users = [
{nkey: "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"}
{nkey: "UA3C5TBZYK5GJQJRWPMU6NFY5JNAEVQB2V2TUZFZDHFJFUYVKTTUOFKZ"}
]
}`
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config file: %v", err)
}
opts, err := ProcessConfigFile(confFileName)
if err != nil {
t.Fatalf("Received an error reading config file: %v", err)
}
lu := len(opts.Nkeys)
if lu != 2 {
t.Fatalf("Expected 2 nkey users, got %d", lu)
}
}
func TestNkeyUsersWithPermsConfig(t *testing.T) {
confFileName := "nkeys.conf"
defer os.Remove(confFileName)
content := `
authorization {
users = [
{nkey: "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV",
permissions = {
publish = "$SYSTEM.>"
subscribe = { deny = ["foo", "bar", "baz"] }
}
}
]
}`
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config file: %v", err)
}
opts, err := ProcessConfigFile(confFileName)
if err != nil {
t.Fatalf("Received an error reading config file: %v", err)
}
lu := len(opts.Nkeys)
if lu != 1 {
t.Fatalf("Expected 1 nkey user, got %d", lu)
}
nk := opts.Nkeys[0]
if nk.Permissions == nil {
fmt.Printf("nk is %+v\n", nk)
t.Fatal("Expected to have permissions")
}
if nk.Permissions.Publish == nil {
t.Fatal("Expected to have publish permissions")
}
if nk.Permissions.Publish.Allow[0] != "$SYSTEM.>" {
t.Fatalf("Expected publish to allow \"$SYSTEM.>\", but got %v\n", nk.Permissions.Publish.Allow[0])
}
if nk.Permissions.Subscribe == nil {
t.Fatal("Expected to have subscribe permissions")
}
if nk.Permissions.Subscribe.Allow != nil {
t.Fatal("Expected to have no subscribe allow permissions")
}
deny := nk.Permissions.Subscribe.Deny
if deny == nil || len(deny) != 3 ||
deny[0] != "foo" || deny[1] != "bar" || deny[2] != "baz" {
t.Fatalf("Expected to have subscribe deny permissions, got %v", deny)
}
}
func TestBadNkeyConfig(t *testing.T) {
confFileName := "nkeys_bad.conf"
defer os.Remove(confFileName)
content := `
authorization {
users = [ {nkey: "Ufoo"}]
}`
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config file: %v", err)
}
if _, err := ProcessConfigFile(confFileName); err == nil {
t.Fatalf("Expected an error from nkey entry with password")
}
}
func TestNkeyWithPassConfig(t *testing.T) {
confFileName := "nkeys_pass.conf"
defer os.Remove(confFileName)
content := `
authorization {
users = [
{nkey: "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV", pass: "foo"}
]
}`
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config file: %v", err)
}
if _, err := ProcessConfigFile(confFileName); err == nil {
t.Fatalf("Expected an error from bad nkey entry")
}
}
func TestTokenWithUserPass(t *testing.T) {
confFileName := "test.conf"
defer os.Remove(confFileName)

View File

@@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
@@ -40,24 +41,25 @@ import (
// Info is the information sent to clients to help them understand information
// about this server.
type Info struct {
ID string `json:"server_id"`
Version string `json:"version"`
Proto int `json:"proto"`
GitCommit string `json:"git_commit,omitempty"`
GoVersion string `json:"go"`
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required,omitempty"`
TLSRequired bool `json:"tls_required,omitempty"`
TLSVerify bool `json:"tls_verify,omitempty"`
MaxPayload int `json:"max_payload"`
IP string `json:"ip,omitempty"`
CID uint64 `json:"client_id,omitempty"`
ID string `json:"server_id"`
Version string `json:"version"`
Proto int `json:"proto"`
GitCommit string `json:"git_commit,omitempty"`
GoVersion string `json:"go"`
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required,omitempty"`
TLSRequired bool `json:"tls_required,omitempty"`
TLSVerify bool `json:"tls_verify,omitempty"`
MaxPayload int `json:"max_payload"`
IP string `json:"ip,omitempty"`
CID uint64 `json:"client_id,omitempty"`
Nonce string `json:"nonce,omitempty"`
ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to.
// Route Specific
ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to.
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
Import *SubjectPermission `json:"import,omitempty"`
Export *SubjectPermission `json:"export,omitempty"`
}
// Server is our main struct.
@@ -65,6 +67,7 @@ type Server struct {
gcid uint64
stats
mu sync.Mutex
prand *rand.Rand
info Info
sl *Sublist
configFile string
@@ -77,6 +80,7 @@ type Server struct {
routes map[uint64]*client
remotes map[string]*client
users map[string]*User
nkeys map[string]*NkeyUser
totalClients uint64
closed *closedRingBuffer
done chan bool
@@ -165,6 +169,7 @@ func New(opts *Options) *Server {
s := &Server{
configFile: opts.ConfigFile,
info: info,
prand: rand.New(rand.NewSource(time.Now().UnixNano())),
sl: NewSublist(),
opts: opts,
done: make(chan bool, 1),
@@ -749,6 +754,13 @@ func (s *Server) copyInfo() Info {
info.ClientConnectURLs = make([]string, len(s.info.ClientConnectURLs))
copy(info.ClientConnectURLs, s.info.ClientConnectURLs)
}
if s.nonceRequired() {
// Nonce handling
var raw [nonceLen]byte
nonce := raw[:]
s.generateNonce(nonce)
info.Nonce = string(nonce)
}
return info
}
@@ -765,6 +777,7 @@ func (s *Server) createClient(conn net.Conn) *client {
// Grab JSON info string
s.mu.Lock()
info := s.copyInfo()
c.nonce = []byte(info.Nonce)
s.totalClients++
s.mu.Unlock()