trust.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. scope := auth.RepositoryScope{
  129. Repository: repoInfo.Name.Name(),
  130. Actions: actions,
  131. Class: repoInfo.Class,
  132. }
  133. creds := simpleCredentialStore{auth: authConfig}
  134. tokenHandlerOptions := auth.TokenHandlerOptions{
  135. Transport: authTransport,
  136. Credentials: creds,
  137. Scopes: []auth.Scope{scope},
  138. ClientID: registry.AuthClientID,
  139. }
  140. tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
  141. basicHandler := auth.NewBasicHandler(creds)
  142. modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
  143. tr := transport.NewTransport(base, modifiers...)
  144. return client.NewNotaryRepository(
  145. trustDirectory(),
  146. repoInfo.Name.Name(),
  147. server,
  148. tr,
  149. getPassphraseRetriever(streams),
  150. trustpinning.TrustPinConfig{})
  151. }
  152. func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
  153. aliasMap := map[string]string{
  154. "root": "root",
  155. "snapshot": "repository",
  156. "targets": "repository",
  157. "default": "repository",
  158. }
  159. baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
  160. env := map[string]string{
  161. "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
  162. "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
  163. "targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
  164. "default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
  165. }
  166. return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
  167. if v := env[alias]; v != "" {
  168. return v, numAttempts > 1, nil
  169. }
  170. // For non-root roles, we can also try the "default" alias if it is specified
  171. if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
  172. return v, numAttempts > 1, nil
  173. }
  174. return baseRetriever(keyName, alias, createNew, numAttempts)
  175. }
  176. }
  177. // NotaryError formats an error message received from the notary service
  178. func NotaryError(repoName string, err error) error {
  179. switch err.(type) {
  180. case *json.SyntaxError:
  181. logrus.Debugf("Notary syntax error: %s", err)
  182. 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)
  183. case signed.ErrExpired:
  184. return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
  185. case trustmanager.ErrKeyNotFound:
  186. return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
  187. case storage.NetworkError:
  188. return fmt.Errorf("Error: error contacting notary server: %v", err)
  189. case storage.ErrMetaNotFound:
  190. return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
  191. case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
  192. return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
  193. case signed.ErrNoKeys:
  194. return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
  195. case signed.ErrLowVersion:
  196. return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
  197. case signed.ErrRoleThreshold:
  198. return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
  199. case client.ErrRepositoryNotExist:
  200. return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
  201. case signed.ErrInsufficientSignatures:
  202. return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err)
  203. }
  204. return err
  205. }