Skip to content

Commit

Permalink
Use AWS SDK to fetch AWS credentials (#1311)
Browse files Browse the repository at this point in the history
* Add AWS SDK core

* Exclude CBOR & ION serialization

* Use AWS SDK to fetch AWS credentials

* Fix imports

* Simlify AuthConfig creation

* Test AwsSdkAuthConfigFactory

* Test AuthConfigFactory

* Make sure custom AWS code is tested

* Go full reflection

* Improve comments + clean imports

* Mention #1311 in changelog

* Cleanup

* Document usage of AWS SDK for Extended Authentication

* Link to documentation when encountering AWS ECR

* Be more precise in AWS SDK usage

Co-Authored-By: Roland Huß <[email protected]>

Co-authored-by: Roland Huß <[email protected]>
  • Loading branch information
sebastiankirsch and rhuss committed Jan 21, 2020
1 parent 583a6d8 commit 8c4fab2
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
30 changes: 30 additions & 0 deletions src/main/asciidoc/inc/_authentication.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<auth>` 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]
----
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>1.11.707</version>
</dependency>
</dependencies>
</plugin>
</plugins>
----
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.
====
19 changes: 19 additions & 0 deletions src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

}
24 changes: 24 additions & 0 deletions src/test/java/com/amazonaws/auth/AWSCredentials.java
Original file line number Diff line number Diff line change
@@ -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 <tt>com.amazonaws:aws-java-sdk-core:1.11.707</tt>.
*/
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;
}

}
18 changes: 18 additions & 0 deletions src/test/java/com/amazonaws/auth/AWSSessionCredentials.java
Original file line number Diff line number Diff line change
@@ -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 <tt>com.amazonaws:aws-java-sdk-core:1.11.707</tt>.
*/
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;}

}
Original file line number Diff line number Diff line change
@@ -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 <tt>com.amazonaws:aws-java-sdk-core:1.11.707</tt>.
*/
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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<AwsSdkAuthConfigFactory> {
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<AwsSdkAuthConfigFactory> {

@Mock
public void $init(Logger log) {
}

@Mock
public AuthConfig createAuthConfig() {
return null;
}

}

}
Original file line number Diff line number Diff line change
@@ -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());
}

}

0 comments on commit 8c4fab2

Please sign in to comment.