Skip to content

Commit 04028e5

Browse files
EXTREME WIP
Signed-off-by: Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com>
1 parent afba301 commit 04028e5

File tree

3 files changed

+159
-56
lines changed

3 files changed

+159
-56
lines changed

pkg/tuf/delegations.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package tuf
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/theupdateframework/notary/client"
8+
"github.com/theupdateframework/notary/tuf/data"
9+
"github.com/theupdateframework/notary/tuf/utils"
10+
)
11+
12+
// delegationAdd creates a new delegation by adding a public key from a certificate to a specific role in a GUN
13+
// https://github.com/theupdateframework/notary/blob/f255ae779066dc28ae4aee196061e58bb38a2b49/cmd/notary/delegations.go
14+
func delegateToReleases(repo client.Repository, releasesKeyID string) error {
15+
role := data.RoleName("targets/releases")
16+
// How Notary v1 denotes "*""
17+
// https://github.com/theupdateframework/notary/blob/f255ae779066dc28ae4aee196061e58bb38a2b49/cmd/notary/delegations.go#L367
18+
allPaths := []string{""}
19+
20+
pubKeys, err := ingestPublicKeys(args)
21+
if err != nil {
22+
return err
23+
}
24+
25+
// Add the delegation to the repository
26+
err = repo.AddDelegation(role, pubKeys, allPaths)
27+
if err != nil {
28+
return fmt.Errorf("failed to create delegation: %v", err)
29+
}
30+
31+
// Make keyID slice for better CLI print
32+
pubKeyIDs := []string{}
33+
for _, pubKey := range pubKeys {
34+
pubKeyID, err := utils.CanonicalKeyID(pubKey)
35+
if err != nil {
36+
return err
37+
}
38+
pubKeyIDs = append(pubKeyIDs, pubKeyID)
39+
}
40+
41+
fmt.Println("")
42+
addingItems := ""
43+
if len(pubKeyIDs) > 0 {
44+
addingItems = addingItems + fmt.Sprintf("with keys %s, ", pubKeyIDs)
45+
}
46+
if d.paths != nil || d.allPaths {
47+
addingItems = addingItems + fmt.Sprintf(
48+
"with paths [%s], ",
49+
strings.Join(prettyPaths(d.paths), "\n"),
50+
)
51+
}
52+
fmt.Printf(
53+
"Addition of delegation role %s %sto repository \"%s\" staged for next publish.\n",
54+
role, addingItems, gun)
55+
fmt.Println("")
56+
57+
return maybeAutoPublish(cmd, d.autoPublish, gun, config, d.retriever)
58+
}

pkg/tuf/keys.go

+53-27
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ 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)
@@ -64,14 +64,40 @@ func readKey(role data.RoleName, keyFilename string, retriever notary.PassRetrie
6464
return privKey, nil
6565
}
6666

