Skip to content

Commit

Permalink
Merge pull request theupdateframework#3 from ethan-lowman-dd/ethan.lo…
Browse files Browse the repository at this point in the history
…wman/delegations-in-client

Iteration on "Support delegations in client"
  • Loading branch information
raphaelgavache authored Jul 14, 2021
2 parents 64a1e27 + 0c822ec commit 22b1e63
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 186 deletions.
27 changes: 21 additions & 6 deletions client/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ func (c *Client) getTargetFileMeta(file string) (data.TargetFileMeta, error) {
if err != nil {
return data.TargetFileMeta{}, err
}
verifiers := map[string]verify.DelegationsVerifier{"root": verify.DelegationsVerifier{c.db}}

verifiers := map[string]verify.DelegationsVerifier{
"root": {DB: c.db},
}

// delegationsIterator covers 5.6.7
// - pre-order depth-first search starting with the top targets
Expand All @@ -26,16 +29,19 @@ func (c *Client) getTargetFileMeta(file string) (data.TargetFileMeta, error) {
if !ok {
return data.TargetFileMeta{}, ErrUnknownTarget{file, snapshot.Version}
}
verifier := verifiers[d.parent]

// covers 5.6.{1,2,3,4,5,6}
verifier := verifiers[d.parent]
target, err := c.loadDelegatedTargets(snapshot, d.child.Name, verifier)
if err != nil {
return data.TargetFileMeta{}, err
}

// stop when the searched TargetFileMeta is found
if m, ok := target.Targets[file]; ok {
return m, nil
}

if target.Delegations != nil {
delegations.add(target.Delegations.Roles, d.child.Name)
targetVerifier, err := verify.NewDelegationsVerifier(target.Delegations)
Expand All @@ -45,6 +51,7 @@ func (c *Client) getTargetFileMeta(file string) (data.TargetFileMeta, error) {
verifiers[d.child.Name] = targetVerifier
}
}

return data.TargetFileMeta{}, ErrMaxDelegations{
File: file,
MaxDelegations: c.MaxDelegations,
Expand All @@ -56,10 +63,12 @@ func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) {
if err := c.getLocalMeta(); err != nil {
return nil, err
}

rawS, ok := c.localMeta["snapshot.json"]
if !ok {
return nil, ErrNoLocalSnapshot
}

snapshot := &data.Snapshot{}
if err := c.db.Unmarshal(rawS, snapshot, "snapshot", c.snapshotVer); err != nil {
return nil, ErrDecodeFailed{"snapshot.json", err}
Expand All @@ -75,6 +84,7 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri
if !ok {
return nil, ErrRoleNotInSnapshot{role, snapshot.Version}
}

// 5.6.1 download target if not in the local store
// 5.6.2 check against snapshot hash
raw, alreadyStored := c.localMetaFromSnapshot(fileName, fileMeta)
Expand All @@ -84,6 +94,7 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri
return nil, err
}
}

target := &data.Targets{}
// 5.6.3 verify signature with parent public keys
// 5.6.5 verify that the targets is not expired
Expand Down Expand Up @@ -114,15 +125,17 @@ func (c *Client) rootTargetDelegation() data.DelegatedRole {
if r == nil {
return data.DelegatedRole{}
}

keyIDs := make([]string, 0, len(r.KeyIDs))
for id, _ := range r.KeyIDs {
keyIDs = append(keyIDs, id)
}

return data.DelegatedRole{
Name: role,
KeyIDs: keyIDs,
Threshold: r.Threshold,
PathMatchers: []string{"*"},
Name: role,
KeyIDs: keyIDs,
Threshold: r.Threshold,
Paths: []string{"*"},
}
}

Expand All @@ -148,7 +161,9 @@ func newDelegationsIterator(role data.DelegatedRole, parent string, file string)
stack: make([]delegation, 0, 1),
visited: make(map[delegationID]struct{}),
}

i.add([]data.DelegatedRole{role}, parent)

return i
}

Expand Down
164 changes: 94 additions & 70 deletions client/delegations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
)

var (
defaultPathMatchers = []string{"tmp", "*"}
notMatchingPathMatchers = []string{"vars", "null"}
defaultPathPatterns = []string{"tmp", "*"}
noMatchPathPatterns = []string{"vars", "null"}
)

