client library for retrieving keys and signatures for all roles

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-01-18 15:15:53 -08:00
parent 60e6d254b3
commit a052d9e105
6 changed files with 195 additions and 5 deletions

View File

@ -169,7 +169,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
// currently we only support server managing timestamps and snapshots, and
// nothing else - timestamps are always managed by the server, and implicit
// (do not have to be passed in as part of `serverManagedRoles`, so that
// the API of Initialize doens't change).
// the API of Initialize doesn't change).
var serverManagesSnapshot bool
locallyManagedKeys := []string{
data.CanonicalTargetsRole,
@ -396,7 +396,7 @@ func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string,
}
// AddTarget creates new changelist entries to add a target to the given roles
// in the repository when the changelist gets appied at publish time.
// in the repository when the changelist gets applied at publish time.
// If roles are unspecified, the default role is "targets".
func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {
@ -454,7 +454,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
for _, role := range roles {
// we don't need to do anything special with removing role from
// roles because listSubtree always processes role and only excludes
// descendent delegations that appear in roles.
// descendant delegations that appear in roles.
r.listSubtree(targets, role, roles...)
}
@ -571,6 +571,56 @@ func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
return allDelegations, nil
}
// RoleWithSignatures is a Role with its associated signatures
type RoleWithSignatures struct {
Signatures []data.Signature
data.Role
}
// GetRepoRoleMetaInfo returns a list of RoleWithSignatures objects for this repo
// This represents the latest metadata for each role in this repo
func (r *NotaryRepository) GetRepoRoleMetaInfo() ([]RoleWithSignatures, error) {
// Update to latest repo state
_, err := r.Update(false)
if err != nil {
return nil, err
}
// Get all role info from our updated keysDB
roles, err := r.tufRepo.GetAllRoles()
if err != nil {
return nil, err
}
var roleWithSigs []RoleWithSignatures
// Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata
for _, role := range roles {
roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil}
switch role.Name {
case data.CanonicalRootRole:
roleWithSig.Signatures = r.tufRepo.Root.Signatures
case data.CanonicalTargetsRole:
roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures
case data.CanonicalSnapshotRole:
roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures
case data.CanonicalTimestampRole:
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
default:
// If the role isn't a delegation, we should error -- this is only possible if we have invalid keyDB state
if !data.IsDelegation(role.Name) {
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
}
if _, ok := r.tufRepo.Targets[role.Name]; ok {
// We'll only find a signature if we've published any targets with this delegation
roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures
}
}
roleWithSigs = append(roleWithSigs, roleWithSig)
}
return roleWithSigs, nil
}
// Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) Publish() error {

View File

