Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deny/Allow Permissions #725

Merged
merged 7 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,43 @@ func (u *User) clone() *User {
return clone
}

// SubjectPermission is an individual allow and deny struct for publish
// and subscribe authorizations.
type SubjectPermission struct {
Allow []string `json:"allow"`
Deny []string `json:"deny"`
}

// Permissions are the allowed subjects on a per
// publish or subscribe basis.
type Permissions struct {
Publish []string `json:"publish"`
Subscribe []string `json:"subscribe"`
Publish *SubjectPermission `json:"publish"`
Subscribe *SubjectPermission `json:"subscribe"`
}

// RoutePermissions are similar to user permissions
// but describe what a server can import/export from and to
// another server.
type RoutePermissions struct {
Import []string `json:"import"`
Export []string `json:"export"`
Import *SubjectPermission `json:"import"`
Export *SubjectPermission `json:"export"`
}

// clone will clone an individual subject permission.
func (p *SubjectPermission) clone() *SubjectPermission {
if p == nil {
return nil
}
clone := &SubjectPermission{}
if p.Allow != nil {
clone.Allow = make([]string, len(p.Allow))
copy(clone.Allow, p.Allow)
}
if p.Deny != nil {
clone.Deny = make([]string, len(p.Deny))
copy(clone.Deny, p.Deny)
}
return clone
}

// clone performs a deep copy of the Permissions struct, returning a new clone
Expand All @@ -79,12 +103,10 @@ func (p *Permissions) clone() *Permissions {
}
clone := &Permissions{}
if p.Publish != nil {
clone.Publish = make([]string, len(p.Publish))
copy(clone.Publish, p.Publish)
clone.Publish = p.Publish.clone()
}
if p.Subscribe != nil {
clone.Subscribe = make([]string, len(p.Subscribe))
copy(clone.Subscribe, p.Subscribe)
clone.Subscribe = p.Subscribe.clone()
}
return clone
}
Expand Down
10 changes: 7 additions & 3 deletions server/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ func TestUserClone(t *testing.T) {
Username: "foo",
Password: "bar",
Permissions: &Permissions{
Publish: []string{"foo"},
Subscribe: []string{"bar"},
Publish: &SubjectPermission{
Allow: []string{"foo"},
},
Subscribe: &SubjectPermission{
Allow: []string{"bar"},
},
},
}

Expand All @@ -54,7 +58,7 @@ func TestUserClone(t *testing.T) {
user, clone)
}

clone.Permissions.Subscribe = []string{"baz"}
clone.Permissions.Subscribe.Allow = []string{"baz"}
if reflect.DeepEqual(user, clone) {
t.Fatal("Expected Users to be different")
}
Expand Down
76 changes: 62 additions & 14 deletions server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,13 @@ type outbound struct {
lft time.Duration // Last flush time.
}

type perm struct {
allow *Sublist
deny *Sublist
}
type permissions struct {
sub *Sublist
pub *Sublist
sub perm
pub perm
pcache map[string]bool
}

