Support Docker configuration authentication including helper support
Add `DockerRegistryAuthentication` implementation that uses standard Docker config to authenticate requests. Prior to this commit, we only supported username/password and token based authentication. This commit allows authentication based on the contents of the Docker configuration file, including support for executing credential helpers. See gh-45269 Signed-off-by: Dmytro Nosan <dimanosan@gmail.com> Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
This commit is contained in:
committed by
Phillip Webb
parent
2a9e30a358
commit
e6fbccef7e
@@ -246,13 +246,13 @@ public class Builder {
|
||||
private void pushImage(ImageReference reference) throws IOException {
|
||||
Consumer<TotalProgressEvent> progressConsumer = this.log.pushingImage(reference);
|
||||
TotalProgressPushListener listener = new TotalProgressPushListener(progressConsumer);
|
||||
String authHeader = authHeader(this.dockerConfiguration.publishRegistryAuthentication());
|
||||
String authHeader = authHeader(this.dockerConfiguration.publishRegistryAuthentication(), reference);
|
||||
this.docker.image().push(reference, listener, authHeader);
|
||||
this.log.pushedImage(reference);
|
||||
}
|
||||
|
||||
private static String authHeader(DockerRegistryAuthentication authentication) {
|
||||
return (authentication != null) ? authentication.getAuthHeader() : null;
|
||||
private static String authHeader(DockerRegistryAuthentication authentication, ImageReference reference) {
|
||||
return (authentication != null) ? authentication.getAuthHeader(reference) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,7 +279,7 @@ public class Builder {
|
||||
Image fetchImage(ImageType type, ImageReference reference) throws IOException {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
Assert.notNull(reference, "'reference' must not be null");
|
||||
String authHeader = authHeader(this.registryAuthentication);
|
||||
String authHeader = authHeader(this.registryAuthentication, reference);
|
||||
Assert.state(authHeader == null || reference.getDomain().equals(this.domain),
|
||||
() -> String.format("%s '%s' must be pulled from the '%s' authenticated registry",
|
||||
StringUtils.capitalize(type.getDescription()), reference, this.domain));
|
||||
@@ -300,7 +300,7 @@ public class Builder {
|
||||
private Image pullImage(ImageReference reference, ImageType imageType) throws IOException {
|
||||
TotalProgressPullListener listener = new TotalProgressPullListener(
|
||||
Builder.this.log.pullingImage(reference, this.defaultPlatform, imageType));
|
||||
String authHeader = authHeader(this.registryAuthentication);
|
||||
String authHeader = authHeader(this.registryAuthentication, reference);
|
||||
Image image = Builder.this.docker.image().pull(reference, this.defaultPlatform, listener, authHeader);
|
||||
Builder.this.log.pulledImage(image, imageType);
|
||||
if (this.defaultPlatform == null) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.MappedObject;
|
||||
|
||||
/**
|
||||
* A class that represents credentials for a server as returned from a
|
||||
* {@link CredentialHelper}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class Credential extends MappedObject {
|
||||
|
||||
/**
|
||||
* If the secret being stored is an identity token, the username should be set to
|
||||
* {@code <token>}.
|
||||
*/
|
||||
private static final String TOKEN_USERNAME = "<token>";
|
||||
|
||||
private final String username;
|
||||
|
||||
private final String secret;
|
||||
|
||||
private String serverUrl;
|
||||
|
||||
Credential(JsonNode node) {
|
||||
super(node, MethodHandles.lookup());
|
||||
this.username = valueAt("/Username", String.class);
|
||||
this.secret = valueAt("/Secret", String.class);
|
||||
this.serverUrl = valueAt("/ServerURL", String.class);
|
||||
}
|
||||
|
||||
String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
String getServerUrl() {
|
||||
return this.serverUrl;
|
||||
}
|
||||
|
||||
boolean isIdentityToken() {
|
||||
return TOKEN_USERNAME.equals(this.username);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sun.jna.Platform;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
|
||||
/**
|
||||
* Invokes a Docker credential helper executable that can be used to get {@link Credential
|
||||
* credentials}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CredentialHelper {
|
||||
|
||||
private static final String USR_LOCAL_BIN = "/usr/local/bin/";
|
||||
|
||||
Set<String> CREDENTIAL_NOT_FOUND_MESSAGES = Set.of("credentials not found in native keychain",
|
||||
"no credentials server URL", "no credentials username");
|
||||
|
||||
private final String executable;
|
||||
|
||||
CredentialHelper(String executable) {
|
||||
this.executable = executable;
|
||||
}
|
||||
|
||||
Credential get(String serverUrl) throws IOException {
|
||||
ProcessBuilder processBuilder = processBuilder("get");
|
||||
Process process = start(processBuilder);
|
||||
try (OutputStream request = process.getOutputStream()) {
|
||||
request.write(serverUrl.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
try {
|
||||
int exitCode = process.waitFor();
|
||||
try (InputStream response = process.getInputStream()) {
|
||||
if (exitCode == 0) {
|
||||
return new Credential(SharedObjectMapper.get().readTree(response));
|
||||
}
|
||||
String errorMessage = new String(response.readAllBytes(), StandardCharsets.UTF_8);
|
||||
if (!isCredentialsNotFoundError(errorMessage)) {
|
||||
throw new IOException("%s' exited with code %d: %s".formatted(process, exitCode, errorMessage));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessBuilder processBuilder(String string) {
|
||||
ProcessBuilder processBuilder = new ProcessBuilder().redirectErrorStream(true);
|
||||
if (Platform.isWindows()) {
|
||||
processBuilder.command("cmd", "/c");
|
||||
}
|
||||
processBuilder.command(this.executable, string);
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private Process start(ProcessBuilder processBuilder) throws IOException {
|
||||
try {
|
||||
return processBuilder.start();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (!Platform.isMac()) {
|
||||
throw ex;
|
||||
}
|
||||
List<String> command = new ArrayList<>(processBuilder.command());
|
||||
command.set(0, USR_LOCAL_BIN + command.get(0));
|
||||
return processBuilder.command(command).start();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCredentialsNotFoundError(String message) {
|
||||
return this.CREDENTIAL_NOT_FOUND_MESSAGES.contains(message.trim());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,6 +63,8 @@ final class DockerConfigurationMetadata {
|
||||
|
||||
private static final String CONTEXT_FILE_NAME = "meta.json";
|
||||
|
||||
private static volatile DockerConfigurationMetadata systemEnvironmentConfigurationMetadata;
|
||||
|
||||
private final String configLocation;
|
||||
|
||||
private final DockerConfig config;
|
||||
@@ -88,11 +90,24 @@ final class DockerConfigurationMetadata {
|
||||
}
|
||||
|
||||
static DockerConfigurationMetadata from(Environment environment) {
|
||||
String configLocation = (environment.get(DOCKER_CONFIG) != null) ? environment.get(DOCKER_CONFIG)
|
||||
: Path.of(System.getProperty("user.home"), CONFIG_DIR).toString();
|
||||
DockerConfigurationMetadata dockerConfigurationMetadata = (environment == Environment.SYSTEM)
|
||||
? DockerConfigurationMetadata.systemEnvironmentConfigurationMetadata : null;
|
||||
if (dockerConfigurationMetadata != null) {
|
||||
return dockerConfigurationMetadata;
|
||||
}
|
||||
String configLocation = environment.get(DOCKER_CONFIG);
|
||||
configLocation = (configLocation != null) ? configLocation : getUserHomeConfigLocation();
|
||||
DockerConfig dockerConfig = createDockerConfig(configLocation);
|
||||
DockerContext dockerContext = createDockerContext(configLocation, dockerConfig.getCurrentContext());
|
||||
return new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext);
|
||||
dockerConfigurationMetadata = new DockerConfigurationMetadata(configLocation, dockerConfig, dockerContext);
|
||||
if (environment == Environment.SYSTEM) {
|
||||
DockerConfigurationMetadata.systemEnvironmentConfigurationMetadata = dockerConfigurationMetadata;
|
||||
}
|
||||
return dockerConfigurationMetadata;
|
||||
}
|
||||
|
||||
private static String getUserHomeConfigLocation() {
|
||||
return Path.of(System.getProperty("user.home"), CONFIG_DIR).toString();
|
||||
}
|
||||
|
||||
private static DockerConfig createDockerConfig(String configLocation) {
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
|
||||
/**
|
||||
* Docker registry authentication configuration.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DockerRegistryAuthentication {
|
||||
|
||||
/**
|
||||
@@ -30,6 +35,17 @@ public interface DockerRegistryAuthentication {
|
||||
*/
|
||||
DockerRegistryAuthentication EMPTY_USER = DockerRegistryAuthentication.user("", "", "", "");
|
||||
|
||||
/**
|
||||
* Returns the auth header that should be used for docker authentication for the given
|
||||
* image reference.
|
||||
* @param imageReference the image reference or {@code null}
|
||||
* @return the auth header
|
||||
* @since 3.5.0
|
||||
*/
|
||||
default String getAuthHeader(ImageReference imageReference) {
|
||||
return getAuthHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the auth header that should be used for docker authentication.
|
||||
* @return the auth header
|
||||
@@ -63,4 +79,33 @@ public interface DockerRegistryAuthentication {
|
||||
return new DockerRegistryUserAuthentication(username, password, serverAddress, email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that returns a new {@link DockerRegistryAuthentication} instance
|
||||
* that uses the standard docker JSON config (including support for credential
|
||||
* helpers) to generate auth headers.
|
||||
* @param fallback the fallback authentication to use if no suitable config is found
|
||||
* @return a new {@link DockerRegistryAuthentication} instance
|
||||
* @since 3.5.0
|
||||
* @see #configuration(DockerRegistryAuthentication, BiConsumer)
|
||||
*/
|
||||
static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback) {
|
||||
return configuration(fallback, (message, ex) -> System.out.println(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that returns a new {@link DockerRegistryAuthentication} instance
|
||||
* that uses the standard docker JSON config (including support for credential
|
||||
* helpers) to generate auth headers.
|
||||
* @param fallback the fallback authentication to use if no suitable config is found
|
||||
* @param credentialHelperExceptionHandler callback that should handle credential
|
||||
* helper exceptions
|
||||
* @return a new {@link DockerRegistryAuthentication} instance
|
||||
* @since 3.5.0
|
||||
* @see #configuration(DockerRegistryAuthentication, BiConsumer)
|
||||
*/
|
||||
static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback,
|
||||
BiConsumer<String, Exception> credentialHelperExceptionHandler) {
|
||||
return new DockerRegistryConfigAuthentication(fallback, credentialHelperExceptionHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.Auth;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.system.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link DockerRegistryAuthentication} for
|
||||
* {@link DockerRegistryAuthentication#configuration(DockerRegistryAuthentication, BiConsumer)}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication {
|
||||
|
||||
private static final String DEFAULT_DOMAIN = "docker.io";
|
||||
|
||||
private static final String INDEX_URL = "https://index.docker.io/v1/";
|
||||
|
||||
private static Map<String, Credential> credentialFromHelperCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final DockerRegistryAuthentication fallback;
|
||||
|
||||
private final BiConsumer<String, Exception> credentialHelperExceptionHandler;
|
||||
|
||||
private final Function<String, CredentialHelper> credentialHelperFactory;
|
||||
|
||||
private final DockerConfig dockerConfig;
|
||||
|
||||
DockerRegistryConfigAuthentication(DockerRegistryAuthentication fallback,
|
||||
BiConsumer<String, Exception> credentialHelperExceptionHandler) {
|
||||
this(fallback, credentialHelperExceptionHandler, Environment.SYSTEM,
|
||||
(helper) -> new CredentialHelper("docker-credential-" + helper.trim()));
|
||||
}
|
||||
|
||||
DockerRegistryConfigAuthentication(DockerRegistryAuthentication fallback,
|
||||
BiConsumer<String, Exception> credentialHelperExceptionHandler, Environment environment,
|
||||
Function<String, CredentialHelper> credentialHelperFactory) {
|
||||
this.fallback = fallback;
|
||||
this.credentialHelperExceptionHandler = credentialHelperExceptionHandler;
|
||||
this.dockerConfig = DockerConfigurationMetadata.from(environment).getConfiguration();
|
||||
this.credentialHelperFactory = credentialHelperFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthHeader() {
|
||||
return getAuthHeader(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthHeader(ImageReference imageReference) {
|
||||
String serverUrl = getServerUrl(imageReference);
|
||||
DockerRegistryAuthentication authentication = getAuthentication(serverUrl);
|
||||
return (authentication != null) ? authentication.getAuthHeader(imageReference) : null;
|
||||
}
|
||||
|
||||
private String getServerUrl(ImageReference imageReference) {
|
||||
String domain = (imageReference != null) ? imageReference.getDomain() : null;
|
||||
return (!DEFAULT_DOMAIN.equals(domain)) ? domain : INDEX_URL;
|
||||
}
|
||||
|
||||
private DockerRegistryAuthentication getAuthentication(String serverUrl) {
|
||||
Credential credentialsFromHelper = getCredentialsFromHelper(serverUrl);
|
||||
Map.Entry<String, Auth> authConfigEntry = getAuthConfigEntry(serverUrl);
|
||||
serverUrl = (authConfigEntry != null) ? authConfigEntry.getKey() : serverUrl;
|
||||
Auth authConfig = (authConfigEntry != null) ? authConfigEntry.getValue() : null;
|
||||
if (credentialsFromHelper != null) {
|
||||
return getAuthentication(credentialsFromHelper, authConfig, serverUrl);
|
||||
}
|
||||
if (authConfigEntry != null) {
|
||||
return DockerRegistryAuthentication.user(authConfig.getUsername(), authConfig.getPassword(), serverUrl,
|
||||
authConfig.getEmail());
|
||||
}
|
||||
return this.fallback;
|
||||
}
|
||||
|
||||
private DockerRegistryAuthentication getAuthentication(Credential credentialsFromHelper, Auth authConfig,
|
||||
String serverUrl) {
|
||||
if (credentialsFromHelper.isIdentityToken()) {
|
||||
return DockerRegistryAuthentication.token(credentialsFromHelper.getSecret());
|
||||
}
|
||||
String username = credentialsFromHelper.getUsername();
|
||||
String password = credentialsFromHelper.getSecret();
|
||||
String serverAddress = (credentialsFromHelper.getServerUrl() != null
|
||||
&& !credentialsFromHelper.getServerUrl().isEmpty()) ? credentialsFromHelper.getServerUrl() : serverUrl;
|
||||
String email = (authConfig != null) ? authConfig.getEmail() : null;
|
||||
return DockerRegistryAuthentication.user(username, password, serverAddress, email);
|
||||
}
|
||||
|
||||
private Credential getCredentialsFromHelper(String serverUrl) {
|
||||
return (StringUtils.hasText(serverUrl))
|
||||
? credentialFromHelperCache.computeIfAbsent(serverUrl, this::computeCredentialsFromHelper) : null;
|
||||
}
|
||||
|
||||
private Credential computeCredentialsFromHelper(String serverUrl) {
|
||||
CredentialHelper credentialHelper = getCredentialHelper(serverUrl);
|
||||
if (credentialHelper != null) {
|
||||
try {
|
||||
return credentialHelper.get(serverUrl);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
String message = "Error retrieving credentials for '%s' due to: %s".formatted(serverUrl,
|
||||
ex.getMessage());
|
||||
this.credentialHelperExceptionHandler.accept(message, ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private CredentialHelper getCredentialHelper(String serverUrl) {
|
||||
String name = this.dockerConfig.getCredHelpers().get(serverUrl);
|
||||
name = (StringUtils.hasText(name)) ? name : this.dockerConfig.getCredsStore();
|
||||
return (name != null) ? this.credentialHelperFactory.apply(name.trim()) : null;
|
||||
}
|
||||
|
||||
private Entry<String, Auth> getAuthConfigEntry(String serverUrl) {
|
||||
for (Map.Entry<String, Auth> candidate : this.dockerConfig.getAuths().entrySet()) {
|
||||
if (candidate.getKey().equals(serverUrl) || candidate.getKey().endsWith("://" + serverUrl)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
@@ -19,7 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Docker registry authentication configuration using a token.
|
||||
* {@link DockerRegistryAuthentication} for
|
||||
* {@link DockerRegistryAuthentication#user(String, String, String, String)}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
@@ -19,7 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Docker registry authentication configuration using user credentials.
|
||||
* {@link DockerRegistryAuthentication} for
|
||||
* {@link DockerRegistryAuthentication#token(String)}.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sun.jna.Platform;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
/**
|
||||
* Tests for {@link CredentialHelper}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class CredentialHelperTests {
|
||||
|
||||
private static CredentialHelper helper;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
String executableName = "docker-credential-test" + ((Platform.isWindows()) ? ".bat" : ".sh");
|
||||
String executable = new ClassPathResource(executableName, CredentialHelperTests.class).getFile()
|
||||
.getAbsolutePath();
|
||||
helper = new CredentialHelper(executable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenKnowUser() throws Exception {
|
||||
Credential credentials = helper.get("user.example.com");
|
||||
assertThat(credentials).isNotNull();
|
||||
assertThat(credentials.isIdentityToken()).isFalse();
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("user.example.com");
|
||||
assertThat(credentials.getUsername()).isEqualTo("username");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenKnowToken() throws Exception {
|
||||
Credential credentials = helper.get("token.example.com");
|
||||
assertThat(credentials).isNotNull();
|
||||
assertThat(credentials.isIdentityToken()).isTrue();
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("token.example.com");
|
||||
assertThat(credentials.getUsername()).isEqualTo("<token>");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenCredentialsMissingMessageReturnsNull() throws Exception {
|
||||
Credential credentials = helper.get("credentials.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenUsernameMissingMessageReturnsNull() throws Exception {
|
||||
Credential credentials = helper.get("username.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenUrlMissingMessageReturnsNull() throws Exception {
|
||||
Credential credentials = helper.get("url.missing.example.com");
|
||||
assertThat(credentials).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenUnknownErrorThrowsException() {
|
||||
assertThatIOException().isThrownBy(() -> helper.get("invalid.example.com"))
|
||||
.withMessageContaining("Unknown error");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenCommandDoesNotExistErrorThrowsException() {
|
||||
String name = "docker-credential-%s".formatted(UUID.randomUUID().toString());
|
||||
assertThatIOException().isThrownBy(() -> new CredentialHelper(name).get("invalid.example.com"))
|
||||
.withMessageContaining(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Credential}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
*/
|
||||
class CredentialsTests {
|
||||
|
||||
@Test
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "user",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
void createWhenUserCredentials() throws Exception {
|
||||
Credential credentials = getCredentials("credentials.json");
|
||||
assertThat(credentials.getUsername()).isEqualTo("user");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/");
|
||||
assertThat(credentials.isIdentityToken()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
void createWhenTokenCredentials() throws Exception {
|
||||
Credential credentials = getCredentials("credentials.json");
|
||||
assertThat(credentials.getUsername()).isEqualTo("<token>");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
assertThat(credentials.getServerUrl()).isEqualTo("https://index.docker.io/v1/");
|
||||
assertThat(credentials.isIdentityToken()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"Username": "user",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
void createWhenNoServerUrl() throws Exception {
|
||||
Credential credentials = getCredentials("credentials.json");
|
||||
assertThat(credentials.getUsername()).isEqualTo("user");
|
||||
assertThat(credentials.getSecret()).isEqualTo("secret");
|
||||
assertThat(credentials.getServerUrl()).isNull();
|
||||
assertThat(credentials.isIdentityToken()).isFalse();
|
||||
}
|
||||
|
||||
private Credential getCredentials(String name) throws IOException {
|
||||
try (InputStream inputStream = new ClassPathResource(name).getInputStream()) {
|
||||
return new Credential(SharedObjectMapper.get().readTree(inputStream));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||
import org.springframework.boot.testsupport.classpath.resources.ResourcesRoot;
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DockerRegistryConfigAuthentication}.
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class DockerRegistryConfigAuthenticationTests {
|
||||
|
||||
private final Map<String, String> environment = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, Exception> helperExceptions = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, CredentialHelper> credentialHelpers = new HashMap<>();
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenAuthForDockerDomain(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://index.docker.io/v1/")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenAuthForLegacyDockerDomain(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("index.docker.io/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://index.docker.io/v1/")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"my-registry.example.com": {
|
||||
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenAuthForCustomDomain(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "my-registry.example.com")
|
||||
.containsEntry("username", "customUser")
|
||||
.containsEntry("password", "customPass")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"https://my-registry.example.com": {
|
||||
"auth": "Y3VzdG9tVXNlcjpjdXN0b21QYXNz"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenAuthForCustomDomainWithLegacyFormat(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("my-registry.example.com/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://my-registry.example.com")
|
||||
.containsEntry("username", "customUser")
|
||||
.containsEntry("password", "customPass")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenEmptyConfigDirectoryReturnsFallback(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop"
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://index.docker.io/v1/",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenUsingHelperFromCredsStore(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("docker.io/ubuntu:latest");
|
||||
mockHelper("desktop", "https://index.docker.io/v1/", "credentials.json");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(1).containsEntry("identitytoken", "secret");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"gcr.io": {
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
},
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"ServerURL": "https://my-gcr.io",
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenUsingHelperFromCredsStoreAndUseEmailFromAuth(@ResourcesRoot Path directory) throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
mockHelper("gcr", "gcr.io", "credentials.json");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "https://my-gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "secret")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@WithResource(name = "credentials.json", content = """
|
||||
{
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenUsingHelperFromCredHelpersUsesProvidedServerUrl(@ResourcesRoot Path directory)
|
||||
throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
mockHelper("gcr", "gcr.io", "credentials.json");
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "secret")
|
||||
.containsEntry("email", null);
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"auths": {
|
||||
"gcr.io": {
|
||||
"auth": "dXNlcm5hbWU6cGFzc3dvcmQ=",
|
||||
"email": "test@gmail.com"
|
||||
}
|
||||
},
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenUsingHelperThatFailsLogsErrorAndReturnsFromAuths(@ResourcesRoot Path directory)
|
||||
throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
CredentialHelper helper = mockHelper("gcr");
|
||||
given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry"));
|
||||
String authHeader = getAuthHeader(imageReference);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "gcr.io")
|
||||
.containsEntry("username", "username")
|
||||
.containsEntry("password", "password")
|
||||
.containsEntry("email", "test@gmail.com");
|
||||
assertThat(this.helperExceptions).hasSize(1);
|
||||
assertThat(this.helperExceptions.keySet().iterator().next())
|
||||
.contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": "gcr"
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenUsingHelperThatFailsAndNoAuthLogsErrorAndReturnsFallback(@ResourcesRoot Path directory)
|
||||
throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
CredentialHelper helper = mockHelper("gcr");
|
||||
given(helper.get("gcr.io")).willThrow(new IOException("Failed to obtain credentials for registry"));
|
||||
String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER);
|
||||
assertThat(this.helperExceptions).hasSize(1);
|
||||
assertThat(this.helperExceptions.keySet().iterator().next())
|
||||
.contains("Error retrieving credentials for 'gcr.io' due to: Failed to obtain credentials for registry");
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
@WithResource(name = "config.json", content = """
|
||||
{
|
||||
"credsStore": "desktop",
|
||||
"credHelpers": {
|
||||
"gcr.io": ""
|
||||
}
|
||||
}
|
||||
""")
|
||||
@Test
|
||||
void getAuthHeaderWhenEmptyCredHelperReturnsFallbackAndDoesNotUseCredStore(@ResourcesRoot Path directory)
|
||||
throws Exception {
|
||||
this.environment.put("DOCKER_CONFIG", directory.toString());
|
||||
ImageReference imageReference = ImageReference.of("gcr.io/ubuntu:latest");
|
||||
String authHeader = getAuthHeader(imageReference, DockerRegistryAuthentication.EMPTY_USER);
|
||||
assertThat(decode(authHeader)).hasSize(4)
|
||||
.containsEntry("serveraddress", "")
|
||||
.containsEntry("username", "")
|
||||
.containsEntry("password", "")
|
||||
.containsEntry("email", "");
|
||||
}
|
||||
|
||||
private String getAuthHeader(ImageReference imageReference) {
|
||||
return getAuthHeader(imageReference, null);
|
||||
}
|
||||
|
||||
private String getAuthHeader(ImageReference imageReference, DockerRegistryAuthentication fallback) {
|
||||
DockerRegistryConfigAuthentication authentication = getAuthentication(fallback);
|
||||
return authentication.getAuthHeader(imageReference);
|
||||
}
|
||||
|
||||
private DockerRegistryConfigAuthentication getAuthentication(DockerRegistryAuthentication fallback) {
|
||||
return new DockerRegistryConfigAuthentication(fallback, this.helperExceptions::put, this.environment::get,
|
||||
this.credentialHelpers::get);
|
||||
}
|
||||
|
||||
private void mockHelper(String name, String serverUrl, String credentialsResourceName) throws Exception {
|
||||
CredentialHelper helper = mockHelper(name);
|
||||
given(helper.get(serverUrl)).willReturn(getCredentials(credentialsResourceName));
|
||||
}
|
||||
|
||||
private CredentialHelper mockHelper(String name) {
|
||||
CredentialHelper helper = mock(CredentialHelper.class);
|
||||
this.credentialHelpers.put(name, helper);
|
||||
return helper;
|
||||
}
|
||||
|
||||
private Credential getCredentials(String resourceName) throws Exception {
|
||||
try (InputStream inputStream = new ClassPathResource(resourceName).getInputStream()) {
|
||||
return new Credential(SharedObjectMapper.get().readTree(inputStream));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> decode(String authHeader) throws Exception {
|
||||
assertThat(authHeader).isNotNull();
|
||||
return SharedObjectMapper.get().readValue(Base64.getDecoder().decode(authHeader), new TypeReference<>() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
|
||||
set /p registryUrl=
|
||||
|
||||
if "%registryUrl%" == "user.example.com" (
|
||||
echo {
|
||||
echo "ServerURL": "%registryUrl%",
|
||||
echo "Username": "username",
|
||||
echo "Secret": "secret"
|
||||
echo }
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "token.example.com" (
|
||||
echo {
|
||||
echo "ServerURL": "%registryUrl%",
|
||||
echo "Username": "<token>",
|
||||
echo "Secret": "secret"
|
||||
echo }
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "url.missing.example.com" (
|
||||
echo no credentials server URL >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "username.missing.example.com" (
|
||||
echo no credentials username >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%registryUrl%" == "credentials.missing.example.com" (
|
||||
echo credentials not found in native keychain >&2
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Unknown error >&2
|
||||
exit /b 1
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
read -r registryUrl
|
||||
|
||||
if [ "$registryUrl" = "user.example.com" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"ServerURL": "${registryUrl}",
|
||||
"Username": "username",
|
||||
"Secret": "secret"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "token.example.com" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"ServerURL": "${registryUrl}",
|
||||
"Username": "<token>",
|
||||
"Secret": "secret"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "url.missing.example.com" ]; then
|
||||
echo "no credentials server URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "username.missing.example.com" ]; then
|
||||
echo "no credentials username" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$registryUrl" = "credentials.missing.example.com" ]; then
|
||||
echo "credentials not found in native keychain" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Unknown error" >&2
|
||||
exit 1
|
||||
Reference in New Issue
Block a user