1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Update libtrust version

Updated to version of libtrust with the trust graph implementation

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2014-09-30 10:42:44 -07:00
parent 61c6f206b0
commit 8a6c7100ea
6 changed files with 1002 additions and 1 deletions

View file

@ -51,7 +51,7 @@ clone hg code.google.com/p/go.net 84a4013f96e0
clone hg code.google.com/p/gosqlite 74691fb6f837 clone hg code.google.com/p/gosqlite 74691fb6f837
clone git github.com/docker/libtrust 136d534cc940 clone git github.com/docker/libtrust d273ef2565ca
# get Go tip's archive/tar, for xattr support and improved performance # get Go tip's archive/tar, for xattr support and improved performance
# TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep # TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep

View file

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

View file

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

View file

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

View file

@ -0,0 +1,227 @@
package trustgraph
import (
"crypto/x509"
"encoding/json"
"io"
"io/ioutil"
"sort"
"strings"
"time"
"github.com/docker/libtrust"
)
type jsonGrant struct {
Subject string `json:"subject"`
Permission uint16 `json:"permission"`
Grantee string `json:"grantee"`
}
type jsonRevocation struct {
Subject string `json:"subject"`
Revocation uint16 `json:"revocation"`
Grantee string `json:"grantee"`
}
type jsonStatement struct {
Revocations []*jsonRevocation `json:"revocations"`
Grants []*jsonGrant `json:"grants"`
Expiration time.Time `json:"expiration"`
IssuedAt time.Time `json:"issuedAt"`
}
func (g *jsonGrant) Grant(statement *Statement) *Grant {
return &Grant{
Subject: g.Subject,
Permission: g.Permission,
Grantee: g.Grantee,
statement: statement,
}
}
// Statement represents a set of grants made from a verifiable
// authority. A statement has an expiration associated with it
// set by the authority.
type Statement struct {
jsonStatement
signature *libtrust.JSONSignature
}
// IsExpired returns whether the statement has expired
func (s *Statement) IsExpired() bool {
return s.Expiration.Before(time.Now().Add(-10 * time.Second))
}
// Bytes returns an indented json representation of the statement
// in a byte array. This value can be written to a file or stream
// without alteration.
func (s *Statement) Bytes() ([]byte, error) {
return s.signature.PrettySignature("signatures")
}
// LoadStatement loads and verifies a statement from an input stream.
func LoadStatement(r io.Reader, authority *x509.CertPool) (*Statement, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
js, err := libtrust.ParsePrettySignature(b, "signatures")
if err != nil {
return nil, err
}
payload, err := js.Payload()
if err != nil {
return nil, err
}
var statement Statement
err = json.Unmarshal(payload, &statement.jsonStatement)
if err != nil {
return nil, err
}
if authority == nil {
_, err = js.Verify()
if err != nil {
return nil, err
}
} else {
_, err = js.VerifyChains(authority)
if err != nil {
return nil, err
}
}
statement.signature = js
return &statement, nil
}
// CreateStatements creates and signs a statement from a stream of grants
// and revocations in a JSON array.
func CreateStatement(grants, revocations io.Reader, expiration time.Duration, key libtrust.PrivateKey, chain []*x509.Certificate) (*Statement, error) {
var statement Statement
err := json.NewDecoder(grants).Decode(&statement.jsonStatement.Grants)
if err != nil {
return nil, err
}
err = json.NewDecoder(revocations).Decode(&statement.jsonStatement.Revocations)
if err != nil {
return nil, err
}
statement.jsonStatement.Expiration = time.Now().UTC().Add(expiration)
statement.jsonStatement.IssuedAt = time.Now().UTC()
b, err := json.MarshalIndent(&statement.jsonStatement, "", " ")
if err != nil {
return nil, err
}
statement.signature, err = libtrust.NewJSONSignature(b)
if err != nil {
return nil, err
}
err = statement.signature.SignWithChain(key, chain)
if err != nil {
return nil, err
}
return &statement, nil
}
type statementList []*Statement
func (s statementList) Len() int {
return len(s)
}
func (s statementList) Less(i, j int) bool {
return s[i].IssuedAt.Before(s[j].IssuedAt)
}
func (s statementList) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// CollapseStatements returns a single list of the valid statements as well as the
// time when the next grant will expire.
func CollapseStatements(statements []*Statement, useExpired bool) ([]*Grant, time.Time, error) {
sorted := make(statementList, 0, len(statements))
for _, statement := range statements {
if useExpired || !statement.IsExpired() {
sorted = append(sorted, statement)
}
}
sort.Sort(sorted)
var minExpired time.Time
var grantCount int
roots := map[string]*grantNode{}
for i, statement := range sorted {
if statement.Expiration.Before(minExpired) || i == 0 {
minExpired = statement.Expiration
}
for _, grant := range statement.Grants {
parts := strings.Split(grant.Grantee, "/")
nodes := roots
g := grant.Grant(statement)
grantCount = grantCount + 1
for _, part := range parts {
node, nodeOk := nodes[part]
if !nodeOk {
node = newGrantNode()
nodes[part] = node
}
node.grants = append(node.grants, g)
nodes = node.children
}
}
for _, revocation := range statement.Revocations {
parts := strings.Split(revocation.Grantee, "/")
nodes := roots
var node *grantNode
var nodeOk bool
for _, part := range parts {
node, nodeOk = nodes[part]
if !nodeOk {
break
}
nodes = node.children
}
if node != nil {
for _, grant := range node.grants {
if isSubName(grant.Subject, revocation.Subject) {
grant.Permission = grant.Permission &^ revocation.Revocation
}
}
}
}
}
retGrants := make([]*Grant, 0, grantCount)
for _, rootNodes := range roots {
retGrants = append(retGrants, rootNodes.grants...)
}
return retGrants, minExpired, nil
}
// FilterStatements filters the statements to statements including the given grants.
func FilterStatements(grants []*Grant) ([]*Statement, error) {
statements := map[*Statement]bool{}
for _, grant := range grants {
if grant.statement != nil {
statements[grant.statement] = true
}
}
retStatements := make([]*Statement, len(statements))
var i int
for statement := range statements {
retStatements[i] = statement
i++
}
return retStatements, nil
}

