Skip to content

Commit 2313823

Browse files
delegate all paths to targets/releases
Signed-off-by: Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com>
1 parent afba301 commit 2313823

File tree

4 files changed

+123
-63
lines changed

4 files changed

+123
-63
lines changed

pkg/tuf/common.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import (
1313

1414
"github.com/docker/distribution/reference"
1515
"github.com/docker/docker/registry"
16+
"github.com/theupdateframework/notary/tuf/data"
1617
)
1718

1819
const (
19-
dockerConfigDir = ".docker"
20+
dockerConfigDir = ".docker"
21+
releasesRoleName = data.RoleName("targets/releases")
2022
)
2123

2224
func DefaultTrustDir() string {

pkg/tuf/delegations.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package tuf
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/theupdateframework/notary/client"
7+
"github.com/theupdateframework/notary/tuf/data"
8+
)
9+
10+
// Delegate all paths ("*") to targets/releases.
11+
// https://github.com/theupdateframework/notary/blob/f255ae779066dc28ae4aee196061e58bb38a2b49/cmd/notary/delegations.go
12+
func delegateToReleases(repo client.Repository, publicKey data.PublicKey) error {
13+
// How Notary v1 denotes "*""
14+
// https://github.com/theupdateframework/notary/blob/f255ae779066dc28ae4aee196061e58bb38a2b49/cmd/notary/delegations.go#L367
15+
allPaths := []string{""}
16+
publicKeys := []data.PublicKey{publicKey}
17+
18+
// Add the delegation to the repository
19+
err := repo.AddDelegation(releasesRoleName, publicKeys, allPaths)
20+
if err != nil {
21+
return fmt.Errorf("failed to create delegation: %v", err)
22+
}
23+
24+
return nil
25+
}

pkg/tuf/keys.go

