github.com/rumpl/bof@v23.0.0-rc.2+incompatible/testutil/registry/registry.go (about)

     1  package registry // import "github.com/docker/docker/testutil/registry"
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/opencontainers/go-digest"
    14  	"gotest.tools/v3/assert"
    15  )
    16  
    17  const (
    18  	// V2binary is the name of the registry v2 binary
    19  	V2binary = "registry-v2"
    20  	// V2binarySchema1 is the name of the registry that serve schema1
    21  	V2binarySchema1 = "registry-v2-schema1"
    22  	// DefaultURL is the default url that will be used by the registry (if not specified otherwise)
    23  	DefaultURL = "127.0.0.1:5000"
    24  )
    25  
    26  // V2 represent a registry version 2
    27  type V2 struct {
    28  	cmd         *exec.Cmd
    29  	registryURL string
    30  	dir         string
    31  	auth        string
    32  	username    string
    33  	password    string
    34  	email       string
    35  }
    36  
    37  // Config contains the test registry configuration
    38  type Config struct {
    39  	schema1     bool
    40  	auth        string
    41  	tokenURL    string
    42  	registryURL string
    43  	stdout      io.Writer
    44  	stderr      io.Writer
    45  }
    46  
    47  // NewV2 creates a v2 registry server
    48  func NewV2(t testing.TB, ops ...func(*Config)) *V2 {
    49  	t.Helper()
    50  	c := &Config{
    51  		registryURL: DefaultURL,
    52  	}
    53  	for _, op := range ops {
    54  		op(c)
    55  	}
    56  	tmp, err := os.MkdirTemp("", "registry-test-")
    57  	assert.NilError(t, err)
    58  	template := `version: 0.1
    59  loglevel: debug
    60  storage:
    61      filesystem:
    62          rootdirectory: %s
    63  http:
    64      addr: %s
    65  %s`
    66  	var (
    67  		authTemplate string
    68  		username     string
    69  		password     string
    70  		email        string
    71  	)
    72  	switch c.auth {
    73  	case "htpasswd":
    74  		htpasswdPath := filepath.Join(tmp, "htpasswd")
    75  		// generated with: htpasswd -Bbn testuser testpassword
    76  		// #nosec G101
    77  		userpasswd := "testuser:$2y$05$sBsSqk0OpSD1uTZkHXc4FeJ0Z70wLQdAX/82UiHuQOKbNbBrzs63m"
    78  		username = "testuser"
    79  		password = "testpassword"
    80  		email = "test@test.org"
    81  		err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644))
    82  		assert.NilError(t, err)
    83  		authTemplate = fmt.Sprintf(`auth:
    84      htpasswd:
    85          realm: basic-realm
    86          path: %s
    87  `, htpasswdPath)
    88  	case "token":
    89  		authTemplate = fmt.Sprintf(`auth:
    90      token:
    91          realm: %s
    92          service: "registry"
    93          issuer: "auth-registry"
    94          rootcertbundle: "fixtures/registry/cert.pem"
    95  `, c.tokenURL)
    96  	}
    97  
    98  	confPath := filepath.Join(tmp, "config.yaml")
    99  	config, err := os.Create(confPath)
   100  	assert.NilError(t, err)
   101  	defer config.Close()
   102  
   103  	if _, err := fmt.Fprintf(config, template, tmp, c.registryURL, authTemplate); err != nil {
   104  		// FIXME(vdemeester) use a defer/clean func
   105  		os.RemoveAll(tmp)
   106  		t.Fatal(err)
   107  	}
   108  
   109  	binary := V2binary
   110  	if c.schema1 {
   111  		binary = V2binarySchema1
   112  	}
   113  	cmd := exec.Command(binary, confPath)
   114  	cmd.Stdout = c.stdout
   115  	cmd.Stderr = c.stderr
   116  	if err := cmd.Start(); err != nil {
   117  		// FIXME(vdemeester) use a defer/clean func
   118  		os.RemoveAll(tmp)
   119  		t.Fatal(err)
   120  	}
   121  	return &V2{
   122  		cmd:         cmd,
   123  		dir:         tmp,
   124  		auth:        c.auth,
   125  		username:    username,
   126  		password:    password,
   127  		email:       email,
   128  		registryURL: c.registryURL,
   129  	}
   130  }
   131  
   132  // WaitReady waits for the registry to be ready to serve requests (or fail after a while)
   133  func (r *V2) WaitReady(t testing.TB) {
   134  	t.Helper()
   135  	var err error
   136  	for i := 0; i != 50; i++ {
   137  		if err = r.Ping(); err == nil {
   138  			return
   139  		}
   140  		time.Sleep(100 * time.Millisecond)
   141  	}
   142  	t.Fatalf("timeout waiting for test registry to become available: %v", err)
   143  }
   144  
   145  // Ping sends an http request to the current registry, and fail if it doesn't respond correctly
   146  func (r *V2) Ping() error {
   147  	// We always ping through HTTP for our test registry.
   148  	resp, err := http.Get(fmt.Sprintf("http://%s/v2/", r.registryURL))
   149  	if err != nil {
   150  		return err
   151  	}
   152  	resp.Body.Close()
   153  
   154  	fail := resp.StatusCode != http.StatusOK
   155  	if r.auth != "" {
   156  		// unauthorized is a _good_ status when pinging v2/ and it needs auth
   157  		fail = fail && resp.StatusCode != http.StatusUnauthorized
   158  	}
   159  	if fail {
   160  		return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode)
   161  	}
   162  	return nil
   163  }
   164  
   165  // Close kills the registry server
   166  func (r *V2) Close() {
   167  	r.cmd.Process.Kill()
   168  	r.cmd.Process.Wait()
   169  	os.RemoveAll(r.dir)
   170  }
   171  
   172  func (r *V2) getBlobFilename(blobDigest digest.Digest) string {
   173  	// Split the digest into its algorithm and hex components.
   174  	dgstAlg, dgstHex := blobDigest.Algorithm(), blobDigest.Encoded()
   175  
   176  	// The path to the target blob data looks something like:
   177  	//   baseDir + "docker/registry/v2/blobs/sha256/a3/a3ed...46d4/data"
   178  	return fmt.Sprintf("%s/docker/registry/v2/blobs/%s/%s/%s/data", r.dir, dgstAlg, dgstHex[:2], dgstHex)
   179  }
   180  
   181  // ReadBlobContents read the file corresponding to the specified digest
   182  func (r *V2) ReadBlobContents(t testing.TB, blobDigest digest.Digest) []byte {
   183  	t.Helper()
   184  	// Load the target manifest blob.
   185  	manifestBlob, err := os.ReadFile(r.getBlobFilename(blobDigest))
   186  	assert.NilError(t, err, "unable to read blob")
   187  	return manifestBlob
   188  }
   189  
   190  // WriteBlobContents write the file corresponding to the specified digest with the given content
   191  func (r *V2) WriteBlobContents(t testing.TB, blobDigest digest.Digest, data []byte) {
   192  	t.Helper()
   193  	err := os.WriteFile(r.getBlobFilename(blobDigest), data, os.FileMode(0644))
   194  	assert.NilError(t, err, "unable to write malicious data blob")
   195  }
   196  
   197  // TempMoveBlobData moves the existing data file aside, so that we can replace it with a
   198  // malicious blob of data for example.
   199  func (r *V2) TempMoveBlobData(t testing.TB, blobDigest digest.Digest) (undo func()) {
   200  	t.Helper()
   201  	tempFile, err := os.CreateTemp("", "registry-temp-blob-")
   202  	assert.NilError(t, err, "unable to get temporary blob file")
   203  	tempFile.Close()
   204  
   205  	blobFilename := r.getBlobFilename(blobDigest)
   206  
   207  	// Move the existing data file aside, so that we can replace it with a
   208  	// another blob of data.
   209  	if err := os.Rename(blobFilename, tempFile.Name()); err != nil {
   210  		// FIXME(vdemeester) use a defer/clean func
   211  		os.Remove(tempFile.Name())
   212  		t.Fatalf("unable to move data blob: %s", err)
   213  	}
   214  
   215  	return func() {
   216  		os.Rename(tempFile.Name(), blobFilename)
   217  		os.Remove(tempFile.Name())
   218  	}
   219  }
   220  
   221  // Username returns the configured user name of the server
   222  func (r *V2) Username() string {
   223  	return r.username
   224  }
   225  
   226  // Password returns the configured password of the server
   227  func (r *V2) Password() string {
   228  	return r.password
   229  }
   230  
   231  // Email returns the configured email of the server
   232  func (r *V2) Email() string {
   233  	return r.email
   234  }
   235  
   236  // Path returns the path where the registry write data
   237  func (r *V2) Path() string {
   238  	return filepath.Join(r.dir, "docker", "registry", "v2")
   239  }