Skip to content

Commit 81e3a3d

Browse files
authored
fix: provide backwards compatibility for image secrets without a hostname (#137)
1 parent a70d5a3 commit 81e3a3d

File tree

2 files changed

+125
-8
lines changed

2 files changed

+125
-8
lines changed

dockerutil/client.go

+31-8
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,39 @@ func parseConfig(cfg dockercfg.Config, reg string) (AuthConfig, error) {
8282
}
8383

8484
if secret != "" {
85-
if username == "" {
86-
return AuthConfig{
87-
IdentityToken: secret,
88-
}, nil
85+
return toAuthConfig(username, secret), nil
86+
}
87+
88+
// This to preserve backwards compatibility with older variants of envbox
89+
// that didn't mandate a hostname key in the config file. We just take the
90+
// first valid auth config we find and use that.
91+
for _, auth := range cfg.AuthConfigs {
92+
if auth.IdentityToken != "" {
93+
return toAuthConfig("", auth.IdentityToken), nil
8994
}
90-
return AuthConfig{
91-
Username: username,
92-
Password: secret,
93-
}, nil
95+
96+
if auth.Username != "" && auth.Password != "" {
97+
return toAuthConfig(auth.Username, auth.Password), nil
98+
}
99+
100+
username, secret, err = dockercfg.DecodeBase64Auth(auth)
101+
if err == nil && secret != "" {
102+
return toAuthConfig(username, secret), nil
103+
}
104+
// Invalid auth config, skip it.
94105
}
95106

96107
return AuthConfig{}, xerrors.Errorf("no auth config found for registry %s: %w", reg, os.ErrNotExist)
97108
}
109+
110+
func toAuthConfig(username, secret string) AuthConfig {
111+
if username == "" {
112+
return AuthConfig{
113+
IdentityToken: secret,
114+
}
115+
}
116+
return AuthConfig{
117+
Username: username,
118+
Password: secret,
119+
}
120+
}

integration/docker_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,100 @@ func TestDocker(t *testing.T) {
393393
require.True(t, recorder.ContainsLog("Envbox startup complete!"))
394394
})
395395

396+
// This test provides backwards compatibility for older variants of envbox that may specify a
397+
// Docker Auth config without a hostname key.
398+
t.Run("NoHostnameAuthConfig", func(t *testing.T) {
399+
t.Parallel()
400+
401+
var (
402+
dir = integrationtest.TmpDir(t)
403+
binds = integrationtest.DefaultBinds(t, dir)
404+
)
405+
406+
pool, err := dockertest.NewPool("")
407+
require.NoError(t, err)
408+
409+
// Create some listeners for the Docker and Coder
410+
// services we'll be running with self signed certs.
411+
bridgeIP := integrationtest.DockerBridgeIP(t)
412+
coderListener, err := net.Listen("tcp", fmt.Sprintf("%s:0", bridgeIP))
413+
require.NoError(t, err)
414+
defer coderListener.Close()
415+
coderAddr := tcpAddr(t, coderListener)
416+
417+
registryListener, err := net.Listen("tcp", fmt.Sprintf("%s:0", bridgeIP))
418+
require.NoError(t, err)
419+
err = registryListener.Close()
420+
require.NoError(t, err)
421+
registryAddr := tcpAddr(t, registryListener)
422+
423+
coderCert := integrationtest.GenerateTLSCertificate(t, "host.docker.internal", coderAddr.IP.String())
424+
dockerCert := integrationtest.GenerateTLSCertificate(t, "host.docker.internal", registryAddr.IP.String())
425+
426+
// Startup our fake Coder "control-plane".
427+
recorder := integrationtest.FakeBuildLogRecorder(t, coderListener, coderCert)
428+
429+
certDir := integrationtest.MkdirAll(t, dir, "certs")
430+
431+
// Write the Coder cert disk.
432+
coderCertPath := filepath.Join(certDir, "coder_cert.pem")
433+
coderKeyPath := filepath.Join(certDir, "coder_key.pem")
434+
integrationtest.WriteCertificate(t, coderCert, coderCertPath, coderKeyPath)
435+
coderCertMount := integrationtest.BindMount(certDir, "/tmp/certs", false)
436+
437+
// Write the Registry cert to disk.
438+
regCertPath := filepath.Join(certDir, "registry_cert.crt")
439+
regKeyPath := filepath.Join(certDir, "registry_key.pem")
440+
integrationtest.WriteCertificate(t, dockerCert, regCertPath, regKeyPath)
441+
442+
username := "coder"
443+
password := "helloworld"
444+
445+
// Start up the docker registry and push an image
446+
// to it that we can reference.
447+
image := integrationtest.RunLocalDockerRegistry(t, pool, integrationtest.RegistryConfig{
448+
HostCertPath: regCertPath,
449+
HostKeyPath: regKeyPath,
450+
Image: integrationtest.UbuntuImage,
451+
TLSPort: strconv.Itoa(registryAddr.Port),
452+
PasswordDir: dir,
453+
Username: username,
454+
Password: password,
455+
})
456+
457+
type authConfigs struct {
458+
Auths map[string]dockerutil.AuthConfig `json:"auths"`
459+
}
460+
461+
auths := authConfigs{
462+
Auths: map[string]dockerutil.AuthConfig{
463+
"": {Username: username, Password: password},
464+
},
465+
}
466+
467+
authStr, err := json.Marshal(auths)
468+
require.NoError(t, err)
469+
470+
envs := []string{
471+
integrationtest.EnvVar(cli.EnvAgentToken, "faketoken"),
472+
integrationtest.EnvVar(cli.EnvAgentURL, fmt.Sprintf("https://%s:%d", "host.docker.internal", coderAddr.Port)),
473+
integrationtest.EnvVar(cli.EnvExtraCertsPath, "/tmp/certs"),
474+
integrationtest.EnvVar(cli.EnvBoxPullImageSecretEnvVar, string(authStr)),
475+
}
476+
477+
// Run the envbox container.
478+
_ = integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{
479+
Image: image.String(),
480+
Username: "coder",
481+
Envs: envs,
482+
OuterMounts: append(binds, coderCertMount),
483+
})
484+
485+
// This indicates we've made it all the way to end
486+
// of the logs we attempt to push.
487+
require.True(t, recorder.ContainsLog("Envbox startup complete!"))
488+
})
489+
396490
// This tests the inverse of SelfSignedCerts. We assert that
397491
// the container fails to startup since we don't have a valid
398492
// cert for the registry. It mainly tests that we aren't

0 commit comments

Comments
 (0)