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 }