@ -1115,7 +1115,7 @@ func testListTarget(t *testing.T, rootType string) {
repo, _ := initializeRepo(t, rootType, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)
// tests need to manually boostrap timestamp as client doesn't generate it
// tests need to manually bootstrap timestamp as client doesn't generate it
err := repo.tufRepo.InitTimestamp()
assert.NoError(t, err, "error creating repository: %s", err)
@ -1171,7 +1171,7 @@ func testListTargetWithDelegates(t *testing.T, rootType string) {
repo, _ := initializeRepo(t, rootType, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)
// tests need to manually boostrap timestamp as client doesn't generate it
// tests need to manually bootstrap timestamp as client doesn't generate it
err := repo.tufRepo.InitTimestamp()
assert.NoError(t, err, "error creating repository: %s", err)
@ -2949,3 +2949,102 @@ func TestDeleteRepoNoCerts(t *testing.T) {
// Assert keys for this repo exist locally
assertRepoHasExpectedKeys(t, repo, rootKeyID, true)
}
// Test that we get a correct map of key IDs
func TestGetRepoRoleMetaInfo(t *testing.T) {
ts := fullTestServer(t)
defer ts.Close()
repo, _ := initializeRepo(t, data.ECDSAKey, "docker.com/notary", ts.URL, false)
defer os.RemoveAll(repo.baseDir)
assert.NoError(t, repo.Publish())
rolesWithSigs, err := repo.GetRepoRoleMetaInfo()
assert.NoError(t, err)
// Should only have base roles at this point
assert.Len(t, rolesWithSigs, len(data.BaseRoles))
// Each base role should only have one key, one signature, and its key should match the signature's key
for _, role := range rolesWithSigs {
assert.Len(t, role.Signatures, 1)
assert.Len(t, role.KeyIDs, 1)
assert.Equal(t, role.Signatures[0].KeyID, role.KeyIDs[0])
}
// Create a delegation on the top level
aKey := createKey(t, repo, "user", true)
assert.NoError(t,
repo.AddDelegation("targets/a", 1, []data.PublicKey{aKey}, []string{""}),
"error creating delegation")
assert.NoError(t, repo.Publish())
rolesWithSigs, err = repo.GetRepoRoleMetaInfo()
assert.NoError(t, err)
assert.Len(t, rolesWithSigs, len(data.BaseRoles)+1)
// The delegation hasn't published any targets or metadata so it won't have a signature yet
for _, role := range rolesWithSigs {
if role.Name == "targets/a" {
assert.Nil(t, role.Signatures)
} else {
assert.Len(t, role.Signatures, 1)
assert.Equal(t, role.Signatures[0].KeyID, role.KeyIDs[0])
}
assert.Len(t, role.KeyIDs, 1)
}
addTarget(t, repo, "current", "../fixtures/intermediate-ca.crt", "targets/a")
assert.NoError(t, repo.Publish())
rolesWithSigs, err = repo.GetRepoRoleMetaInfo()
assert.NoError(t, err)
assert.Len(t, rolesWithSigs, len(data.BaseRoles)+1)
// The delegation should have a signature now
for _, role := range rolesWithSigs {
assert.Len(t, role.Signatures, 1)
assert.Equal(t, role.Signatures[0].KeyID, role.KeyIDs[0])
assert.Len(t, role.KeyIDs, 1)
}
// Create another delegation, one level further
bKey := createKey(t, repo, "user", true)
assert.NoError(t,
repo.AddDelegation("targets/a/b", 1, []data.PublicKey{bKey}, []string{""}),
"error creating delegation")
assert.NoError(t, repo.Publish())
rolesWithSigs, err = repo.GetRepoRoleMetaInfo()
assert.NoError(t, err)
assert.Len(t, rolesWithSigs, len(data.BaseRoles)+2)
// The nested delegation hasn't published any targets or metadata so it won't have a signature yet
for _, role := range rolesWithSigs {
if role.Name == "targets/a/b" {
assert.Nil(t, role.Signatures)
} else {
assert.Len(t, role.Signatures, 1)
assert.Equal(t, role.Signatures[0].KeyID, role.KeyIDs[0])
}
assert.Len(t, role.KeyIDs, 1)
}
// Now make another repo and check that we don't pick up its roles
repo2, _ := initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false)
defer os.RemoveAll(repo2.baseDir)
assert.NoError(t, repo2.Publish())
// repo2 only has the base roles
rolesWithSigs2, err := repo2.GetRepoRoleMetaInfo()
assert.NoError(t, err)
assert.Len(t, rolesWithSigs2, len(data.BaseRoles))
// original repo stays in same state (base roles + 2 delegations)
rolesWithSigs, err = repo.GetRepoRoleMetaInfo()
assert.NoError(t, err)
assert.Len(t, rolesWithSigs, len(data.BaseRoles)+2)
}

View File

@ -37,6 +37,13 @@ func (e ErrNoSuchRole) Error() string {
return fmt.Sprintf("role does not exist: %s", e.Role)
}
// ErrNoRoles indicates no roles exist for this repo
type ErrNoRoles struct{}
func (e ErrNoRoles) Error() string {
return fmt.Sprintf("no roles exist")
}
// ErrInvalidRole represents an error regarding a role. Typically
// something like a role for which sone of the public keys were
// not found in the TUF repo.

View File

@ -58,6 +58,15 @@ func (db *KeyDB) AddRole(r *data.Role) error {
return nil
}
// GetAllRoles gets all roles from the database
func (db *KeyDB) GetAllRoles() []*data.Role {
roles := []*data.Role{}
for _, role := range db.roles {
roles = append(roles, role)
}
return roles
}
// GetKey pulls a key out of the database by its ID
func (db *KeyDB) GetKey(id string) data.PublicKey {
return db.keys[id]

View File

@ -173,6 +173,15 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
return nil
}
// GetAllRoles returns a list of all role entries for this TUF repo
func (tr *Repo) GetAllRoles() ([]*data.Role, error) {
roles := tr.keysDB.GetAllRoles()
if len(roles) == 0 {
return nil, data.ErrNoRoles{}
}
return roles, nil
}
// GetDelegation finds the role entry representing the provided
// role name or ErrInvalidRole
func (tr *Repo) GetDelegation(role string) (*data.Role, error) {

View File

@ -936,3 +936,19 @@ func TestAddBaseKeysToRoot(t *testing.T) {
}
}
}
func TestGetAllRoles(t *testing.T) {
ed25519 := signed.NewEd25519()
keyDB := keys.NewDB()
repo := initRepo(t, ed25519, keyDB)
// After we init, we get the base roles
roles, err := repo.GetAllRoles()
assert.Len(t, roles, len(data.BaseRoles))
// Clear the keysDB, check that we error
repo.keysDB = keys.NewDB()
roles, err = repo.GetAllRoles()
assert.Error(t, err)
assert.IsType(t, data.ErrNoRoles{}, err)
}