67+
// Attempt to read a role key from a file, and return it as a data.PrivateKey
68+
func readPublicKey(args []string) ([]data.PublicKey, error) {
69+
pubKeys := []data.PublicKey{}
70+
if len(args) > 2 {
71+
pubKeyPaths := args[2:]
72+
for _, pubKeyPath := range pubKeyPaths {
73+
// Read public key bytes from PEM file
74+
pubKeyBytes, err := ioutil.ReadFile(pubKeyPath)
75+
if err != nil {
76+
if os.IsNotExist(err) {
77+
return nil, fmt.Errorf("file for public key does not exist: %s", pubKeyPath)
78+
}
79+
return nil, fmt.Errorf("unable to read public key from file: %s", pubKeyPath)
80+
}
81+
82+
// Parse PEM bytes into type PublicKey
83+
pubKey, err := utils.ParsePEMPublicKey(pubKeyBytes)
84+
if err != nil {
85+
return nil, fmt.Errorf("unable to parse valid public key certificate from PEM file %s: %v", pubKeyPath, err)
86+
}
87+
pubKeys = append(pubKeys, pubKey)
88+
}
89+
}
90+
return pubKeys, nil
91+
}
92+
6793
// importRootKey imports the root key from path then adds the key to repo
6894
// returns key ids
6995
// https://github.com/theupdateframework/notary/blob/f255ae779066dc28ae4aee196061e58bb38a2b49/cmd/notary/tuf.go#L413
7096
func importRootKey(rootKey string, nRepo client.Repository, retriever notary.PassRetriever) ([]string, error) {
7197
var rootKeyList []string
7298

7399
if rootKey != "" {
74-
privKey, err := readKey(data.CanonicalRootRole, rootKey, retriever)
100+
privKey, err := readPrivateKey(data.CanonicalRootRole, rootKey, retriever)
75101
if err != nil {
76102
return nil, err
77103
}
@@ -97,25 +123,25 @@ func importRootKey(rootKey string, nRepo client.Repository, retriever notary.Pas
97123
return []string{}, nil
98124
}
99125

100-
// Try to reuse a single targets key across repositories.
126+
// Try to reuse a single key for the given rolename across repositories.
101127
// FIXME: Unfortunately, short of forking Notary or sending a PR upstream, there isn't an easy way to prevent it
102128
// from automagically creating a new, local targets key per TUF metadata repository. We fix this here by undoing
103129
// more than one new, local targets key, and reusing any existing local targets key, just like the way Notary
104130
// reuses the root key.
105-
func reuseTargetsKey(r client.Repository) error {
131+
func reuseKey(r client.Repository, rolename data.RoleName) (string, error) {
106132
var (
107-
err error
108-
thisTargetsKeyID, thatTargetsKeyID string
133+
err error
134+
thisKeyID, thatKeyID string
109135
)
110136

111-
// Get all known targets keys.
112-
targetsKeyList := r.GetCryptoService().ListKeys(data.CanonicalTargetsRole)
113-
// Try to extract a single targets key we can reuse.
114-
switch len(targetsKeyList) {
137+
// Get all known keys for this rolename.
138+
keyList := r.GetCryptoService().ListKeys(rolename)
139+
// Try to extract a single key we can reuse.
140+
switch len(keyList) {
115141
case 0:
116-
err = fmt.Errorf("no targets key despite having initialized a repo")
142+
err = fmt.Errorf("no %s key despite having initialized a repo", rolename)
117143
case 1:
118-
log.Debug("Nothing to do, only one targets key available")
144+
log.Debug("Nothing to do, only one %s key available", rolename)
119145
case 2:
120146
// First, we publish current changes to repository in order to list roles.
121147
// FIXME: Find a find better way to list roles w/o publishing changes first.
@@ -132,34 +158,34 @@ func reuseTargetsKey(r client.Repository) error {
132158
break
133159
}
134160

135-
// Get the current targets key.
161+
// Get the current key for the given rolename.
136162
// NOTE: We do not delete it, in case the user wants to keep it.
137163
for _, roleWithSig := range roleWithSigs {
138164
role := roleWithSig.Role
139-
if role.Name == data.CanonicalTargetsRole {
165+
if role.Name == rolename {
140166
if len(role.KeyIDs) == 1 {
141-
thisTargetsKeyID = role.KeyIDs[0]
142-
log.Debugf("This targets keyid: %s", thisTargetsKeyID)
167+
thisKeyID = role.KeyIDs[0]
168+
log.Debugf("This %s keyid: %s", rolename, thisKeyID)
143169
} else {
144-
return fmt.Errorf("this targets role has more than 1 key")
170+
return thatKeyID, fmt.Errorf("this %s role has more than 1 key", rolename)
145171
}
146172
}
147173
}
148174

149-
// Get and reuse the other targets key.
150-
for _, keyID := range targetsKeyList {
151-
if keyID != thisTargetsKeyID {
152-
thatTargetsKeyID = keyID
175+
// Get and reuse the other key for the given rolename.
176+
for _, keyID := range keyList {
177+
if keyID != thisKeyID {
178+
thatKeyID = keyID
153179
break
154180
}
155181
}
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})
159-
log.Debugf("After targets key rotation")
182+
log.Debugf("That %s keyID: %s", rolename, thatKeyID)
183+
log.Debugf("Before rotating %s key from %s to %s", rolename, thisKeyID, thatKeyID)
184+
err = r.RotateKey(rolename, false, []string{thatKeyID})
185+
log.Debugf("After %s key rotation", rolename)
160186
default:
161-
err = fmt.Errorf("there are more than 2 targets keys")
187+
err = fmt.Errorf("there are more than 2 %s keys", rolename)
162188
}
163189

164-
return err
190+
return thatKeyID, err
165191
}

pkg/tuf/sign.go

+48-29
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,76 @@ 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.
73-
if err = reuseTargetsKey(repo); err != nil {
74-
return nil, fmt.Errorf("cannot reuse targets keys: %v", err)
84+
if _, err := reuseKey(repo, data.CanonicalTargetsRole); err != nil {
85+
return fmt.Errorf("cannot reuse %s keys: %v", data.CanonicalTargetsRole)
86+
}
87+
88+
// Reuse targets/releases key.
89+
releasesRoleName := data.RoleName("targets/releases")
90+
// FIXME: logic is faulty right now, because targets/releases will not exist by default.
91+
releasesKeyID, err := reuseKey(repo, releasesRoleName)
92+
if err != nil {
93+
return fmt.Errorf("cannot reuse %s keys: %v", releasesRoleName, err)
94+
}
95+
96+
// Delegate to targets/releases.
97+
err = delegateToReleases(repo, releasesKeyID)
98+
if err != nil {
99+
return fmt.Errorf("cannot delegate to %s: %v", releasesRoleName, err)
75100
}
76101

77102
default:
78-
return nil, fmt.Errorf("cannot list targets: %v", err)
103+
return fmt.Errorf("cannot list targets: %v", err)
79104
}
80105
}
106+
return nil
107+
}
81108

82-
target, err := client.NewTarget(tag, file, custom)
109+
// clearChangelist clears the notary staging changelist
110+
func clearChangeList(notaryRepo client.Repository) error {
111+
cl, err := notaryRepo.GetChangelist()
83112
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
113+
return err
93114
}
94-
95-
err = repo.Publish()
96-
return target, err
115+
return cl.Clear("")
97116
}

0 commit comments

Comments
 (0)