77e69b9cf3
Signed-off-by: Olivier Gambier <olivier@docker.com>
227 lines
5.4 KiB
Go
227 lines
5.4 KiB
Go
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
|
|
}
|