This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new df119580f61b CAMEL-26685: camel-docker - imagepull/imagepush use the 
endpoint auth only for its own registry (#29314)
df119580f61b is described below

commit df119580f61bed2dfc84413178a098f00d12eee6
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 6 10:49:53 2026 +0100

    CAMEL-26685: camel-docker - imagepull/imagepush use the endpoint auth only 
for its own registry (#29314)
    
    The imagepull and imagepush operations added the registry authentication of
    the endpoint to every pull and push, whatever registry the image reference
    names, and failed with a NullPointerException ("Configured username is 
null.")
    when the endpoint had no username.
    
    They now add the authentication only when a username is configured and the
    registry host and port of the image (Docker Hub for an image without a
    registry host) match those of serverAddress. Otherwise the pull or push runs
    without authentication.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/docs/docker-component.adoc       |  9 +++
 .../src/main/docs/docker-component.adoc            |  9 +++
 .../camel/component/docker/DockerHelper.java       | 59 ++++++++++++++++++++
 .../docker/producer/AsyncDockerProducer.java       | 35 +++++++++++-
 .../camel/component/docker/DockerHelperTest.java   | 34 +++++++++++
 .../docker/headers/BaseDockerHeaderTest.java       |  9 +++
 .../docker/headers/PullImageCmdHeaderTest.java     | 16 ++++++
 ...Test.java => PullImageCmdRegistryAuthTest.java} | 65 +++++++++++++++++-----
 .../docker/headers/PushImageCmdHeaderTest.java     | 38 +++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 11 ++++
 10 files changed, 268 insertions(+), 17 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docker-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docker-component.adoc
index 3fb41fd24d6d..83eab4153dd2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docker-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docker-component.adoc
@@ -88,6 +88,15 @@ The other headers, such as `CamelDockerContainerId`, still 
take precedence over
 messages come from an untrusted producer through a consumer that does not 
filter out `Camel*` headers, remove these
 headers with `removeHeaders("CamelDocker*")` before the `docker` endpoint.
 
+=== Registry authentication
+
+The `imagepull` and `imagepush` operations send the `username`, `password` and 
`email` of the endpoint only for an
+image in the registry of `serverAddress`: the registry host and port of the 
image reference must match those of
+`serverAddress`. An image reference without a registry host, such as `ubuntu` 
or `team/app`, is on Docker Hub, which
+matches the default `serverAddress` (`https://index.docker.io/v1/`). An image 
in another registry is pulled or pushed
+without authentication, and so is every image when the endpoint has no 
`username`. To pull or push with
+authentication from several registries, use one endpoint per registry.
+
 === Streaming operations
 
 The operations `imagebuild`, `imagepull`, `imagepush`, `containerattach`, 
`containerlog`, `containerwait` and
diff --git a/components/camel-docker/src/main/docs/docker-component.adoc 
b/components/camel-docker/src/main/docs/docker-component.adoc
index 3fb41fd24d6d..83eab4153dd2 100644
--- a/components/camel-docker/src/main/docs/docker-component.adoc
+++ b/components/camel-docker/src/main/docs/docker-component.adoc
@@ -88,6 +88,15 @@ The other headers, such as `CamelDockerContainerId`, still 
take precedence over
 messages come from an untrusted producer through a consumer that does not 
filter out `Camel*` headers, remove these
 headers with `removeHeaders("CamelDocker*")` before the `docker` endpoint.
 
+=== Registry authentication
+
+The `imagepull` and `imagepush` operations send the `username`, `password` and 
`email` of the endpoint only for an
+image in the registry of `serverAddress`: the registry host and port of the 
image reference must match those of
+`serverAddress`. An image reference without a registry host, such as `ubuntu` 
or `team/app`, is on Docker Hub, which
+matches the default `serverAddress` (`https://index.docker.io/v1/`). An image 
in another registry is pulled or pushed
+without authentication, and so is every image when the endpoint has no 
`username`. To pull or push with
+authentication from several registries, use one endpoint per registry.
+
 === Streaming operations
 
 The operations `imagebuild`, `imagepull`, `imagepush`, `containerattach`, 
`containerlog`, `containerwait` and
diff --git 
a/components/camel-docker/src/main/java/org/apache/camel/component/docker/DockerHelper.java
 
b/components/camel-docker/src/main/java/org/apache/camel/component/docker/DockerHelper.java
index 634ceda8634c..2d66a7d358d6 100644
--- 
a/components/camel-docker/src/main/java/org/apache/camel/component/docker/DockerHelper.java
+++ 
b/components/camel-docker/src/main/java/org/apache/camel/component/docker/DockerHelper.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.docker;
 
 import java.lang.reflect.Array;
+import java.util.Locale;
 
 import org.apache.camel.Message;
 import org.apache.camel.util.StringHelper;
@@ -28,6 +29,7 @@ import org.apache.commons.lang3.BooleanUtils;
 public final class DockerHelper {
 
     private static final String STRING_DELIMITER = ";";
+    private static final String DOCKER_HUB = "docker.io";
 
     private DockerHelper() {
         // Helper class
@@ -148,6 +150,63 @@ public final class DockerHelper {
         return null;
     }
 
+    /**
+     * Returns the registry host (with the port, if any) of an image 
reference, following the rules of Docker: the first
+     * path component is a registry when it contains a dot or a colon, is 
<tt>localhost</tt> or has upper case letters;
+     * otherwise the image is on Docker Hub. The names of Docker Hub are 
returned as <tt>docker.io</tt>, and the host is
+     * in lower case.
+     *
+     * @param  image the image reference, such as <tt>ubuntu:24.04</tt> or 
<tt>myregistry:5000/team/app@sha256:...</tt>
+     * @return       the registry host, or <tt>null</tt> if the image is empty
+     */
+    public static String getImageRegistryHost(String image) {
+        if (image == null || image.isBlank()) {
+            return null;
+        }
+        int slash = image.indexOf('/');
+        if (slash == -1) {
+            return DOCKER_HUB;
+        }
+        String first = image.substring(0, slash);
+        if (first.indexOf('.') == -1 && first.indexOf(':') == -1 && 
!"localhost".equals(first)
+                && first.equals(first.toLowerCase(Locale.ROOT))) {
+            return DOCKER_HUB;
+        }
+        return normalizeRegistryHost(first);
+    }
+
+    /**
+     * Returns the registry host (with the port, if any) of a registry server 
address, such as
+     * <tt>https://index.docker.io/v1/</tt> or <tt>myregistry:5000</tt>. The 
scheme and the path are ignored, the names
+     * of Docker Hub are returned as <tt>docker.io</tt>, and the host is in 
lower case.
+     *
+     * @param  serverAddress the server address of the registry
+     * @return               the registry host, or <tt>null</tt> if the server 
address is empty
+     */
+    public static String getServerAddressRegistryHost(String serverAddress) {
+        if (serverAddress == null || serverAddress.isBlank()) {
+            return null;
+        }
+        String host = serverAddress.trim();
+        int scheme = host.indexOf("://");
+        if (scheme != -1) {
+            host = host.substring(scheme + 3);
+        }
+        int slash = host.indexOf('/');
+        if (slash != -1) {
+            host = host.substring(0, slash);
+        }
+        return host.isEmpty() ? null : normalizeRegistryHost(host);
+    }
+
+    private static String normalizeRegistryHost(String host) {
+        String answer = host.toLowerCase(Locale.ROOT);
+        if ("index.docker.io".equals(answer) || 
"registry-1.docker.io".equals(answer)) {
+            return DOCKER_HUB;
+        }
+        return answer;
+    }
+
     /**
      * @param  headerName name of the header
      * @param  message    the Camel message
diff --git 
a/components/camel-docker/src/main/java/org/apache/camel/component/docker/producer/AsyncDockerProducer.java
 
b/components/camel-docker/src/main/java/org/apache/camel/component/docker/producer/AsyncDockerProducer.java
index ce8986214dee..803af6353afb 100644
--- 
a/components/camel-docker/src/main/java/org/apache/camel/component/docker/producer/AsyncDockerProducer.java
+++ 
b/components/camel-docker/src/main/java/org/apache/camel/component/docker/producer/AsyncDockerProducer.java
@@ -330,7 +330,7 @@ public class AsyncDockerProducer extends 
DefaultAsyncProducer {
             pullImageCmd.withTag(tag);
         }
 
-        AuthConfig authConfig = client.authConfig();
+        AuthConfig authConfig = getRegistryAuthConfig(client, message, 
repository);
 
         if (authConfig != null) {
             pullImageCmd.withAuthConfig(authConfig);
@@ -359,7 +359,7 @@ public class AsyncDockerProducer extends 
DefaultAsyncProducer {
             pushImageCmd.withTag(tag);
         }
 
-        AuthConfig authConfig = client.authConfig();
+        AuthConfig authConfig = getRegistryAuthConfig(client, message, name);
 
         if (authConfig != null) {
             pushImageCmd.withAuthConfig(authConfig);
@@ -369,6 +369,37 @@ public class AsyncDockerProducer extends 
DefaultAsyncProducer {
 
     }
 
+    /**
+     * The registry authentication of the endpoint for a pull or push of the 
given image. It applies only when a
+     * username is configured and the image is in the registry of the server 
address of the endpoint; otherwise the
+     * request is sent without authentication.
+     */
+    private AuthConfig getRegistryAuthConfig(DockerClient client, Message 
message, String image) {
+        // the username header applies only when the endpoint allows it, as in 
DockerClientFactory
+        Message credentialHeaders = 
configuration.isAllowConnectionAndCredentialsFromHeaders() ? message : null;
+        String username = 
DockerHelper.getProperty(DockerConstants.DOCKER_USERNAME, configuration, 
credentialHeaders,
+                String.class, configuration.getUsername());
+        if (username == null) {
+            LOG.debug("No registry username configured: sending the request 
for image {} without authentication", image);
+            return null;
+        }
+
+        AuthConfig authConfig = client.authConfig();
+        if (authConfig == null) {
+            return null;
+        }
+
+        String imageRegistry = DockerHelper.getImageRegistryHost(image);
+        String endpointRegistry = 
DockerHelper.getServerAddressRegistryHost(authConfig.getRegistryAddress());
+        if (imageRegistry == null || !imageRegistry.equals(endpointRegistry)) {
+            LOG.debug("Image {} is in registry {}, not in the registry {} of 
the endpoint:"
+                      + " sending the request without the registry 
authentication of the endpoint",
+                    image, imageRegistry, endpointRegistry);
+            return null;
+        }
+        return authConfig;
+    }
+
     /**
      * Produce a attach container request
      */
diff --git 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/DockerHelperTest.java
 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/DockerHelperTest.java
index 14d26adec5d4..592f82fd4025 100644
--- 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/DockerHelperTest.java
+++ 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/DockerHelperTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.docker;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 public class DockerHelperTest {
 
@@ -28,4 +29,37 @@ public class DockerHelperTest {
         assertEquals("registry", headerField);
     }
 
+    @Test
+    void registryHostOfImageReference() {
+        assertEquals("docker.io", DockerHelper.getImageRegistryHost("ubuntu"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("ubuntu:24.04"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("library/ubuntu"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("team/app:1.0"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("docker.io/library/ubuntu"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("index.docker.io/team/app"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("registry-1.docker.io/team/app"));
+        assertEquals("docker.io", 
DockerHelper.getImageRegistryHost("ubuntu@sha256:0123456789abcdef"));
+        assertEquals("localhost", 
DockerHelper.getImageRegistryHost("localhost/app"));
+        assertEquals("localhost:5000", 
DockerHelper.getImageRegistryHost("localhost:5000/team/app:1.0"));
+        assertEquals("registry.example.com", 
DockerHelper.getImageRegistryHost("registry.example.com/team/app"));
+        assertEquals("registry.example.com:5000",
+                
DockerHelper.getImageRegistryHost("Registry.Example.com:5000/team/app@sha256:0123456789abcdef"));
+        assertEquals("myregistry", 
DockerHelper.getImageRegistryHost("MyRegistry/app"));
+        assertNull(DockerHelper.getImageRegistryHost(null));
+        assertNull(DockerHelper.getImageRegistryHost(""));
+    }
+
+    @Test
+    void registryHostOfServerAddress() {
+        assertEquals("docker.io", 
DockerHelper.getServerAddressRegistryHost("https://index.docker.io/v1/";));
+        assertEquals("docker.io", 
DockerHelper.getServerAddressRegistryHost("http://docker.io/v1";));
+        assertEquals("docker.io", 
DockerHelper.getServerAddressRegistryHost("docker.io"));
+        assertEquals("docker.io", 
DockerHelper.getServerAddressRegistryHost("registry-1.docker.io"));
+        assertEquals("registry.example.com:5000",
+                
DockerHelper.getServerAddressRegistryHost("https://Registry.Example.com:5000/v2/";));
+        assertEquals("localhost:5000", 
DockerHelper.getServerAddressRegistryHost("localhost:5000"));
+        assertNull(DockerHelper.getServerAddressRegistryHost(null));
+        assertNull(DockerHelper.getServerAddressRegistryHost(" "));
+    }
+
 }
diff --git 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/BaseDockerHeaderTest.java
 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/BaseDockerHeaderTest.java
index b4b2eacbc1a8..0647fcd6592c 100644
--- 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/BaseDockerHeaderTest.java
+++ 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/BaseDockerHeaderTest.java
@@ -75,6 +75,7 @@ public abstract class BaseDockerHeaderTest<T> extends 
CamelTestSupport {
         dockerConfiguration.setHost(getHost());
         dockerConfiguration.setPort(getPort());
         dockerConfiguration.setServerAddress(getServerAddress());
+        dockerConfiguration.setUsername(getUsername());
         dockerConfiguration.setSecure(isSecure());
         dockerConfiguration.setTlsVerify(isTlsVerify());
         dockerConfiguration.setSocket(isSocket());
@@ -112,6 +113,13 @@ public abstract class BaseDockerHeaderTest<T> extends 
CamelTestSupport {
         return "https://index.docker.io/v1/";;
     }
 
+    /**
+     * The registry username of the endpoint, none by default.
+     */
+    protected String getUsername() {
+        return null;
+    }
+
     public boolean isSecure() {
         return false;
     }
@@ -161,6 +169,7 @@ public abstract class BaseDockerHeaderTest<T> extends 
CamelTestSupport {
         clientProfile.setPort(getPort());
         clientProfile.setEmail(getEmail());
         clientProfile.setServerAddress(getServerAddress());
+        clientProfile.setUsername(getUsername());
         clientProfile.setMaxPerRouteConnections(getMaxPerRouteConnections());
         clientProfile.setMaxTotalConnections(getMaxTotalConnections());
         clientProfile.setSecure(isSecure());
diff --git 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
index 101e0642f994..1e483431b56e 100644
--- 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
+++ 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
@@ -64,6 +64,22 @@ public class PullImageCmdHeaderTest extends 
BaseDockerHeaderTest<PullImageCmd> {
 
     }
 
+    @Test
+    void pullImageWithoutUsernameSendsNoCredentials() {
+        // docker-java fails like this when the registry authentication is 
asked for without a username
+        Mockito.lenient().when(dockerClient.authConfig())
+                .thenThrow(new NullPointerException("Configured username is 
null."));
+
+        Map<String, Object> headers = getDefaultParameters();
+        headers.put(DockerConstants.DOCKER_REPOSITORY, "docker/empty");
+
+        template.sendBodyAndHeaders("direct:in", "", headers);
+
+        Mockito.verify(dockerClient, Mockito.never()).authConfig();
+        Mockito.verify(mockObject, Mockito.never()).withAuthConfig(any());
+        Mockito.verify(mockObject, Mockito.times(1)).exec(any());
+    }
+
     @Override
     protected void setupMocks() {
         
Mockito.when(dockerClient.pullImageCmd(anyString())).thenReturn(mockObject);
diff --git 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdRegistryAuthTest.java
similarity index 53%
copy from 
components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
copy to 
components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdRegistryAuthTest.java
index 101e0642f994..d5e02fee511d 100644
--- 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdHeaderTest.java
+++ 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PullImageCmdRegistryAuthTest.java
@@ -20,23 +20,24 @@ import java.util.Map;
 
 import com.github.dockerjava.api.command.PullImageCmd;
 import com.github.dockerjava.api.command.PullImageResultCallback;
+import com.github.dockerjava.api.model.AuthConfig;
 import org.apache.camel.component.docker.DockerConstants;
 import org.apache.camel.component.docker.DockerOperation;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 
 /**
- * Validates Pull Image Request headers are applied properly
+ * Validates that a pull image request uses the registry authentication of the 
endpoint only for images of the registry
+ * of the endpoint
  */
-public class PullImageCmdHeaderTest extends BaseDockerHeaderTest<PullImageCmd> 
{
-    private static final Logger LOG = 
LoggerFactory.getLogger(PullImageCmdHeaderTest.class);
+public class PullImageCmdRegistryAuthTest extends 
BaseDockerHeaderTest<PullImageCmd> {
+
+    private static final String USERNAME = "jdoe";
+    private static final String SERVER_ADDRESS = 
"https://registry.example.com:5000/v2/";;
 
     @Mock
     private PullImageCmd mockObject;
@@ -45,23 +46,57 @@ public class PullImageCmdHeaderTest extends 
BaseDockerHeaderTest<PullImageCmd> {
     private PullImageResultCallback callback;
 
     @Test
-    void pullImageHeaderTest() {
+    void pullImageOfEndpointRegistrySendsEndpointCredentials() {
+        AuthConfig authConfig = endpointAuthConfig();
+        Mockito.when(dockerClient.authConfig()).thenReturn(authConfig);
+
+        pull("registry.example.com:5000/team/app");
+
+        Mockito.verify(mockObject, 
Mockito.times(1)).withAuthConfig(authConfig);
+        Mockito.verify(mockObject, Mockito.times(1)).exec(any());
+    }
+
+    @Test
+    void pullImageOfOtherRegistrySendsNoCredentials() {
+        
Mockito.when(dockerClient.authConfig()).thenReturn(endpointAuthConfig());
+
+        pull("registry.example.org/team/app");
+
+        Mockito.verify(mockObject, Mockito.never()).withAuthConfig(any());
+        Mockito.verify(mockObject, Mockito.times(1)).exec(any());
+    }
+
+    @Test
+    void pullImageOfDockerHubSendsNoCredentials() {
+        
Mockito.when(dockerClient.authConfig()).thenReturn(endpointAuthConfig());
+
+        pull("team/app");
 
-        String repository = "docker/empty";
-        String tag = "1.0";
-        String registry = "registry";
+        Mockito.verify(mockObject, Mockito.never()).withAuthConfig(any());
+        Mockito.verify(mockObject, Mockito.times(1)).exec(any());
+    }
 
+    private void pull(String repository) {
         Map<String, Object> headers = getDefaultParameters();
         headers.put(DockerConstants.DOCKER_REPOSITORY, repository);
-        headers.put(DockerConstants.DOCKER_TAG, tag);
-        headers.put(DockerConstants.DOCKER_REGISTRY, registry);
 
         template.sendBodyAndHeaders("direct:in", "", headers);
 
         Mockito.verify(dockerClient, 
Mockito.times(1)).pullImageCmd(repository);
-        Mockito.verify(mockObject, Mockito.times(1)).withTag(eq(tag));
-        Mockito.verify(mockObject, 
Mockito.times(1)).withRegistry(eq(registry));
+    }
+
+    private static AuthConfig endpointAuthConfig() {
+        return new 
AuthConfig().withUsername(USERNAME).withPassword("secret").withRegistryAddress(SERVER_ADDRESS);
+    }
 
+    @Override
+    protected String getServerAddress() {
+        return SERVER_ADDRESS;
+    }
+
+    @Override
+    protected String getUsername() {
+        return USERNAME;
     }
 
     @Override
@@ -71,7 +106,7 @@ public class PullImageCmdHeaderTest extends 
BaseDockerHeaderTest<PullImageCmd> {
         try {
             Mockito.when(callback.awaitCompletion()).thenReturn(callback);
         } catch (InterruptedException e) {
-            LOG.warn("Interrupted while setting up mocks", e);
+            Thread.currentThread().interrupt();
         }
     }
 
diff --git 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PushImageCmdHeaderTest.java
 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PushImageCmdHeaderTest.java
index f3f5fa2e789f..f976ee130960 100644
--- 
a/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PushImageCmdHeaderTest.java
+++ 
b/components/camel-docker/src/test/java/org/apache/camel/component/docker/headers/PushImageCmdHeaderTest.java
@@ -20,6 +20,7 @@ import java.util.Map;
 
 import com.github.dockerjava.api.async.ResultCallback;
 import com.github.dockerjava.api.command.PushImageCmd;
+import com.github.dockerjava.api.model.AuthConfig;
 import com.github.dockerjava.api.model.Frame;
 import org.apache.camel.component.docker.DockerClientProfile;
 import org.apache.camel.component.docker.DockerConstants;
@@ -70,6 +71,43 @@ public class PushImageCmdHeaderTest extends 
BaseDockerHeaderTest<PushImageCmd> {
 
     }
 
+    @Test
+    void pushImageOfEndpointRegistrySendsEndpointCredentials() {
+        AuthConfig authConfig = endpointAuthConfig();
+        Mockito.when(dockerClient.authConfig()).thenReturn(authConfig);
+
+        template.sendBodyAndHeaders("direct:in", "", 
credentialHeaders("jdoe/imagename"));
+
+        Mockito.verify(dockerClient, 
Mockito.times(1)).pushImageCmd("jdoe/imagename");
+        Mockito.verify(mockObject, 
Mockito.times(1)).withAuthConfig(authConfig);
+    }
+
+    @Test
+    void pushImageOfOtherRegistrySendsNoCredentials() {
+        
Mockito.when(dockerClient.authConfig()).thenReturn(endpointAuthConfig());
+
+        template.sendBodyAndHeaders("direct:in", "", 
credentialHeaders("registry.example.com:5000/jdoe/imagename"));
+
+        Mockito.verify(dockerClient, 
Mockito.times(1)).pushImageCmd("registry.example.com:5000/jdoe/imagename");
+        Mockito.verify(mockObject, Mockito.never()).withAuthConfig(any());
+        Mockito.verify(mockObject, Mockito.times(1)).exec(any());
+    }
+
+    private Map<String, Object> credentialHeaders(String imageName) {
+        Map<String, Object> headers = getDefaultParameters();
+        headers.put(DockerConstants.DOCKER_USERNAME, userName);
+        headers.put(DockerConstants.DOCKER_PASSWORD, password);
+        headers.put(DockerConstants.DOCKER_EMAIL, email);
+        headers.put(DockerConstants.DOCKER_SERVER_ADDRESS, serverAddress);
+        headers.put(DockerConstants.DOCKER_NAME, imageName);
+        return headers;
+    }
+
+    private AuthConfig endpointAuthConfig() {
+        return new 
AuthConfig().withUsername(userName).withPassword(password).withEmail(email)
+                .withRegistryAddress(serverAddress);
+    }
+
     @Override
     protected void setupMocks() {
         
Mockito.when(dockerClient.pushImageCmd(anyString())).thenReturn(mockObject);
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 4ff53d3c933a..df5abd97c5b6 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2166,6 +2166,17 @@ The other headers, such as `CamelDockerContainerId`, 
`CamelDockerEmail` and `Cam
 precedence over the options of the endpoint, as before. The `events` and 
`stats` consumers do not change: they always
 used the options of the endpoint.
 
+=== camel-docker - imagepull and imagepush authenticate only to the registry 
of serverAddress
+
+The `imagepull` and `imagepush` operations of the `docker` component added the 
registry authentication of the
+endpoint (`username`, `password`, `email` and `serverAddress`) to every pull 
and push, whatever registry the image
+reference names. They now add it only when the registry host and port of the 
image (Docker Hub for an image without a
+registry host, such as `ubuntu` or `team/app`) match those of `serverAddress`. 
An image in another registry is pulled
+or pushed without authentication. Set `serverAddress` to the registry of the 
images, or use one endpoint per registry.
+
+Without a `username` on the endpoint, `imagepull` and `imagepush` failed with 
a `NullPointerException`
+("Configured username is null."). They now pull or push without authentication.
+
 === camel-cli-connector - every action with a request id gets an answer
 
 An action written to `~/.camel/<pid>-action-<requestId>.json` now always gets 
an answer in

Reply via email to