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"
"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
}
2015-12-11 23:11:42 -05:00
func ( cli * DockerCli ) getNotaryRepository ( repoInfo * registry . RepositoryInfo , authConfig types . AuthConfig ) ( * 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
modifiers := registry . DockerHeaders ( http . Header { } )
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 }
2015-12-11 14:00:13 -05:00
tokenHandler := auth . NewTokenHandler ( authTransport , creds , repoInfo . FullName ( ) , "push" , "pull" )
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 {
2015-12-23 19:34:46 -05:00
"root" : "root" ,
"snapshot" : "repository" ,
"targets" : "repository" ,
"targets/releases" : "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 {
2015-12-23 19:34:46 -05: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" ) ,
"targets/releases" : 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
}
2015-12-23 19:34:46 -05:00
if env [ "snapshot" ] == "" || env [ "targets" ] == "" || env [ "targets/releases" ] == "" {
2015-10-22 12:08:50 -04:00
if passphrase := os . Getenv ( "DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE" ) ; passphrase != "" {
env [ "snapshot" ] = passphrase
env [ "targets" ] = passphrase
2015-12-23 19:34:46 -05:00
env [ "targets/releases" ] = 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
}
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
2015-12-11 22:11:20 -05:00
authConfig := registry . ResolveAuthConfig ( cli . configFile . AuthConfigs , 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
}
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
}
2015-12-04 13:56:04 -05:00
return cli . client . ImageTag ( 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
notaryRepo , err := cli . getNotaryRepository ( repoInfo , authConfig )
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
}
refs = append ( refs , t )
}
} 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
}
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 ( )
targets := [ ] target { }
handleTarget := func ( aux * json . RawMessage ) {
var pushResult distribution . PushResult
err := json . Unmarshal ( * aux , & pushResult )
if err == nil && pushResult . Tag != "" && pushResult . Digest . Validate ( ) == nil {
targets = append ( targets , target {
reference : registry . ParseReference ( pushResult . Tag ) ,
digest : pushResult . Digest ,
size : int64 ( pushResult . Size ) ,
} )
}
2015-07-15 16:42:45 -04:00
}
2015-12-21 18:02:44 -05:00
err = jsonmessage . DisplayJSONMessagesStream ( responseBody , cli . out , cli . outFd , cli . isTerminalOut , handleTarget )
if err != nil {
return err
}
2015-07-15 16:42:45 -04:00
if tag == "" {
fmt . Fprintf ( cli . out , "No tag specified, skipping trust metadata push\n" )
return nil
}
if len ( targets ) == 0 {
fmt . Fprintf ( cli . out , "No targets found, skipping trust metadata push\n" )
return nil
}
fmt . Fprintf ( cli . out , "Signing and pushing trust metadata\n" )
repo , err := cli . getNotaryRepository ( repoInfo , authConfig )
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
}
for _ , target := range targets {
h , err := hex . DecodeString ( target . digest . Hex ( ) )
if err != nil {
return err
}
t := & client . Target {
Name : target . reference . String ( ) ,
Hashes : data . Hashes {
string ( target . digest . Algorithm ( ) ) : h ,
} ,
Length : int64 ( target . size ) ,
}
2015-12-18 21:47:35 -05:00
if err := repo . AddTarget ( t , releasesRole ) ; err != nil {
2015-07-15 16:42:45 -04:00
return err
}
}
err = repo . Publish ( )
2016-01-07 21:43:01 -05:00
if _ , ok := err . ( client . ErrRepoNotInitialized ) ; ! ok {
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
2015-10-30 20:37:08 -04:00
keys := repo . CryptoService . ListKeys ( data . CanonicalRootRole )
2015-07-15 16:42:45 -04:00
2015-10-30 20:37:08 -04:00
var rootKeyID string
// always select the first root key
if len ( keys ) > 0 {
sort . Strings ( keys )
rootKeyID = keys [ 0 ]
} else {
rootPublicKey , err := repo . CryptoService . Create ( data . CanonicalRootRole , data . ECDSAKey )
2015-07-15 16:42:45 -04:00
if err != nil {
return err
}
2015-10-30 20:37:08 -04:00
rootKeyID = rootPublicKey . ID ( )
2015-07-15 16:42:45 -04:00
}
2015-10-30 20:37:08 -04:00
if err := repo . Initialize ( rootKeyID ) ; err != nil {
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , err )
2015-07-15 16:42:45 -04:00
}
2015-12-11 14:00:13 -05:00
fmt . Fprintf ( cli . out , "Finished initializing %q\n" , repoInfo . FullName ( ) )
2015-07-15 16:42:45 -04:00
2015-12-18 21:47:35 -05:00
return notaryError ( repoInfo . FullName ( ) , repo . Publish ( ) )
2015-07-15 16:42:45 -04:00
}