Finalized opts parser for subject authorization

This commit is contained in:
Derek Collison
2016-06-16 13:49:26 -07:00
parent 1d74a0dea3
commit 449aa6e8ef
4 changed files with 267 additions and 25 deletions

View 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}
]
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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 {