github.com/kunnos/engine@v1.13.1/cli/trust/trust.go (about) 1 package trust 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net" 7 "net/http" 8 "net/url" 9 "os" 10 "path" 11 "path/filepath" 12 "time" 13 14 "github.com/sirupsen/logrus" 15 "github.com/docker/distribution/registry/client/auth" 16 "github.com/docker/distribution/registry/client/auth/challenge" 17 "github.com/docker/distribution/registry/client/transport" 18 "github.com/docker/docker/api/types" 19 registrytypes "github.com/docker/docker/api/types/registry" 20 "github.com/docker/docker/cli/command" 21 "github.com/docker/docker/cliconfig" 22 "github.com/docker/docker/registry" 23 "github.com/docker/go-connections/tlsconfig" 24 "github.com/docker/notary" 25 "github.com/docker/notary/client" 26 "github.com/docker/notary/passphrase" 27 "github.com/docker/notary/storage" 28 "github.com/docker/notary/trustmanager" 29 "github.com/docker/notary/trustpinning" 30 "github.com/docker/notary/tuf/data" 31 "github.com/docker/notary/tuf/signed" 32 ) 33 34 var ( 35 // ReleasesRole is the role named "releases" 36 ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases") 37 ) 38 39 func trustDirectory() string { 40 return filepath.Join(cliconfig.ConfigDir(), "trust") 41 } 42 43 // certificateDirectory returns the directory containing 44 // TLS certificates for the given server. An error is 45 // returned if there was an error parsing the server string. 46 func certificateDirectory(server string) (string, error) { 47 u, err := url.Parse(server) 48 if err != nil { 49 return "", err 50 } 51 52 return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil 53 } 54 55 // Server returns the base URL for the trust server. 56 func Server(index *registrytypes.IndexInfo) (string, error) { 57 if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" { 58 urlObj, err := url.Parse(s) 59 if err != nil || urlObj.Scheme != "https" { 60 return "", fmt.Errorf("valid https URL required for trust server, got %s", s) 61 } 62 63 return s, nil 64 } 65 if index.Official { 66 return registry.NotaryServer, nil 67 } 68 return "https://" + index.Name, nil 69 } 70 71 type simpleCredentialStore struct { 72 auth types.AuthConfig 73 } 74 75 func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) { 76 return scs.auth.Username, scs.auth.Password 77 } 78 79 func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string { 80 return scs.auth.IdentityToken 81 } 82 83 func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) { 84 } 85 86 // GetNotaryRepository returns a NotaryRepository which stores all the 87 // information needed to operate on a notary repository. 88 // It creates an HTTP transport providing authentication support. 89 func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) { 90 server, err := Server(repoInfo.Index) 91 if err != nil { 92 return nil, err 93 } 94 95 var cfg = tlsconfig.ClientDefault() 96 cfg.InsecureSkipVerify = !repoInfo.Index.Secure 97 98 // Get certificate base directory 99 certDir, err := certificateDirectory(server) 100 if err != nil { 101 return nil, err 102 } 103 logrus.Debugf("reading certificate directory: %s", certDir) 104 105 if err := registry.ReadCertsDirectory(cfg, certDir); err != nil { 106 return nil, err 107 } 108 109 base := &http.Transport{ 110 Proxy: http.ProxyFromEnvironment, 111 Dial: (&net.Dialer{ 112 Timeout: 30 * time.Second, 113 KeepAlive: 30 * time.Second, 114 DualStack: true, 115 }).Dial, 116 TLSHandshakeTimeout: 10 * time.Second, 117 TLSClientConfig: cfg, 118 DisableKeepAlives: true, 119 } 120 121 // Skip configuration headers since request is not going to Docker daemon 122 modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{}) 123 authTransport := transport.NewTransport(base, modifiers...) 124 pingClient := &http.Client{ 125 Transport: authTransport, 126 Timeout: 5 * time.Second, 127 } 128 endpointStr := server + "/v2/" 129 req, err := http.NewRequest("GET", endpointStr, nil) 130 if err != nil { 131 return nil, err 132 } 133 134 challengeManager := challenge.NewSimpleManager() 135 136 resp, err := pingClient.Do(req) 137 if err != nil { 138 // Ignore error on ping to operate in offline mode 139 logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err) 140 } else { 141 defer resp.Body.Close() 142 143 // Add response to the challenge manager to parse out 144 // authentication header and register authentication method 145 if err := challengeManager.AddResponse(resp); err != nil { 146 return nil, err 147 } 148 } 149 150 scope := auth.RepositoryScope{ 151 Repository: repoInfo.FullName(), 152 Actions: actions, 153 Class: repoInfo.Class, 154 } 155 creds := simpleCredentialStore{auth: authConfig} 156 tokenHandlerOptions := auth.TokenHandlerOptions{ 157 Transport: authTransport, 158 Credentials: creds, 159 Scopes: []auth.Scope{scope}, 160 ClientID: registry.AuthClientID, 161 } 162 tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) 163 basicHandler := auth.NewBasicHandler(creds) 164 modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) 165 tr := transport.NewTransport(base, modifiers...) 166 167 return client.NewNotaryRepository( 168 trustDirectory(), 169 repoInfo.FullName(), 170 server, 171 tr, 172 getPassphraseRetriever(streams), 173 trustpinning.TrustPinConfig{}) 174 } 175 176 func getPassphraseRetriever(streams command.Streams) notary.PassRetriever { 177 aliasMap := map[string]string{ 178 "root": "root", 179 "snapshot": "repository", 180 "targets": "repository", 181 "default": "repository", 182 } 183 baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap) 184 env := map[string]string{ 185 "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), 186 "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 187 "targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 188 "default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 189 } 190 191 return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { 192 if v := env[alias]; v != "" { 193 return v, numAttempts > 1, nil 194 } 195 // For non-root roles, we can also try the "default" alias if it is specified 196 if v := env["default"]; v != "" && alias != data.CanonicalRootRole { 197 return v, numAttempts > 1, nil 198 } 199 return baseRetriever(keyName, alias, createNew, numAttempts) 200 } 201 } 202 203 // NotaryError formats an error message received from the notary service 204 func NotaryError(repoName string, err error) error { 205 switch err.(type) { 206 case *json.SyntaxError: 207 logrus.Debugf("Notary syntax error: %s", err) 208 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) 209 case signed.ErrExpired: 210 return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err) 211 case trustmanager.ErrKeyNotFound: 212 return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err) 213 case storage.NetworkError: 214 return fmt.Errorf("Error: error contacting notary server: %v", err) 215 case storage.ErrMetaNotFound: 216 return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err) 217 case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType: 218 return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err) 219 case signed.ErrNoKeys: 220 return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err) 221 case signed.ErrLowVersion: 222 return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err) 223 case signed.ErrRoleThreshold: 224 return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err) 225 case client.ErrRepositoryNotExist: 226 return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err) 227 case signed.ErrInsufficientSignatures: 228 return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err) 229 } 230 231 return err 232 }