mirror of https://github.com/docker/docs.git
add GetBaseRole and GetDelegationRole with path validation
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
a754574b85
commit
d379f9918c
|
@ -120,12 +120,12 @@ type BaseRole struct {
|
|||
Threshold int `json:"threshold"`
|
||||
}
|
||||
|
||||
// Returns true for BaseRole
|
||||
// IsBaseRole returns true for BaseRole
|
||||
func (b BaseRole) IsBaseRole() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns false for BaseRole
|
||||
// IsDelegationRole returns false for BaseRole
|
||||
func (b BaseRole) IsDelegationRole() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ func (b BaseRole) GetThreshold() int {
|
|||
|
||||
// ListKeys retrieves the public keys valid for this role
|
||||
func (b BaseRole) ListKeys() KeyList {
|
||||
keys := make(KeyList)
|
||||
keys := KeyList{}
|
||||
for _, key := range b.Keys {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (b BaseRole) ListKeys() KeyList {
|
|||
|
||||
// ListKeyIDs retrieves the list of key IDs valid for this role
|
||||
func (b BaseRole) ListKeyIDs() []string {
|
||||
keyIDs := make([]string)
|
||||
keyIDs := []string{}
|
||||
for id := range b.Keys {
|
||||
keyIDs = append(keyIDs, id)
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ func (b BaseRole) ListPaths() ([]string, error) {
|
|||
return nil, fmt.Errorf("%s is not a delegation role", b.Name)
|
||||
}
|
||||
|
||||
// ListPaths returns an error for non-delegations
|
||||
// ListPathHashPrefixes returns an error for non-delegations
|
||||
func (b BaseRole) ListPathHashPrefixes() ([]string, error) {
|
||||
return nil, fmt.Errorf("%s is not a delegation role", b.Name)
|
||||
}
|
||||
|
@ -175,12 +175,12 @@ type DelegationRole struct {
|
|||
PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
|
||||
}
|
||||
|
||||
// Returns false for DelegationRole
|
||||
// IsBaseRole returns false for DelegationRole
|
||||
func (d DelegationRole) IsBaseRole() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true for DelegationRole
|
||||
// IsDelegationRole returns true for DelegationRole
|
||||
func (d DelegationRole) IsDelegationRole() bool {
|
||||
return true
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ func (d DelegationRole) GetThreshold() int {
|
|||
|
||||
// ListKeys retrieves the public keys valid for this role
|
||||
func (d DelegationRole) ListKeys() KeyList {
|
||||
keys := make(KeyList)
|
||||
keys := KeyList{}
|
||||
for _, key := range d.Keys {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ func (d DelegationRole) ListKeys() KeyList {
|
|||
|
||||
// ListKeyIDs retrieves the list of key IDs valid for this role
|
||||
func (d DelegationRole) ListKeyIDs() []string {
|
||||
keyIDs := make([]string)
|
||||
keyIDs := []string{}
|
||||
for id := range d.Keys {
|
||||
keyIDs = append(keyIDs, id)
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ func (d DelegationRole) ListPaths() ([]string, error) {
|
|||
return d.Paths, nil
|
||||
}
|
||||
|
||||
// ListPaths lists the paths of this rol
|
||||
// ListPathHashPrefixes lists the paths of this role
|
||||
func (d DelegationRole) ListPathHashPrefixes() ([]string, error) {
|
||||
return d.PathHashPrefixes, nil
|
||||
}
|
||||
|
|
132
tuf/tuf.go
132
tuf/tuf.go
|
@ -173,6 +173,138 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetBaseRole gets a base role from this repo's metadata
|
||||
func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) {
|
||||
if !data.IsBaseRole(name) {
|
||||
return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"}
|
||||
}
|
||||
if tr.Root == nil {
|
||||
return data.BaseRole{}, ErrNotLoaded{data.CanonicalRootRole}
|
||||
}
|
||||
roleData, ok := tr.Root.Signed.Roles[name]
|
||||
if !ok {
|
||||
return data.BaseRole{}, ErrNotLoaded{name}
|
||||
}
|
||||
// Get all public keys for the base role from TUF metadata
|
||||
keyIDs := roleData.KeyIDs
|
||||
pubKeys := make(map[string]data.PublicKey)
|
||||
for _, keyID := range keyIDs {
|
||||
pubKey, ok := tr.Root.Signed.Keys[keyID]
|
||||
if !ok {
|
||||
return data.BaseRole{}, fmt.Errorf("key with ID %s, specified as a signing key for role %s, was not found in root metadata", keyID, name)
|
||||
}
|
||||
pubKeys[keyID] = pubKey
|
||||
}
|
||||
|
||||
return data.BaseRole{
|
||||
Name: name,
|
||||
Keys: pubKeys,
|
||||
Threshold: roleData.Threshold,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself
|
||||
func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) {
|
||||
if !data.IsDelegation(name) {
|
||||
return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"}
|
||||
}
|
||||
if tr.Root == nil {
|
||||
return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole}
|
||||
}
|
||||
_, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole]
|
||||
if !ok {
|
||||
return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole}
|
||||
}
|
||||
// Traverse target metadata, down to delegation itself
|
||||
// Get all public keys for the base role from TUF metadata
|
||||
signedTargetData, ok := tr.Targets[data.CanonicalTargetsRole]
|
||||
if !ok {
|
||||
return data.DelegationRole{}, fmt.Errorf("TUF data for targets does not exist in targets metadata", name)
|
||||
}
|
||||
|
||||
// Keep track of paths and path hash prefixes to validate as we traverse the delegations tree, targets implicitly has ""
|
||||
whiteListedPaths := []string{""}
|
||||
whiteListedPathHashes := []string{""}
|
||||
|
||||
// Start with top level roles in targets
|
||||
delegationRoles := signedTargetData.Signed.Delegations.Roles
|
||||
var foundRole *data.Role
|
||||
for len(delegationRoles) > 0 {
|
||||
delgRole := delegationRoles[0]
|
||||
delegationRoles = delegationRoles[1:]
|
||||
|
||||
// If this role is delegated above or is our desired role, validate paths and traverse its child roles
|
||||
if delgRole.Name == name || strings.HasPrefix(name, delgRole.Name+"/") {
|
||||
if okPaths := validateDelegationPathPrefixes(whiteListedPaths, delgRole.Paths); !okPaths {
|
||||
return data.DelegationRole{}, fmt.Errorf("Invalid paths specified for delegation %s", delgRole.Name)
|
||||
}
|
||||
whiteListedPaths = delgRole.Paths
|
||||
if okPathHashPrefixes := validateDelegationPathPrefixes(whiteListedPathHashes, delgRole.PathHashPrefixes); !okPathHashPrefixes {
|
||||
return data.DelegationRole{}, fmt.Errorf("Invalid path hash prefixes specified for delegation %s", delgRole.Name)
|
||||
}
|
||||
|
||||
// If we found the role, we can exit the loop
|
||||
if delgRole.Name == name {
|
||||
foundRole = delgRole
|
||||
break
|
||||
}
|
||||
|
||||
// If this is a parent role, keep traversing
|
||||
whiteListedPathHashes = delgRole.PathHashPrefixes
|
||||
delegationRoles = append(delegationRoles, tr.Targets[delgRole.Name].Signed.Delegations.Roles...)
|
||||
}
|
||||
}
|
||||
|
||||
if foundRole == nil {
|
||||
return data.DelegationRole{}, fmt.Errorf("could not find valid delegation for role %s", name)
|
||||
}
|
||||
|
||||
pubKeys := make(map[string]data.PublicKey)
|
||||
parentData, ok := tr.Targets[path.Dir(foundRole.Name)]
|
||||
if !ok {
|
||||
return data.DelegationRole{}, fmt.Errorf("delegation parent data for %s does not exist in targets metadata", foundRole.Name)
|
||||
}
|
||||
for _, keyID := range foundRole.KeyIDs {
|
||||
pubKey, ok := parentData.Signed.Delegations.Keys[keyID]
|
||||
if !ok {
|
||||
return data.DelegationRole{}, fmt.Errorf("delegation %s does not exist in targets metadata", foundRole.Name)
|
||||
}
|
||||
pubKeys[keyID] = pubKey
|
||||
}
|
||||
|
||||
return data.DelegationRole{
|
||||
BaseRole: data.BaseRole{
|
||||
Name: name,
|
||||
Keys: pubKeys,
|
||||
Threshold: foundRole.Threshold,
|
||||
},
|
||||
Paths: foundRole.Paths,
|
||||
PathHashPrefixes: foundRole.PathHashPrefixes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Returns true if the delegationPaths are prefixed by parentPaths
|
||||
func validateDelegationPathPrefixes(parentPaths, delegationPaths []string) bool {
|
||||
if len(delegationPaths) == 0 {
|
||||
return true
|
||||
}
|
||||
// Validate each individual delegation path
|
||||
for _, delgPath := range delegationPaths {
|
||||
isPrefixed := false
|
||||
for _, parentPath := range parentPaths {
|
||||
if strings.HasPrefix(delgPath, parentPath) {
|
||||
isPrefixed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If the delegation path did not match prefix against any parent path, it is not valid
|
||||
if !isPrefixed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
|
||||
func (tr *Repo) GetAllLoadedRoles() []*data.Role {
|
||||
return tr.keysDB.GetAllRoles()
|
||||
|
|
200
tuf/tuf_test.go
200
tuf/tuf_test.go
|
@ -953,3 +953,203 @@ func TestGetAllRoles(t *testing.T) {
|
|||
roles = repo.GetAllLoadedRoles()
|
||||
assert.Len(t, roles, 0)
|
||||
}
|
||||
|
||||
func TestGetBaseRoles(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
// After we init, we get the base roles
|
||||
for _, role := range data.BaseRoles {
|
||||
baseRole, err := repo.GetBaseRole(role)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, role, baseRole.GetName())
|
||||
keyIDs := repo.cryptoService.ListKeys(role)
|
||||
for _, keyID := range keyIDs {
|
||||
_, ok := baseRole.Keys[keyID]
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, baseRole.ListKeyIDs(), keyID)
|
||||
}
|
||||
// initRepo should set all key thresholds to 1
|
||||
assert.Equal(t, 1, baseRole.GetThreshold())
|
||||
assert.False(t, baseRole.IsDelegationRole())
|
||||
assert.True(t, baseRole.IsBaseRole())
|
||||
_, err = baseRole.ListPathHashPrefixes()
|
||||
assert.Error(t, err)
|
||||
_, err = baseRole.ListPaths()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBaseRolesInvalidName(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
_, err := repo.GetBaseRole("invalid")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = repo.GetBaseRole("targets/delegation")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetDelegationValidRoles(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
testKey1, err := ed25519.Create("targets/test", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
role, err := data.NewRole(
|
||||
"targets/test", 1, []string{testKey1.ID()}, []string{"path", "anotherpath"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey1})
|
||||
assert.NoError(t, err)
|
||||
|
||||
delgRole, err := repo.GetDelegationRole("targets/test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, delgRole.IsDelegationRole())
|
||||
assert.False(t, delgRole.IsBaseRole())
|
||||
assert.Equal(t, "targets/test", delgRole.GetName())
|
||||
assert.Equal(t, 1, delgRole.GetThreshold())
|
||||
assert.Equal(t, []string{testKey1.ID()}, delgRole.ListKeyIDs())
|
||||
delgPaths, err := delgRole.ListPaths()
|
||||
assert.NoError(t, err)
|
||||
delgPathPrefixes, err := delgRole.ListPathHashPrefixes()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, delgPathPrefixes)
|
||||
assert.Equal(t, []string{"path", "anotherpath"}, delgPaths)
|
||||
assert.Equal(t, testKey1, delgRole.Keys[testKey1.ID()])
|
||||
|
||||
testKey2, err := ed25519.Create("targets/a", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
role, err = data.NewRole(
|
||||
"targets/a", 1, []string{testKey2.ID()}, []string{""}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
delgRole, err = repo.GetDelegationRole("targets/a")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, delgRole.IsDelegationRole())
|
||||
assert.False(t, delgRole.IsBaseRole())
|
||||
assert.Equal(t, "targets/a", delgRole.GetName())
|
||||
assert.Equal(t, 1, delgRole.GetThreshold())
|
||||
assert.Equal(t, []string{testKey2.ID()}, delgRole.ListKeyIDs())
|
||||
delgPaths, err = delgRole.ListPaths()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{""}, delgPaths)
|
||||
delgPathPrefixes, err = delgRole.ListPathHashPrefixes()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, delgPathPrefixes)
|
||||
assert.Equal(t, testKey2, delgRole.Keys[testKey2.ID()])
|
||||
|
||||
testKey3, err := ed25519.Create("targets/test/b", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
role, err = data.NewRole(
|
||||
"targets/test/b", 1, []string{testKey3.ID()}, []string{"path/subpath", "anotherpath"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey3})
|
||||
assert.NoError(t, err)
|
||||
|
||||
delgRole, err = repo.GetDelegationRole("targets/test/b")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, delgRole.IsDelegationRole())
|
||||
assert.False(t, delgRole.IsBaseRole())
|
||||
assert.Equal(t, "targets/test/b", delgRole.GetName())
|
||||
assert.Equal(t, 1, delgRole.GetThreshold())
|
||||
assert.Equal(t, []string{testKey3.ID()}, delgRole.ListKeyIDs())
|
||||
delgPaths, err = delgRole.ListPaths()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"path/subpath", "anotherpath"}, delgPaths)
|
||||
delgPathPrefixes, err = delgRole.ListPathHashPrefixes()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, delgPathPrefixes)
|
||||
assert.Equal(t, testKey3, delgRole.Keys[testKey3.ID()])
|
||||
|
||||
testKey4, err := ed25519.Create("targets/test/c", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
// Try adding empty paths, ensure this is valid
|
||||
role, err = data.NewRole(
|
||||
"targets/test/c", 1, []string{testKey4.ID()}, []string{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey3})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetDelegationRolesInvalidName(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
_, err := repo.GetDelegationRole("invalid")
|
||||
assert.Error(t, err)
|
||||
|
||||
for _, role := range data.BaseRoles {
|
||||
_, err = repo.GetDelegationRole(role)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDelegationRolesInvalidPaths(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
testKey1, err := ed25519.Create("targets/test", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
role, err := data.NewRole(
|
||||
"targets/test", 1, []string{testKey1.ID()}, []string{"path", "anotherpath"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey1})
|
||||
assert.NoError(t, err)
|
||||
|
||||
testKey2, err := ed25519.Create("targets/test/b", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
// Now we add a delegation with a path that is not prefixed by its parent delegation
|
||||
role, err = data.NewRole(
|
||||
"targets/test/b", 1, []string{testKey2.ID()}, []string{"invalidpath"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Getting this delegation should fail path verification
|
||||
_, err = repo.GetDelegationRole("targets/test/b")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetDelegationRolesInvalidPathHashPrefix(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
testKey1, err := ed25519.Create("targets/test", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
role, err := data.NewRole(
|
||||
"targets/test", 1, []string{testKey1.ID()}, []string{}, []string{"pathhash", "anotherpathhash"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey1})
|
||||
assert.NoError(t, err)
|
||||
|
||||
testKey2, err := ed25519.Create("targets/test/b", data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
// Now we add a delegation with a path that is not prefixed by its parent delegation
|
||||
role, err = data.NewRole(
|
||||
"targets/test/b", 1, []string{testKey2.ID()}, []string{}, []string{"invalidpathhash"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = repo.UpdateDelegations(role, data.KeyList{testKey2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Getting this delegation should fail path verification
|
||||
_, err = repo.GetDelegationRole("targets/test/b")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue