From 871543a8c57212796c227cea4af00ae0eb4a3dca Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 20 Jun 2025 22:13:36 +0200 Subject: [PATCH 1/4] client: Client.ServiceUpdate: don't manually construct header value Signed-off-by: Sebastiaan van Stijn --- client/service_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/service_update.go b/client/service_update.go index 278e305d02..9c7c3e2218 100644 --- a/client/service_update.go +++ b/client/service_update.go @@ -71,7 +71,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version headers["version"] = []string{cli.version} } if options.EncodedRegistryAuth != "" { - headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth} + headers.Set(registry.AuthHeader, options.EncodedRegistryAuth) } resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) defer ensureReaderClosed(resp) From 79b4e1888346f6ea0659a00976ea84a2d570a4c9 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 20 Jun 2025 22:17:29 +0200 Subject: [PATCH 2/4] client: add staticAuth utility Add a small utility to create a "RequestAuthConfig" from a static value. Signed-off-by: Sebastiaan van Stijn --- client/auth.go | 14 ++++++++++++++ client/image_pull_test.go | 27 +++++++++++---------------- client/image_push_test.go | 13 ++++++------- 3 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 client/auth.go diff --git a/client/auth.go b/client/auth.go new file mode 100644 index 0000000000..7d858877b7 --- /dev/null +++ b/client/auth.go @@ -0,0 +1,14 @@ +package client + +import ( + "context" + + "github.com/docker/docker/api/types/registry" +) + +// staticAuth creates a privilegeFn from the given registryAuth. +func staticAuth(registryAuth string) registry.RequestAuthConfig { + return func(ctx context.Context) (string, error) { + return registryAuth, nil + } +} diff --git a/client/image_pull_test.go b/client/image_pull_test.go index 8ad23f5fcc..88b8b18e95 100644 --- a/client/image_pull_test.go +++ b/client/image_pull_test.go @@ -48,43 +48,41 @@ func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) { client := &Client{ client: newMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")), } - privilegeFunc := func(_ context.Context) (string, error) { - return "", errors.New("Error requesting privilege") - } _, err := client.ImagePull(context.Background(), "myimage", image.PullOptions{ - PrivilegeFunc: privilegeFunc, + PrivilegeFunc: func(_ context.Context) (string, error) { + return "", errors.New("error requesting privilege") + }, }) - assert.Check(t, is.Error(err, "Error requesting privilege")) + assert.Check(t, is.Error(err, "error requesting privilege")) } func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) { client := &Client{ client: newMockClient(errorMock(http.StatusUnauthorized, "Unauthorized error")), } - privilegeFunc := func(_ context.Context) (string, error) { - return "a-auth-header", nil - } _, err := client.ImagePull(context.Background(), "myimage", image.PullOptions{ - PrivilegeFunc: privilegeFunc, + PrivilegeFunc: staticAuth("a-auth-header"), }) assert.Check(t, is.ErrorType(err, cerrdefs.IsUnauthorized)) } func TestImagePullWithPrivilegedFuncNoError(t *testing.T) { const expectedURL = "/images/create" + const invalidAuth = "NotValid" + const validAuth = "IAmValid" client := &Client{ client: newMockClient(func(req *http.Request) (*http.Response, error) { if !strings.HasPrefix(req.URL.Path, expectedURL) { return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL) } auth := req.Header.Get(registry.AuthHeader) - if auth == "NotValid" { + if auth == invalidAuth { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: io.NopCloser(bytes.NewReader([]byte("Invalid credentials"))), }, nil } - if auth != "IAmValid" { + if auth != validAuth { return nil, fmt.Errorf("invalid auth header: expected %s, got %s", "IAmValid", auth) } query := req.URL.Query() @@ -102,12 +100,9 @@ func TestImagePullWithPrivilegedFuncNoError(t *testing.T) { }, nil }), } - privilegeFunc := func(_ context.Context) (string, error) { - return "IAmValid", nil - } resp, err := client.ImagePull(context.Background(), "myimage", image.PullOptions{ - RegistryAuth: "NotValid", - PrivilegeFunc: privilegeFunc, + RegistryAuth: invalidAuth, + PrivilegeFunc: staticAuth(validAuth), }) assert.NilError(t, err) body, err := io.ReadAll(resp) diff --git a/client/image_push_test.go b/client/image_push_test.go index c4fd95d344..16d17947ec 100644 --- a/client/image_push_test.go +++ b/client/image_push_test.go @@ -75,19 +75,21 @@ func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) func TestImagePushWithPrivilegedFuncNoError(t *testing.T) { const expectedURL = "/images/docker.io/myname/myimage/push" + const invalidAuth = "NotValid" + const validAuth = "IAmValid" client := &Client{ client: newMockClient(func(req *http.Request) (*http.Response, error) { if !strings.HasPrefix(req.URL.Path, expectedURL) { return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) } auth := req.Header.Get(registry.AuthHeader) - if auth == "NotValid" { + if auth == invalidAuth { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: io.NopCloser(bytes.NewReader([]byte("Invalid credentials"))), }, nil } - if auth != "IAmValid" { + if auth != validAuth { return nil, fmt.Errorf("invalid auth header: expected %s, got %s", "IAmValid", auth) } query := req.URL.Query() @@ -101,12 +103,9 @@ func TestImagePushWithPrivilegedFuncNoError(t *testing.T) { }, nil }), } - privilegeFunc := func(_ context.Context) (string, error) { - return "IAmValid", nil - } resp, err := client.ImagePush(context.Background(), "myname/myimage:tag", image.PushOptions{ - RegistryAuth: "NotValid", - PrivilegeFunc: privilegeFunc, + RegistryAuth: invalidAuth, + PrivilegeFunc: staticAuth(validAuth), }) assert.NilError(t, err) body, err := io.ReadAll(resp) From ca0afe91b975ff00773978400c339604c212f8e7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 23 Jun 2025 09:50:58 +0200 Subject: [PATCH 3/4] client: client.tryImageCreate: accept registry.RequestAuthConfig Directly accept a privilege-func, and set the auth-header optionally. Signed-off-by: Sebastiaan van Stijn --- client/image_create.go | 18 +++++++++++++----- client/image_pull.go | 8 ++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/client/image_create.go b/client/image_create.go index 1e044d7779..103163a5ce 100644 --- a/client/image_create.go +++ b/client/image_create.go @@ -26,15 +26,23 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } - resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) + resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if err != nil { return nil, err } return resp.Body, nil } -func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { - return cli.post(ctx, "/images/create", query, nil, http.Header{ - registry.AuthHeader: {registryAuth}, - }) +func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) { + hdr := http.Header{} + if resolveAuth != nil { + registryAuth, err := resolveAuth(ctx) + if err != nil { + return nil, err + } + if registryAuth != "" { + hdr.Set(registry.AuthHeader, registryAuth) + } + } + return cli.post(ctx, "/images/create", query, nil, hdr) } diff --git a/client/image_pull.go b/client/image_pull.go index ab7606b456..54aeed8a1f 100644 --- a/client/image_pull.go +++ b/client/image_pull.go @@ -34,13 +34,9 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.P query.Set("platform", strings.ToLower(options.Platform)) } - resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) + resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) - if privilegeErr != nil { - return nil, privilegeErr - } - resp, err = cli.tryImageCreate(ctx, query, newAuthHeader) + resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc) } if err != nil { return nil, err From 1c0d381f4e5c04e559a4c0a80655f8e219e6003f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 23 Jun 2025 09:54:03 +0200 Subject: [PATCH 4/4] client: client.tryImagePush: accept registry.RequestAuthConfig Directly accept a privilege-func, and set the auth-header optionally. Signed-off-by: Sebastiaan van Stijn --- client/image_push.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/client/image_push.go b/client/image_push.go index cbbe9a25d6..5d320b2af1 100644 --- a/client/image_push.go +++ b/client/image_push.go @@ -51,13 +51,9 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu query.Set("platform", string(pJson)) } - resp, err := cli.tryImagePush(ctx, ref.Name(), query, options.RegistryAuth) + resp, err := cli.tryImagePush(ctx, ref.Name(), query, staticAuth(options.RegistryAuth)) if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) - if privilegeErr != nil { - return nil, privilegeErr - } - resp, err = cli.tryImagePush(ctx, ref.Name(), query, newAuthHeader) + resp, err = cli.tryImagePush(ctx, ref.Name(), query, options.PrivilegeFunc) } if err != nil { return nil, err @@ -65,8 +61,16 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu return resp.Body, nil } -func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) { - return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{ - registry.AuthHeader: {registryAuth}, - }) +func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) { + hdr := http.Header{} + if resolveAuth != nil { + registryAuth, err := resolveAuth(ctx) + if err != nil { + return nil, err + } + if registryAuth != "" { + hdr.Set(registry.AuthHeader, registryAuth) + } + } + return cli.post(ctx, "/images/"+imageID+"/push", query, nil, hdr) }