trust.go 7.6 KB

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