-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
common.go
156 lines (137 loc) · 5.41 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package trust
import (
"context"
"encoding/hex"
"fmt"
"sort"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/fvbommel/sortorder"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/client"
"github.com/theupdateframework/notary/tuf/data"
)
// trustTagKey represents a unique signed tag and hex-encoded hash pair
type trustTagKey struct {
SignedTag string
Digest string
}
// trustTagRow encodes all human-consumable information for a signed tag, including signers
type trustTagRow struct {
trustTagKey
Signers []string
}
// trustRepo represents consumable information about a trusted repository
type trustRepo struct {
Name string
SignedTags []trustTagRow
Signers []trustSigner
AdministrativeKeys []trustSigner
}
// trustSigner represents a trusted signer in a trusted repository
// a signer is defined by a name and list of trustKeys
type trustSigner struct {
Name string `json:",omitempty"`
Keys []trustKey `json:",omitempty"`
}
// trustKey contains information about trusted keys
type trustKey struct {
ID string `json:",omitempty"`
}
// lookupTrustInfo returns processed signature and role information about a notary repository.
// This information is to be pretty printed or serialized into a machine-readable format.
func lookupTrustInfo(cli command.Cli, remote string) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) {
ctx := context.Background()
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
if err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
tag := imgRefAndAuth.Tag()
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
defer clearChangeList(notaryRepo)
// Retrieve all released signatures, match them, and pretty print them
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
logrus.Debug(trust.NotaryError(remote, err))
// print an empty table if we don't have signed targets, but have an initialized notary repo
if _, ok := err.(client.ErrNoSuchTarget); !ok {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signatures or cannot access %s", remote)
}
}
signatureRows := matchReleasedSignatures(allSignedTargets)
// get the administrative roles
adminRolesWithSigs, err := notaryRepo.ListRoles()
if err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signers for %s", remote)
}
// get delegation roles with the canonical key IDs
delegationRoles, err := notaryRepo.GetDelegationRoles()
if err != nil {
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
}
return signatureRows, adminRolesWithSigs, delegationRoles, nil
}
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
adminKeyList := roleWithSigs.KeyIDs
sort.Strings(adminKeyList)
var role string
switch roleWithSigs.Name {
case data.CanonicalTargetsRole:
role = "Repository Key"
case data.CanonicalRootRole:
role = "Root Key"
default:
return ""
}
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
}
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
signerRoleToKeyIDs := make(map[string][]string)
for _, delRole := range rawDelegationRoles {
switch delRole.Name {
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
continue
default:
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
}
}
return signerRoleToKeyIDs
}
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) []trustTagRow {
signatureRows := []trustTagRow{}
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
releasedTargetRows := map[trustTagKey][]string{}
for _, tgt := range allTargets {
if isReleasedTarget(tgt.Role.Name) {
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
releasedTargetRows[releasedKey] = []string{}
}
}
// now fill out all signers on released keys
for _, tgt := range allTargets {
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
// only considered released targets
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
}
}
// compile the final output as a sorted slice
for targetKey, signers := range releasedTargetRows {
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
}
sort.Slice(signatureRows, func(i, j int) bool {
return sortorder.NaturalLess(signatureRows[i].SignedTag, signatureRows[j].SignedTag)
})
return signatureRows
}