2015-07-15 16:42:45 -04:00
package client
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
2015-12-18 21:47:35 -05:00
"path"
2015-07-15 16:42:45 -04:00
"path/filepath"
2015-07-31 18:01:50 -04:00
"sort"
2015-07-15 16:42:45 -04:00
"strconv"
"time"
2016-03-16 15:19:22 -04:00
"golang.org/x/net/context"
2015-07-15 16:42:45 -04:00
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/cliconfig"
2015-12-21 18:02:44 -05:00
"github.com/docker/docker/distribution"
"github.com/docker/docker/pkg/jsonmessage"
2015-07-15 16:42:45 -04:00
flag "github.com/docker/docker/pkg/mflag"
2015-12-04 16:55:15 -05:00
"github.com/docker/docker/reference"
2015-07-15 16:42:45 -04:00
"github.com/docker/docker/registry"
2016-01-04 19:05:26 -05:00
apiclient "github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
2015-12-29 19:27:12 -05:00
"github.com/docker/go-connections/tlsconfig"
2015-07-15 16:42:45 -04:00
"github.com/docker/notary/client"
2015-10-30 20:37:08 -04:00
"github.com/docker/notary/passphrase"
2015-07-15 16:42:45 -04:00
"github.com/docker/notary/trustmanager"
2015-10-30 20:37:08 -04:00
"github.com/docker/notary/tuf/data"
2015-12-18 21:47:35 -05:00
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
2015-07-15 16:42:45 -04:00
)
2015-12-18 21:47:35 -05:00
var (
releasesRole = path . Join ( data . CanonicalTargetsRole , "releases" )
untrusted bool
)
2015-07-15 16:42:45 -04:00
func addTrustedFlags ( fs * flag . FlagSet , verify bool ) {
var trusted bool
2015-07-24 04:59:42 -04:00
if e := os . Getenv ( "DOCKER_CONTENT_TRUST" ) ; e != "" {
2015-07-15 16:42:45 -04:00
if t , err := strconv . ParseBool ( e ) ; t || err != nil {
// treat any other value as true
trusted = true
}
}
message := "Skip image signing"
if verify {
message = "Skip image verification"
}
2015-07-24 04:59:42 -04:00
fs . BoolVar ( & untrusted , [ ] string { "-disable-content-trust" } , ! trusted , message )
2015-07-15 16:42:45 -04:00
}
func isTrusted ( ) bool {
return ! untrusted
}
type target struct {
reference registry . Reference
digest digest . Digest
size int64
}
func ( cli * DockerCli ) trustDirectory ( ) string {
return filepath . Join ( cliconfig . ConfigDir ( ) , "trust" )
}
// certificateDirectory returns the directory containing
// TLS certificates for the given server. An error is
// returned if there was an error parsing the server string.
func ( cli * DockerCli ) certificateDirectory ( server string ) ( string , error ) {
u , err := url . Parse ( server )
if err != nil {
return "" , err
}
return filepath . Join ( cliconfig . ConfigDir ( ) , "tls" , u . Host ) , nil
}
2015-12-11 21:14:52 -05:00
func trustServer ( index * registrytypes . IndexInfo ) ( string , error ) {
2015-07-24 04:59:42 -04:00
if s := os . Getenv ( "DOCKER_CONTENT_TRUST_SERVER" ) ; s != "" {
2015-10-08 14:10:38 -04:00
urlObj , err := url . Parse ( s )
if err != nil || urlObj . Scheme != "https" {
return "" , fmt . Errorf ( "valid https URL required for trust server, got %s" , s )
2015-07-15 16:42:45 -04:00
}
2015-10-08 14:10:38 -04:00
return s , nil
2015-07-15 16:42:45 -04:00
}
if index . Official {
2015-10-08 14:10:38 -04:00
return registry . NotaryServer , nil
2015-07-15 16:42:45 -04:00
}
2015-10-08 14:10:38 -04:00
return "https://" + index . Name , nil
2015-07-15 16:42:45 -04:00
}
type simpleCredentialStore struct {
2015-12-11 23:11:42 -05:00
auth types . AuthConfig
2015-07-15 16:42:45 -04:00
}
func ( scs simpleCredentialStore ) Basic ( u * url . URL ) ( string , string ) {
return scs . auth . Username , scs . auth . Password
}
2016-02-23 18:18:04 -05:00
func ( scs simpleCredentialStore ) RefreshToken ( u * url . URL , service string ) string {
return scs . auth . IdentityToken
}
func ( scs simpleCredentialStore ) SetRefreshToken ( * url . URL , string , string ) {
}
2016-02-16 21:36:09 -05:00
// getNotaryRepository returns a NotaryRepository which stores all the
// information needed to operate on a notary repository.
// It creates a HTTP transport providing authentication support.
func ( cli * DockerCli ) getNotaryRepository ( repoInfo * registry . RepositoryInfo , authConfig types . AuthConfig , actions ... string ) ( * client . NotaryRepository , error ) {
2015-10-08 14:10:38 -04:00
server , err := trustServer ( repoInfo . Index )
if err != nil {
return nil , err
2015-07-15 16:42:45 -04:00
}
var cfg = tlsconfig . ClientDefault
cfg . InsecureSkipVerify = ! repoInfo . Index . Secure
// Get certificate base directory
certDir , err := cli . certificateDirectory ( server )
if err != nil {
return nil , err
}
logrus . Debugf ( "reading certificate directory: %s" , certDir )
if err := registry . ReadCertsDirectory ( & cfg , certDir ) ; err != nil {
return nil , err
}
base := & http . Transport {
Proxy : http . ProxyFromEnvironment ,
Dial : ( & net . Dialer {
Timeout : 30 * time . Second ,
KeepAlive : 30 * time . Second ,
DualStack : true ,
} ) . Dial ,
TLSHandshakeTimeout : 10 * time . Second ,
TLSClientConfig : & cfg ,
DisableKeepAlives : true ,
}
// Skip configuration headers since request is not going to Docker daemon
2016-03-18 17:42:40 -04:00
modifiers := registry . DockerHeaders ( clientUserAgent ( ) , http . Header { } )
2015-07-15 16:42:45 -04:00
authTransport := transport . NewTransport ( base , modifiers ... )
pingClient := & http . Client {
Transport : authTransport ,
Timeout : 5 * time . Second ,
}
endpointStr := server + "/v2/"
req , err := http . NewRequest ( "GET" , endpointStr , nil )
if err != nil {
return nil , err
}
2015-09-03 23:01:03 -04:00
challengeManager := auth . NewSimpleChallengeManager ( )
2015-07-15 16:42:45 -04:00
resp , err := pingClient . Do ( req )
if err != nil {
2015-09-03 23:01:03 -04:00
// Ignore error on ping to operate in offline mode
logrus . Debugf ( "Error pinging notary server %q: %s" , endpointStr , err )
} else {
defer resp . Body . Close ( )
2015-07-15 16:42:45 -04:00
2015-09-03 23:01:03 -04:00
// Add response to the challenge manager to parse out
// authentication header and register authentication method
if err := challengeManager . AddResponse ( resp ) ; err != nil {
return nil , err
}
2015-07-15 16:42:45 -04:00
}
creds := simpleCredentialStore { auth : authConfig }
2016-02-16 21:36:09 -05:00
tokenHandler := auth . NewTokenHandler ( authTransport , creds , repoInfo . FullName ( ) , actions ... )
2015-07-15 16:42:45 -04:00
basicHandler := auth . NewBasicHandler ( creds )
modifiers = append ( modifiers , transport . RequestModifier ( auth . NewAuthorizer ( challengeManager , tokenHandler , basicHandler ) ) )
tr := transport . NewTransport ( base , modifiers ... )
2015-12-11 14:00:13 -05:00
return client . NewNotaryRepository ( cli . trustDirectory ( ) , repoInfo . FullName ( ) , server , tr , cli . getPassphraseRetriever ( ) )
2015-07-15 16:42:45 -04:00
}
func convertTarget ( t client . Target ) ( target , error ) {
h , ok := t . Hashes [ "sha256" ]
if ! ok {
return target { } , errors . New ( "no valid hash, expecting sha256" )
}
return target {
reference : registry . ParseReference ( t . Name ) ,
digest : digest . NewDigestFromHex ( "sha256" , hex . EncodeToString ( h ) ) ,
size : t . Length ,
} , nil
}
func ( cli * DockerCli ) getPassphraseRetriever ( ) passphrase . Retriever {
2015-07-31 18:01:50 -04:00
aliasMap := map [ string ] string {
2016-03-17 13:23:18 -04:00
"root" : "root" ,
"snapshot" : "repository" ,
"targets" : "repository" ,
"default" : "repository" ,
2015-07-31 18:01:50 -04:00
}
baseRetriever := passphrase . PromptRetrieverWithInOut ( cli . in , cli . out , aliasMap )
2015-07-15 16:42:45 -04:00
env := map [ string ] string {
2016-03-17 13:23:18 -04:00
"root" : os . Getenv ( "DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE" ) ,
"snapshot" : os . Getenv ( "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" ) ,
"targets" : os . Getenv ( "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" ) ,
"default" : os . Getenv ( "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE" ) ,
2015-07-15 16:42:45 -04:00
}
2015-10-09 15:12:28 -04:00
// Backwards compatibility with old env names. We should remove this in 1.10
2015-10-09 15:14:46 -04:00
if env [ "root" ] == "" {
2015-10-22 12:08:50 -04:00
if passphrase := os . Getenv ( "DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE" ) ; passphrase != "" {
env [ "root" ] = passphrase
fmt . Fprintf ( cli . err , "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n" )
}
2015-10-09 15:14:46 -04:00
}
2016-03-17 13:23:18 -04:00
if env [ "snapshot" ] == "" || env [ "targets" ] == "" || env [ "default" ] == "" {
2015-10-22 12:08:50 -04:00
if passphrase := os . Getenv ( "DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE" ) ; passphrase != "" {
env [ "snapshot" ] = passphrase
env [ "targets" ] = passphrase
2016-03-17 13:23:18 -04:00
env [ "default" ] = passphrase
2015-10-22 12:08:50 -04:00
fmt . Fprintf ( cli . err , "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n" )
}
2015-10-09 15:12:28 -04:00
}
2015-07-15 16:42:45 -04:00
return func ( keyName string , alias string , createNew bool , numAttempts int ) ( string , bool , error ) {
if v := env [ alias ] ; v != "" {
return v , numAttempts > 1 , nil
}
2016-03-17 13:23:18 -04:00
// For non-root roles, we can also try the "default" alias if it is specified
if v := env [ "default" ] ; v != "" && alias != data . CanonicalRootRole {
return v , numAttempts > 1 , nil
}
2015-07-15 16:42:45 -04:00
return baseRetriever ( keyName , alias , createNew , numAttempts )
}
}
2015-11-18 17:20:54 -05:00
func ( cli * DockerCli ) trustedReference ( ref reference . NamedTagged ) ( reference . Canonical , error ) {
repoInfo , err := registry . ParseRepositoryInfo ( ref )
2015-07-15 16:42:45 -04:00
if err != nil {
return nil , err
}
// Resolve the Auth config relevant for this server
2016-02-07 19:55:17 -05:00
authConfig := cli . resolveAuthConfig ( repoInfo . Index )
2015-07-15 16:42:45 -04:00
notaryRepo , err := cli . getNotaryRepository ( repoInfo , authConfig )
if err != nil {
fmt . Fprintf ( cli . out , "Error establishing connection to trust repository: %s\n" , err )
return nil , err
}
2015-12-18 21:47:35 -05:00
t , err := notaryRepo . GetTargetByName ( ref . Tag ( ) , releasesRole , data . CanonicalTargetsRole )
2015-07-15 16:42:45 -04:00
if err != nil {
return nil , err
}
2016-03-16 22:59:18 -04:00
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if t . Role != releasesRole && t . Role != data . CanonicalTargetsRole {
return nil , notaryError ( repoInfo . FullName ( ) , fmt . Errorf ( "No trust data for %s" , ref . Tag ( ) ) )
}
2015-12-18 21:47:35 -05:00
r , err := convertTarget ( t . Target )
2015-07-15 16:42:45 -04:00
if err != nil {
return nil , err
}
2015-11-18 17:20:54 -05:00
return reference . WithDigest ( ref , r . digest )
2015-07-15 16:42:45 -04:00
}
2015-11-18 17:20:54 -05:00
func ( cli * DockerCli ) tagTrusted ( trustedRef reference . Canonical , ref reference . NamedTagged ) error {
fmt . Fprintf ( cli . out , "Tagging %s as %s\n" , trustedRef . String ( ) , ref . String ( ) )
2015-07-15 16:42:45 -04:00
2015-12-04 17:02:06 -05:00
options := types . ImageTagOptions {
2015-12-04 13:56:04 -05:00
ImageID : trustedRef . String ( ) ,
RepositoryName : trustedRef . Name ( ) ,
Tag : ref . Tag ( ) ,
Force : true ,
2015-07-15 16:42:45 -04:00
}
2016-03-16 15:19:22 -04:00
return cli . client . ImageTag ( context . Background ( ) , options )
2015-07-15 16:42:45 -04:00
}
2015-12-18 21:47:35 -05:00
func notaryError ( repoName string , err error ) error {
2015-07-15 16:42:45 -04:00
switch err . ( type ) {
case * json . SyntaxError :
logrus . Debugf ( "Notary syntax error: %s" , err )
2015-12-18 21:47:35 -05:00
return fmt . Errorf ( "Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?" , repoName )
2016-01-07 21:43:01 -05:00
case signed . ErrExpired :
2015-12-18 21:47:35 -05:00
return fmt . Errorf ( "Error: remote repository %s out-of-date: %v" , repoName , err )
2015-07-15 16:42:45 -04:00
case trustmanager . ErrKeyNotFound :
2015-12-18 21:47:35 -05:00
return fmt . Errorf ( "Error: signing keys for remote repository %s not found: %v" , repoName , err )
2015-09-03 23:01:03 -04:00
case * net . OpError :
2015-12-18 21:47:35 -05:00
return fmt . Errorf ( "Error: error contacting notary server: %v" , err )
case store . ErrMetaNotFound :
2016-01-07 21:43:01 -05:00
return fmt . Errorf ( "Error: trust data missing for remote repository %s or remote repository not found: %v" , repoName , err )
2015-12-18 21:47:35 -05:00
case signed . ErrInvalidKeyType :
2016-01-07 21:43:01 -05:00
return fmt . Errorf ( "Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v" , repoName , err )
2015-12-18 21:47:35 -05:00
case signed . ErrNoKeys :
2016-01-14 15:21:55 -05:00
return fmt . Errorf ( "Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v" , repoName , err )
2015-12-18 21:47:35 -05:00
case signed . ErrLowVersion :
2016-01-07 21:43:01 -05:00
return fmt . Errorf ( "Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v" , repoName , err )
2016-01-14 15:21:55 -05:00
case signed . ErrRoleThreshold :
2016-01-07 21:43:01 -05:00
return fmt . Errorf ( "Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v" , repoName , err )
case client . ErrRepositoryNotExist :
2016-01-14 15:21:55 -05:00
return fmt . Errorf ( "Error: remote trust data does not exist for %s: %v" , repoName , err )
case signed . ErrInsufficientSignatures :
return fmt . Errorf ( "Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v" , repoName , err )
2015-07-15 16:42:45 -04:00
}
return err
}
2016-01-04 19:05:26 -05:00
func ( cli * DockerCli ) trustedPull ( repoInfo * registry . RepositoryInfo , ref registry . Reference , authConfig types . AuthConfig , requestPrivilege apiclient . RequestPrivilegeFunc ) error {
2015-12-06 15:17:34 -05:00
var refs [ ] target
2015-07-15 16:42:45 -04:00
2016-02-16 21:36:09 -05:00
notaryRepo , err := cli . getNotaryRepository ( repoInfo , authConfig , "pull" )
2015-07-15 16:42:45 -04:00
if err != nil {
fmt . Fprintf ( cli . out , "Error establishing connection to trust repository: %s\n" , err )
return err
}
if ref . String ( ) == "" {
// List all targets
2015-12-18 21:47:35 -05:00
targets , err := notaryRepo . ListTargets ( releasesRole , data . CanonicalTargetsRole )
2015-07-15 16:42:45 -04:00
if err != nil {
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
for _ , tgt := range targets {
2015-12-18 21:47:35 -05:00
t , err := convertTarget ( tgt . Target )
2015-07-15 16:42:45 -04:00
if err != nil {
2015-12-11 14:00:13 -05:00
fmt . Fprintf ( cli . out , "Skipping target for %q\n" , repoInfo . Name ( ) )
2015-07-15 16:42:45 -04:00
continue
}
2016-03-16 22:59:18 -04:00
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if tgt . Role != releasesRole && tgt . Role != data . CanonicalTargetsRole {
continue
}
2015-07-15 16:42:45 -04:00
refs = append ( refs , t )
}
2016-03-16 22:59:18 -04:00
if len ( refs ) == 0 {
return notaryError ( repoInfo . FullName ( ) , fmt . Errorf ( "No trusted tags for %s" , repoInfo . FullName ( ) ) )
}
2015-07-15 16:42:45 -04:00
} else {
2015-12-18 21:47:35 -05:00
t , err := notaryRepo . GetTargetByName ( ref . String ( ) , releasesRole , data . CanonicalTargetsRole )
2015-07-15 16:42:45 -04:00
if err != nil {
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
2016-03-16 22:59:18 -04:00
// Only get the tag if it's in the top level targets role or the releases delegation role
// ignore it if it's in any other delegation roles
if t . Role != releasesRole && t . Role != data . CanonicalTargetsRole {
return notaryError ( repoInfo . FullName ( ) , fmt . Errorf ( "No trust data for %s" , ref . String ( ) ) )
}
2016-03-09 03:18:30 -05:00
logrus . Debugf ( "retrieving target for %s role\n" , t . Role )
2015-12-18 21:47:35 -05:00
r , err := convertTarget ( t . Target )
2015-07-15 16:42:45 -04:00
if err != nil {
return err
}
refs = append ( refs , r )
}
for i , r := range refs {
displayTag := r . reference . String ( )
if displayTag != "" {
displayTag = ":" + displayTag
}
2015-12-11 14:00:13 -05:00
fmt . Fprintf ( cli . out , "Pull (%d of %d): %s%s@%s\n" , i + 1 , len ( refs ) , repoInfo . Name ( ) , displayTag , r . digest )
2015-07-15 16:42:45 -04:00
2015-12-11 14:00:13 -05:00
if err := cli . imagePullPrivileged ( authConfig , repoInfo . Name ( ) , r . digest . String ( ) , requestPrivilege ) ; err != nil {
2015-07-15 16:42:45 -04:00
return err
}
// If reference is not trusted, tag by trusted reference
if ! r . reference . HasDigest ( ) {
2015-12-11 14:00:13 -05:00
tagged , err := reference . WithTag ( repoInfo , r . reference . String ( ) )
2015-11-18 17:20:54 -05:00
if err != nil {
return err
}
2015-12-11 14:00:13 -05:00
trustedRef , err := reference . WithDigest ( repoInfo , r . digest )
2015-12-10 14:01:34 -05:00
if err != nil {
return err
}
2015-11-18 17:20:54 -05:00
if err := cli . tagTrusted ( trustedRef , tagged ) ; err != nil {
2015-07-15 16:42:45 -04:00
return err
}
}
}
return nil
}
2016-01-04 19:05:26 -05:00
func ( cli * DockerCli ) trustedPush ( repoInfo * registry . RepositoryInfo , tag string , authConfig types . AuthConfig , requestPrivilege apiclient . RequestPrivilegeFunc ) error {
2015-12-21 18:02:44 -05:00
responseBody , err := cli . imagePushPrivileged ( authConfig , repoInfo . Name ( ) , tag , requestPrivilege )
if err != nil {
2015-07-15 16:42:45 -04:00
return err
}
2015-12-21 18:02:44 -05:00
defer responseBody . Close ( )
2016-02-04 21:56:43 -05:00
// If it is a trusted push we would like to find the target entry which match the
// tag provided in the function and then do an AddTarget later.
target := & client . Target { }
// Count the times of calling for handleTarget,
// if it is called more that once, that should be considered an error in a trusted push.
cnt := 0
2015-12-21 18:02:44 -05:00
handleTarget := func ( aux * json . RawMessage ) {
2016-02-04 21:56:43 -05:00
cnt ++
if cnt > 1 {
// handleTarget should only be called one. This will be treated as an error.
return
}
2015-12-21 18:02:44 -05:00
var pushResult distribution . PushResult
err := json . Unmarshal ( * aux , & pushResult )
if err == nil && pushResult . Tag != "" && pushResult . Digest . Validate ( ) == nil {
2016-02-04 21:56:43 -05:00
h , err := hex . DecodeString ( pushResult . Digest . Hex ( ) )
if err != nil {
target = nil
return
}
target . Name = registry . ParseReference ( pushResult . Tag ) . String ( )
target . Hashes = data . Hashes { string ( pushResult . Digest . Algorithm ( ) ) : h }
target . Length = int64 ( pushResult . Size )
2015-12-21 18:02:44 -05:00
}
2015-07-15 16:42:45 -04:00
}
2016-02-04 21:56:43 -05:00
// We want trust signatures to always take an explicit tag,
// otherwise it will act as an untrusted push.
if tag == "" {
if err = jsonmessage . DisplayJSONMessagesStream ( responseBody , cli . out , cli . outFd , cli . isTerminalOut , nil ) ; err != nil {
return err
}
fmt . Fprintln ( cli . out , "No tag specified, skipping trust metadata push" )
return nil
}
if err = jsonmessage . DisplayJSONMessagesStream ( responseBody , cli . out , cli . outFd , cli . isTerminalOut , handleTarget ) ; err != nil {
2015-12-21 18:02:44 -05:00
return err
}
2015-07-15 16:42:45 -04:00
2016-02-04 21:56:43 -05:00
if cnt > 1 {
return fmt . Errorf ( "internal error: only one call to handleTarget expected" )
2015-07-15 16:42:45 -04:00
}
2016-02-04 21:56:43 -05:00
if target == nil {
fmt . Fprintln ( cli . out , "No targets found, please provide a specific tag in order to sign it" )
2015-07-15 16:42:45 -04:00
return nil
}
2016-02-04 21:56:43 -05:00
fmt . Fprintln ( cli . out , "Signing and pushing trust metadata" )
2015-07-15 16:42:45 -04:00
2016-02-16 21:36:09 -05:00
repo , err := cli . getNotaryRepository ( repoInfo , authConfig , "push" , "pull" )
2015-07-15 16:42:45 -04:00
if err != nil {
2016-01-07 21:43:01 -05:00
fmt . Fprintf ( cli . out , "Error establishing connection to notary repository: %s\n" , err )
2015-07-15 16:42:45 -04:00
return err
}
2016-03-07 14:48:11 -05:00
// get the latest repository metadata so we can figure out which roles to sign
_ , err = repo . Update ( false )
switch err . ( type ) {
case client . ErrRepoNotInitialized , client . ErrRepositoryNotExist :
keys := repo . CryptoService . ListKeys ( data . CanonicalRootRole )
var rootKeyID string
// always select the first root key
if len ( keys ) > 0 {
sort . Strings ( keys )
rootKeyID = keys [ 0 ]
} else {
2016-03-17 13:23:18 -04:00
rootPublicKey , err := repo . CryptoService . Create ( data . CanonicalRootRole , "" , data . ECDSAKey )
2016-03-07 14:48:11 -05:00
if err != nil {
return err
}
rootKeyID = rootPublicKey . ID ( )
}
// Initialize the notary repository with a remotely managed snapshot key
if err := repo . Initialize ( rootKeyID , data . CanonicalSnapshotRole ) ; err != nil {
return notaryError ( repoInfo . FullName ( ) , err )
}
fmt . Fprintf ( cli . out , "Finished initializing %q\n" , repoInfo . FullName ( ) )
err = repo . AddTarget ( target , data . CanonicalTargetsRole )
case nil :
// already initialized and we have successfully downloaded the latest metadata
err = cli . addTargetToAllSignableRoles ( repo , target )
default :
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
2016-02-23 23:06:40 -05:00
if err == nil {
2016-03-07 14:48:11 -05:00
err = repo . Publish ( )
}
if err != nil {
2016-02-23 23:06:40 -05:00
fmt . Fprintf ( cli . out , "Failed to sign %q:%s - %s\n" , repoInfo . FullName ( ) , tag , err . Error ( ) )
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
2016-03-07 14:48:11 -05:00
fmt . Fprintf ( cli . out , "Successfully signed %q:%s\n" , repoInfo . FullName ( ) , tag )
return nil
}
2015-07-15 16:42:45 -04:00
2016-03-07 14:48:11 -05:00
// Attempt to add the image target to all the top level delegation roles we can
// (based on whether we have the signing key and whether the role's path allows
// us to).
// If there are no delegation roles, we add to the targets role.
func ( cli * DockerCli ) addTargetToAllSignableRoles ( repo * client . NotaryRepository , target * client . Target ) error {
var signableRoles [ ] string
// translate the full key names, which includes the GUN, into just the key IDs
2016-03-16 22:59:18 -04:00
allCanonicalKeyIDs := make ( map [ string ] struct { } )
2016-03-07 14:48:11 -05:00
for fullKeyID := range repo . CryptoService . ListAllKeys ( ) {
2016-03-16 22:59:18 -04:00
allCanonicalKeyIDs [ path . Base ( fullKeyID ) ] = struct { } { }
2016-03-07 14:48:11 -05:00
}
allDelegationRoles , err := repo . GetDelegationRoles ( )
if err != nil {
return err
}
2016-03-16 22:59:18 -04:00
// if there are no delegation roles, then just try to sign it into the targets role
if len ( allDelegationRoles ) == 0 {
return repo . AddTarget ( target , data . CanonicalTargetsRole )
}
// there are delegation roles, find every delegation role we have a key for, and
// attempt to sign into into all those roles.
2016-03-07 14:48:11 -05:00
for _ , delegationRole := range allDelegationRoles {
// We do not support signing any delegation role that isn't a direct child of the targets role.
// Also don't bother checking the keys if we can't add the target
// to this role due to path restrictions
if path . Dir ( delegationRole . Name ) != data . CanonicalTargetsRole || ! delegationRole . CheckPaths ( target . Name ) {
continue
}
for _ , canonicalKeyID := range delegationRole . KeyIDs {
if _ , ok := allCanonicalKeyIDs [ canonicalKeyID ] ; ok {
signableRoles = append ( signableRoles , delegationRole . Name )
2016-03-16 22:59:18 -04:00
break
2016-03-07 14:48:11 -05:00
}
2015-07-15 16:42:45 -04:00
}
}
2016-03-16 22:59:18 -04:00
if len ( signableRoles ) == 0 {
2016-03-07 14:48:11 -05:00
return fmt . Errorf ( "no valid signing keys for delegation roles" )
2015-07-15 16:42:45 -04:00
}
2016-03-07 14:48:11 -05:00
return repo . AddTarget ( target , signableRoles ... )
2015-07-15 16:42:45 -04:00
}