func TestDelegationsIterator(t *testing.T) {
Expand All @@ -30,44 +30,68 @@ func TestDelegationsIterator(t *testing.T) {
resultOrder []string
}{
{
"no termination",
map[string][]data.DelegatedRole{
"a": []data.DelegatedRole{{Name: "b", PathMatchers: defaultPathMatchers}, {Name: "e", PathMatchers: defaultPathMatchers}},
"b": []data.DelegatedRole{{Name: "c", PathMatchers: defaultPathMatchers}, {Name: "d", PathMatchers: defaultPathMatchers}},
testName: "no termination",
roles: map[string][]data.DelegatedRole{
"a": {
{Name: "b", Paths: defaultPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
"b": {
{Name: "c", Paths: defaultPathPatterns},
{Name: "d", Paths: defaultPathPatterns},
},
},
data.DelegatedRole{Name: "a", PathMatchers: defaultPathMatchers},
"",
[]string{"a", "b", "c", "d", "e"},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "c", "d", "e"},
},
{
"terminated in b",
map[string][]data.DelegatedRole{
"a": []data.DelegatedRole{{Name: "b", PathMatchers: defaultPathMatchers, Terminating: true}, {Name: "e", PathMatchers: defaultPathMatchers}},
"b": []data.DelegatedRole{{Name: "c", PathMatchers: defaultPathMatchers}, {Name: "d", PathMatchers: defaultPathMatchers}},
testName: "terminated in b",
roles: map[string][]data.DelegatedRole{
"a": {
{Name: "b", Paths: defaultPathPatterns, Terminating: true},
{Name: "e", Paths: defaultPathPatterns},
},
"b": {
{Name: "c", Paths: defaultPathPatterns},
{Name: "d", Paths: defaultPathPatterns},
},
},
data.DelegatedRole{Name: "a", PathMatchers: defaultPathMatchers},
"",
[]string{"a", "b", "c", "d"},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "c", "d"},
},
{
"path does not match b",
map[string][]data.DelegatedRole{
"a": []data.DelegatedRole{{Name: "b", PathMatchers: notMatchingPathMatchers}, {Name: "e", PathMatchers: defaultPathMatchers}},
"b": []data.DelegatedRole{{Name: "c", PathMatchers: defaultPathMatchers}, {Name: "d", PathMatchers: defaultPathMatchers}},
testName: "path does not match b",
roles: map[string][]data.DelegatedRole{
"a": {
{Name: "b", Paths: noMatchPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
"b": {
{Name: "c", Paths: defaultPathPatterns},
{Name: "d", Paths: defaultPathPatterns},
},
},
data.DelegatedRole{Name: "a", PathMatchers: defaultPathMatchers},
"",
[]string{"a", "e"},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "e"},
},
{
"cycle avoided",
map[string][]data.DelegatedRole{
"a": []data.DelegatedRole{{Name: "b", PathMatchers: defaultPathMatchers}, {Name: "e", PathMatchers: defaultPathMatchers}},
"b": []data.DelegatedRole{{Name: "a", PathMatchers: defaultPathMatchers}, {Name: "d", PathMatchers: defaultPathMatchers}},
testName: "cycle avoided",
roles: map[string][]data.DelegatedRole{
"a": {
{Name: "b", Paths: defaultPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
"b": {
{Name: "a", Paths: defaultPathPatterns},
{Name: "d", Paths: defaultPathPatterns},
},
},
data.DelegatedRole{Name: "a", PathMatchers: defaultPathMatchers},
"",
[]string{"a", "b", "a", "e", "d"},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "a", "e", "d"},
},
}

Expand All @@ -87,9 +111,9 @@ func TestDelegationsIterator(t *testing.T) {
}
d.add(delegations, r.child.Name)
}
assert.Equal(t, len(iterationOrder), len(tt.resultOrder))
assert.Equal(t, len(tt.resultOrder), len(iterationOrder))
for i, role := range iterationOrder {
assert.Equal(t, role, tt.resultOrder[i])
assert.Equal(t, tt.resultOrder[i], role)
}
})
}
Expand All @@ -104,7 +128,7 @@ func TestGetTargetMeta(t *testing.T) {

f, err := c.getTargetFileMeta("f.txt")
assert.Nil(t, err)
assert.Equal(t, f.Length, int64(15))
assert.Equal(t, int64(15), f.Length)
}

func TestMaxDelegations(t *testing.T) {
Expand All @@ -115,7 +139,7 @@ func TestMaxDelegations(t *testing.T) {
assert.Nil(t, err)
c.MaxDelegations = 2
_, err = c.getTargetFileMeta("c.txt")
assert.Equal(t, err, ErrMaxDelegations{File: "c.txt", MaxDelegations: 2, SnapshotVersion: 2})
assert.Equal(t, ErrMaxDelegations{File: "c.txt", MaxDelegations: 2, SnapshotVersion: 2}, err)
}

func TestMetaNotFound(t *testing.T) {
Expand All @@ -125,7 +149,7 @@ func TestMetaNotFound(t *testing.T) {
_, err := c.Update()
assert.Nil(t, err)
_, err = c.getTargetFileMeta("unknown.txt")
assert.Equal(t, err, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2})
assert.Equal(t, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2}, err)
}

type fakeRemote struct {
Expand Down Expand Up @@ -209,69 +233,69 @@ func TestPersistedMeta(t *testing.T) {
fileContent string
}{
{
"unknown",
[]expectedTargets{
file: "unknown",
targets: []expectedTargets{
{
"targets.json",
2,
name: "targets.json",
version: 2,
},
},
ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
"",
downloadError: ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
fileContent: "",
},
{
"b.txt",
[]expectedTargets{
file: "b.txt",
targets: []expectedTargets{
{
"targets.json",
2,
name: "targets.json",
version: 2,
},
{
"a.json",
1,
name: "a.json",
version: 1,
},
{
"b.json",
1,
name: "b.json",
version: 1,
},
},
nil,
"Contents: b.txt",
downloadError: nil,
fileContent: "Contents: b.txt",
},
{
"f.txt",
[]expectedTargets{
file: "f.txt",
targets: []expectedTargets{
{
"targets.json",
2,
name: "targets.json",
version: 2,
},
{
"a.json",
1,
name: "a.json",
version: 1,
},
{
"b.json",
1,
name: "b.json",
version: 1,
},
{
"c.json",
1,
name: "c.json",
version: 1,
},
{
"d.json",
1,
name: "d.json",
version: 1,
},
{
"e.json",
1,
name: "e.json",
version: 1,
},
{
"f.json",
1,
name: "f.json",
version: 1,
},
},
nil,
"Contents: f.txt",
downloadError: nil,
fileContent: "Contents: f.txt",
},
}

Expand All @@ -291,7 +315,7 @@ func TestPersistedMeta(t *testing.T) {
}
for _, targets := range tt.targets {
storedVersion, err := versionOfStoredTargets(targets.name, persisted)
assert.Equal(t, storedVersion, targets.version)
assert.Equal(t, targets.version, storedVersion)
assert.Nil(t, err)
delete(persisted, targets.name)
}
Expand Down
Loading

0 comments on commit 22b1e63

Please sign in to comment.