Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src/main/java/land/oras/auth/AuthStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static AuthStore newStore(List<Path> configPaths) {
/**
* Creates a new FileStore from default location.
* If the {@code REGISTRY_AUTH_FILE} environment variable is set it is used exclusively.
* Otherwise, the Docker config and (when {@code XDG_RUNTIME_DIR} is set) the Podman auth file are searched.
* Otherwise, the Docker config and the Podman auth files are searched.
*
* @return FileStore instance.
*/
Expand All @@ -123,19 +123,39 @@ public static AuthStore newStore() {
}

/**
* Returns the ordered list of auth file paths to search when no {@code REGISTRY_AUTH_FILE} is set.
* Docker config is always included; the Podman auth file is added when {@code XDG_RUNTIME_DIR} is set.
* Returns the ordered list of auth file paths to search when no {@code REGISTRY_AUTH_FILE} is set:
* the Docker config, the Podman config home auth file and, when {@code XDG_RUNTIME_DIR} is set,
* the Podman runtime auth file. Later files take precedence over earlier ones.
*
* @return list of candidate paths.
*/
private static List<Path> defaultAuthPaths() {
Path dockerPath = Path.of(System.getProperty("user.home"), ".docker", "config.json");
List<Path> paths = new ArrayList<>();
paths.add(dockerConfigDir().resolve("config.json"));
// https://github.com/containers/image/blob/main/pkg/docker/config/config.go
paths.add(xdgConfigHome().resolve("containers").resolve("auth.json"));
String xdgRuntimeDir = System.getenv("XDG_RUNTIME_DIR");
if (xdgRuntimeDir != null) {
// https://docs.podman.io/en/stable/markdown/podman-login.1.html#description
return List.of(dockerPath, Path.of(xdgRuntimeDir, "containers", "auth.json"));
paths.add(Path.of(xdgRuntimeDir, "containers", "auth.json"));
}
return List.of(dockerPath);
return paths;
}

private static Path dockerConfigDir() {
String dockerConfig = System.getenv("DOCKER_CONFIG");
if (dockerConfig != null && !dockerConfig.isEmpty()) {
return Path.of(dockerConfig);
}
return Path.of(System.getProperty("user.home"), ".docker");
}

private static Path xdgConfigHome() {
String xdgConfigHome = System.getenv("XDG_CONFIG_HOME");
if (xdgConfigHome != null && !xdgConfigHome.isEmpty()) {
return Path.of(xdgConfigHome);
}
return Path.of(System.getProperty("user.home"), ".config");
}

/**
Expand Down
104 changes: 104 additions & 0 deletions src/test/java/land/oras/auth/AuthStoreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ class AuthStoreTest {
@TempDir
private static Path xdgRuntimeDir;

@TempDir
private static Path xdgConfigHome;

@TempDir
private static Path podmanHomeDir;

@TempDir
private static Path dockerConfigDir;

private AuthStore authStore;
private AuthStore.Config mockConfig;
private AuthStore.Credential mockCredential;
Expand Down Expand Up @@ -110,6 +119,18 @@ static void init() throws Exception {
Files.createDirectory(xdgRuntimeDir.resolve("containers"));
Files.writeString(xdgRuntimeDir.resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);

// Write a sample Podman config file in the XDG config home
Files.createDirectory(xdgConfigHome.resolve("containers"));
Files.writeString(xdgConfigHome.resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);

// Write a sample Podman config file below a home directory, as podman does on macOS
Files.createDirectories(podmanHomeDir.resolve(".config").resolve("containers"));
Files.writeString(
podmanHomeDir.resolve(".config").resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);

// Write a sample Docker config file in a DOCKER_CONFIG directory
Files.writeString(dockerConfigDir.resolve("config.json"), SAMPLE_DOCKER_CONFIG);

Path helper = Path.of("docker-credential-fake");
String newPath =
helper.toAbsolutePath().getParent() + System.getProperty("path.separator") + System.getenv("PATH");
Expand All @@ -129,6 +150,8 @@ void testShouldReadCredentialsFromCredentialHelperNullCheck() throws Exception {
new EnvironmentVariables()
.set("XDG_RUNTIME_DIR", "not-used")
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand All @@ -148,6 +171,8 @@ void testShouldReadCredentialsFromCredentialStoreNullCheck() throws Exception {
new EnvironmentVariables()
.set("XDG_RUNTIME_DIR", "not-used")
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand Down Expand Up @@ -180,6 +205,8 @@ void testShouldReadCredentialsFromCredentialHelperFake() throws Exception {
.set("XDG_RUNTIME_DIR", "not-used")
.set("PATH", newPath)
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand Down Expand Up @@ -212,6 +239,8 @@ void testShouldReadCredentialsFromCredentialHelperHandleNonZeroReturnCode() thro
.set("XDG_RUNTIME_DIR", "not-used")
.set("PATH", newPath)
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand All @@ -231,6 +260,8 @@ void testShouldReadCredentialsFromDockerConfig() throws Exception {
new EnvironmentVariables()
.set("XDG_RUNTIME_DIR", "not-used")
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand Down Expand Up @@ -262,6 +293,8 @@ void testShouldReadCredentialsFromPodManConfig() throws Exception {
new EnvironmentVariables()
.set("XDG_RUNTIME_DIR", xdgRuntimeDir.toAbsolutePath().toString())
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", "not-used").execute(() -> {
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
Expand Down Expand Up @@ -367,11 +400,78 @@ void testHierarchicalCredentialLookupNoMatch() throws Exception {
assertNull(credential);
}

@Test
void testShouldReadCredentialsFromPodmanConfigHome() throws Exception {
new EnvironmentVariables()
.set("XDG_CONFIG_HOME", xdgConfigHome.toAbsolutePath().toString())
.remove("XDG_RUNTIME_DIR")
.remove("REGISTRY_AUTH_FILE")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", "not-used").execute(() -> {
AuthStore authStoreInstance = AuthStore.newStore();
assertNotNull(authStoreInstance);

AuthStore.Credential credential =
authStoreInstance.get(ContainerRef.parse("registry.other.com/foo/bar:latest"));
assertNotNull(credential);
assertEquals(USERNAME, credential.username());
assertEquals(PASSWORD, credential.password());
});
});
}

@Test
void testShouldReadCredentialsFromPodmanConfigHomeViaUserHome() throws Exception {
new EnvironmentVariables()
.remove("XDG_CONFIG_HOME")
.remove("XDG_RUNTIME_DIR")
.remove("REGISTRY_AUTH_FILE")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties(
"user.home", podmanHomeDir.toAbsolutePath().toString())
.execute(() -> {
AuthStore authStoreInstance = AuthStore.newStore();
assertNotNull(authStoreInstance);

AuthStore.Credential credential =
authStoreInstance.get(ContainerRef.parse("registry.other.com/foo/bar:latest"));
assertNotNull(credential);
assertEquals(USERNAME, credential.username());
assertEquals(PASSWORD, credential.password());
});
});
}

@Test
void testDockerConfigEnvOverridesUserHome() throws Exception {
new EnvironmentVariables()
.set("DOCKER_CONFIG", dockerConfigDir.toAbsolutePath().toString())
.remove("XDG_CONFIG_HOME")
.remove("XDG_RUNTIME_DIR")
.remove("REGISTRY_AUTH_FILE")
.execute(() -> {
new SystemProperties("user.home", "not-used").execute(() -> {
AuthStore authStoreInstance = AuthStore.newStore();
assertNotNull(authStoreInstance);

AuthStore.Credential credential =
authStoreInstance.get(ContainerRef.parse("registry.example.com/foo/bar:latest"));
assertNotNull(credential);
assertEquals(USERNAME, credential.username());
assertEquals(PASSWORD, credential.password());
});
});
}

@Test
void testWithoutXdgRuntimeDir() throws Exception {
new EnvironmentVariables()
.remove("XDG_RUNTIME_DIR")
.remove("REGISTRY_AUTH_FILE")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
assertNull(System.getenv("XDG_RUNTIME_DIR"));
AuthStore authStoreInstance = AuthStore.newStore();
Expand All @@ -387,6 +487,8 @@ void testRegistryAuthFileIsUsedWhenSet() throws Exception {
new EnvironmentVariables()
.set("REGISTRY_AUTH_FILE", authFile.toAbsolutePath().toString())
.remove("XDG_RUNTIME_DIR")
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
AuthStore authStoreInstance = AuthStore.newStore();
Expand Down Expand Up @@ -416,6 +518,8 @@ void testRegistryAuthFileTakesPrecedenceOverDefaults() throws Exception {
new EnvironmentVariables()
.set("REGISTRY_AUTH_FILE", authFile.toAbsolutePath().toString())
.set("XDG_RUNTIME_DIR", xdgRuntimeDir.toAbsolutePath().toString())
.remove("XDG_CONFIG_HOME")
.remove("DOCKER_CONFIG")
.execute(() -> {
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
AuthStore authStoreInstance = AuthStore.newStore();
Expand Down
Loading