+50-34
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424
func getPassphraseRetriever() notary.PassRetriever {
2525
baseRetriever := passphrase.PromptRetriever()
2626
env := map[string]string{
27-
"root": os.Getenv("SIGNY_ROOT_PASSPHRASE"),
28-
"targets": os.Getenv("SIGNY_TARGETS_PASSPHRASE"),
29-
"releases": os.Getenv("SIGNY_RELEASES_PASSPHRASE"),
27+
"root": os.Getenv("SIGNY_ROOT_PASSPHRASE"),
28+
"targets": os.Getenv("SIGNY_TARGETS_PASSPHRASE"),
29+
"targets/releases": os.Getenv("SIGNY_RELEASES_PASSPHRASE"),
3030
}
3131

3232
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
@@ -39,18 +39,20 @@ func getPassphraseRetriever() notary.PassRetriever {
3939

4040
// Attempt to read a role key from a file, and return it as a data.PrivateKey
4141
// If key is for the Root role, it must be encrypted
42-
func readKey(role data.RoleName, keyFilename string, retriever notary.PassRetriever) (data.PrivateKey, error) {
42+
func readPrivateKey(role data.RoleName, keyFilename string, retriever notary.PassRetriever) (data.PrivateKey, error) {
4343
pemBytes, err := ioutil.ReadFile(keyFilename)
4444
if err != nil {
4545
return nil, fmt.Errorf("Error reading input root key file: %v", err)
4646
}
47+
4748
isEncrypted := true
4849
if err = cryptoservice.CheckRootKeyIsEncrypted(pemBytes); err != nil {
4950
if role == data.CanonicalRootRole {
5051
return nil, err
5152
}
5253
isEncrypted = false
5354
}
55+
5456
var privKey data.PrivateKey
5557
if isEncrypted {
5658
privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", data.CanonicalRootRole.String())
@@ -71,7 +73,7 @@ func importRootKey(rootKey string, nRepo client.Repository, retriever notary.Pas
7173
var rootKeyList []string
7274

7375
if rootKey != "" {
74-
privKey, err := readKey(data.CanonicalRootRole, rootKey, retriever)
76+
privKey, err := readPrivateKey(data.CanonicalRootRole, rootKey, retriever)
7577
if err != nil {
7678
return nil, err
7779
}
@@ -89,77 +91,91 @@ func importRootKey(rootKey string, nRepo client.Repository, retriever notary.Pas
8991
// Chooses the first root key available, which is initialization specific
9092
// but should return the HW one first.
9193
rootKeyID := rootKeyList[0]
92-
log.Debugf("Signy found root key, using: %s\n", rootKeyID)
93-
94+
log.Debugf("found root key: %s\n", rootKeyID)
9495
return []string{rootKeyID}, nil
9596
}
9697

9798
return []string{}, nil
9899
}
99100

101+
// Try to reuse a single targets/releases key across repositories.
102+
func reuseReleasesKey(r client.Repository) (data.PublicKey, error) {
103+
// Get all known targets keys.
104+
cryptoService := r.GetCryptoService()
105+
keyList := cryptoService.ListKeys(releasesRoleName)
106+
107+
// Try to extract a single targets/releases key we can reuse.
108+
switch len(keyList) {
109+
case 0:
110+
log.Debugf("No %s key available, need to make one", releasesRoleName)
111+
return cryptoService.Create(releasesRoleName, r.GetGUN(), data.ECDSAKey)
112+
case 1:
113+
log.Debugf("Nothing to do, only one %s key available", releasesRoleName)
114+
return cryptoService.GetKey(keyList[0]), nil
115+
default:
116+
return nil, fmt.Errorf("there is more than one %s keys", releasesRoleName)
117+
}
118+
}
119+
100120
// Try to reuse a single targets key across repositories.
101121
// FIXME: Unfortunately, short of forking Notary or sending a PR upstream, there isn't an easy way to prevent it
102122
// from automagically creating a new, local targets key per TUF metadata repository. We fix this here by undoing
103123
// more than one new, local targets key, and reusing any existing local targets key, just like the way Notary
104124
// reuses the root key.
105125
func reuseTargetsKey(r client.Repository) error {
106-
var (
107-
err error
108-
thisTargetsKeyID, thatTargetsKeyID string
109-
)
110-
111126
// Get all known targets keys.
112-
targetsKeyList := r.GetCryptoService().ListKeys(data.CanonicalTargetsRole)
127+
keyList := r.GetCryptoService().ListKeys(data.CanonicalTargetsRole)
128+
113129
// Try to extract a single targets key we can reuse.
114-
switch len(targetsKeyList) {
130+
switch len(keyList) {
115131
case 0:
116-
err = fmt.Errorf("no targets key despite having initialized a repo")
132+
return fmt.Errorf("no targets key despite having initialized a repo")
117133
case 1:
118134
log.Debug("Nothing to do, only one targets key available")
135+
return nil
119136
case 2:
120137
// First, we publish current changes to repository in order to list roles.
121138
// FIXME: Find a find better way to list roles w/o publishing changes first.
122-
publishErr := r.Publish()
123-
if publishErr != nil {
124-
err = publishErr
125-
break
139+
err := r.Publish()
140+
if err != nil {
141+
return err
126142
}
127143

128144
// Get the current top-level roles.
129-
roleWithSigs, listRolesErr := r.ListRoles()
130-
if listRolesErr != nil {
131-
err = listRolesErr
132-
break
145+
roleWithSigs, err := r.ListRoles()
146+
if err != nil {
147+
return err
133148
}
134149

135150
// Get the current targets key.
136151
// NOTE: We do not delete it, in case the user wants to keep it.
152+
var thisKeyID string
137153
for _, roleWithSig := range roleWithSigs {
138154
role := roleWithSig.Role
139155
if role.Name == data.CanonicalTargetsRole {
140156
if len(role.KeyIDs) == 1 {
141-
thisTargetsKeyID = role.KeyIDs[0]
142-
log.Debugf("This targets keyid: %s", thisTargetsKeyID)
157+
thisKeyID = role.KeyIDs[0]
158+
log.Debugf("This targets keyid: %s", thisKeyID)
143159
} else {
144160
return fmt.Errorf("this targets role has more than 1 key")
145161
}
146162
}
147163
}
148164

149165
// Get and reuse the other targets key.
150-
for _, keyID := range targetsKeyList {
151-
if keyID != thisTargetsKeyID {
152-
thatTargetsKeyID = keyID
166+
var thatKeyID string
167+
for _, keyID := range keyList {
168+
if keyID != thisKeyID {
169+
thatKeyID = keyID
153170
break
154171
}
155172
}
156-
log.Debugf("That targets keyID: %s", thatTargetsKeyID)
157-
log.Debugf("Before rotating targets key from %s to %s", thisTargetsKeyID, thatTargetsKeyID)
158-
err = r.RotateKey(data.CanonicalTargetsRole, false, []string{thatTargetsKeyID})
173+
log.Debugf("That targets keyID: %s", thatKeyID)
174+
log.Debugf("Before rotating targets key from %s to %s", thisKeyID, thatKeyID)
175+
err = r.RotateKey(data.CanonicalTargetsRole, false, []string{thatKeyID})
159176
log.Debugf("After targets key rotation")
177+
return err
160178
default:
161-
err = fmt.Errorf("there are more than 2 targets keys")
179+
return fmt.Errorf("there are more than two targets keys")
162180
}
163-
164-
return err
165181
}

pkg/tuf/sign.go

+45-28
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ import (
99
"github.com/theupdateframework/notary/tuf/data"
1010
)
1111

12-
// clearChangelist clears the notary staging changelist
13-
func clearChangeList(notaryRepo client.Repository) error {
14-
cl, err := notaryRepo.GetChangelist()
15-
if err != nil {
16-
return err
17-
}
18-
return cl.Clear("")
19-
}
20-
2112
// SignAndPublish signs an artifact, then publishes the metadata to a trust server
2213
func SignAndPublish(trustDir, trustServer, ref, file, tlscacert, rootKey, timeout string, custom *canonicaljson.RawMessage) (*client.Target, error) {
2314
if err := EnsureTrustDir(trustDir); err != nil {
@@ -50,48 +41,74 @@ func SignAndPublish(trustDir, trustServer, ref, file, tlscacert, rootKey, timeou
5041
if err != nil {
5142
return nil, fmt.Errorf("cannot clear change list: %v", err)
5243
}
53-
5444
defer clearChangeList(repo)
5545

56-
if _, err = repo.ListTargets(); err != nil {
46+
err = reuseKeys(repo, rootKey)
47+
if err != nil {
48+
return nil, fmt.Errorf("cannot reuse keys: %v", err)
49+
}
50+
51+
target, err := client.NewTarget(tag, file, custom)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
// If roles is empty, we default to adding to targets
57+
if err = repo.AddTarget(target, data.NewRoleList([]string{})...); err != nil {
58+
return nil, err
59+
}
60+
61+
err = repo.Publish()
62+
return target, err
63+
}
64+
65+
// reuse root and top-level targets keys
66+
func reuseKeys(repo client.Repository, rootKey string) error {
67+
if _, err := repo.ListTargets(); err != nil {
5768
switch err.(type) {
5869
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
5970
// Reuse root key.
6071
rootKeyIDs, err := importRootKey(rootKey, repo, getPassphraseRetriever())
6172
if err != nil {
62-
return nil, err
73+
return err
6374
}
6475

6576
// NOTE: 2nd variadic argument is to indicate that snapshot is managed remotely.
6677
// The impact of a timestamp + snapshot key compromise is not terrible:
6778
// https://docs.docker.com/notary/service_architecture/#threat-model
6879
if err = repo.Initialize(rootKeyIDs, data.CanonicalSnapshotRole); err != nil {
69-
return nil, fmt.Errorf("cannot initialize repo: %v", err)
80+
return fmt.Errorf("cannot initialize repo: %v", err)
7081
}
7182

7283
// Reuse targets key.
7384
if err = reuseTargetsKey(repo); err != nil {
74-
return nil, fmt.Errorf("cannot reuse targets keys: %v", err)
85+
return fmt.Errorf("cannot reuse %s keys: %v", data.CanonicalTargetsRole, err)
86+
}
87+
88+
// Reuse targets/releases key.
89+
releasesPublicKey, err := reuseReleasesKey(repo)
90+
if err != nil {
91+
return fmt.Errorf("cannot reuse %s keys: %v", releasesRoleName, err)
92+
}
93+
94+
// Delegate to targets/releases.
95+
err = delegateToReleases(repo, releasesPublicKey)
96+
if err != nil {
97+
return fmt.Errorf("cannot delegate to %s: %v", releasesRoleName, err)
7598
}
7699

77100
default:
78-
return nil, fmt.Errorf("cannot list targets: %v", err)
101+
return fmt.Errorf("cannot list targets: %v", err)
79102
}
80103
}
104+
return nil
105+
}
81106

82-
target, err := client.NewTarget(tag, file, custom)
107+
// clearChangelist clears the notary staging changelist
108+
func clearChangeList(notaryRepo client.Repository) error {
109+
cl, err := notaryRepo.GetChangelist()
83110
if err != nil {
84-
return nil, err
85-
}
86-
87-
// TODO - Radu M
88-
// decide whether to allow actually passing roles as flags
89-
90-
// If roles is empty, we default to adding to targets
91-
if err = repo.AddTarget(target, data.NewRoleList([]string{})...); err != nil {
92-
return nil, err
111+
return err
93112
}
94-
95-
err = repo.Publish()
96-
return target, err
113+
return cl.Clear("")
97114
}

0 commit comments

Comments
 (0)