-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from dmcgowan/trust_graph
Trust graph
- Loading branch information
Showing
5 changed files
with
1,001 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package trustgraph | ||
|
||
import "github.com/docker/libtrust" | ||
|
||
// TrustGraph represents a graph of authorization mapping | ||
// public keys to nodes and grants between nodes. | ||
type TrustGraph interface { | ||
// Verifies that the given public key is allowed to perform | ||
// the given action on the given node according to the trust | ||
// graph. | ||
Verify(libtrust.PublicKey, string, uint16) (bool, error) | ||
|
||
// GetGrants returns an array of all grant chains which are used to | ||
// allow the requested permission. | ||
GetGrants(libtrust.PublicKey, string, uint16) ([][]*Grant, error) | ||
} | ||
|
||
// Grant represents a transfer of permission from one part of the | ||
// trust graph to another. This is the only way to delegate | ||
// permission between two different sub trees in the graph. | ||
type Grant struct { | ||
// Subject is the namespace being granted | ||
Subject string | ||
|
||
// Permissions is a bit map of permissions | ||
Permission uint16 | ||
|
||
// Grantee represents the node being granted | ||
// a permission scope. The grantee can be | ||
// either a namespace item or a key id where namespace | ||
// items will always start with a '/'. | ||
Grantee string | ||
|
||
// statement represents the statement used to create | ||
// this object. | ||
statement *Statement | ||
} | ||
|
||
// Permissions | ||
// Read node 0x01 (can read node, no sub nodes) | ||
// Write node 0x02 (can write to node object, cannot create subnodes) | ||
// Read subtree 0x04 (delegates read to each sub node) | ||
// Write subtree 0x08 (delegates write to each sub node, included create on the subject) | ||
// | ||
// Permission shortcuts | ||
// ReadItem = 0x01 | ||
// WriteItem = 0x03 | ||
// ReadAccess = 0x07 | ||
// WriteAccess = 0x0F | ||
// Delegate = 0x0F |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package trustgraph | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/docker/libtrust" | ||
) | ||
|
||
type grantNode struct { | ||
grants []*Grant | ||
children map[string]*grantNode | ||
} | ||
|
||
type memoryGraph struct { | ||
roots map[string]*grantNode | ||
} | ||
|
||
func newGrantNode() *grantNode { | ||
return &grantNode{ | ||
grants: []*Grant{}, | ||
children: map[string]*grantNode{}, | ||
} | ||
} | ||
|
||
// NewMemoryGraph returns a new in memory trust graph created from | ||
// a static list of grants. This graph is immutable after creation | ||
// and any alterations should create a new instance. | ||
func NewMemoryGraph(grants []*Grant) TrustGraph { | ||
roots := map[string]*grantNode{} | ||
for _, grant := range grants { | ||
parts := strings.Split(grant.Grantee, "/") | ||
nodes := roots | ||
var node *grantNode | ||
var nodeOk bool | ||
for _, part := range parts { | ||
node, nodeOk = nodes[part] | ||
if !nodeOk { | ||
node = newGrantNode() | ||
nodes[part] = node | ||
} | ||
if part != "" { | ||
node.grants = append(node.grants, grant) | ||
} | ||
nodes = node.children | ||
} | ||
} | ||
return &memoryGraph{roots} | ||
} | ||
|
||
func (g *memoryGraph) getGrants(name string) []*Grant { | ||
nameParts := strings.Split(name, "/") | ||
nodes := g.roots | ||
var node *grantNode | ||
var nodeOk bool | ||
for _, part := range nameParts { | ||
node, nodeOk = nodes[part] | ||
if !nodeOk { | ||
return nil | ||
} | ||
nodes = node.children | ||
} | ||
return node.grants | ||
} | ||
|
||
func isSubName(name, sub string) bool { | ||
if strings.HasPrefix(name, sub) { | ||
if len(name) == len(sub) || name[len(sub)] == '/' { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
type walkFunc func(*Grant, []*Grant) bool | ||
|
||
func foundWalkFunc(*Grant, []*Grant) bool { | ||
return true | ||
} | ||
|
||
func (g *memoryGraph) walkGrants(start, target string, permission uint16, f walkFunc, chain []*Grant, visited map[*Grant]bool, collect bool) bool { | ||
if visited == nil { | ||
visited = map[*Grant]bool{} | ||
} | ||
grants := g.getGrants(start) | ||
subGrants := make([]*Grant, 0, len(grants)) | ||
for _, grant := range grants { | ||
if visited[grant] { | ||
continue | ||
} | ||
visited[grant] = true | ||
if grant.Permission&permission == permission { | ||
if isSubName(target, grant.Subject) { | ||
if f(grant, chain) { | ||
return true | ||
} | ||
} else { | ||
subGrants = append(subGrants, grant) | ||
} | ||
} | ||
} | ||
for _, grant := range subGrants { | ||
var chainCopy []*Grant | ||
if collect { | ||
chainCopy = make([]*Grant, len(chain)+1) | ||
copy(chainCopy, chain) | ||
chainCopy[len(chainCopy)-1] = grant | ||
} else { | ||
chainCopy = nil | ||
} | ||
|
||
if g.walkGrants(grant.Subject, target, permission, f, chainCopy, visited, collect) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (g *memoryGraph) Verify(key libtrust.PublicKey, node string, permission uint16) (bool, error) { | ||
return g.walkGrants(key.KeyID(), node, permission, foundWalkFunc, nil, nil, false), nil | ||
} | ||
|
||
func (g *memoryGraph) GetGrants(key libtrust.PublicKey, node string, permission uint16) ([][]*Grant, error) { | ||
grants := [][]*Grant{} | ||
collect := func(grant *Grant, chain []*Grant) bool { | ||
grantChain := make([]*Grant, len(chain)+1) | ||
copy(grantChain, chain) | ||
grantChain[len(grantChain)-1] = grant | ||
grants = append(grants, grantChain) | ||
return false | ||
} | ||
g.walkGrants(key.KeyID(), node, permission, collect, nil, nil, true) | ||
return grants, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package trustgraph | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/docker/libtrust" | ||
) | ||
|
||
func createTestKeysAndGrants(count int) ([]*Grant, []libtrust.PrivateKey) { | ||
grants := make([]*Grant, count) | ||
keys := make([]libtrust.PrivateKey, count) | ||
for i := 0; i < count; i++ { | ||
pk, err := libtrust.GenerateECP256PrivateKey() | ||
if err != nil { | ||
panic(err) | ||
} | ||
grant := &Grant{ | ||
Subject: fmt.Sprintf("/user-%d", i+1), | ||
Permission: 0x0f, | ||
Grantee: pk.KeyID(), | ||
} | ||
keys[i] = pk | ||
grants[i] = grant | ||
} | ||
return grants, keys | ||
} | ||
|
||
func testVerified(t *testing.T, g TrustGraph, k libtrust.PublicKey, keyName, target string, permission uint16) { | ||
if ok, err := g.Verify(k, target, permission); err != nil { | ||
t.Fatalf("Unexpected error during verification: %s", err) | ||
} else if !ok { | ||
t.Errorf("key failed verification\n\tKey: %s(%s)\n\tNamespace: %s", keyName, k.KeyID(), target) | ||
} | ||
} | ||
|
||
func testNotVerified(t *testing.T, g TrustGraph, k libtrust.PublicKey, keyName, target string, permission uint16) { | ||
if ok, err := g.Verify(k, target, permission); err != nil { | ||
t.Fatalf("Unexpected error during verification: %s", err) | ||
} else if ok { | ||
t.Errorf("key should have failed verification\n\tKey: %s(%s)\n\tNamespace: %s", keyName, k.KeyID(), target) | ||
} | ||
} | ||
|
||
func TestVerify(t *testing.T) { | ||
grants, keys := createTestKeysAndGrants(4) | ||
extraGrants := make([]*Grant, 3) | ||
extraGrants[0] = &Grant{ | ||
Subject: "/user-3", | ||
Permission: 0x0f, | ||
Grantee: "/user-2", | ||
} | ||
extraGrants[1] = &Grant{ | ||
Subject: "/user-3/sub-project", | ||
Permission: 0x0f, | ||
Grantee: "/user-4", | ||
} | ||
extraGrants[2] = &Grant{ | ||
Subject: "/user-4", | ||
Permission: 0x07, | ||
Grantee: "/user-1", | ||
} | ||
grants = append(grants, extraGrants...) | ||
|
||
g := NewMemoryGraph(grants) | ||
|
||
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-1", 0x0f) | ||
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-1/some-project/sub-value", 0x0f) | ||
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-4", 0x07) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-2/", 0x0f) | ||
testVerified(t, g, keys[2].PublicKey(), "user-key-3", "/user-3/sub-value", 0x0f) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3/sub-value", 0x0f) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3", 0x0f) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3/", 0x0f) | ||
testVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-3/sub-project", 0x0f) | ||
testVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-3/sub-project/app", 0x0f) | ||
testVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-4", 0x0f) | ||
|
||
testNotVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-2", 0x0f) | ||
testNotVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-3/sub-value", 0x0f) | ||
testNotVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-4", 0x0f) | ||
testNotVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-1/", 0x0f) | ||
testNotVerified(t, g, keys[2].PublicKey(), "user-key-3", "/user-2", 0x0f) | ||
testNotVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-4", 0x0f) | ||
testNotVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-3", 0x0f) | ||
} | ||
|
||
func TestCircularWalk(t *testing.T) { | ||
grants, keys := createTestKeysAndGrants(3) | ||
user1Grant := &Grant{ | ||
Subject: "/user-2", | ||
Permission: 0x0f, | ||
Grantee: "/user-1", | ||
} | ||
user2Grant := &Grant{ | ||
Subject: "/user-1", | ||
Permission: 0x0f, | ||
Grantee: "/user-2", | ||
} | ||
grants = append(grants, user1Grant, user2Grant) | ||
|
||
g := NewMemoryGraph(grants) | ||
|
||
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-1", 0x0f) | ||
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-2", 0x0f) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-2", 0x0f) | ||
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-1", 0x0f) | ||
testVerified(t, g, keys[2].PublicKey(), "user-key-3", "/user-3", 0x0f) | ||
|
||
testNotVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-3", 0x0f) | ||
testNotVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3", 0x0f) | ||
} | ||
|
||
func assertGrantSame(t *testing.T, actual, expected *Grant) { | ||
if actual != expected { | ||
t.Fatalf("Unexpected grant retrieved\n\tExpected: %v\n\tActual: %v", expected, actual) | ||
} | ||
} | ||
|
||
func TestGetGrants(t *testing.T) { | ||
grants, keys := createTestKeysAndGrants(5) | ||
extraGrants := make([]*Grant, 4) | ||
extraGrants[0] = &Grant{ | ||
Subject: "/user-3/friend-project", | ||
Permission: 0x0f, | ||
Grantee: "/user-2/friends", | ||
} | ||
extraGrants[1] = &Grant{ | ||
Subject: "/user-3/sub-project", | ||
Permission: 0x0f, | ||
Grantee: "/user-4", | ||
} | ||
extraGrants[2] = &Grant{ | ||
Subject: "/user-2/friends", | ||
Permission: 0x0f, | ||
Grantee: "/user-5/fun-project", | ||
} | ||
extraGrants[3] = &Grant{ | ||
Subject: "/user-5/fun-project", | ||
Permission: 0x0f, | ||
Grantee: "/user-1", | ||
} | ||
grants = append(grants, extraGrants...) | ||
|
||
g := NewMemoryGraph(grants) | ||
|
||
grantChains, err := g.GetGrants(keys[3], "/user-3/sub-project/specific-app", 0x0f) | ||
if err != nil { | ||
t.Fatalf("Error getting grants: %s", err) | ||
} | ||
if len(grantChains) != 1 { | ||
t.Fatalf("Expected number of grant chains returned, expected %d, received %d", 1, len(grantChains)) | ||
} | ||
if len(grantChains[0]) != 2 { | ||
t.Fatalf("Unexpected number of grants retrieved\n\tExpected: %d\n\tActual: %d", 2, len(grantChains[0])) | ||
} | ||
assertGrantSame(t, grantChains[0][0], grants[3]) | ||
assertGrantSame(t, grantChains[0][1], extraGrants[1]) | ||
|
||
grantChains, err = g.GetGrants(keys[0], "/user-3/friend-project/fun-app", 0x0f) | ||
if err != nil { | ||
t.Fatalf("Error getting grants: %s", err) | ||
} | ||
if len(grantChains) != 1 { | ||
t.Fatalf("Expected number of grant chains returned, expected %d, received %d", 1, len(grantChains)) | ||
} | ||
if len(grantChains[0]) != 4 { | ||
t.Fatalf("Unexpected number of grants retrieved\n\tExpected: %d\n\tActual: %d", 2, len(grantChains[0])) | ||
} | ||
assertGrantSame(t, grantChains[0][0], grants[0]) | ||
assertGrantSame(t, grantChains[0][1], extraGrants[3]) | ||
assertGrantSame(t, grantChains[0][2], extraGrants[2]) | ||
assertGrantSame(t, grantChains[0][3], extraGrants[0]) | ||
} |
Oops, something went wrong.