diff --git a/doc/changelog.md b/doc/changelog.md index 492b90bbe..8a7514d4b 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -3,6 +3,7 @@ * **0.32-SNAPSHOT** - Update to jnr-unixsocket 0.25 to solve concurrency issues (hopefully fixing #552) - Udate ECR AuthorizationToken URL to new endpoint (#1317) + - Allow including `com.amazonaws:aws-java-sdk-core` to pick up various forms of AWS credentials with which to authenticate at AWS ECR ([#1311](https://github.com/fabric8io/docker-maven-plugin/issues/1311)) * **0.32.0** (2020-01-08) - Support building dockerFile without pushing it to docker server ([#1197](https://github.com/fabric8io/docker-maven-plugin/issues/1197)) diff --git a/src/main/asciidoc/inc/_authentication.adoc b/src/main/asciidoc/inc/_authentication.adoc index bc17daf49..dd6f30faa 100644 --- a/src/main/asciidoc/inc/_authentication.adoc +++ b/src/main/asciidoc/inc/_authentication.adoc @@ -187,7 +187,37 @@ In case you're using temporary security credentials provided by the AWS Security To do so, either specify the `docker.auth` system property or provide an `` element alongside username & password in the `authConfig`. d-m-p will attempt to read AWS credentials from some well-known spots in case there is no explicit configuration: + * it will pick up ENV variables link:https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html[as documented for the AWS CLI] + * it will pick up temporary credentials of link:https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html[the IAM role of an EC2 instance] + * it will pick up temporary credentials of link:https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html[the IAM role of a fargate task (OR ECS with EC2 with ECS_AWSVPC_BLOCK_IMDS as "true")] + If any of these authentication information is accessible, it will be used. + +[NOTE] +==== +For a more complete, robust and reliable authentication experience, you can add the AWS SDK for Java as a dependency. + +[source,xml] +---- + + + io.fabric8 + docker-maven-plugin + + + com.amazonaws + aws-java-sdk-core + 1.11.707 + + + + +---- + +This extra dependency allows the usage of all link:https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html[options] that the AWS default credential provider chain provides. + +If the AWS SDK is found in the classpath, it takes precedence over the custom AWS credentials lookup mechanisms listed above. +==== diff --git a/src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java b/src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java index 92987b77c..4552ac050 100644 --- a/src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java +++ b/src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java @@ -39,6 +39,7 @@ import io.fabric8.maven.docker.access.AuthConfig; import io.fabric8.maven.docker.access.ecr.EcrExtendedAuth; +import io.fabric8.maven.docker.util.aws.AwsSdkAuthConfigFactory; /** * Factory for creating docker specific authentication configuration @@ -227,6 +228,12 @@ private AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, S // check EC2 instance role if registry is ECR if (EcrExtendedAuth.isAwsRegistry(registry)) { + ret = getAuthConfigViaAwsSdk(); + if (ret != null) { + log.debug("AuthConfig: AWS credentials from AWS SDK"); + return ret; + } + ret = getAuthConfigFromAwsEnvironmentVariables(); if (ret != null) { log.debug("AuthConfig: AWS credentials from ENV variables"); @@ -265,6 +272,18 @@ private AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, S return null; } + private AuthConfig getAuthConfigViaAwsSdk() { + try { + Class.forName("com.amazonaws.auth.DefaultAWSCredentialsProviderChain"); + } catch (ClassNotFoundException e) { + log.info("It appears that you're using AWS ECR." + + " Consider integrating the AWS SDK in order to make use of common AWS authentication mechanisms," + + " see https://dmp.fabric8.io/#extended-authentication"); + return null; + } + return new AwsSdkAuthConfigFactory(log).createAuthConfig(); + } + /** * Try using the AWS credentials provided via ENV variables. * See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html diff --git a/src/main/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactory.java b/src/main/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactory.java new file mode 100644 index 000000000..d5504341b --- /dev/null +++ b/src/main/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactory.java @@ -0,0 +1,40 @@ +package io.fabric8.maven.docker.util.aws; + +import io.fabric8.maven.docker.access.AuthConfig; +import io.fabric8.maven.docker.util.Logger; + +public class AwsSdkAuthConfigFactory { + + private final Logger log; + + public AwsSdkAuthConfigFactory(Logger log) { + this.log = log; + } + + public AuthConfig createAuthConfig() { + try { + Class credentialsProviderChainClass = Class.forName("com.amazonaws.auth.DefaultAWSCredentialsProviderChain"); + Object credentialsProviderChain = credentialsProviderChainClass.getDeclaredConstructor().newInstance(); + Object credentials = credentialsProviderChainClass.getMethod("getCredentials").invoke(credentialsProviderChain); + if (credentials == null) { + return null; + } + + Class sessionCredentialsClass = Class.forName("com.amazonaws.auth.AWSSessionCredentials"); + String sessionToken = sessionCredentialsClass.isInstance(credentials) + ? (String) sessionCredentialsClass.getMethod("getSessionToken").invoke(credentials) : null; + + Class credentialsClass = Class.forName("com.amazonaws.auth.AWSCredentials"); + return new AuthConfig( + (String) credentialsClass.getMethod("getAWSAccessKeyId").invoke(credentials), + (String) credentialsClass.getMethod("getAWSSecretKey").invoke(credentials), + "none", + sessionToken + ); + } catch (Throwable t) { + log.debug("Failed to fetch AWS credentials: %s", t); + return null; + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/amazonaws/auth/AWSCredentials.java b/src/test/java/com/amazonaws/auth/AWSCredentials.java new file mode 100644 index 000000000..411aacbf0 --- /dev/null +++ b/src/test/java/com/amazonaws/auth/AWSCredentials.java @@ -0,0 +1,24 @@ +package com.amazonaws.auth; + +/** + * Shameless copy of the original for testing {@link io.fabric8.maven.docker.util.aws.AwsSdkAuthConfigFactory}. + * Based on com.amazonaws:aws-java-sdk-core:1.11.707. + */ +public class AWSCredentials { + private final String accessKeyId; + private final String secretKey; + + public AWSCredentials(String accessKeyId, String secretKey) { + this.accessKeyId = accessKeyId; + this.secretKey = secretKey; + } + + public String getAWSAccessKeyId() { + return accessKeyId; + } + + public String getAWSSecretKey() { + return secretKey; + } + +} diff --git a/src/test/java/com/amazonaws/auth/AWSSessionCredentials.java b/src/test/java/com/amazonaws/auth/AWSSessionCredentials.java new file mode 100644 index 000000000..5c7b19f6c --- /dev/null +++ b/src/test/java/com/amazonaws/auth/AWSSessionCredentials.java @@ -0,0 +1,18 @@ +package com.amazonaws.auth; + +/** + * Shameless copy of the original for testing {@link io.fabric8.maven.docker.util.aws.AwsSdkAuthConfigFactory}. + * Based on com.amazonaws:aws-java-sdk-core:1.11.707. + */ +public class AWSSessionCredentials extends AWSCredentials { + + private final String sessionKey; + + public AWSSessionCredentials(String accessKeyId, String secretKey, String sessionKey) { + super(accessKeyId, secretKey); + this.sessionKey = sessionKey; + } + + public String getSessionToken() {return sessionKey;} + +} diff --git a/src/test/java/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.java b/src/test/java/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.java new file mode 100644 index 000000000..ff8bae0ec --- /dev/null +++ b/src/test/java/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.java @@ -0,0 +1,23 @@ +package com.amazonaws.auth; + +import static java.lang.System.getenv; + +/** + * Shameless copy of the original for testing {@link io.fabric8.maven.docker.util.aws.AwsSdkAuthConfigFactory}. + * Based on com.amazonaws:aws-java-sdk-core:1.11.707. + */ +public final class DefaultAWSCredentialsProviderChain { + + public AWSCredentials getCredentials() { + String accessKeyId = getenv("AWSCredentials.AWSAccessKeyId"); + if (accessKeyId == null) { + return null; + } + String secretKey = getenv("AWSCredentials.AWSSecretKey"); + String sessionToken = getenv("AWSSessionCredentials.SessionToken"); + return sessionToken == null + ? new AWSCredentials(accessKeyId, secretKey) + : new AWSSessionCredentials(accessKeyId,secretKey,sessionToken); + } + +} \ No newline at end of file diff --git a/src/test/java/io/fabric8/maven/docker/util/AuthConfigFactoryTest.java b/src/test/java/io/fabric8/maven/docker/util/AuthConfigFactoryTest.java index 1b4e5276f..38bfc3426 100644 --- a/src/test/java/io/fabric8/maven/docker/util/AuthConfigFactoryTest.java +++ b/src/test/java/io/fabric8/maven/docker/util/AuthConfigFactoryTest.java @@ -15,8 +15,10 @@ import com.google.gson.JsonNull; import com.google.gson.JsonObject; import io.fabric8.maven.docker.access.AuthConfig; +import io.fabric8.maven.docker.util.aws.AwsSdkAuthConfigFactory; import mockit.Expectations; import mockit.Mock; +import mockit.MockUp; import mockit.Mocked; import org.apache.http.entity.StringEntity; import org.apache.http.impl.bootstrap.HttpServer; @@ -522,8 +524,20 @@ public void exec(File homeDir) throws IOException, MojoExecutionException { }); } + @Test + public void getAuthConfigViaAwsSdk() throws MojoExecutionException { + String accessKeyId = randomUUID().toString(); + String secretAccessKey = randomUUID().toString(); + new MockedAwsSdkAuthConfigFactory(accessKeyId, secretAccessKey); + + AuthConfig authConfig = factory.createAuthConfig(false, true, null, settings, "user", ECR_NAME); + + verifyAuthConfig(authConfig, accessKeyId, secretAccessKey, null, null); + } + @Test public void ecsTaskRole() throws IOException, MojoExecutionException { + givenAwsSdkIsDisabled(); String containerCredentialsUri = "/v2/credentials/" + randomUUID().toString(); String accessKeyId = randomUUID().toString(); String secretAccessKey = randomUUID().toString(); @@ -538,6 +552,7 @@ public void ecsTaskRole() throws IOException, MojoExecutionException { @Test public void fargateTaskRole() throws IOException, MojoExecutionException { + givenAwsSdkIsDisabled(); String containerCredentialsUri = "v2/credentials/" + randomUUID().toString(); String accessKeyId = randomUUID().toString(); String secretAccessKey = randomUUID().toString(); @@ -552,6 +567,7 @@ public void fargateTaskRole() throws IOException, MojoExecutionException { @Test public void awsTemporaryCredentialsArePickedUpFromEnvironment() throws MojoExecutionException { + givenAwsSdkIsDisabled(); String accessKeyId = randomUUID().toString(); String secretAccessKey = randomUUID().toString(); String sessionToken = randomUUID().toString(); @@ -566,6 +582,7 @@ public void awsTemporaryCredentialsArePickedUpFromEnvironment() throws MojoExecu @Test public void awsStaticCredentialsArePickedUpFromEnvironment() throws MojoExecutionException { + givenAwsSdkIsDisabled(); String accessKeyId = randomUUID().toString(); String secretAccessKey = randomUUID().toString(); environmentVariables.set("AWS_ACCESS_KEY_ID", accessKeyId); @@ -578,6 +595,7 @@ public void awsStaticCredentialsArePickedUpFromEnvironment() throws MojoExecutio @Test public void incompleteAwsCredentialsAreIgnored() throws MojoExecutionException { + givenAwsSdkIsDisabled(); environmentVariables.set("AWS_ACCESS_KEY_ID", randomUUID().toString()); AuthConfig authConfig = factory.createAuthConfig(false, true, null, settings, "user", ECR_NAME); @@ -654,4 +672,41 @@ private void verifyAuthConfig(AuthConfig config, String username, String passwor verifyAuthConfig(config, username, password, email, null); } + private static void givenAwsSdkIsDisabled() { + new DisableAwsSdkAuthConfigFactory(); + } + + private static class MockedAwsSdkAuthConfigFactory extends MockUp { + private final String accessKeyId; + private final String secretAccessKey; + + public MockedAwsSdkAuthConfigFactory(String accessKeyId, String secretAccessKey) { + this.accessKeyId = accessKeyId; + this.secretAccessKey = secretAccessKey; + } + + @Mock + public void $init(Logger log) { + } + + @Mock + public AuthConfig createAuthConfig() { + return new AuthConfig(accessKeyId, secretAccessKey, null,null); + } + + } + + private static class DisableAwsSdkAuthConfigFactory extends MockUp { + + @Mock + public void $init(Logger log) { + } + + @Mock + public AuthConfig createAuthConfig() { + return null; + } + + } + } diff --git a/src/test/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactoryTest.java b/src/test/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactoryTest.java new file mode 100644 index 000000000..99800715d --- /dev/null +++ b/src/test/java/io/fabric8/maven/docker/util/aws/AwsSdkAuthConfigFactoryTest.java @@ -0,0 +1,72 @@ +package io.fabric8.maven.docker.util.aws; + +import io.fabric8.maven.docker.access.AuthConfig; +import io.fabric8.maven.docker.util.Logger; +import mockit.Mocked; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.EnvironmentVariables; + +import static java.util.UUID.randomUUID; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class AwsSdkAuthConfigFactoryTest { + + @Rule + public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + @Mocked + private Logger log; + private AwsSdkAuthConfigFactory objectUnderTest; + + + @Before + public void setup() { + objectUnderTest = new AwsSdkAuthConfigFactory(log); + } + + @Test + public void nullValueIsPassedOn() { + AuthConfig authConfig = objectUnderTest.createAuthConfig(); + + assertNull(authConfig); + } + + @Test + public void reflectionWorksForBasicCredentials() { + String accessKey = randomUUID().toString(); + String secretKey = randomUUID().toString(); + environmentVariables.set("AWSCredentials.AWSAccessKeyId", accessKey); + environmentVariables.set("AWSCredentials.AWSSecretKey", secretKey); + + AuthConfig authConfig = objectUnderTest.createAuthConfig(); + + assertNotNull(authConfig); + assertEquals(accessKey, authConfig.getUsername()); + assertEquals(secretKey, authConfig.getPassword()); + assertNull(authConfig.getAuth()); + assertNull(authConfig.getIdentityToken()); + } + + @Test + public void reflectionWorksForSessionCredentials() { + String accessKey = randomUUID().toString(); + String secretKey = randomUUID().toString(); + String sessionToken = randomUUID().toString(); + environmentVariables.set("AWSCredentials.AWSAccessKeyId", accessKey); + environmentVariables.set("AWSCredentials.AWSSecretKey", secretKey); + environmentVariables.set("AWSSessionCredentials.SessionToken", sessionToken); + + AuthConfig authConfig = objectUnderTest.createAuthConfig(); + + assertNotNull(authConfig); + assertEquals(accessKey, authConfig.getUsername()); + assertEquals(secretKey, authConfig.getPassword()); + assertEquals(sessionToken, authConfig.getAuth()); + assertNull(authConfig.getIdentityToken()); + } + +} \ No newline at end of file