Skip to content

Commit 666563b

Browse files
committed
Merge pull request moby#21046 from cyli/use-notary-cli
Sign all first-level delegation roles when doing a trusted push
2 parents 9f327b4 + 88d73eb commit 666563b

20 files changed

+988
-87
lines changed

api/client/trust.go

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Can
258258
if err != nil {
259259
return nil, err
260260
}
261+
// Only list tags in the top level targets role or the releases delegation role - ignore
262+
// all other delegation roles
263+
if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
264+
return nil, notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
265+
}
261266
r, err := convertTarget(t.Target)
262267
if err != nil {
263268
return nil, err
@@ -331,13 +336,28 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr
331336
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name())
332337
continue
333338
}
339+
// Only list tags in the top level targets role or the releases delegation role - ignore
340+
// all other delegation roles
341+
if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole {
342+
continue
343+
}
334344
refs = append(refs, t)
335345
}
346+
if len(refs) == 0 {
347+
return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
348+
}
336349
} else {
337350
t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole)
338351
if err != nil {
339352
return notaryError(repoInfo.FullName(), err)
340353
}
354+
// Only get the tag if it's in the top level targets role or the releases delegation role
355+
// ignore it if it's in any other delegation roles
356+
if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
357+
return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String()))
358+
}
359+
360+
logrus.Debugf("retrieving target for %s role\n", t.Role)
341361
r, err := convertTarget(t.Target)
342362
if err != nil {
343363
return err
@@ -441,39 +461,95 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
441461
return err
442462
}
443463

444-
if err := repo.AddTarget(target, releasesRole); err != nil {
445-
return err
464+
// get the latest repository metadata so we can figure out which roles to sign
465+
_, err = repo.Update(false)
466+
467+
switch err.(type) {
468+
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
469+
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
470+
var rootKeyID string
471+
// always select the first root key
472+
if len(keys) > 0 {
473+
sort.Strings(keys)
474+
rootKeyID = keys[0]
475+
} else {
476+
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
477+
if err != nil {
478+
return err
479+
}
480+
rootKeyID = rootPublicKey.ID()
481+
}
482+
483+
// Initialize the notary repository with a remotely managed snapshot key
484+
if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
485+
return notaryError(repoInfo.FullName(), err)
486+
}
487+
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
488+
err = repo.AddTarget(target, data.CanonicalTargetsRole)
489+
case nil:
490+
// already initialized and we have successfully downloaded the latest metadata
491+
err = cli.addTargetToAllSignableRoles(repo, target)
492+
default:
493+
return notaryError(repoInfo.FullName(), err)
446494
}
447495

448-
err = repo.Publish()
449496
if err == nil {
450-
fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
451-
return nil
452-
} else if _, ok := err.(client.ErrRepoNotInitialized); !ok {
497+
err = repo.Publish()
498+
}
499+
500+
if err != nil {
453501
fmt.Fprintf(cli.out, "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
454502
return notaryError(repoInfo.FullName(), err)
455503
}
456504

457-
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
505+
fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
506+
return nil
507+
}
458508

459-
var rootKeyID string
460-
// always select the first root key
461-
if len(keys) > 0 {
462-
sort.Strings(keys)
463-
rootKeyID = keys[0]
464-
} else {
465-
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
466-
if err != nil {
467-
return err
509+
// Attempt to add the image target to all the top level delegation roles we can
510+
// (based on whether we have the signing key and whether the role's path allows
511+
// us to).
512+
// If there are no delegation roles, we add to the targets role.
513+
func (cli *DockerCli) addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error {
514+
var signableRoles []string
515+
516+
// translate the full key names, which includes the GUN, into just the key IDs
517+
allCanonicalKeyIDs := make(map[string]struct{})
518+
for fullKeyID := range repo.CryptoService.ListAllKeys() {
519+
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
520+
}
521+
522+
allDelegationRoles, err := repo.GetDelegationRoles()
523+
if err != nil {
524+
return err
525+
}
526+
527+
// if there are no delegation roles, then just try to sign it into the targets role
528+
if len(allDelegationRoles) == 0 {
529+
return repo.AddTarget(target, data.CanonicalTargetsRole)
530+
}
531+
532+
// there are delegation roles, find every delegation role we have a key for, and
533+
// attempt to sign into into all those roles.
534+
for _, delegationRole := range allDelegationRoles {
535+
// We do not support signing any delegation role that isn't a direct child of the targets role.
536+
// Also don't bother checking the keys if we can't add the target
537+
// to this role due to path restrictions
538+
if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) {
539+
continue
540+
}
541+
542+
for _, canonicalKeyID := range delegationRole.KeyIDs {
543+
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
544+
signableRoles = append(signableRoles, delegationRole.Name)
545+
break
546+
}
468547
}
469-
rootKeyID = rootPublicKey.ID()
470548
}
471549

472-
// Initialize the notary repository with a remotely managed snapshot key
473-
if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
474-
return notaryError(repoInfo.FullName(), err)
550+
if len(signableRoles) == 0 {
551+
return fmt.Errorf("no valid signing keys for delegation roles")
475552
}
476-
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
477553

478-
return notaryError(repoInfo.FullName(), repo.Publish())
554+
return repo.AddTarget(target, signableRoles...)
479555
}

