Skip to content

Commit e557d25

Browse files
authored
fix: parse image auth config correctly (#103)
1 parent 569d5de commit e557d25

File tree

6 files changed

+270
-50
lines changed

6 files changed

+270
-50
lines changed

cli/docker.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,14 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker
397397
if err != nil {
398398
return xerrors.Errorf("set oom score: %w", err)
399399
}
400+
ref, err := name.NewTag(flags.innerImage)
401+
if err != nil {
402+
return xerrors.Errorf("parse ref: %w", err)
403+
}
400404

401405
var dockerAuth dockerutil.AuthConfig
402406
if flags.imagePullSecret != "" {
403-
dockerAuth, err = dockerutil.ParseAuthConfig(flags.imagePullSecret)
407+
dockerAuth, err = dockerutil.AuthConfigFromString(flags.imagePullSecret, ref.RegistryStr())
404408
if err != nil {
405409
return xerrors.Errorf("parse auth config: %w", err)
406410
}
@@ -409,10 +413,6 @@ func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.Docker
409413
log.Info(ctx, "checking for docker config file", slog.F("path", flags.dockerConfig))
410414
if _, err := fs.Stat(flags.dockerConfig); err == nil {
411415
log.Info(ctx, "detected file", slog.F("image", flags.innerImage))
412-
ref, err := name.NewTag(flags.innerImage)
413-
if err != nil {
414-
return xerrors.Errorf("parse ref: %w", err)
415-
}
416416
dockerAuth, err = dockerutil.AuthConfigFromPath(flags.dockerConfig, ref.RegistryStr())
417417
if err != nil && !xerrors.Is(err, os.ErrNotExist) {
418418
return xerrors.Errorf("auth config from file: %w", err)

cli/docker_test.go

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,98 @@ func TestDocker(t *testing.T) {
6363
execer.AssertCommandsCalled(t)
6464
})
6565

66+
t.Run("Images", func(t *testing.T) {
67+
t.Parallel()
68+
69+
type testcase struct {
70+
name string
71+
image string
72+
success bool
73+
}
74+
75+
testcases := []testcase{
76+
{
77+
name: "Repository",
78+
image: "ubuntu",
79+
success: true,
80+
},
81+
{
82+
name: "RepositoryPath",
83+
image: "ubuntu/ubuntu",
84+
success: true,
85+
},
86+
87+
{
88+
name: "RepositoryLatest",
89+
image: "ubuntu:latest",
90+
success: true,
91+
},
92+
{
93+
name: "RepositoryTag",
94+
image: "ubuntu:24.04",
95+
success: true,
96+
},
97+
{
98+
name: "RepositoryPathTag",
99+
image: "ubuntu/ubuntu:18.04",
100+
success: true,
101+
},
102+
{
103+
name: "RegistryRepository",
104+
image: "gcr.io/ubuntu",
105+
success: true,
106+
},
107+
{
108+
name: "RegistryRepositoryTag",
109+
image: "gcr.io/ubuntu:24.04",
110+
success: true,
111+
},
112+
}
113+
114+
for _, tc := range testcases {
115+
tc := tc
116+
t.Run(tc.name, func(t *testing.T) {
117+
t.Parallel()
118+
119+
ctx, cmd := clitest.New(t, "docker",
120+
"--image="+tc.image,
121+
"--username=root",
122+
"--agent-token=hi",
123+
)
124+
125+
called := make(chan struct{})
126+
execer := clitest.Execer(ctx)
127+
client := clitest.DockerClient(t, ctx)
128+
execer.AddCommands(&xunixfake.FakeCmd{
129+
FakeCmd: &testingexec.FakeCmd{
130+
Argv: []string{
131+
"sysbox-mgr",
132+
},
133+
},
134+
WaitFn: func() error { close(called); select {} }, //nolint:revive
135+
})
136+
137+
var created bool
138+
client.ContainerCreateFn = func(_ context.Context, conf *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *v1.Platform, _ string) (container.CreateResponse, error) {
139+
created = true
140+
require.Equal(t, tc.image, conf.Image)
141+
return container.CreateResponse{}, nil
142+
}
143+
144+
err := cmd.ExecuteContext(ctx)
145+
if !tc.success {
146+
require.Error(t, err)
147+
return
148+
}
149+
150+
<-called
151+
require.NoError(t, err)
152+
require.True(t, created, "container create fn not called")
153+
execer.AssertCommandsCalled(t)
154+
})
155+
}
156+
})
157+
66158
// Test that dockerd is configured correctly.
67159
t.Run("DockerdConfigured", func(t *testing.T) {
68160
t.Parallel()
@@ -384,13 +476,13 @@ func TestDocker(t *testing.T) {
384476
t.Parallel()
385477

386478
ctx, cmd := clitest.New(t, "docker",
387-
"--image=ubuntu",
479+
"--image=us.gcr.io/ubuntu",
388480
"--username=root",
389481
"--agent-token=hi",
390482
fmt.Sprintf("--image-secret=%s", rawDockerAuth),
391483
)
392484

393-
raw := []byte(`{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }","auth":"X2pzb25fa2V5OnsKCgkidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAoJInByb2plY3RfaWQiOiAic29tZS10ZXN0IiwKCSJwcml2YXRlX2tleV9pZCI6ICJibGFoYmxhaCIsCgkicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCm15a2V5LS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQoiLAoJImNsaWVudF9lbWFpbCI6ICJ0ZXN0QHRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAoJImNsaWVudF9pZCI6ICIxMjMiLAoJImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKCSJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iLAoJImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAoJImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo=","email":"test@test.iam.gserviceaccount.com"}`)
485+
raw := []byte(`{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }"}`)
394486
authB64 := base64.URLEncoding.EncodeToString(raw)
395487

396488
client := clitest.DockerClient(t, ctx)

dockerutil/client.go

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,29 @@ func (a AuthConfig) Base64() (string, error) {
4848
return base64.URLEncoding.EncodeToString(authStr), nil
4949
}
5050

51-
func AuthConfigFromPath(path string, registry string) (AuthConfig, error) {
51+
func AuthConfigFromPath(path string, reg string) (AuthConfig, error) {
5252
var config dockercfg.Config
5353
err := dockercfg.FromFile(path, &config)
5454
if err != nil {
5555
return AuthConfig{}, xerrors.Errorf("load config: %w", err)
5656
}
5757

58-
hostname := dockercfg.ResolveRegistryHost(registry)
58+
return parseConfig(config, reg)
59+
}
5960

60-
if config, ok := config.AuthConfigs[registry]; ok {
61-
return AuthConfig(config), nil
61+
func AuthConfigFromString(raw string, reg string) (AuthConfig, error) {
62+
var cfg dockercfg.Config
63+
err := json.Unmarshal([]byte(raw), &cfg)
64+
if err != nil {
65+
return AuthConfig{}, xerrors.Errorf("parse config: %w", err)
6266
}
67+
return parseConfig(cfg, reg)
68+
}
6369

64-
username, secret, err := config.GetRegistryCredentials(hostname)
70+
func parseConfig(cfg dockercfg.Config, registry string) (AuthConfig, error) {
71+
hostname := dockercfg.ResolveRegistryHost(registry)
72+
73+
username, secret, err := cfg.GetRegistryCredentials(hostname)
6574
if err != nil {
6675
return AuthConfig{}, xerrors.Errorf("get credentials from helper: %w", err)
6776
}
@@ -80,22 +89,3 @@ func AuthConfigFromPath(path string, registry string) (AuthConfig, error) {
8089

8190
return AuthConfig{}, xerrors.Errorf("no auth config found for registry %s: %w", registry, os.ErrNotExist)
8291
}
83-
84-
func ParseAuthConfig(raw string) (AuthConfig, error) {
85-
type dockerConfig struct {
86-
AuthConfigs map[string]dockertypes.AuthConfig `json:"auths"`
87-
}
88-
89-
var conf dockerConfig
90-
if err := json.Unmarshal([]byte(raw), &conf); err != nil {
91-
return AuthConfig{}, xerrors.Errorf("parse docker auth config secret: %w", err)
92-
}
93-
if len(conf.AuthConfigs) != 1 {
94-
return AuthConfig{}, xerrors.Errorf("number of image pull auth configs not equal to 1 (%d)", len(conf.AuthConfigs))
95-
}
96-
for _, regConfig := range conf.AuthConfigs {
97-
return AuthConfig(regConfig), nil
98-
}
99-
100-
return AuthConfig{}, xerrors.New("no auth configs parsed.")
101-
}

dockerutil/client_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dockerutil_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/coder/envbox/dockerutil"
9+
)
10+
11+
func TestAuthConfigFromString(t *testing.T) {
12+
t.Parallel()
13+
14+
t.Run("Auth", func(t *testing.T) {
15+
t.Parallel()
16+
17+
//nolint:gosec // this is a test
18+
creds := `{ "auths": { "docker.registry.test": { "auth": "Zm9vQGJhci5jb206YWJjMTIz" } } }`
19+
expectedUsername := "foo@bar.com"
20+
expectedPassword := "abc123"
21+
22+
cfg, err := dockerutil.AuthConfigFromString(creds, "docker.registry.test")
23+
require.NoError(t, err)
24+
require.Equal(t, expectedUsername, cfg.Username)
25+
require.Equal(t, expectedPassword, cfg.Password)
26+
})
27+
28+
t.Run("UsernamePassword", func(t *testing.T) {
29+
t.Parallel()
30+
31+
//nolint:gosec // this is a test
32+
creds := `{ "auths": { "docker.registry.test": { "username": "foobarbaz", "password": "123abc" } } }`
33+
expectedUsername := "foobarbaz"
34+
expectedPassword := "123abc"
35+
36+
cfg, err := dockerutil.AuthConfigFromString(creds, "docker.registry.test")
37+
require.NoError(t, err)
38+
require.Equal(t, expectedUsername, cfg.Username)
39+
require.Equal(t, expectedPassword, cfg.Password)
40+
})
41+
}

integration/docker_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package integration_test
55

66
import (
7+
"encoding/json"
78
"fmt"
89
"net"
910
"os"
@@ -17,6 +18,7 @@ import (
1718
"github.com/stretchr/testify/require"
1819

1920
"github.com/coder/envbox/cli"
21+
"github.com/coder/envbox/dockerutil"
2022
"github.com/coder/envbox/integration/integrationtest"
2123
)
2224

@@ -318,19 +320,39 @@ func TestDocker(t *testing.T) {
318320
regKeyPath := filepath.Join(certDir, "registry_key.pem")
319321
integrationtest.WriteCertificate(t, dockerCert, regCertPath, regKeyPath)
320322

323+
username := "coder"
324+
password := "helloworld"
325+
321326
// Start up the docker registry and push an image
322327
// to it that we can reference.
323328
image := integrationtest.RunLocalDockerRegistry(t, pool, integrationtest.RegistryConfig{
324329
HostCertPath: regCertPath,
325330
HostKeyPath: regKeyPath,
326331
Image: integrationtest.UbuntuImage,
327332
TLSPort: strconv.Itoa(registryAddr.Port),
333+
PasswordDir: dir,
334+
Username: username,
335+
Password: password,
328336
})
329337

338+
type authConfigs struct {
339+
Auths map[string]dockerutil.AuthConfig `json:"auths"`
340+
}
341+
342+
auths := authConfigs{
343+
Auths: map[string]dockerutil.AuthConfig{
344+
image.Registry(): {Username: username, Password: password},
345+
},
346+
}
347+
348+
authStr, err := json.Marshal(auths)
349+
require.NoError(t, err)
350+
330351
envs := []string{
331352
integrationtest.EnvVar(cli.EnvAgentToken, "faketoken"),
332353
integrationtest.EnvVar(cli.EnvAgentURL, fmt.Sprintf("https://%s:%d", "host.docker.internal", coderAddr.Port)),
333354
integrationtest.EnvVar(cli.EnvExtraCertsPath, "/tmp/certs"),
355+
integrationtest.EnvVar(cli.EnvBoxPullImageSecretEnvVar, string(authStr)),
334356
}
335357

336358
// Run the envbox container.

0 commit comments

Comments
 (0)