Skip to content

Commit d92bf60

Browse files
committed
Merge pull request cesanta#57 from carsonoid/acl_mongo_sort
Add optional sort_keys field for ACLs.
2 parents 5292646 + 06086f3 commit d92bf60

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

auth_server/authz/acl_mongo.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package authz
22

33
import (
4+
"errors"
45
"fmt"
56
"sync"
67
"time"
@@ -11,6 +12,13 @@ import (
1112
"gopkg.in/mgo.v2/bson"
1213
)
1314

15+
type MongoACL []MongoACLEntry
16+
17+
type MongoACLEntry struct {
18+
ACLEntry `bson:",inline"`
19+
Seq *int
20+
}
21+
1422
type ACLMongoConfig struct {
1523
MongoConfig *mgo_session.Config `yaml:"dial_info,omitempty"`
1624
Collection string `yaml:"collection,omitempty"`
@@ -28,9 +36,9 @@ type aclMongoAuthorizer struct {
2836
CacheTTL time.Duration `yaml:"cache_ttl,omitempty"`
2937
}
3038

31-
// NewACLMongoAuthorizer creates a new ACL Mongo authorizer
39+
// NewACLMongoAuthorizer creates a new ACL MongoDB authorizer
3240
func NewACLMongoAuthorizer(c *ACLMongoConfig) (Authorizer, error) {
33-
// Attempt to create new mongo session.
41+
// Attempt to create new MongoDB session.
3442
session, err := mgo_session.New(c.MongoConfig)
3543
if err != nil {
3644
return nil, err
@@ -67,7 +75,7 @@ func (ma *aclMongoAuthorizer) Authorize(ai *AuthRequestInfo) ([]string, error) {
6775
// Validate ensures that any custom config options
6876
// in a Config are set correctly.
6977
func (c *ACLMongoConfig) Validate(configKey string) error {
70-
//First validate the mongo config.
78+
//First validate the MongoDB config.
7179
if err := c.MongoConfig.Validate(configKey); err != nil {
7280
return err
7381
}
@@ -119,7 +127,7 @@ func (ma *aclMongoAuthorizer) continuouslyUpdateACLCache() {
119127

120128
func (ma *aclMongoAuthorizer) updateACLCache() error {
121129
// Get ACL from MongoDB
122-
var newACL ACL
130+
var newACL MongoACL
123131

124132
// Copy our session
125133
tmp_session := ma.session.Copy()
@@ -128,13 +136,43 @@ func (ma *aclMongoAuthorizer) updateACLCache() error {
128136
defer tmp_session.Close()
129137

130138
collection := tmp_session.DB(ma.config.MongoConfig.DialInfo.Database).C(ma.config.Collection)
131-
err := collection.Find(bson.M{}).All(&newACL)
132-
if err != nil {
139+
140+
// Create sequence index obj
141+
index := mgo.Index{
142+
Key: []string{"seq"},
143+
Unique: true,
144+
DropDups: false, // Error on duplicate key document instead of drop.
145+
}
146+
147+
// Enforce a sequence index. This is fine to do frequently per the docs:
148+
// https://godoc.org/gopkg.in/mgo.v2#Collection.EnsureIndex:
149+
// Once EnsureIndex returns successfully, following requests for the same index
150+
// will not contact the server unless Collection.DropIndex is used to drop the same
151+
// index, or Session.ResetIndexCache is called.
152+
if err := collection.EnsureIndex(index); err != nil {
133153
return err
134154
}
155+
156+
// Get all ACLs that have the required key
157+
if err := collection.Find(bson.M{}).Sort("seq").All(&newACL); err != nil {
158+
return err
159+
}
160+
135161
glog.V(2).Infof("Number of new ACL entries from MongoDB: %d", len(newACL))
136162

137-
newStaticAuthorizer, err := NewACLAuthorizer(newACL)
163+
// It is possible that the top document in the collection exists with a nil Seq.
164+
// if that's true we pull it out of the slice and complain about it.
165+
if len(newACL) > 0 && newACL[0].Seq == nil {
166+
topACL := newACL[0]
167+
return errors.New(fmt.Sprintf("Seq not set for ACL entry: %+v", topACL))
168+
}
169+
170+
var retACL ACL
171+
for _, e := range newACL {
172+
retACL = append(retACL, e.ACLEntry)
173+
}
174+
175+
newStaticAuthorizer, err := NewACLAuthorizer(retACL)
138176
if err != nil {
139177
return err
140178
}
@@ -144,7 +182,7 @@ func (ma *aclMongoAuthorizer) updateACLCache() error {
144182
ma.staticAuthorizer = newStaticAuthorizer
145183
ma.lock.Unlock()
146184

147-
glog.V(2).Infof("Got new ACL from MongoDB: %s", newACL)
148-
glog.V(1).Infof("Installed new ACL from MongoDB (%d entries)", len(newACL))
185+
glog.V(2).Infof("Got new ACL from MongoDB: %s", retACL)
186+
glog.V(1).Infof("Installed new ACL from MongoDB (%d entries)", len(retACL))
149187
return nil
150188
}

docs/Backend_MongoDB.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,20 @@ MongoDB quite easily. Below you can find a list of ACL entries that are ready to
3838
be imported into MongoDB. Those ACL entries reflect what's specified in the
3939
`example/reference.yml` file under the `acl` section (aka static ACL).
4040

41+
The added field of seq is used to provide a reliable order which MongoDB does not
42+
guarantee by default, i.e. [Natural Sorting](https://docs.mongodb.org/manual/reference/method/cursor.sort/#return-natural-order).
43+
44+
``seq`` is a required field in all MongoDB ACL documents. Any documents without this key will be excluded. seq uniqeness is also enforced.
45+
4146
**reference_acl.json**
4247

4348
```json
44-
{"match" : {"account" : "admin"}, "actions" : ["*"], "comment" : "Admin has full access to everything."}
45-
{"match" : {"account" : "test", "name" : "test-*"}, "actions" : ["*"], "comment" : "User \"test\" has full access to test-* images but nothing else. (1)"}
46-
{"match" : {"account" : "test"}, "actions" : [], "comment" : "User \"test\" has full access to test-* images but nothing else. (2)"}
47-
{"match" : {"account" : "/.+/"}, "actions" : ["pull"], "comment" : "All logged in users can pull all images."}
48-
{"match" : {"account" : "/.+/", "name" : "${account}/*"}, "actions" : ["*"], "comment" : "All logged in users can push all images that are in a namespace beginning with their name"}
49-
{"match" : {"account" : "", "name" : "hello-world"}, "actions" : ["pull"], "comment" : "Anonymous users can pull \"hello-world\"."}
49+
{"seq": 10, "match" : {"account" : "admin"}, "actions" : ["*"], "comment" : "Admin has full access to everything."}
50+
{"seq": 20, "match" : {"account" : "test", "name" : "test-*"}, "actions" : ["*"], "comment" : "User \"test\" has full access to test-* images but nothing else. (1)"}
51+
{"seq": 30, "match" : {"account" : "test"}, "actions" : [], "comment" : "User \"test\" has full access to test-* images but nothing else. (2)"}
52+
{"seq": 40, "match" : {"account" : "/.+/"}, "actions" : ["pull"], "comment" : "All logged in users can pull all images."}
53+
{"seq": 50, "match" : {"account" : "/.+/", "name" : "${account}/*"}, "actions" : ["*"], "comment" : "All logged in users can push all images that are in a namespace beginning with their name"}
54+
{"seq": 60, "match" : {"account" : "", "name" : "hello-world"}, "actions" : ["pull"], "comment" : "Anonymous users can pull \"hello-world\"."}
5055
```
5156

5257
**Note** that each document entry must span exactly one line or otherwise the

0 commit comments

Comments
 (0)