Skip to content

Commit a851884

Browse files
authored
Merge pull request #2 from andreaTP/fix-podman-auth-config-home
Search the Podman config home and DOCKER_CONFIG for credentials
2 parents 587500e + 27f2028 commit a851884

2 files changed

Lines changed: 130 additions & 6 deletions

File tree

‎src/main/java/land/oras/auth/AuthStore.java‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static AuthStore newStore(List<Path> configPaths) {
109109
/**
110110
* Creates a new FileStore from default location.
111111
* If the {@code REGISTRY_AUTH_FILE} environment variable is set it is used exclusively.
112-
* Otherwise, the Docker config and (when {@code XDG_RUNTIME_DIR} is set) the Podman auth file are searched.
112+
* Otherwise, the Docker config and the Podman auth files are searched.
113113
*
114114
* @return FileStore instance.
115115
*/
@@ -123,19 +123,39 @@ public static AuthStore newStore() {
123123
}
124124

125125
/**
126-
* Returns the ordered list of auth file paths to search when no {@code REGISTRY_AUTH_FILE} is set.
127-
* Docker config is always included; the Podman auth file is added when {@code XDG_RUNTIME_DIR} is set.
126+
* Returns the ordered list of auth file paths to search when no {@code REGISTRY_AUTH_FILE} is set:
127+
* the Docker config, the Podman config home auth file and, when {@code XDG_RUNTIME_DIR} is set,
128+
* the Podman runtime auth file. Later files take precedence over earlier ones.
128129
*
129130
* @return list of candidate paths.
130131
*/
131132
private static List<Path> defaultAuthPaths() {
132-
Path dockerPath = Path.of(System.getProperty("user.home"), ".docker", "config.json");
133+
List<Path> paths = new ArrayList<>();
134+
paths.add(dockerConfigDir().resolve("config.json"));
135+
// https://github.com/containers/image/blob/main/pkg/docker/config/config.go
136+
paths.add(xdgConfigHome().resolve("containers").resolve("auth.json"));
133137
String xdgRuntimeDir = System.getenv("XDG_RUNTIME_DIR");
134138
if (xdgRuntimeDir != null) {
135139
// https://docs.podman.io/en/stable/markdown/podman-login.1.html#description
136-
return List.of(dockerPath, Path.of(xdgRuntimeDir, "containers", "auth.json"));
140+
paths.add(Path.of(xdgRuntimeDir, "containers", "auth.json"));
137141
}
138-
return List.of(dockerPath);
142+
return paths;
143+
}
144+
145+
private static Path dockerConfigDir() {
146+
String dockerConfig = System.getenv("DOCKER_CONFIG");
147+
if (dockerConfig != null && !dockerConfig.isEmpty()) {
148+
return Path.of(dockerConfig);
149+
}
150+
return Path.of(System.getProperty("user.home"), ".docker");
151+
}
152+
153+
private static Path xdgConfigHome() {
154+
String xdgConfigHome = System.getenv("XDG_CONFIG_HOME");
155+
if (xdgConfigHome != null && !xdgConfigHome.isEmpty()) {
156+
return Path.of(xdgConfigHome);
157+
}
158+
return Path.of(System.getProperty("user.home"), ".config");
139159
}
140160

141161
/**

‎src/test/java/land/oras/auth/AuthStoreTest.java‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ class AuthStoreTest {
5151
@TempDir
5252
private static Path xdgRuntimeDir;
5353

54+
@TempDir
55+
private static Path xdgConfigHome;
56+
57+
@TempDir
58+
private static Path podmanHomeDir;
59+
60+
@TempDir
61+
private static Path dockerConfigDir;
62+
5463
private AuthStore authStore;
5564
private AuthStore.Config mockConfig;
5665
private AuthStore.Credential mockCredential;
@@ -110,6 +119,18 @@ static void init() throws Exception {
110119
Files.createDirectory(xdgRuntimeDir.resolve("containers"));
111120
Files.writeString(xdgRuntimeDir.resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);
112121

122+
// Write a sample Podman config file in the XDG config home
123+
Files.createDirectory(xdgConfigHome.resolve("containers"));
124+
Files.writeString(xdgConfigHome.resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);
125+
126+
// Write a sample Podman config file below a home directory, as podman does on macOS
127+
Files.createDirectories(podmanHomeDir.resolve(".config").resolve("containers"));
128+
Files.writeString(
129+
podmanHomeDir.resolve(".config").resolve("containers").resolve("auth.json"), SAMPLE_PODMAN_CONFIG);
130+
131+
// Write a sample Docker config file in a DOCKER_CONFIG directory
132+
Files.writeString(dockerConfigDir.resolve("config.json"), SAMPLE_DOCKER_CONFIG);
133+
113134
Path helper = Path.of("docker-credential-fake");
114135
String newPath =
115136
helper.toAbsolutePath().getParent() + System.getProperty("path.separator") + System.getenv("PATH");
@@ -129,6 +150,8 @@ void testShouldReadCredentialsFromCredentialHelperNullCheck() throws Exception {
129150
new EnvironmentVariables()
130151
.set("XDG_RUNTIME_DIR", "not-used")
131152
.remove("REGISTRY_AUTH_FILE")
153+
.remove("XDG_CONFIG_HOME")
154+
.remove("DOCKER_CONFIG")
132155
.execute(() -> {
133156
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
134157
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -148,6 +171,8 @@ void testShouldReadCredentialsFromCredentialStoreNullCheck() throws Exception {
148171
new EnvironmentVariables()
149172
.set("XDG_RUNTIME_DIR", "not-used")
150173
.remove("REGISTRY_AUTH_FILE")
174+
.remove("XDG_CONFIG_HOME")
175+
.remove("DOCKER_CONFIG")
151176
.execute(() -> {
152177
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
153178
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -180,6 +205,8 @@ void testShouldReadCredentialsFromCredentialHelperFake() throws Exception {
180205
.set("XDG_RUNTIME_DIR", "not-used")
181206
.set("PATH", newPath)
182207
.remove("REGISTRY_AUTH_FILE")
208+
.remove("XDG_CONFIG_HOME")
209+
.remove("DOCKER_CONFIG")
183210
.execute(() -> {
184211
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
185212
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -212,6 +239,8 @@ void testShouldReadCredentialsFromCredentialHelperHandleNonZeroReturnCode() thro
212239
.set("XDG_RUNTIME_DIR", "not-used")
213240
.set("PATH", newPath)
214241
.remove("REGISTRY_AUTH_FILE")
242+
.remove("XDG_CONFIG_HOME")
243+
.remove("DOCKER_CONFIG")
215244
.execute(() -> {
216245
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
217246
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -231,6 +260,8 @@ void testShouldReadCredentialsFromDockerConfig() throws Exception {
231260
new EnvironmentVariables()
232261
.set("XDG_RUNTIME_DIR", "not-used")
233262
.remove("REGISTRY_AUTH_FILE")
263+
.remove("XDG_CONFIG_HOME")
264+
.remove("DOCKER_CONFIG")
234265
.execute(() -> {
235266
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
236267
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -262,6 +293,8 @@ void testShouldReadCredentialsFromPodManConfig() throws Exception {
262293
new EnvironmentVariables()
263294
.set("XDG_RUNTIME_DIR", xdgRuntimeDir.toAbsolutePath().toString())
264295
.remove("REGISTRY_AUTH_FILE")
296+
.remove("XDG_CONFIG_HOME")
297+
.remove("DOCKER_CONFIG")
265298
.execute(() -> {
266299
new SystemProperties("user.home", "not-used").execute(() -> {
267300
assertNotNull(System.getenv("XDG_RUNTIME_DIR"));
@@ -367,11 +400,78 @@ void testHierarchicalCredentialLookupNoMatch() throws Exception {
367400
assertNull(credential);
368401
}
369402

403+
@Test
404+
void testShouldReadCredentialsFromPodmanConfigHome() throws Exception {
405+
new EnvironmentVariables()
406+
.set("XDG_CONFIG_HOME", xdgConfigHome.toAbsolutePath().toString())
407+
.remove("XDG_RUNTIME_DIR")
408+
.remove("REGISTRY_AUTH_FILE")
409+
.remove("DOCKER_CONFIG")
410+
.execute(() -> {
411+
new SystemProperties("user.home", "not-used").execute(() -> {
412+
AuthStore authStoreInstance = AuthStore.newStore();
413+
assertNotNull(authStoreInstance);
414+
415+
AuthStore.Credential credential =
416+
authStoreInstance.get(ContainerRef.parse("registry.other.com/foo/bar:latest"));
417+
assertNotNull(credential);
418+
assertEquals(USERNAME, credential.username());
419+
assertEquals(PASSWORD, credential.password());
420+
});
421+
});
422+
}
423+
424+
@Test
425+
void testShouldReadCredentialsFromPodmanConfigHomeViaUserHome() throws Exception {
426+
new EnvironmentVariables()
427+
.remove("XDG_CONFIG_HOME")
428+
.remove("XDG_RUNTIME_DIR")
429+
.remove("REGISTRY_AUTH_FILE")
430+
.remove("DOCKER_CONFIG")
431+
.execute(() -> {
432+
new SystemProperties(
433+
"user.home", podmanHomeDir.toAbsolutePath().toString())
434+
.execute(() -> {
435+
AuthStore authStoreInstance = AuthStore.newStore();
436+
assertNotNull(authStoreInstance);
437+
438+
AuthStore.Credential credential =
439+
authStoreInstance.get(ContainerRef.parse("registry.other.com/foo/bar:latest"));
440+
assertNotNull(credential);
441+
assertEquals(USERNAME, credential.username());
442+
assertEquals(PASSWORD, credential.password());
443+
});
444+
});
445+
}
446+
447+
@Test
448+
void testDockerConfigEnvOverridesUserHome() throws Exception {
449+
new EnvironmentVariables()
450+
.set("DOCKER_CONFIG", dockerConfigDir.toAbsolutePath().toString())
451+
.remove("XDG_CONFIG_HOME")
452+
.remove("XDG_RUNTIME_DIR")
453+
.remove("REGISTRY_AUTH_FILE")
454+
.execute(() -> {
455+
new SystemProperties("user.home", "not-used").execute(() -> {
456+
AuthStore authStoreInstance = AuthStore.newStore();
457+
assertNotNull(authStoreInstance);
458+
459+
AuthStore.Credential credential =
460+
authStoreInstance.get(ContainerRef.parse("registry.example.com/foo/bar:latest"));
461+
assertNotNull(credential);
462+
assertEquals(USERNAME, credential.username());
463+
assertEquals(PASSWORD, credential.password());
464+
});
465+
});
466+
}
467+
370468
@Test
371469
void testWithoutXdgRuntimeDir() throws Exception {
372470
new EnvironmentVariables()
373471
.remove("XDG_RUNTIME_DIR")
374472
.remove("REGISTRY_AUTH_FILE")
473+
.remove("XDG_CONFIG_HOME")
474+
.remove("DOCKER_CONFIG")
375475
.execute(() -> {
376476
assertNull(System.getenv("XDG_RUNTIME_DIR"));
377477
AuthStore authStoreInstance = AuthStore.newStore();
@@ -387,6 +487,8 @@ void testRegistryAuthFileIsUsedWhenSet() throws Exception {
387487
new EnvironmentVariables()
388488
.set("REGISTRY_AUTH_FILE", authFile.toAbsolutePath().toString())
389489
.remove("XDG_RUNTIME_DIR")
490+
.remove("XDG_CONFIG_HOME")
491+
.remove("DOCKER_CONFIG")
390492
.execute(() -> {
391493
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
392494
AuthStore authStoreInstance = AuthStore.newStore();
@@ -416,6 +518,8 @@ void testRegistryAuthFileTakesPrecedenceOverDefaults() throws Exception {
416518
new EnvironmentVariables()
417519
.set("REGISTRY_AUTH_FILE", authFile.toAbsolutePath().toString())
418520
.set("XDG_RUNTIME_DIR", xdgRuntimeDir.toAbsolutePath().toString())
521+
.remove("XDG_CONFIG_HOME")
522+
.remove("DOCKER_CONFIG")
419523
.execute(() -> {
420524
new SystemProperties("user.home", homeDir.toAbsolutePath().toString()).execute(() -> {
421525
AuthStore authStoreInstance = AuthStore.newStore();

0 commit comments

Comments
 (0)