github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/testutil/testregistry/testregistry_linux.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package testregistry
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"os"
    23  	"path/filepath"
    24  	"strconv"
    25  
    26  	"github.com/containerd/nerdctl/v2/pkg/testutil"
    27  	"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
    28  	"github.com/containerd/nerdctl/v2/pkg/testutil/testca"
    29  
    30  	"golang.org/x/crypto/bcrypt"
    31  	"gotest.tools/v3/assert"
    32  )
    33  
    34  type TestRegistry struct {
    35  	IP         net.IP
    36  	ListenIP   net.IP
    37  	ListenPort int
    38  	HostsDir   string // contains "<HostIP>:<ListenPort>/hosts.toml"
    39  	Cleanup    func()
    40  	Logs       func()
    41  }
    42  
    43  func NewPlainHTTP(base *testutil.Base, port int) *TestRegistry {
    44  	hostIP, err := nettestutil.NonLoopbackIPv4()
    45  	assert.NilError(base.T, err)
    46  	// listen on 0.0.0.0 to enable 127.0.0.1
    47  	listenIP := net.ParseIP("0.0.0.0")
    48  	listenPort := port
    49  	base.T.Logf("hostIP=%q, listenIP=%q, listenPort=%d", hostIP, listenIP, listenPort)
    50  
    51  	registryContainerName := "reg-" + testutil.Identifier(base.T)
    52  	cmd := base.Cmd("run",
    53  		"-d",
    54  		"-p", fmt.Sprintf("%s:%d:5000", listenIP, listenPort),
    55  		"--name", registryContainerName,
    56  		testutil.RegistryImage)
    57  	cmd.AssertOK()
    58  	if _, err = nettestutil.HTTPGet(fmt.Sprintf("http://%s:%d/v2", hostIP.String(), listenPort), 30, false); err != nil {
    59  		base.Cmd("rm", "-f", registryContainerName).Run()
    60  		base.T.Fatal(err)
    61  	}
    62  	return &TestRegistry{
    63  		IP:         hostIP,
    64  		ListenIP:   listenIP,
    65  		ListenPort: listenPort,
    66  		Cleanup:    func() { base.Cmd("rm", "-f", registryContainerName).AssertOK() },
    67  	}
    68  }
    69  
    70  func NewAuthWithHTTP(base *testutil.Base, user, pass string, listenPort int, authPort int) *TestRegistry {
    71  	name := testutil.Identifier(base.T)
    72  	hostIP, err := nettestutil.NonLoopbackIPv4()
    73  	assert.NilError(base.T, err)
    74  	// listen on 0.0.0.0 to enable 127.0.0.1
    75  	listenIP := net.ParseIP("0.0.0.0")
    76  	base.T.Logf("hostIP=%q, listenIP=%q, listenPort=%d, authPort=%d", hostIP, listenIP, listenPort, authPort)
    77  
    78  	ca := testca.New(base.T)
    79  	registryCert := ca.NewCert(hostIP.String())
    80  	authCert := ca.NewCert(hostIP.String())
    81  
    82  	// Prepare configuration file for authentication server
    83  	// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml
    84  	authConfigFile, err := os.CreateTemp("", "authconfig")
    85  	assert.NilError(base.T, err)
    86  	bpass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
    87  	assert.NilError(base.T, err)
    88  	authConfigFileName := authConfigFile.Name()
    89  	_, err = authConfigFile.Write([]byte(fmt.Sprintf(`
    90  server:
    91    addr: ":5100"
    92    certificate: "/auth/domain.crt"
    93    key: "/auth/domain.key"
    94  token:
    95    issuer: "Acme auth server"
    96    expiration: 900
    97  users:
    98    "%s":
    99      password: "%s"
   100  acl:
   101    - match: {account: "%s"}
   102      actions: ["*"]
   103  `, user, string(bpass), user)))
   104  	assert.NilError(base.T, err)
   105  
   106  	// Run authentication server
   107  	authContainerName := fmt.Sprintf("auth-%s-%d", name, authPort)
   108  	cmd := base.Cmd("run",
   109  		"-d",
   110  		"-p", fmt.Sprintf("%s:%d:5100", listenIP, authPort),
   111  		"--name", authContainerName,
   112  		"-v", authCert.CertPath+":/auth/domain.crt",
   113  		"-v", authCert.KeyPath+":/auth/domain.key",
   114  		"-v", authConfigFileName+":/config/auth_config.yml",
   115  		testutil.DockerAuthImage,
   116  		"/config/auth_config.yml")
   117  	cmd.AssertOK()
   118  
   119  	// Run docker_auth-enabled registry
   120  	// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml
   121  	registryContainerName := fmt.Sprintf("%s-%s-%d", "reg", name, listenPort)
   122  	cmd = base.Cmd("run",
   123  		"-d",
   124  		"-p", fmt.Sprintf("%s:%d:5000", listenIP, listenPort),
   125  		"--name", registryContainerName,
   126  		"--env", "REGISTRY_AUTH=token",
   127  		"--env", "REGISTRY_AUTH_TOKEN_REALM="+fmt.Sprintf("https://%s:%d/auth", hostIP.String(), authPort),
   128  		"--env", "REGISTRY_AUTH_TOKEN_SERVICE=Docker registry",
   129  		"--env", "REGISTRY_AUTH_TOKEN_ISSUER=Acme auth server",
   130  		"--env", "REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/auth/domain.crt",
   131  		// rootcertbundle is not CA cert: https://github.com/distribution/distribution/issues/1143
   132  		"-v", authCert.CertPath+":/auth/domain.crt",
   133  		testutil.RegistryImage)
   134  	cmd.AssertOK()
   135  	joined := net.JoinHostPort(hostIP.String(), strconv.Itoa(listenPort))
   136  	if _, err = nettestutil.HTTPGet(fmt.Sprintf("http://%s/v2", joined), 30, true); err != nil {
   137  		base.Cmd("rm", "-f", registryContainerName).Run()
   138  		base.T.Fatal(err)
   139  	}
   140  	hostsDir, err := os.MkdirTemp(base.T.TempDir(), "certs.d")
   141  	assert.NilError(base.T, err)
   142  	hostsSubDir := filepath.Join(hostsDir, joined)
   143  	err = os.MkdirAll(hostsSubDir, 0700)
   144  	assert.NilError(base.T, err)
   145  	hostsTOMLPath := filepath.Join(hostsSubDir, "hosts.toml")
   146  	// See https://github.com/containerd/containerd/blob/main/docs/hosts.md
   147  	hostsTOML := fmt.Sprintf(`
   148  server = "https://%s"
   149  [host."https://%s"]
   150    ca = %q
   151  		`, joined, joined, ca.CertPath)
   152  	base.T.Logf("Writing %q: %q", hostsTOMLPath, hostsTOML)
   153  	err = os.WriteFile(hostsTOMLPath, []byte(hostsTOML), 0700)
   154  	assert.NilError(base.T, err)
   155  	return &TestRegistry{
   156  		IP:         hostIP,
   157  		ListenIP:   listenIP,
   158  		ListenPort: listenPort,
   159  		HostsDir:   hostsDir,
   160  		Cleanup: func() {
   161  			base.Cmd("rm", "-f", registryContainerName).AssertOK()
   162  			base.Cmd("rm", "-f", authContainerName).AssertOK()
   163  			assert.NilError(base.T, registryCert.Close())
   164  			assert.NilError(base.T, authCert.Close())
   165  			assert.NilError(base.T, authConfigFile.Close())
   166  			os.Remove(authConfigFileName)
   167  		},
   168  		Logs: func() {
   169  			base.T.Logf("%s: %q", registryContainerName, base.Cmd("logs", registryContainerName).Run().String())
   170  			base.T.Logf("%s: %q", authContainerName, base.Cmd("logs", authContainerName).Run().String())
   171  		},
   172  	}
   173  }
   174  
   175  func NewHTTPS(base *testutil.Base, user, pass string) *TestRegistry {
   176  	name := testutil.Identifier(base.T)
   177  	hostIP, err := nettestutil.NonLoopbackIPv4()
   178  	assert.NilError(base.T, err)
   179  	// listen on 0.0.0.0 to enable 127.0.0.1
   180  	listenIP := net.ParseIP("0.0.0.0")
   181  	const listenPort = 5000 // TODO: choose random empty port
   182  	const authPort = 5100   // TODO: choose random empty port
   183  	base.T.Logf("hostIP=%q, listenIP=%q, listenPort=%d, authPort=%d", hostIP, listenIP, listenPort, authPort)
   184  
   185  	ca := testca.New(base.T)
   186  	registryCert := ca.NewCert(hostIP.String())
   187  	authCert := ca.NewCert(hostIP.String())
   188  
   189  	// Prepare configuration file for authentication server
   190  	// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml
   191  	authConfigFile, err := os.CreateTemp("", "authconfig")
   192  	assert.NilError(base.T, err)
   193  	bpass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
   194  	assert.NilError(base.T, err)
   195  	authConfigFileName := authConfigFile.Name()
   196  	_, err = authConfigFile.Write([]byte(fmt.Sprintf(`
   197  server:
   198    addr: ":5100"
   199    certificate: "/auth/domain.crt"
   200    key: "/auth/domain.key"
   201  token:
   202    issuer: "Acme auth server"
   203    expiration: 900
   204  users:
   205    "%s":
   206      password: "%s"
   207  acl:
   208    - match: {account: "%s"}
   209      actions: ["*"]
   210  `, user, string(bpass), user)))
   211  	assert.NilError(base.T, err)
   212  
   213  	// Run authentication server
   214  	authContainerName := "auth-" + name
   215  	cmd := base.Cmd("run",
   216  		"-d",
   217  		"-p", fmt.Sprintf("%s:%d:5100", listenIP, authPort),
   218  		"--name", authContainerName,
   219  		"-v", authCert.CertPath+":/auth/domain.crt",
   220  		"-v", authCert.KeyPath+":/auth/domain.key",
   221  		"-v", authConfigFileName+":/config/auth_config.yml",
   222  		testutil.DockerAuthImage,
   223  		"/config/auth_config.yml")
   224  	cmd.AssertOK()
   225  
   226  	// Run docker_auth-enabled registry
   227  	// Details: https://github.com/cesanta/docker_auth/blob/1.7.1/examples/simple.yml
   228  	registryContainerName := "reg-" + name
   229  	cmd = base.Cmd("run",
   230  		"-d",
   231  		"-p", fmt.Sprintf("%s:%d:5000", listenIP, listenPort),
   232  		"--name", registryContainerName,
   233  		"--env", "REGISTRY_AUTH=token",
   234  		"--env", "REGISTRY_AUTH_TOKEN_REALM="+fmt.Sprintf("https://%s:%d/auth", hostIP.String(), authPort),
   235  		"--env", "REGISTRY_AUTH_TOKEN_SERVICE=Docker registry",
   236  		"--env", "REGISTRY_AUTH_TOKEN_ISSUER=Acme auth server",
   237  		"--env", "REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/auth/domain.crt",
   238  		"--env", "REGISTRY_HTTP_TLS_CERTIFICATE=/registry/domain.crt",
   239  		"--env", "REGISTRY_HTTP_TLS_KEY=/registry/domain.key",
   240  		// rootcertbundle is not CA cert: https://github.com/distribution/distribution/issues/1143
   241  		"-v", authCert.CertPath+":/auth/domain.crt",
   242  		"-v", registryCert.CertPath+":/registry/domain.crt",
   243  		"-v", registryCert.KeyPath+":/registry/domain.key",
   244  		testutil.RegistryImage)
   245  	cmd.AssertOK()
   246  	joined := net.JoinHostPort(hostIP.String(), strconv.Itoa(listenPort))
   247  	if _, err = nettestutil.HTTPGet(fmt.Sprintf("https://%s/v2", joined), 30, true); err != nil {
   248  		base.Cmd("rm", "-f", registryContainerName).Run()
   249  		base.T.Fatal(err)
   250  	}
   251  	hostsDir, err := os.MkdirTemp(base.T.TempDir(), "certs.d")
   252  	assert.NilError(base.T, err)
   253  	hostsSubDir := filepath.Join(hostsDir, joined)
   254  	err = os.MkdirAll(hostsSubDir, 0700)
   255  	assert.NilError(base.T, err)
   256  	hostsTOMLPath := filepath.Join(hostsSubDir, "hosts.toml")
   257  	// See https://github.com/containerd/containerd/blob/main/docs/hosts.md
   258  	hostsTOML := fmt.Sprintf(`
   259  server = "https://%s"
   260  [host."https://%s"]
   261    ca = %q
   262  		`, joined, joined, ca.CertPath)
   263  	base.T.Logf("Writing %q: %q", hostsTOMLPath, hostsTOML)
   264  	err = os.WriteFile(hostsTOMLPath, []byte(hostsTOML), 0700)
   265  	assert.NilError(base.T, err)
   266  	return &TestRegistry{
   267  		IP:         hostIP,
   268  		ListenIP:   listenIP,
   269  		ListenPort: listenPort,
   270  		HostsDir:   hostsDir,
   271  		Cleanup: func() {
   272  			base.Cmd("rm", "-f", registryContainerName).AssertOK()
   273  			base.Cmd("rm", "-f", authContainerName).AssertOK()
   274  			assert.NilError(base.T, registryCert.Close())
   275  			assert.NilError(base.T, authCert.Close())
   276  			assert.NilError(base.T, authConfigFile.Close())
   277  			os.Remove(authConfigFileName)
   278  		},
   279  		Logs: func() {
   280  			base.T.Logf("%s: %q", registryContainerName, base.Cmd("logs", registryContainerName).Run().String())
   281  			base.T.Logf("%s: %q", authContainerName, base.Cmd("logs", authContainerName).Run().String())
   282  		},
   283  	}
   284  }