-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Docker authentiation using credential store/helpers
- Loading branch information
Showing
11 changed files
with
243 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,3 +46,5 @@ node_modules/ | |
|
||
.gradle/ | ||
build/ | ||
out/ | ||
*.class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
core/src/main/java/org/testcontainers/utility/RegistryAuthLocator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package org.testcontainers.utility; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.github.dockerjava.api.model.AuthConfig; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.slf4j.Logger; | ||
import org.zeroturnaround.exec.ProcessExecutor; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.File; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static org.slf4j.LoggerFactory.getLogger; | ||
|
||
/** | ||
* Utility to look up registry authentication information for an image. | ||
*/ | ||
public class RegistryAuthLocator { | ||
|
||
private static final Logger log = getLogger(RegistryAuthLocator.class); | ||
|
||
private final AuthConfig defaultAuthConfig; | ||
private final File configFile; | ||
private final String commandPathPrefix; | ||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
||
@VisibleForTesting | ||
RegistryAuthLocator(AuthConfig defaultAuthConfig, File configFile, String commandPathPrefix) { | ||
this.defaultAuthConfig = defaultAuthConfig; | ||
this.configFile = configFile; | ||
this.commandPathPrefix = commandPathPrefix; | ||
} | ||
|
||
/** | ||
* @param defaultAuthConfig an AuthConfig object that should be returned if there is no overriding authentication | ||
* available for images that are looked up | ||
*/ | ||
public RegistryAuthLocator(AuthConfig defaultAuthConfig) { | ||
this.defaultAuthConfig = defaultAuthConfig; | ||
this.configFile = new File(System.getProperty("user.home") + "/.docker/config.json"); | ||
this.commandPathPrefix = ""; | ||
} | ||
|
||
/** | ||
* Looks up an AuthConfig for a given image name. | ||
* | ||
* @param dockerImageName image name to be looked up (potentially including a registry URL part) | ||
* @return an AuthConfig that is applicable to this specific image OR the defaultAuthConfig that has been set for | ||
* this {@link RegistryAuthLocator}. | ||
*/ | ||
public AuthConfig lookupAuthConfig(DockerImageName dockerImageName) { | ||
log.debug("Looking up auth config for image: {}", dockerImageName); | ||
try { | ||
final JsonNode config = OBJECT_MAPPER.readTree(configFile); | ||
|
||
final String reposName = dockerImageName.getRegistry(); | ||
final JsonNode auths = config.at("/auths/" + reposName); | ||
|
||
if (!auths.isMissingNode() && auths.size() == 0) { | ||
// auths/<registry> is an empty dict - use a credential helper | ||
return authConfigUsingCredentialsStoreOrHelper(reposName, config); | ||
} | ||
} catch (Exception e) { | ||
log.error("Failure when attempting to lookup auth config. Falling back to docker-java default behaviour", e); | ||
} | ||
return defaultAuthConfig; | ||
} | ||
|
||
private AuthConfig authConfigUsingCredentialsStoreOrHelper(String hostName, JsonNode config) throws Exception { | ||
|
||
final String credsStoreName = config.at("/credsStore").asText(); | ||
final String credHelper = config.at("/credHelpers/" + hostName).asText(); | ||
|
||
if (StringUtils.isNotBlank(credHelper)) { | ||
return runCredentialProvider(hostName, credHelper); | ||
} else if (StringUtils.isNotBlank(credsStoreName)) { | ||
return runCredentialProvider(hostName, credsStoreName); | ||
} else { | ||
throw new UnsupportedOperationException(); | ||
} | ||
} | ||
|
||
private AuthConfig runCredentialProvider(String hostName, String credHelper) throws Exception { | ||
final String credentialHelperName = commandPathPrefix + "docker-credential-" + credHelper; | ||
String data; | ||
|
||
log.debug("Executing docker credential helper: {} to locate auth config for: {}", | ||
credentialHelperName, hostName); | ||
|
||
try { | ||
data = new ProcessExecutor() | ||
.command(credentialHelperName, "get") | ||
.redirectInput(new ByteArrayInputStream(hostName.getBytes())) | ||
.readOutput(true) | ||
.exitValueNormal() | ||
.timeout(30, TimeUnit.SECONDS) | ||
.execute() | ||
.outputUTF8() | ||
.trim(); | ||
} catch (Exception e) { | ||
log.error("Failure running docker credential helper ({})", credentialHelperName); | ||
throw e; | ||
} | ||
|
||
final JsonNode helperResponse = OBJECT_MAPPER.readTree(data); | ||
log.debug("Credential helper provided auth config for: {}", hostName); | ||
|
||
return new AuthConfig() | ||
.withRegistryAddress(helperResponse.at("/ServerURL").asText()) | ||
.withUsername(helperResponse.at("/Username").asText()) | ||
.withPassword(helperResponse.at("/Secret").asText()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
core/src/test/java/org/testcontainers/utility/RegistryAuthLocatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.testcontainers.utility; | ||
|
||
import com.github.dockerjava.api.model.AuthConfig; | ||
import com.google.common.io.Resources; | ||
import org.apache.commons.lang.SystemUtils; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.junit.Assume; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
import java.io.File; | ||
import java.net.URISyntaxException; | ||
import java.nio.file.Paths; | ||
|
||
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; | ||
import static org.rnorth.visibleassertions.VisibleAssertions.assertNull; | ||
|
||
public class RegistryAuthLocatorTest { | ||
|
||
@BeforeClass | ||
public static void nonWindowsTest() throws Exception { | ||
Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS); | ||
} | ||
|
||
@Test | ||
public void lookupAuthConfigWithoutCredentials() throws URISyntaxException { | ||
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-store.json"); | ||
|
||
final AuthConfig authConfig = authLocator.lookupAuthConfig(new DockerImageName("unauthenticated.registry.org/org/repo")); | ||
|
||
assertEquals("Default docker registry URL is set on auth config", "https://index.docker.io/v1/", authConfig.getRegistryAddress()); | ||
assertNull("No username is set", authConfig.getUsername()); | ||
assertNull("No password is set", authConfig.getPassword()); | ||
} | ||
|
||
@Test | ||
public void lookupAuthConfigUsingStore() throws URISyntaxException { | ||
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-store.json"); | ||
|
||
final AuthConfig authConfig = authLocator.lookupAuthConfig(new DockerImageName("registry.example.com/org/repo")); | ||
|
||
assertEquals("Correct server URL is obtained from a credential store", "url", authConfig.getRegistryAddress()); | ||
assertEquals("Correct username is obtained from a credential store", "username", authConfig.getUsername()); | ||
assertEquals("Correct secret is obtained from a credential store", "secret", authConfig.getPassword()); | ||
} | ||
|
||
@Test | ||
public void lookupAuthConfigUsingHelper() throws URISyntaxException { | ||
final RegistryAuthLocator authLocator = createTestAuthLocator("config-with-helper.json"); | ||
|
||
final AuthConfig authConfig = authLocator.lookupAuthConfig(new DockerImageName("registry.example.com/org/repo")); | ||
|
||
assertEquals("Correct server URL is obtained from a credential store", "url", authConfig.getRegistryAddress()); | ||
assertEquals("Correct username is obtained from a credential store", "username", authConfig.getUsername()); | ||
assertEquals("Correct secret is obtained from a credential store", "secret", authConfig.getPassword()); | ||
} | ||
|
||
@NotNull | ||
private RegistryAuthLocator createTestAuthLocator(String configName) throws URISyntaxException { | ||
final File configFile = Paths.get(Resources.getResource("auth-config/" + configName).toURI()).toFile(); | ||
return new RegistryAuthLocator(new AuthConfig(), configFile, configFile.getParentFile().getAbsolutePath() + "/"); | ||
} | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
core/src/test/resources/auth-config/config-with-helper-and-store.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"auths": { | ||
"registry.example.com": {} | ||
}, | ||
"HttpHeaders": { | ||
"User-Agent": "Docker-Client/18.03.0-ce (darwin)" | ||
}, | ||
"credsStore": "fake", | ||
"credHelpers": { | ||
"registry.example.com": "fake" | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
core/src/test/resources/auth-config/config-with-helper.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"auths": { | ||
"registry.example.com": {} | ||
}, | ||
"HttpHeaders": { | ||
"User-Agent": "Docker-Client/18.03.0-ce (darwin)" | ||
}, | ||
"credHelpers": { | ||
"registry.example.com": "fake" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"auths": { | ||
"registry.example.com": {} | ||
}, | ||
"HttpHeaders": { | ||
"User-Agent": "Docker-Client/18.03.0-ce (darwin)" | ||
}, | ||
"credsStore": "fake" | ||
} |
13 changes: 13 additions & 0 deletions
13
core/src/test/resources/auth-config/docker-credential-fake
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/bin/sh | ||
|
||
if [[ $1 != "get" ]]; then | ||
exit 1 | ||
fi | ||
|
||
read > /dev/null | ||
|
||
echo '{' \ | ||
' "ServerURL": "url",' \ | ||
' "Username": "username",' \ | ||
' "Secret": "secret"' \ | ||
'}' |