Update `DockerConfigurationMetadata` to support credentials

Update `DockerConfigurationMetadata` with support for `credsStore`,
`credHelpers` and `auth` sections. These values will be required to
support credential helper based authentication.

See gh-45269

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-04-23 21:25:46 -07:00 committed by Phillip Webb
parent dd49de03ee
commit 958e28d890
3 changed files with 139 additions and 2 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,7 +23,11 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Collections;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
@ -148,15 +152,50 @@ final class DockerConfigurationMetadata {
private final String currentContext;
private final String credsStore;
private final Map<String, String> credHelpers;
private final Map<String, Auth> auths;
private DockerConfig(JsonNode node) {
super(node, MethodHandles.lookup());
this.currentContext = valueAt("/currentContext", String.class);
this.credsStore = valueAt("/credsStore", String.class);
this.credHelpers = extractCredHelpers();
this.auths = extractAuths();
}
private Map<String, Auth> extractAuths() {
Map<String, Auth> auths = new LinkedHashMap<>();
getNode().at("/auths")
.fields()
.forEachRemaining((entry) -> auths.put(entry.getKey(), new Auth(entry.getValue())));
return Map.copyOf(auths);
}
@SuppressWarnings("unchecked")
private Map<String, String> extractCredHelpers() {
Map<String, String> credHelpers = valueAt("/credHelpers", Map.class);
return (credHelpers != null) ? Map.copyOf(credHelpers) : Collections.emptyMap();
}
String getCurrentContext() {
return this.currentContext;
}
String getCredsStore() {
return this.credsStore;
}
Map<String, String> getCredHelpers() {
return this.credHelpers;
}
Map<String, Auth> getAuths() {
return this.auths;
}
static DockerConfig fromJson(String json) throws JsonProcessingException {
return new DockerConfig(SharedObjectMapper.get().readTree(json));
}
@ -167,6 +206,45 @@ final class DockerConfigurationMetadata {
}
static final class Auth extends MappedObject {
private final String username;
private final String password;
private final String email;
Auth(JsonNode node) {
super(node, MethodHandles.lookup());
String username = valueAt("/username", String.class);
String password = valueAt("/password", String.class);
String auth = valueAt("/auth", String.class);
if (auth != null) {
String[] parts = new String(Base64.getDecoder().decode(auth)).split(":", 2);
if (parts.length == 2) {
username = parts[0];
password = parts[1];
}
}
this.username = username;
this.password = password;
this.email = valueAt("/email", String.class);
}
String getUsername() {
return this.username;
}
String getPassword() {
return this.password;
}
String getEmail() {
return this.email;
}
}
static final class DockerContext extends MappedObject {
private final String dockerHost;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
@ -46,6 +47,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
this.environment.put("DOCKER_CONFIG", pathToResource("with-context/config.json"));
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("test-context");
assertThat(config.getConfiguration().getAuths()).isEmpty();
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
assertThat(config.getConfiguration().getCredsStore()).isNull();
assertThat(config.getContext().getDockerHost()).isEqualTo("unix:///home/user/.docker/docker.sock");
assertThat(config.getContext().isTlsVerify()).isFalse();
assertThat(config.getContext().getTlsPath()).isNull();
@ -56,6 +60,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
this.environment.put("DOCKER_CONFIG", pathToResource("without-context/config.json"));
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
assertThat(config.getConfiguration().getCurrentContext()).isNull();
assertThat(config.getConfiguration().getAuths()).isEmpty();
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
assertThat(config.getConfiguration().getCredsStore()).isNull();
assertThat(config.getContext().getDockerHost()).isNull();
assertThat(config.getContext().isTlsVerify()).isFalse();
assertThat(config.getContext().getTlsPath()).isNull();
@ -66,6 +73,9 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
this.environment.put("DOCKER_CONFIG", pathToResource("with-default-context/config.json"));
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
assertThat(config.getConfiguration().getCurrentContext()).isEqualTo("default");
assertThat(config.getConfiguration().getAuths()).isEmpty();
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
assertThat(config.getConfiguration().getCredsStore()).isNull();
assertThat(config.getContext().getDockerHost()).isNull();
assertThat(config.getContext().isTlsVerify()).isFalse();
assertThat(config.getContext().getTlsPath()).isNull();
@ -95,10 +105,38 @@ class DockerConfigurationMetadataTests extends AbstractJsonTests {
this.environment.put("DOCKER_CONFIG", "docker-config-dummy-path");
DockerConfigurationMetadata config = DockerConfigurationMetadata.from(this.environment::get);
assertThat(config.getConfiguration().getCurrentContext()).isNull();
assertThat(config.getConfiguration().getAuths()).isEmpty();
assertThat(config.getConfiguration().getCredHelpers()).isEmpty();
assertThat(config.getConfiguration().getCredsStore()).isNull();
assertThat(config.getContext().getDockerHost()).isNull();
assertThat(config.getContext().isTlsVerify()).isFalse();
}
@Test
void configWithAuthIsRead() throws Exception {
this.environment.put("DOCKER_CONFIG", pathToResource("with-auth/config.json"));
DockerConfigurationMetadata metadata = DockerConfigurationMetadata.from(this.environment::get);
DockerConfig configuration = metadata.getConfiguration();
assertThat(configuration.getCredsStore()).isEqualTo("desktop");
assertThat(configuration.getCredHelpers()).hasSize(3)
.containsEntry("azurecr.io", "acr-env")
.containsEntry("ecr.us-east-1.amazonaws.com", "ecr-login")
.containsEntry("gcr.io", "gcr");
assertThat(configuration.getAuths()).hasSize(3).hasEntrySatisfying("https://index.docker.io/v1/", (auth) -> {
assertThat(auth.getUsername()).isEqualTo("username");
assertThat(auth.getPassword()).isEqualTo("password");
assertThat(auth.getEmail()).isEqualTo("test@gmail.com");
}).hasEntrySatisfying("custom-registry.example.com", (auth) -> {
assertThat(auth.getUsername()).isEqualTo("customUser");
assertThat(auth.getPassword()).isEqualTo("customPass");
assertThat(auth.getEmail()).isNull();
}).hasEntrySatisfying("my-registry.example.com", (auth) -> {
assertThat(auth.getUsername()).isEqualTo("user");
assertThat(auth.getPassword()).isEqualTo("password");
assertThat(auth.getEmail()).isNull();
});
}
private String pathToResource(String resource) throws URISyntaxException {
URL url = getClass().getResource(resource);
return Paths.get(url.toURI()).getParent().toAbsolutePath().toString();

View File

@ -0,0 +1,21 @@
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
"email": "test@gmail.com"
},
"custom-registry.example.com": {
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
},
"my-registry.example.com": {
"username": "user",
"password": "password"
}
},
"credsStore": "desktop",
"credHelpers": {
"gcr.io": "gcr",
"ecr.us-east-1.amazonaws.com": "ecr-login",
"azurecr.io": "acr-env"
}
}