registry.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package registry
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "github.com/opencontainers/go-digest"
  10. )
  11. const (
  12. v2binary = "registry-v2"
  13. v2binarySchema1 = "registry-v2-schema1"
  14. )
  15. type testingT interface {
  16. logT
  17. Fatal(...interface{})
  18. Fatalf(string, ...interface{})
  19. }
  20. type logT interface {
  21. Logf(string, ...interface{})
  22. }
  23. // V2 represent a registry version 2
  24. type V2 struct {
  25. cmd *exec.Cmd
  26. registryURL string
  27. dir string
  28. auth string
  29. username string
  30. password string
  31. email string
  32. }
  33. // NewV2 creates a v2 registry server
  34. func NewV2(schema1 bool, auth, tokenURL, registryURL string) (*V2, error) {
  35. tmp, err := ioutil.TempDir("", "registry-test-")
  36. if err != nil {
  37. return nil, err
  38. }
  39. template := `version: 0.1
  40. loglevel: debug
  41. storage:
  42. filesystem:
  43. rootdirectory: %s
  44. http:
  45. addr: %s
  46. %s`
  47. var (
  48. authTemplate string
  49. username string
  50. password string
  51. email string
  52. )
  53. switch auth {
  54. case "htpasswd":
  55. htpasswdPath := filepath.Join(tmp, "htpasswd")
  56. // generated with: htpasswd -Bbn testuser testpassword
  57. userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
  58. username = "testuser"
  59. password = "testpassword"
  60. email = "test@test.org"
  61. if err := ioutil.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
  62. return nil, err
  63. }
  64. authTemplate = fmt.Sprintf(`auth:
  65. htpasswd:
  66. realm: basic-realm
  67. path: %s
  68. `, htpasswdPath)
  69. case "token":
  70. authTemplate = fmt.Sprintf(`auth:
  71. token:
  72. realm: %s
  73. service: "registry"
  74. issuer: "auth-registry"
  75. rootcertbundle: "fixtures/registry/cert.pem"
  76. `, tokenURL)
  77. }
  78. confPath := filepath.Join(tmp, "config.yaml")
  79. config, err := os.Create(confPath)
  80. if err != nil {
  81. return nil, err
  82. }
  83. defer config.Close()
  84. if _, err := fmt.Fprintf(config, template, tmp, registryURL, authTemplate); err != nil {
  85. os.RemoveAll(tmp)
  86. return nil, err
  87. }
  88. binary := v2binary
  89. if schema1 {
  90. binary = v2binarySchema1
  91. }
  92. cmd := exec.Command(binary, confPath)
  93. if err := cmd.Start(); err != nil {
  94. os.RemoveAll(tmp)
  95. return nil, err
  96. }
  97. return &V2{
  98. cmd: cmd,
  99. dir: tmp,
  100. auth: auth,
  101. username: username,
  102. password: password,
  103. email: email,
  104. registryURL: registryURL,
  105. }, nil
  106. }
  107. // Ping sends an http request to the current registry, and fail if it doesn't respond correctly
  108. func (r *V2) Ping() error {
  109. // We always ping through HTTP for our test registry.
  110. resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
  111. if err != nil {
  112. return err
  113. }
  114. resp.Body.Close()
  115. fail := resp.StatusCode != http.StatusOK
  116. if r.auth != "" {
  117. // unauthorized is a _good_ status when pinging v2/ and it needs auth
  118. fail = fail && resp.StatusCode != http.StatusUnauthorized
  119. }
  120. if fail {
  121. return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
  122. }
  123. return nil
  124. }
  125. // Close kills the registry server
  126. func (r *V2) Close() {
  127. r.cmd.Process.Kill()
  128. r.cmd.Process.Wait()
  129. os.RemoveAll(r.dir)
  130. }
  131. func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
  132. // Split the digest into its algorithm and hex components.
  133. dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Hex()
  134. // The path to the target blob data looks something like:
  135. // baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
  136. return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
  137. }
  138. // ReadBlobContents read the file corresponding to the specified digest
  139. func (r *V2) ReadBlobContents(t testingT, blobDigest digest.Digest) []byte {
  140. // Load the target manifest blob.
  141. manifestBlob, err := ioutil.ReadFile(r.getBlobFilename(blobDigest))
  142. if err != nil {
  143. t.Fatalf("unable to read blob: %s", err)
  144. }
  145. return manifestBlob
  146. }
  147. // WriteBlobContents write the file corresponding to the specified digest with the given content
  148. func (r *V2) WriteBlobContents(t testingT, blobDigest digest.Digest, data []byte) {
  149. if err := ioutil.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644)); err != nil {
  150. t.Fatalf("unable to write malicious data blob: %s", err)
  151. }
  152. }
  153. // TempMoveBlobData moves the existing data file aside, so that we can replace it with a
  154. // malicious blob of data for example.
  155. func (r *V2) TempMoveBlobData(t testingT, blobDigest digest.Digest) (undo func()) {
  156. tempFile, err := ioutil.TempFile("", "registry-temp-blob-")
  157. if err != nil {
  158. t.Fatalf("unable to get temporary blob file: %s", err)
  159. }
  160. tempFile.Close()
  161. blobFilename := r.getBlobFilename(blobDigest)
  162. // Move the existing data file aside, so that we can replace it with a
  163. // another blob of data.
  164. if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
  165. os.Remove(tempFile.Name())
  166. t.Fatalf("unable to move data blob: %s", err)
  167. }
  168. return func() {
  169. os.Rename(tempFile.Name(), blobFilename)
  170. os.Remove(tempFile.Name())
  171. }
  172. }
  173. // Username returns the configured user name of the server
  174. func (r *V2) Username() string {
  175. return r.username
  176. }
  177. // Password returns the configured password of the server
  178. func (r *V2) Password() string {
  179. return r.password
  180. }
  181. // Email returns the configured email of the server
  182. func (r *V2) Email() string {
  183. return r.email
  184. }
  185. // Path returns the path where the registry write data
  186. func (r *V2) Path() string {
  187. return filepath.Join(r.dir, "docker", "registry", "v2")
  188. }