View file

@ -0,0 +1,417 @@
package trustgraph
import (
"bytes"
"crypto/x509"
"encoding/json"
"testing"
"time"
"github.com/docker/libtrust"
"github.com/docker/libtrust/testutil"
)
const testStatementExpiration = time.Hour * 5
func generateStatement(grants []*Grant, key libtrust.PrivateKey, chain []*x509.Certificate) (*Statement, error) {
var statement Statement
statement.Grants = make([]*jsonGrant, len(grants))
for i, grant := range grants {
statement.Grants[i] = &jsonGrant{
Subject: grant.Subject,
Permission: grant.Permission,
Grantee: grant.Grantee,
}
}
statement.IssuedAt = time.Now()
statement.Expiration = time.Now().Add(testStatementExpiration)
statement.Revocations = make([]*jsonRevocation, 0)
marshalled, err := json.MarshalIndent(statement.jsonStatement, "", " ")
if err != nil {
return nil, err
}
sig, err := libtrust.NewJSONSignature(marshalled)
if err != nil {
return nil, err
}
err = sig.SignWithChain(key, chain)
if err != nil {
return nil, err
}
statement.signature = sig
return &statement, nil
}
func generateTrustChain(t *testing.T, chainLen int) (libtrust.PrivateKey, *x509.CertPool, []*x509.Certificate) {
caKey, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("Error generating key: %s", err)
}
ca, err := testutil.GenerateTrustCA(caKey.CryptoPublicKey(), caKey.CryptoPrivateKey())
if err != nil {
t.Fatalf("Error generating ca: %s", err)
}
parent := ca
parentKey := caKey
chain := make([]*x509.Certificate, chainLen)
for i := chainLen - 1; i > 0; i-- {
intermediatekey, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("Error generate key: %s", err)
}
chain[i], err = testutil.GenerateIntermediate(intermediatekey.CryptoPublicKey(), parentKey.CryptoPrivateKey(), parent)
if err != nil {
t.Fatalf("Error generating intermdiate certificate: %s", err)
}
parent = chain[i]
parentKey = intermediatekey
}
trustKey, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("Error generate key: %s", err)
}
chain[0], err = testutil.GenerateTrustCert(trustKey.CryptoPublicKey(), parentKey.CryptoPrivateKey(), parent)
if err != nil {
t.Fatalf("Error generate trust cert: %s", err)
}
caPool := x509.NewCertPool()
caPool.AddCert(ca)
return trustKey, caPool, chain
}
func TestLoadStatement(t *testing.T) {
grantCount := 4
grants, _ := createTestKeysAndGrants(grantCount)
trustKey, caPool, chain := generateTrustChain(t, 6)
statement, err := generateStatement(grants, trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statementBytes, err := statement.Bytes()
if err != nil {
t.Fatalf("Error getting statement bytes: %s", err)
}
s2, err := LoadStatement(bytes.NewReader(statementBytes), caPool)
if err != nil {
t.Fatalf("Error loading statement: %s", err)
}
if len(s2.Grants) != grantCount {
t.Fatalf("Unexpected grant length\n\tExpected: %d\n\tActual: %d", grantCount, len(s2.Grants))
}
pool := x509.NewCertPool()
_, err = LoadStatement(bytes.NewReader(statementBytes), pool)
if err == nil {
t.Fatalf("No error thrown verifying without an authority")
} else if _, ok := err.(x509.UnknownAuthorityError); !ok {
t.Fatalf("Unexpected error verifying without authority: %s", err)
}
s2, err = LoadStatement(bytes.NewReader(statementBytes), nil)
if err != nil {
t.Fatalf("Error loading statement: %s", err)
}
if len(s2.Grants) != grantCount {
t.Fatalf("Unexpected grant length\n\tExpected: %d\n\tActual: %d", grantCount, len(s2.Grants))
}
badData := make([]byte, len(statementBytes))
copy(badData, statementBytes)
badData[0] = '['
_, err = LoadStatement(bytes.NewReader(badData), nil)
if err == nil {
t.Fatalf("No error thrown parsing bad json")
}
alteredData := make([]byte, len(statementBytes))
copy(alteredData, statementBytes)
alteredData[30] = '0'
_, err = LoadStatement(bytes.NewReader(alteredData), nil)
if err == nil {
t.Fatalf("No error thrown from bad data")
}
}
func TestCollapseGrants(t *testing.T) {
grantCount := 8
grants, keys := createTestKeysAndGrants(grantCount)
linkGrants := make([]*Grant, 4)
linkGrants[0] = &Grant{
Subject: "/user-3",
Permission: 0x0f,
Grantee: "/user-2",
}
linkGrants[1] = &Grant{
Subject: "/user-3/sub-project",
Permission: 0x0f,
Grantee: "/user-4",
}
linkGrants[2] = &Grant{
Subject: "/user-6",
Permission: 0x0f,
Grantee: "/user-7",
}
linkGrants[3] = &Grant{
Subject: "/user-6/sub-project/specific-app",
Permission: 0x0f,
Grantee: "/user-5",
}
trustKey, pool, chain := generateTrustChain(t, 3)
statements := make([]*Statement, 3)
var err error
statements[0], err = generateStatement(grants[0:4], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[1], err = generateStatement(grants[4:], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[2], err = generateStatement(linkGrants, trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statementsCopy := make([]*Statement, len(statements))
for i, statement := range statements {
b, err := statement.Bytes()
if err != nil {
t.Fatalf("Error getting statement bytes: %s", err)
}
verifiedStatement, err := LoadStatement(bytes.NewReader(b), pool)
if err != nil {
t.Fatalf("Error loading statement: %s", err)
}
// Force sort by reversing order
statementsCopy[len(statementsCopy)-i-1] = verifiedStatement
}
statements = statementsCopy
collapsedGrants, expiration, err := CollapseStatements(statements, false)
if len(collapsedGrants) != 12 {
t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants))
}
if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) {
t.Fatalf("Unexpected expiration time: %s", expiration.String())
}
g := NewMemoryGraph(collapsedGrants)
testVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-1", 0x0f)
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-2", 0x0f)
testVerified(t, g, keys[2].PublicKey(), "user-key-3", "/user-3", 0x0f)
testVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-4", 0x0f)
testVerified(t, g, keys[4].PublicKey(), "user-key-5", "/user-5", 0x0f)
testVerified(t, g, keys[5].PublicKey(), "user-key-6", "/user-6", 0x0f)
testVerified(t, g, keys[6].PublicKey(), "user-key-7", "/user-7", 0x0f)
testVerified(t, g, keys[7].PublicKey(), "user-key-8", "/user-8", 0x0f)
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3", 0x0f)
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-3/sub-project/specific-app", 0x0f)
testVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-3/sub-project", 0x0f)
testVerified(t, g, keys[6].PublicKey(), "user-key-7", "/user-6", 0x0f)
testVerified(t, g, keys[6].PublicKey(), "user-key-7", "/user-6/sub-project/specific-app", 0x0f)
testVerified(t, g, keys[4].PublicKey(), "user-key-5", "/user-6/sub-project/specific-app", 0x0f)
testNotVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-3", 0x0f)
testNotVerified(t, g, keys[3].PublicKey(), "user-key-4", "/user-6/sub-project", 0x0f)
testNotVerified(t, g, keys[4].PublicKey(), "user-key-5", "/user-6/sub-project", 0x0f)
// Add revocation grant
statements = append(statements, &Statement{
jsonStatement{
IssuedAt: time.Now(),
Expiration: time.Now().Add(testStatementExpiration),
Grants: []*jsonGrant{},
Revocations: []*jsonRevocation{
&jsonRevocation{
Subject: "/user-1",
Revocation: 0x0f,
Grantee: keys[0].KeyID(),
},
&jsonRevocation{
Subject: "/user-2",
Revocation: 0x08,
Grantee: keys[1].KeyID(),
},
&jsonRevocation{
Subject: "/user-6",
Revocation: 0x0f,
Grantee: "/user-7",
},
&jsonRevocation{
Subject: "/user-9",
Revocation: 0x0f,
Grantee: "/user-10",
},
},
},
nil,
})
collapsedGrants, expiration, err = CollapseStatements(statements, false)
if len(collapsedGrants) != 12 {
t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants))
}
if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) {
t.Fatalf("Unexpected expiration time: %s", expiration.String())
}
g = NewMemoryGraph(collapsedGrants)
testNotVerified(t, g, keys[0].PublicKey(), "user-key-1", "/user-1", 0x0f)
testNotVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-2", 0x0f)
testNotVerified(t, g, keys[6].PublicKey(), "user-key-7", "/user-6/sub-project/specific-app", 0x0f)
testVerified(t, g, keys[1].PublicKey(), "user-key-2", "/user-2", 0x07)
}
func TestFilterStatements(t *testing.T) {
grantCount := 8
grants, keys := createTestKeysAndGrants(grantCount)
linkGrants := make([]*Grant, 3)
linkGrants[0] = &Grant{
Subject: "/user-3",
Permission: 0x0f,
Grantee: "/user-2",
}
linkGrants[1] = &Grant{
Subject: "/user-5",
Permission: 0x0f,
Grantee: "/user-4",
}
linkGrants[2] = &Grant{
Subject: "/user-7",
Permission: 0x0f,
Grantee: "/user-6",
}
trustKey, _, chain := generateTrustChain(t, 3)
statements := make([]*Statement, 5)
var err error
statements[0], err = generateStatement(grants[0:2], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[1], err = generateStatement(grants[2:4], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[2], err = generateStatement(grants[4:6], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[3], err = generateStatement(grants[6:], trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
statements[4], err = generateStatement(linkGrants, trustKey, chain)
if err != nil {
t.Fatalf("Error generating statement: %s", err)
}
collapsed, _, err := CollapseStatements(statements, false)
if err != nil {
t.Fatalf("Error collapsing grants: %s", err)
}
// Filter 1, all 5 statements
filter1, err := FilterStatements(collapsed)
if err != nil {
t.Fatalf("Error filtering statements: %s", err)
}
if len(filter1) != 5 {
t.Fatalf("Wrong number of statements, expected %d, received %d", 5, len(filter1))
}
// Filter 2, one statement
filter2, err := FilterStatements([]*Grant{collapsed[0]})
if err != nil {
t.Fatalf("Error filtering statements: %s", err)
}
if len(filter2) != 1 {
t.Fatalf("Wrong number of statements, expected %d, received %d", 1, len(filter2))
}
// Filter 3, 2 statements, from graph lookup
g := NewMemoryGraph(collapsed)
lookupGrants, err := g.GetGrants(keys[1], "/user-3", 0x0f)
if err != nil {
t.Fatalf("Error looking up grants: %s", err)
}
if len(lookupGrants) != 1 {
t.Fatalf("Wrong numberof grant chains returned from lookup, expected %d, received %d", 1, len(lookupGrants))
}
if len(lookupGrants[0]) != 2 {
t.Fatalf("Wrong number of grants looked up, expected %d, received %d", 2, len(lookupGrants))
}
filter3, err := FilterStatements(lookupGrants[0])
if err != nil {
t.Fatalf("Error filtering statements: %s", err)
}
if len(filter3) != 2 {
t.Fatalf("Wrong number of statements, expected %d, received %d", 2, len(filter3))
}
}
func TestCreateStatement(t *testing.T) {
grantJSON := bytes.NewReader([]byte(`[
{
"subject": "/user-2",
"permission": 15,
"grantee": "/user-1"
},
{
"subject": "/user-7",
"permission": 1,
"grantee": "/user-9"
},
{
"subject": "/user-3",
"permission": 15,
"grantee": "/user-2"
}
]`))
revocationJSON := bytes.NewReader([]byte(`[
{
"subject": "user-8",
"revocation": 12,
"grantee": "user-9"
}
]`))
trustKey, pool, chain := generateTrustChain(t, 3)
statement, err := CreateStatement(grantJSON, revocationJSON, testStatementExpiration, trustKey, chain)
if err != nil {
t.Fatalf("Error creating statement: %s", err)
}
b, err := statement.Bytes()
if err != nil {
t.Fatalf("Error retrieving bytes: %s", err)
}
verified, err := LoadStatement(bytes.NewReader(b), pool)
if err != nil {
t.Fatalf("Error loading statement: %s", err)
}
if len(verified.Grants) != 3 {
t.Errorf("Unexpected number of grants, expected %d, received %d", 3, len(verified.Grants))
}
if len(verified.Revocations) != 1 {
t.Errorf("Unexpected number of revocations, expected %d, received %d", 1, len(verified.Revocations))
}
}