2016-12-05 19:06:29 -05:00
package trust
import (
2018-05-03 21:02:44 -04:00
"context"
2016-12-05 19:06:29 -05:00
"encoding/json"
2017-09-12 12:39:13 -04:00
"io"
2016-12-05 19:06:29 -05:00
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"time"
2017-04-17 18:07:56 -04:00
cliconfig "github.com/docker/cli/cli/config"
2017-09-12 12:39:13 -04:00
"github.com/docker/distribution/reference"
2016-12-05 19:06:29 -05:00
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/auth/challenge"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry"
"github.com/docker/go-connections/tlsconfig"
2017-09-12 12:39:13 -04:00
digest "github.com/opencontainers/go-digest"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2017-08-07 05:52:40 -04:00
"github.com/sirupsen/logrus"
2017-10-30 12:21:41 -04:00
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/client"
"github.com/theupdateframework/notary/passphrase"
"github.com/theupdateframework/notary/storage"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/trustpinning"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/signed"
2016-12-05 19:06:29 -05:00
)
var (
// ReleasesRole is the role named "releases"
2017-08-24 18:42:21 -04:00
ReleasesRole = data . RoleName ( path . Join ( data . CanonicalTargetsRole . String ( ) , "releases" ) )
2017-09-12 12:39:13 -04:00
// ActionsPullOnly defines the actions for read-only interactions with a Notary Repository
ActionsPullOnly = [ ] string { "pull" }
// ActionsPushAndPull defines the actions for read-write interactions with a Notary Repository
ActionsPushAndPull = [ ] string { "pull" , "push" }
2018-03-27 17:11:29 -04:00
// NotaryServer is the endpoint serving the Notary trust server
NotaryServer = "https://notary.docker.io"
2016-12-05 19:06:29 -05:00
)
2017-09-26 14:33:54 -04:00
// GetTrustDirectory returns the base trust directory name
func GetTrustDirectory ( ) string {
2016-12-25 14:31:52 -05:00
return filepath . Join ( cliconfig . Dir ( ) , "trust" )
2016-12-05 19:06:29 -05:00
}
// 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 certificateDirectory ( server string ) ( string , error ) {
u , err := url . Parse ( server )
if err != nil {
return "" , err
}
2016-12-25 14:31:52 -05:00
return filepath . Join ( cliconfig . Dir ( ) , "tls" , u . Host ) , nil
2016-12-05 19:06:29 -05:00
}
// Server returns the base URL for the trust server.
func Server ( index * registrytypes . IndexInfo ) ( string , error ) {
if s := os . Getenv ( "DOCKER_CONTENT_TRUST_SERVER" ) ; s != "" {
urlObj , err := url . Parse ( s )
if err != nil || urlObj . Scheme != "https" {
2017-03-09 13:23:45 -05:00
return "" , errors . Errorf ( "valid https URL required for trust server, got %s" , s )
2016-12-05 19:06:29 -05:00
}
return s , nil
}
if index . Official {
2018-03-27 17:11:29 -04:00
return NotaryServer , nil
2016-12-05 19:06:29 -05:00
}
return "https://" + index . Name , nil
}
type simpleCredentialStore struct {
auth types . AuthConfig
}
func ( scs simpleCredentialStore ) Basic ( u * url . URL ) ( string , string ) {
return scs . auth . Username , scs . auth . Password
}
func ( scs simpleCredentialStore ) RefreshToken ( u * url . URL , service string ) string {
return scs . auth . IdentityToken
}
func ( scs simpleCredentialStore ) SetRefreshToken ( * url . URL , string , string ) {
}
// GetNotaryRepository returns a NotaryRepository which stores all the
// information needed to operate on a notary repository.
// It creates an HTTP transport providing authentication support.
2017-09-12 12:39:13 -04:00
func GetNotaryRepository ( in io . Reader , out io . Writer , userAgent string , repoInfo * registry . RepositoryInfo , authConfig * types . AuthConfig , actions ... string ) ( client . Repository , error ) {
2016-12-05 19:06:29 -05:00
server , err := Server ( repoInfo . Index )
if err != nil {
return nil , err
}
var cfg = tlsconfig . ClientDefault ( )
cfg . InsecureSkipVerify = ! repoInfo . Index . Secure
// Get certificate base directory
certDir , err := 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
2017-11-08 11:34:04 -05:00
modifiers := registry . Headers ( userAgent , http . Header { } )
2016-12-05 19:06:29 -05: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
}
challengeManager := challenge . NewSimpleManager ( )
resp , err := pingClient . Do ( req )
if err != nil {
// Ignore error on ping to operate in offline mode
logrus . Debugf ( "Error pinging notary server %q: %s" , endpointStr , err )
} else {
defer resp . Body . Close ( )
// 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
}
}
2016-12-27 15:51:00 -05:00
scope := auth . RepositoryScope {
2017-01-25 19:54:18 -05:00
Repository : repoInfo . Name . Name ( ) ,
2016-12-27 15:51:00 -05:00
Actions : actions ,
Class : repoInfo . Class ,
}
2017-09-12 12:39:13 -04:00
creds := simpleCredentialStore { auth : * authConfig }
2016-12-27 15:51:00 -05:00
tokenHandlerOptions := auth . TokenHandlerOptions {
Transport : authTransport ,
Credentials : creds ,
Scopes : [ ] auth . Scope { scope } ,
ClientID : registry . AuthClientID ,
}
tokenHandler := auth . NewTokenHandlerWithOptions ( tokenHandlerOptions )
2016-12-05 19:06:29 -05:00
basicHandler := auth . NewBasicHandler ( creds )
2017-06-09 16:41:53 -04:00
modifiers = append ( modifiers , auth . NewAuthorizer ( challengeManager , tokenHandler , basicHandler ) )
2016-12-05 19:06:29 -05:00
tr := transport . NewTransport ( base , modifiers ... )
2017-09-11 17:07:00 -04:00
return client . NewFileCachedRepository (
2017-09-26 14:33:54 -04:00
GetTrustDirectory ( ) ,
2017-08-24 18:42:21 -04:00
data . GUN ( repoInfo . Name . Name ( ) ) ,
2016-12-05 19:06:29 -05:00
server ,
tr ,
2017-09-26 14:33:54 -04:00
GetPassphraseRetriever ( in , out ) ,
2016-12-05 19:06:29 -05:00
trustpinning . TrustPinConfig { } )
}
2017-09-26 14:33:54 -04:00
// GetPassphraseRetriever returns a passphrase retriever that utilizes Content Trust env vars
func GetPassphraseRetriever ( in io . Reader , out io . Writer ) notary . PassRetriever {
2016-12-05 19:06:29 -05:00
aliasMap := map [ string ] string {
"root" : "root" ,
"snapshot" : "repository" ,
"targets" : "repository" ,
"default" : "repository" ,
}
2017-09-12 12:39:13 -04:00
baseRetriever := passphrase . PromptRetrieverWithInOut ( in , out , aliasMap )
2016-12-05 19:06:29 -05:00
env := map [ string ] string {
"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" ) ,
}
return func ( keyName string , alias string , createNew bool , numAttempts int ) ( string , bool , error ) {
if v := env [ alias ] ; v != "" {
return v , numAttempts > 1 , nil
}
// For non-root roles, we can also try the "default" alias if it is specified
2017-08-24 18:42:21 -04:00
if v := env [ "default" ] ; v != "" && alias != data . CanonicalRootRole . String ( ) {
2016-12-05 19:06:29 -05:00
return v , numAttempts > 1 , nil
}
return baseRetriever ( keyName , alias , createNew , numAttempts )
}
}
// NotaryError formats an error message received from the notary service
func NotaryError ( repoName string , err error ) error {
switch err . ( type ) {
case * json . SyntaxError :
logrus . Debugf ( "Notary syntax error: %s" , err )
2017-03-09 13:23:45 -05:00
return errors . 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-12-05 19:06:29 -05:00
case signed . ErrExpired :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: remote repository %s out-of-date: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case trustmanager . ErrKeyNotFound :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: signing keys for remote repository %s not found: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case storage . NetworkError :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: error contacting notary server: %v" , err )
2016-12-05 19:06:29 -05:00
case storage . ErrMetaNotFound :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: trust data missing for remote repository %s or remote repository not found: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case trustpinning . ErrRootRotationFail , trustpinning . ErrValidationFail , signed . ErrInvalidKeyType :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case signed . ErrNoKeys :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case signed . ErrLowVersion :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case signed . ErrRoleThreshold :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case client . ErrRepositoryNotExist :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: remote trust data does not exist for %s: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
case signed . ErrInsufficientSignatures :
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v" , repoName , err )
2016-12-05 19:06:29 -05:00
}
return err
}
2017-08-25 17:49:40 -04:00
// GetSignableRoles returns a list of roles for which we have valid signing
// keys, given a notary repository and a target
2017-09-11 17:07:00 -04:00
func GetSignableRoles ( repo client . Repository , target * client . Target ) ( [ ] data . RoleName , error ) {
2017-08-25 17:49:40 -04:00
var signableRoles [ ] data . RoleName
// translate the full key names, which includes the GUN, into just the key IDs
allCanonicalKeyIDs := make ( map [ string ] struct { } )
2017-09-11 17:07:00 -04:00
for fullKeyID := range repo . GetCryptoService ( ) . ListAllKeys ( ) {
2017-08-25 17:49:40 -04:00
allCanonicalKeyIDs [ path . Base ( fullKeyID ) ] = struct { } { }
}
allDelegationRoles , err := repo . GetDelegationRoles ( )
if err != nil {
return signableRoles , err
}
// if there are no delegation roles, then just try to sign it into the targets role
if len ( allDelegationRoles ) == 0 {
signableRoles = append ( signableRoles , data . CanonicalTargetsRole )
return signableRoles , nil
}
// there are delegation roles, find every delegation role we have a key for, and
// attempt to sign into into all those roles.
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 . String ( ) ) != data . CanonicalTargetsRole . String ( ) || ! delegationRole . CheckPaths ( target . Name ) {
continue
}
for _ , canonicalKeyID := range delegationRole . KeyIDs {
if _ , ok := allCanonicalKeyIDs [ canonicalKeyID ] ; ok {
signableRoles = append ( signableRoles , delegationRole . Name )
break
}
}
}
if len ( signableRoles ) == 0 {
return signableRoles , errors . Errorf ( "no valid signing keys for delegation roles" )
}
return signableRoles , nil
}
2017-09-12 12:39:13 -04:00
// ImageRefAndAuth contains all reference information and the auth config for an image request
type ImageRefAndAuth struct {
2017-09-26 12:53:21 -04:00
original string
2017-09-12 12:39:13 -04:00
authConfig * types . AuthConfig
reference reference . Named
repoInfo * registry . RepositoryInfo
tag string
digest digest . Digest
}
2017-09-14 17:58:28 -04:00
// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name
2017-10-31 06:21:09 -04:00
// as an ImageRefAndAuth struct
2018-03-06 05:15:18 -05:00
func GetImageReferencesAndAuth ( ctx context . Context , rs registry . Service ,
authResolver func ( ctx context . Context , index * registrytypes . IndexInfo ) types . AuthConfig ,
imgName string ,
) ( ImageRefAndAuth , error ) {
2017-09-14 17:58:28 -04:00
ref , err := reference . ParseNormalizedNamed ( imgName )
if err != nil {
2017-09-26 12:53:21 -04:00
return ImageRefAndAuth { } , err
2017-09-14 17:58:28 -04:00
}
// Resolve the Repository name from fqn to RepositoryInfo
2018-03-06 05:15:18 -05:00
var repoInfo * registry . RepositoryInfo
if rs != nil {
repoInfo , err = rs . ResolveRepository ( ref )
} else {
repoInfo , err = registry . ParseRepositoryInfo ( ref )
}
2017-09-14 17:58:28 -04:00
if err != nil {
2017-09-26 12:53:21 -04:00
return ImageRefAndAuth { } , err
2017-09-14 17:58:28 -04:00
}
authConfig := authResolver ( ctx , repoInfo . Index )
2017-09-26 12:53:21 -04:00
return ImageRefAndAuth {
original : imgName ,
authConfig : & authConfig ,
reference : ref ,
repoInfo : repoInfo ,
tag : getTag ( ref ) ,
digest : getDigest ( ref ) ,
} , nil
2017-09-14 17:58:28 -04:00
}
func getTag ( ref reference . Named ) string {
switch x := ref . ( type ) {
case reference . Canonical , reference . Digested :
return ""
case reference . NamedTagged :
return x . Tag ( )
default :
return ""
}
}
func getDigest ( ref reference . Named ) digest . Digest {
switch x := ref . ( type ) {
case reference . Canonical :
return x . Digest ( )
case reference . Digested :
return x . Digest ( )
default :
return digest . Digest ( "" )
}
}
2017-09-12 12:39:13 -04:00
// AuthConfig returns the auth information (username, etc) for a given ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) AuthConfig ( ) * types . AuthConfig {
return imgRefAuth . authConfig
}
// Reference returns the Image reference for a given ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) Reference ( ) reference . Named {
return imgRefAuth . reference
}
// RepoInfo returns the repository information for a given ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) RepoInfo ( ) * registry . RepositoryInfo {
return imgRefAuth . repoInfo
}
// Tag returns the Image tag for a given ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) Tag ( ) string {
return imgRefAuth . tag
}
// Digest returns the Image digest for a given ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) Digest ( ) digest . Digest {
return imgRefAuth . digest
}
2017-09-26 12:53:21 -04:00
// Name returns the image name used to initialize the ImageRefAndAuth
func ( imgRefAuth * ImageRefAndAuth ) Name ( ) string {
return imgRefAuth . original
}