Expand Down Expand Up @@ -322,22 +326,39 @@ func (c *client) RegisterUser(user *User) {
// Initializes client.perms structure.
// Lock is held on entry.
func (c *client) setPermissions(perms *Permissions) {
// Pre-allocate all to simplify checks later.
c.perms = &permissions{}
c.perms.sub = NewSublist()
c.perms.pub = NewSublist()
c.perms.pcache = make(map[string]bool)

// Loop over publish permissions
for _, pubSubject := range perms.Publish {
if len(perms.Publish.Allow) > 0 {
c.perms.pub.allow = NewSublist()
}
for _, pubSubject := range perms.Publish.Allow {
sub := &subscription{subject: []byte(pubSubject)}
c.perms.pub.allow.Insert(sub)
}
if len(perms.Publish.Deny) > 0 {
c.perms.pub.deny = NewSublist()
}
for _, pubSubject := range perms.Publish.Deny {
sub := &subscription{subject: []byte(pubSubject)}
c.perms.pub.Insert(sub)
c.perms.pub.deny.Insert(sub)
}

// Loop over subscribe permissions
for _, subSubject := range perms.Subscribe {
if len(perms.Subscribe.Allow) > 0 {
c.perms.sub.allow = NewSublist()
}
for _, subSubject := range perms.Subscribe.Allow {
sub := &subscription{subject: []byte(subSubject)}
c.perms.sub.Insert(sub)
c.perms.sub.allow.Insert(sub)
}
if len(perms.Subscribe.Deny) > 0 {
c.perms.sub.deny = NewSublist()
}
for _, subSubject := range perms.Subscribe.Deny {
sub := &subscription{subject: []byte(subSubject)}
c.perms.sub.deny.Insert(sub)
}
}

Expand Down Expand Up @@ -1184,11 +1205,24 @@ func (c *client) processSub(argo []byte) (err error) {

// canSubscribe determines if the client is authorized to subscribe to the
// given subject. Assumes caller is holding lock.
func (c *client) canSubscribe(sub []byte) bool {
func (c *client) canSubscribe(subject []byte) bool {
if c.perms == nil {
return true
}
return len(c.perms.sub.Match(string(sub)).psubs) > 0

allowed := true

// Check allow list. If no allow list that means all are allowed. Deny can overrule.
if c.perms.sub.allow != nil {
r := c.perms.sub.allow.Match(string(subject))
allowed = len(r.psubs) != 0
}
// If we have a deny list and we think we are allowed, check that as well.
if allowed && c.perms.sub.deny != nil {
r := c.perms.sub.deny.Match(string(subject))
allowed = len(r.psubs) == 0
}
return allowed
}

// Low level unsubscribe for a given client.
Expand Down Expand Up @@ -1400,10 +1434,24 @@ func (c *client) pubAllowed(subject []byte) bool {
if ok {
return allowed
}
// Cache miss
r := c.perms.pub.Match(string(subject))
allowed = len(r.psubs) != 0

// Cache miss, check allow then deny as needed.
if c.perms.pub.allow != nil {
r := c.perms.pub.allow.Match(string(subject))
allowed = len(r.psubs) != 0
} else {
// No entries means all are allowed. Deny will overrule as needed.
allowed = true
}
// If we have a deny list and are currently allowed, check that as well.
if allowed && c.perms.pub.deny != nil {
r := c.perms.pub.deny.Match(string(subject))
allowed = len(r.psubs) == 0
}

// Update our cache here.
c.perms.pcache[string(subject)] = allowed

// Prune if needed.
if len(c.perms.pcache) > maxPermCacheSize {
c.prunePubPermsCache()
Expand Down
33 changes: 33 additions & 0 deletions server/configs/new_style_authorization.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
listen: 127.0.0.1:4222

authorization {
# Our new style role based permissions.
# These support both allow and deny.

# If allow is empty it means all or ">"
# If deny is empty it means none, or empty list.

normal_user = {
# Can send to foo, bar or baz only.
publish = {
allow = ["foo", "bar", "baz"]
}
# Can subscribe to everything but $SYSTEM prefixed subjects.
subscribe = {
deny = "$SYSTEM.>"
}
}

admin_user = {
publish = "$SYSTEM.>"
subscribe = {
deny = ["foo", "bar", "baz"]
}
}

# Users listed with persmissions.
users = [
{user: alice, password: foo, permissions: $normal_user}
{user: bob, password: special, permissions: $admin_user}
]
}
79 changes: 69 additions & 10 deletions server/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,14 @@ func parseCluster(cm map[string]interface{}, opts *Options) error {
// The parsing sets Import into Publish and Export into Subscribe, convert
// accordingly.
opts.Cluster.Permissions = &RoutePermissions{
Import: auth.defaultPermissions.Publish,
Export: auth.defaultPermissions.Subscribe,
Import: &SubjectPermission{
Allow: auth.defaultPermissions.Publish.Allow,
Deny: auth.defaultPermissions.Publish.Deny,
},
Export: &SubjectPermission{
Allow: auth.defaultPermissions.Subscribe.Allow,
Deny: auth.defaultPermissions.Subscribe.Deny,
},
}
}
case "routes":
Expand Down Expand Up @@ -538,24 +544,36 @@ func parseUserPermissions(pm map[string]interface{}) (*Permissions, error) {
// Import is Publish
// Export is Subscribe
case "pub", "publish", "import":
subjects, err := parseSubjects(v)
perms, err := parseVariablePermissions(v)
if err != nil {
return nil, err
}
p.Publish = subjects
p.Publish = perms
case "sub", "subscribe", "export":
subjects, err := parseSubjects(v)
perms, err := parseVariablePermissions(v)
if err != nil {
return nil, err
}
p.Subscribe = subjects
p.Subscribe = perms
default:
return nil, fmt.Errorf("Unknown field %s parsing permissions", k)
}
}
return p, nil
}

// Tope level parser for authorization configurations.
func parseVariablePermissions(v interface{}) (*SubjectPermission, error) {
switch v.(type) {
case map[string]interface{}:
// New style with allow and/or deny properties.
return parseSubjectPermission(v.(map[string]interface{}))
default:
// Old style
return parseOldPermissionStyle(v)
}
}

// Helper function to parse subject singeltons and/or arrays
func parseSubjects(v interface{}) ([]string, error) {
var subjects []string
Expand All @@ -575,17 +593,58 @@ func parseSubjects(v interface{}) ([]string, error) {
default:
return nil, fmt.Errorf("Expected subject permissions to be a subject, or array of subjects, got %T", v)
}
return checkSubjectArray(subjects)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we don't check for validity here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, need to rework this and use parseSubject() more. I see duplicate code. One sec.

if err := checkSubjectArray(subjects); err != nil {
return nil, err
}
return subjects, nil
}

// Helper function to parse old style authorization configs.
func parseOldPermissionStyle(v interface{}) (*SubjectPermission, error) {
subjects, err := parseSubjects(v)
if err != nil {
return nil, err
}
return &SubjectPermission{Allow: subjects}, nil
}

// Helper function to parse new style authorization into a SubjectPermission with Allow and Deny.
func parseSubjectPermission(m map[string]interface{}) (*SubjectPermission, error) {
if len(m) == 0 {
return nil, nil
}

p := &SubjectPermission{}

for k, v := range m {
switch strings.ToLower(k) {
case "allow":
subjects, err := parseSubjects(v)
if err != nil {
return nil, err
}
p.Allow = subjects
case "deny":
subjects, err := parseSubjects(v)
if err != nil {
return nil, err
}
p.Deny = subjects
default:
return nil, fmt.Errorf("Unknown field name %q parsing subject permissions, only 'allow' or 'deny' are permitted", k)
}
}
return p, nil
}

// Helper function to validate subjects, etc for account permissioning.
func checkSubjectArray(sa []string) ([]string, error) {
func checkSubjectArray(sa []string) error {
for _, s := range sa {
if !IsValidSubject(s) {
return nil, fmt.Errorf("Subject %q is not a valid subject", s)
return fmt.Errorf("Subject %q is not a valid subject", s)
}
}
return sa, nil
return nil
}

// PrintTLSHelpAndDie prints TLS usage and exits.
Expand Down
Loading