mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Finalized opts parser for subject authorization
This commit is contained in:
37
server/configs/authorization.conf
Normal file
37
server/configs/authorization.conf
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright 2016 Apcera Inc. All rights reserved.
|
||||
|
||||
listen: 127.0.0.1:4222
|
||||
|
||||
authorization {
|
||||
# Our role based permissions.
|
||||
|
||||
# Superuser can do anything.
|
||||
super_user = {
|
||||
publish = "*"
|
||||
subscribe = ">"
|
||||
}
|
||||
# Can do requests on foo or bar, and subscribe to anything
|
||||
# that is a response to an _INBOX.
|
||||
#
|
||||
# Notice that authorization filters can be singletons or arrays.
|
||||
req_pub_user = {
|
||||
publish = ["req.foo", "req.bar"]
|
||||
subscribe = "_INBOX.>"
|
||||
}
|
||||
|
||||
# Setup a default user that can subscribe to anything, but has
|
||||
# no publish capabilities.
|
||||
default_user = {
|
||||
subscribe = "PUBLIC.>"
|
||||
}
|
||||
|
||||
# Default permissions if none presented. e.g. susan below.
|
||||
default_permissions: $default_user
|
||||
|
||||
# Users listed with persmissions.
|
||||
users = [
|
||||
{user: alice, password: foo, permissions: $super_user}
|
||||
{user: bob, password: bar, permissions: $req_pub_user}
|
||||
{user: susan, password: baz}
|
||||
]
|
||||
}
|
||||
156
server/opts.go
156
server/opts.go
@@ -5,7 +5,6 @@ package server
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -20,26 +19,16 @@ import (
|
||||
|
||||
// For multiple accounts/users.
|
||||
type User struct {
|
||||
Username string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Permissions Authorization `json:"permissions"`
|
||||
MaxConns int `json:"max_connections"`
|
||||
MaxSubs int `json:"max_subscriptions"`
|
||||
Username string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Permissions *Permissions `json:"permissions"`
|
||||
}
|
||||
|
||||
// Authorization are the allowed subjects on a per
|
||||
// publish or subscribe basis.
|
||||
type Authorization struct {
|
||||
pub *Permission `json:"publish"`
|
||||
sub *Permission `json:"subscribe"`
|
||||
}
|
||||
|
||||
// Permission is for describing the subjects and rate limits
|
||||
// that an account connection can publish or subscribe to and
|
||||
// what limits if any exist for message and/or byte rates.
|
||||
type Permission struct {
|
||||
Subjects []string `json:"subjects"`
|
||||
// FIXME(dlc) figure out rates.
|
||||
type Permissions struct {
|
||||
Publish []string `json:"publish"`
|
||||
Subscribe []string `json:"subscribe"`
|
||||
}
|
||||
|
||||
// Options block for gnatsd server.
|
||||
@@ -52,7 +41,7 @@ type Options struct {
|
||||
NoSigs bool `json:"-"`
|
||||
Logtime bool `json:"-"`
|
||||
MaxConn int `json:"max_connections"`
|
||||
Users []User `json:"-"`
|
||||
Users []*User `json:"-"`
|
||||
Username string `json:"-"`
|
||||
Password string `json:"-"`
|
||||
Authorization string `json:"-"`
|
||||
@@ -89,13 +78,15 @@ type Options struct {
|
||||
TLSConfig *tls.Config `json:"-"`
|
||||
}
|
||||
|
||||
// Configuration file quthorization section.
|
||||
type authorization struct {
|
||||
// Singles
|
||||
user string
|
||||
pass string
|
||||
// Multiple Users
|
||||
users []User
|
||||
timeout float64
|
||||
users []*User
|
||||
timeout float64
|
||||
defaultPermissions *Permissions
|
||||
}
|
||||
|
||||
// TLSConfigOpts holds the parsed tls config information,
|
||||
@@ -343,17 +334,134 @@ func parseAuthorization(am map[string]interface{}) (*authorization, error) {
|
||||
}
|
||||
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)
|
||||
users, err := parseUsers(mv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.users = users
|
||||
case "default_permission", "default_permissions":
|
||||
pm, ok := mv.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Expected default permissions to be a map/struct, got %+v", mv)
|
||||
}
|
||||
permissions, err := parseUserPermissions(pm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth.defaultPermissions = permissions
|
||||
}
|
||||
|
||||
// Now check for permission defaults with multiple users, etc.
|
||||
if auth.users != nil && auth.defaultPermissions != nil {
|
||||
for _, user := range auth.users {
|
||||
if user.Permissions == nil {
|
||||
user.Permissions = auth.defaultPermissions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// Helper function to parse multiple users array with optional permissions.
|
||||
func parseUsers(mv interface{}) ([]*User, 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{}
|
||||
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)
|
||||
}
|
||||
user := &User{}
|
||||
for k, v := range um {
|
||||
switch strings.ToLower(k) {
|
||||
case "user", "username":
|
||||
user.Username = v.(string)
|
||||
case "pass", "password":
|
||||
user.Password = v.(string)
|
||||
case "permission", "permissions", "authroization":
|
||||
pm, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Expected user permissions to be a map/struct, got %+v", v)
|
||||
}
|
||||
permissions, 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")
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Helper function to parse user/account permissions
|
||||
func parseUserPermissions(pm map[string]interface{}) (*Permissions, error) {
|
||||
p := &Permissions{}
|
||||
for k, v := range pm {
|
||||
switch strings.ToLower(k) {
|
||||
case "pub", "publish":
|
||||
subjects, err := parseSubjects(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Publish = subjects
|
||||
case "sub", "subscribe":
|
||||
subjects, err := parseSubjects(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Subscribe = subjects
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown field %s parsing permissions", k)
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Helper function to parse subject singeltons and/or arrays
|
||||
func parseSubjects(v interface{}) ([]string, error) {
|
||||
var subjects []string
|
||||
switch v.(type) {
|
||||
case string:
|
||||
subjects = append(subjects, v.(string))
|
||||
case []string:
|
||||
subjects = v.([]string)
|
||||
case []interface{}:
|
||||
for _, i := range v.([]interface{}) {
|
||||
subject, ok := i.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Subject in permissions array can not be cast to string")
|
||||
}
|
||||
subjects = append(subjects, subject)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Expected subject permissions to be a subject, or array of subjects, got %T", v)
|
||||
}
|
||||
return checkSubjectArray(subjects)
|
||||
}
|
||||
|
||||
// Helper function to validate subjects, etc for account permissioning.
|
||||
func checkSubjectArray(sa []string) ([]string, error) {
|
||||
for _, s := range sa {
|
||||
if !IsValidSubject(s) {
|
||||
return nil, fmt.Errorf("Subject %q is not a valid subject", s)
|
||||
}
|
||||
}
|
||||
return sa, nil
|
||||
}
|
||||
|
||||
// PrintTLSHelpAndDie prints TLS usage and exits.
|
||||
func PrintTLSHelpAndDie() {
|
||||
fmt.Printf("%s\n", tlsUsage)
|
||||
|
||||
@@ -398,10 +398,85 @@ func TestMultipleUsersConfig(t *testing.T) {
|
||||
processOptions(opts)
|
||||
}
|
||||
|
||||
// Test highly depends on contents of the config file listed below. Any changes to that file
|
||||
// may very weel break this test.
|
||||
func TestAuthorizationConfig(t *testing.T) {
|
||||
opts, err := ProcessConfigFile("./configs/authorization.conf")
|
||||
if err != nil {
|
||||
t.Fatalf("Received an error reading config file: %v\n", err)
|
||||
}
|
||||
processOptions(opts)
|
||||
lu := len(opts.Users)
|
||||
if lu != 3 {
|
||||
t.Fatalf("Expected 3 users, got %d\n", lu)
|
||||
}
|
||||
// Build a map
|
||||
mu := make(map[string]*User)
|
||||
for _, u := range opts.Users {
|
||||
mu[u.Username] = u
|
||||
}
|
||||
alice, ok := mu["alice"]
|
||||
if !ok {
|
||||
t.Fatalf("Expected to see user Alice\n")
|
||||
}
|
||||
// Check for permissions details
|
||||
if alice.Permissions == nil {
|
||||
t.Fatalf("Expected Alice's permissions to be non-nil\n")
|
||||
}
|
||||
if alice.Permissions.Publish == nil {
|
||||
t.Fatalf("Expected Alice's publish permissions to be non-nil\n")
|
||||
}
|
||||
if len(alice.Permissions.Publish) != 1 {
|
||||
t.Fatalf("Expected Alice's publish permissions to have 1 element, got %d\n",
|
||||
len(alice.Permissions.Publish))
|
||||
}
|
||||
pubPerm := alice.Permissions.Publish[0]
|
||||
if pubPerm != "*" {
|
||||
t.Fatalf("Expected Alice's publish permissions to be '*', got %q\n", pubPerm)
|
||||
}
|
||||
if alice.Permissions.Subscribe == nil {
|
||||
t.Fatalf("Expected Alice's subscribe permissions to be non-nil\n")
|
||||
}
|
||||
if len(alice.Permissions.Subscribe) != 1 {
|
||||
t.Fatalf("Expected Alice's subscribe permissions to have 1 element, got %d\n",
|
||||
len(alice.Permissions.Subscribe))
|
||||
}
|
||||
subPerm := alice.Permissions.Subscribe[0]
|
||||
if subPerm != ">" {
|
||||
t.Fatalf("Expected Alice's subscribe permissions to be '>', got %q\n", subPerm)
|
||||
}
|
||||
|
||||
bob, ok := mu["bob"]
|
||||
if !ok {
|
||||
t.Fatalf("Expected to see user Bob\n")
|
||||
}
|
||||
if bob.Permissions == nil {
|
||||
t.Fatalf("Expected Bob's permissions to be non-nil\n")
|
||||
}
|
||||
|
||||
susan, ok := mu["susan"]
|
||||
if !ok {
|
||||
t.Fatalf("Expected to see user Susan\n")
|
||||
}
|
||||
if susan.Permissions == nil {
|
||||
t.Fatalf("Expected Susan's permissions to be non-nil\n")
|
||||
}
|
||||
// Check susan closely since she inherited the default permissions.
|
||||
if susan.Permissions == nil {
|
||||
t.Fatalf("Expected Susan's permissions to be non-nil\n")
|
||||
}
|
||||
if susan.Permissions.Publish != nil {
|
||||
t.Fatalf("Expected Susan's publish permissions to be nil\n")
|
||||
}
|
||||
if susan.Permissions.Subscribe == nil {
|
||||
t.Fatalf("Expected Susan's subscribe permissions to be non-nil\n")
|
||||
}
|
||||
if len(susan.Permissions.Subscribe) != 1 {
|
||||
t.Fatalf("Expected Susan's subscribe permissions to have 1 element, got %d\n",
|
||||
len(susan.Permissions.Subscribe))
|
||||
}
|
||||
subPerm = susan.Permissions.Subscribe[0]
|
||||
if subPerm != "PUBLIC.>" {
|
||||
t.Fatalf("Expected Susan's subscribe permissions to be 'PUBLIC.>', got %q\n", subPerm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +565,29 @@ func visitLevel(l *level, depth int) int {
|
||||
return maxDepth
|
||||
}
|
||||
|
||||
// IsValidLiteralSubject returns true if a subject is valid, false otherwise
|
||||
// IsValidSubject returns true if a subject is valid, false otherwise
|
||||
func IsValidSubject(subject string) bool {
|
||||
if subject == "" {
|
||||
return false
|
||||
}
|
||||
sfwc := false
|
||||
tokens := strings.Split(string(subject), tsep)
|
||||
for _, t := range tokens {
|
||||
if len(t) == 0 || sfwc {
|
||||
return false
|
||||
}
|
||||
if len(t) > 1 {
|
||||
continue
|
||||
}
|
||||
switch t[0] {
|
||||
case fwc:
|
||||
sfwc = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsValidLiteralSubject returns true if a subject is valid and literal (no wildcards), false otherwise
|
||||
func IsValidLiteralSubject(subject string) bool {
|
||||
tokens := strings.Split(string(subject), tsep)
|
||||
for _, t := range tokens {
|
||||
|
||||
Reference in New Issue
Block a user