docs/security/trust/content_trust.md

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,13 @@ $ docker pull someimage@sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d8
108108
```
109109

110110
Trust for an image tag is managed through the use of signing keys. A key set is
111-
created when an operation using content trust is first invoked. Docker's content
112-
trust makes use of four different keys:
111+
created when an operation using content trust is first invoked. A key set consists
112+
of the following classes of keys:
113113

114-
| Key | Description |
115-
|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
116-
| root key | Root of content trust for a image tag. When content trust is enabled, you create the root key once. |
117-
| target and snapshot | These two keys are known together as the "repository" key. When content trust is enabled, you create this key when you add a new image repository. If you have the root key, you can export the repository key and allow other publishers to sign the image tags. |
118-
| timestamp | This key applies to a repository. It allows Docker repositories to have freshness security guarantees without requiring periodic content refreshes on the client's side. |
119-
120-
With the exception of the timestamp, all the keys are generated and stored locally
121-
client-side. The timestamp is safely generated and stored in a signing server that
122-
is deployed alongside the Docker registry. All keys are generated in a backend
123-
service that isn't directly exposed to the internet and are encrypted at rest.
114+
- an offline key that is the root of content trust for a image tag
115+
- repository or tagging keys that sign tags
116+
- server-managed keys such as the timestamp key, which provides freshness
117+
security guarantees for your repository
124118

125119
The following image depicts the various signing keys and their relationships:
126120

@@ -133,9 +127,9 @@ The following image depicts the various signing keys and their relationships:
133127
>tag from this repository prior to the loss.
134128
135129
You should backup the root key somewhere safe. Given that it is only required
136-
to create new repositories, it is a good idea to store it offline. Make sure you
137-
read [Manage keys for content trust](trust_key_mng.md) information
138-
for details on securing, and backing up your keys.
130+
to create new repositories, it is a good idea to store it offline.
131+
For details on securing, and backing up your keys, make sure you
132+
read how to [manage keys for content trust](trust_key_mng.md).
139133

140134
## Survey of typical content trust operations
141135

@@ -302,4 +296,5 @@ $ docker push --disable-content-trust docker/trusttest:untrusted
302296

303297
* [Manage keys for content trust](trust_key_mng.md)
304298
* [Automation with content trust](trust_automation.md)
299+
* [Delegations for content trust](trust_delegation.md)
305300
* [Play in a content trust sandbox](trust_sandbox.md)

docs/security/trust/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ The following topics are available:
1717
* [Content trust in Docker](content_trust.md)
1818
* [Manage keys for content trust](trust_key_mng.md)
1919
* [Automation with content trust](trust_automation.md)
20+
* [Delegations for content trust](trust_delegation.md)
2021
* [Play in a content trust sandbox](trust_sandbox.md)

docs/security/trust/trust_automation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ unable to process Dockerfile: No trust data for notrust
7373

7474
## Related information
7575

76-
* [Content trust in Docker](content_trust.md)
76+
* [Content trust in Docker](content_trust.md)
7777
* [Manage keys for content trust](trust_key_mng.md)
78+
* [Delegations for content trust](trust_delegation.md)
7879
* [Play in a content trust sandbox](trust_sandbox.md)
7980

0 commit comments

Comments
 (0)