diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java index 50c97929a0..08bfd59139 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java @@ -140,25 +140,34 @@ Optional retrieve(DockerConfig dockerConfig, Consumer logg // Lastly, find defined auth. AuthTemplate auth = dockerConfig.getAuthFor(registryAlias); - if (auth != null && auth.getAuth() != null) { - // 'auth' is a basic authentication token that should be parsed back into credentials - String usernameColonPassword = - new String(Base64.decodeBase64(auth.getAuth()), StandardCharsets.UTF_8); - String username = usernameColonPassword.substring(0, usernameColonPassword.indexOf(":")); - String password = usernameColonPassword.substring(usernameColonPassword.indexOf(":") + 1); - logger.accept( - LogEvent.info("Docker config auths section defines credentials for " + registryAlias)); - if (auth.getIdentityToken() != null - // These username and password checks may be unnecessary, but doing so to restrict the - // scope only to the Azure behavior to maintain maximum backward-compatibilty. - && username.equals("00000000-0000-0000-0000-000000000000") - && password.isEmpty()) { + if (auth != null) { + if (auth.getAuth() != null) { + // 'auth' is a basic authentication token that should be parsed back into credentials + String usernameColonPassword = + new String(Base64.decodeBase64(auth.getAuth()), StandardCharsets.UTF_8); + String username = usernameColonPassword.substring(0, usernameColonPassword.indexOf(":")); + String password = usernameColonPassword.substring(usernameColonPassword.indexOf(":") + 1); logger.accept( - LogEvent.info("Using 'identityToken' in Docker config auth for " + registryAlias)); - return Optional.of( - Credential.from(Credential.OAUTH2_TOKEN_USER_NAME, auth.getIdentityToken())); + LogEvent.info( + "Docker config auths section defines credentials for " + registryAlias)); + if (auth.getIdentityToken() != null + // These username and password checks may be unnecessary, but doing so to restrict the + // scope only to the Azure behavior to maintain maximum backward-compatibilty. + && username.equals("00000000-0000-0000-0000-000000000000") + && password.isEmpty()) { + logger.accept( + LogEvent.info("Using 'identityToken' in Docker config auth for " + registryAlias)); + return Optional.of( + Credential.from(Credential.OAUTH2_TOKEN_USER_NAME, auth.getIdentityToken())); + } + return Optional.of(Credential.from(username, password)); + } else if (auth.getUsername() != null && auth.getPassword() != null) { + logger.accept( + LogEvent.info( + "Docker config auths section defines username and password for " + + registryAlias)); + return Optional.of(Credential.from(auth.getUsername(), auth.getPassword())); } - return Optional.of(Credential.from(username, password)); } } return Optional.empty(); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java index acb49e15b8..99120e3ea4 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java @@ -33,6 +33,8 @@ * "auths": { * "registry": { * "auth": "username:password string in base64", + * "username": "..." + * "password": "..." * "identityToken": "..." * }, * "anotherregistry": {}, @@ -66,6 +68,10 @@ public static class AuthTemplate implements JsonTemplate { @Nullable private String auth; + @Nullable private String username; + + @Nullable private String password; + // Both "identitytoken" and "identityToken" have been observed. For example, // https://github.com/GoogleContainerTools/jib/issues/2488 // https://github.com/spotify/docker-client/issues/580 @@ -76,6 +82,16 @@ public String getAuth() { return auth; } + @Nullable + public String getUsername() { + return username; + } + + @Nullable + public String getPassword() { + return password; + } + @Nullable public String getIdentityToken() { return identityToken; diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetrieverTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetrieverTest.java index 47803f2de5..509ed28ed4 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetrieverTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetrieverTest.java @@ -76,6 +76,36 @@ public void testRetrieve_hasAuth() throws IOException { "Docker config auths section defines credentials for some other registry")); } + @Test + public void testRetrieve_authTakesPrecedenceOverUsernameAndPassword() throws IOException { + DockerConfigCredentialRetriever dockerConfigCredentialRetriever = + DockerConfigCredentialRetriever.create("auth and userpw registry", dockerConfigFile); + + Optional credentials = dockerConfigCredentialRetriever.retrieve(mockLogger); + Assert.assertTrue(credentials.isPresent()); + Assert.assertEquals("some", credentials.get().getUsername()); + Assert.assertEquals("auth", credentials.get().getPassword()); + Mockito.verify(mockLogger) + .accept( + LogEvent.info( + "Docker config auths section defines credentials for auth and userpw registry")); + } + + @Test + public void testRetrieve_hasAuthWithUsernameAndPassword() throws IOException { + DockerConfigCredentialRetriever dockerConfigCredentialRetriever = + DockerConfigCredentialRetriever.create("userpw registry", dockerConfigFile); + + Optional credentials = dockerConfigCredentialRetriever.retrieve(mockLogger); + Assert.assertTrue(credentials.isPresent()); + Assert.assertEquals("someuser", credentials.get().getUsername()); + Assert.assertEquals("somepw", credentials.get().getPassword()); + Mockito.verify(mockLogger) + .accept( + LogEvent.info( + "Docker config auths section defines username and password for userpw registry")); + } + @Test public void testRetrieve_hasAuth_legacyConfigFormat() throws IOException, URISyntaxException { dockerConfigFile = Paths.get(Resources.getResource("core/json/legacy_dockercfg").toURI()); diff --git a/jib-core/src/test/resources/core/json/dockerconfig.json b/jib-core/src/test/resources/core/json/dockerconfig.json index 8df57f4153..06a4c77c8b 100644 --- a/jib-core/src/test/resources/core/json/dockerconfig.json +++ b/jib-core/src/test/resources/core/json/dockerconfig.json @@ -2,6 +2,8 @@ "auths":{ "some other registry":{"auth":"c29tZTpvdGhlcjphdXRo"}, "some registry":{"auth":"c29tZTphdXRo","password":"ignored"}, + "auth and userpw registry":{"auth":"c29tZTphdXRo","username":"ignored","password":"ignored"}, + "userpw registry":{"username":"someuser","password":"somepw"}, "https://registry":{"auth":"dG9rZW4="}, "example.com":{"auth":"should not match example"}, @@ -21,4 +23,4 @@ "another.example.com.in.helpers":"should not match example" } -} \ No newline at end of file +}