Skip to content

Commit

Permalink
Merge pull request #674 from rhuss/663-extended-auth
Browse files Browse the repository at this point in the history
PR merged! Thanks!
  • Loading branch information
fusesource-ci authored Jan 2, 2017
2 parents c77945e + a44b821 commit ea6c4cf
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 63 deletions.
2 changes: 1 addition & 1 deletion doc/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

* **0.18.2**
- Better log message when waiting for URL (#640)
- Extended authentication for AWS ECR
- Extended authentication for AWS ECR (#663)
- Add two new goals: "volume-create" and "volume-remove" for volume handling independent of images.


Expand Down
12 changes: 6 additions & 6 deletions src/main/java/io/fabric8/maven/docker/access/AuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public class AuthConfig {

public AuthConfig(Map<String,String> params) {
this(params.get("username"),
params.get("password"),
params.get("email"),
params.get("auth"));
params.get("password"),
params.get("email"),
params.get("auth"));
}

public AuthConfig(String username, String password, String email, String auth) {
Expand All @@ -42,11 +42,11 @@ public AuthConfig(String username, String password, String email, String auth) {
/**
* Constructor which takes an base64 encoded credentials in the form 'user:password'
*
* @param credentialsDockerEncoded the docker encoded user and password
* @param credentialsEncoded the docker encoded user and password
* @param email the email to use for authentication
*/
public AuthConfig(String credentialsDockerEncoded, String email) {
String credentials = new String(Base64.decodeBase64(credentialsDockerEncoded));
public AuthConfig(String credentialsEncoded, String email) {
String credentials = new String(Base64.decodeBase64(credentialsEncoded));
String[] parsedCreds = credentials.split(":",2);
username = parsedCreds[0];
password = parsedCreds[1];
Expand Down
35 changes: 16 additions & 19 deletions src/main/java/io/fabric8/maven/docker/access/ecr/AwsSigner4.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,12 @@
import io.fabric8.maven.docker.access.AuthConfig;

/**
* AwsSigner4 implementation that signs requests with the AWS4 signing protocol.
* AwsSigner4 implementation that signs requests with the AWS4 signing protocol. Refer to the AWS docs for mor details.
*
* @author chas
* @since 2016-12-9
*/
public class AwsSigner4 {

private static final Comparator<NameValuePair> PAIR_NAME_COMPARATOR = new Comparator<NameValuePair>() {
@Override
public int compare(NameValuePair l, NameValuePair r) {
return l.getName().compareToIgnoreCase(r.getName());
}
};
class AwsSigner4 {

// a-f must be lower case
final private static char[] HEXITS = "0123456789abcdef".toCharArray();
Expand All @@ -46,7 +39,7 @@ public int compare(NameValuePair l, NameValuePair r) {
* @param region The aws region.
* @param service The aws service.
*/
public AwsSigner4(String region, String service) {
AwsSigner4(String region, String service) {
this.region = region;
this.service = service;
}
Expand All @@ -58,7 +51,7 @@ public AwsSigner4(String region, String service) {
* @param credentials The credentials to use when signing.
* @param signingTime The invocation time to use;
*/
public void sign(HttpRequest request, AuthConfig credentials, Date signingTime) {
void sign(HttpRequest request, AuthConfig credentials, Date signingTime) {
AwsSigner4Request sr = new AwsSigner4Request(region, service, request, signingTime);
if(!request.containsHeader("X-Amz-Date")) {
request.addHeader("X-Amz-Date", sr.getSigningDateTime());
Expand Down Expand Up @@ -101,13 +94,12 @@ final byte[] task3(AwsSigner4Request sr, AuthConfig credentials) {
return hmacSha256(getSigningKey(sr, credentials), task2(sr));
}

static byte[] getSigningKey(AwsSigner4Request sr, AuthConfig credentials) {
private static byte[] getSigningKey(AwsSigner4Request sr, AuthConfig credentials) {
byte[] kSecret = ("AWS4" + credentials.getPassword()).getBytes(StandardCharsets.UTF_8);
byte[] kDate = hmacSha256(kSecret, sr.getSigningDate());
byte[] kRegion = hmacSha256(kDate, sr.getRegion());
byte[] kService = hmacSha256(kRegion, sr.getService());
byte[] signingKey = hmacSha256(kService, "aws4_request");
return signingKey;
return hmacSha256(kService, "aws4_request");
}

/**
Expand All @@ -129,19 +121,24 @@ private String getCanonicalQuery(URI uri) {
return "";
}
List<NameValuePair> params = URLEncodedUtils.parse(query, StandardCharsets.UTF_8);
Collections.sort(params, PAIR_NAME_COMPARATOR);
Collections.sort(params, new Comparator<NameValuePair>() {
@Override
public int compare(NameValuePair l, NameValuePair r) {
return l.getName().compareToIgnoreCase(r.getName());
}
});
return URLEncodedUtils.format(params, StandardCharsets.UTF_8);
}

static void hexEncode(StringBuilder dst, byte[] src) {
for ( int i = 0; i < src.length; ++i ) {
int v = src[i] & 0xFF;
for (byte aSrc : src) {
int v = aSrc & 0xFF;
dst.append(HEXITS[v >>> 4]);
dst.append(HEXITS[v & 0x0F]);
}
}

static byte[] hmacSha256(byte[] key, String value) {
private static byte[] hmacSha256(byte[] key, String value) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
Expand All @@ -161,7 +158,7 @@ private static byte[] sha256(byte[] bytes) {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes);
return md.digest();
}
}
catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ public class EcrExtendedAuth {
Pattern.compile("^(\\d{12})\\.dkr\\.ecr\\.([a-z\\-0-9]+)\\.amazonaws\\.com$");

private final Logger logger;
private final Matcher matcher;
private final boolean isValid;
private final boolean isAwsRegistry;
private final String accountId;
private final String region;

/**
* Initialize an extended authentication for ecr registry.
Expand All @@ -45,17 +46,24 @@ public class EcrExtendedAuth {
*/
public EcrExtendedAuth(Logger logger, String registry) {
this.logger = logger;
matcher = AWS_REGISTRY.matcher(registry);
isValid = matcher.matches();
logger.debug("registry = %s, isValid= %b", registry, isValid);
Matcher matcher = AWS_REGISTRY.matcher(registry);
isAwsRegistry = matcher.matches();
if (isAwsRegistry) {
accountId = matcher.group(1);
region = matcher.group(2);
} else {
accountId = null;
region = null;
}
logger.debug("registry = %s, isValid= %b", registry, isAwsRegistry);
}

/**
* Is the registry an ecr registry?
* @return true, if the registry matches the ecr pattern
*/
public boolean isValidRegistry() {
return isValid;
public boolean isAwsRegistry() {
return isAwsRegistry;
}

/**
Expand Down Expand Up @@ -86,12 +94,12 @@ CloseableHttpClient createClient() {
return HttpClients.createDefault();
}

JSONObject executeRequest(CloseableHttpClient client, HttpPost request) throws IOException, MojoExecutionException {
private JSONObject executeRequest(CloseableHttpClient client, HttpPost request) throws IOException, MojoExecutionException {
try {
CloseableHttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
logger.debug("Response status %d", statusCode);
if(statusCode != HttpStatus.SC_OK) {
if (statusCode != HttpStatus.SC_OK) {
throw new MojoExecutionException("AWS authentication failure");
}

Expand All @@ -105,11 +113,9 @@ JSONObject executeRequest(CloseableHttpClient client, HttpPost request) throws I
}

HttpPost createSignedRequest(AuthConfig localCredentials, Date time) {
String accountId = matcher.group(1);
String region = matcher.group(2);
String host = "ecr." + region + ".amazonaws.com";

logger.debug("GetAuthorizationToken from %s", host);
logger.debug("Get ECR AuthorizationToken from %s", host);

HttpPost request = new HttpPost("https://" + host + '/');
request.setHeader("host", host);
Expand Down
33 changes: 13 additions & 20 deletions src/main/java/io/fabric8/maven/docker/util/AuthConfigFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ public void setLog(Logger log) {
* credentials are not from docker settings, they will be interpreted as iam credentials
* and exchanged for ecr credentials.
*
* @param logger The logger for tracing
* @param isPush if true this AuthConfig is created for a push, if false it's for a pull
* @param skipExtendedAuth if false, do not execute extended authentication methods
* @param authConfig String-String Map holding configuration info from the plugin's configuration. Can be <code>null</code> in
Expand All @@ -107,16 +106,11 @@ public AuthConfig createAuthConfig(boolean isPush, boolean skipExtendedAuth, Map

AuthConfig ret = createStandardAuthConfig(isPush, authConfig, settings, user, registry);
if (ret != null) {
if (registry == null ) {
log.debug("default registry; no extended auth");
return ret;
}
if (skipExtendedAuth) {
log.debug("skipping extended auth");
if (registry == null || skipExtendedAuth) {
return ret;
}
try {
return extendedAuthentication(registry, ret);
return extendedAuthentication(ret, registry);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
Expand All @@ -125,31 +119,30 @@ public AuthConfig createAuthConfig(boolean isPush, boolean skipExtendedAuth, Map
// Finally check ~/.docker/config.json
ret = getAuthConfigFromDockerConfig(registry);
if(ret != null) {
log.debug("found credentials in ~.docker/config.json");
log.debug("AuthConfig: credentials from ~.docker/config.json");
return ret;
}

log.debug("no credentials found");
log.debug("AuthConfig: no credentials found");
return null;
}

/**
* Try various extended authentication method. Currently only supports amazon ECR
*
* @param logger The logger for tracing
* @param standardAuthConfig The locally stored credentials.
* @param registry The registry to authenticated against.
* @param localCredentials The locally stored credentials.
* @return The given credentials, if registry does not need extended authentication;
* else, the credentials after authentication.
* @throws IOException
* @throws MojoExecutionException
*/
private AuthConfig extendedAuthentication(String registry, AuthConfig localCredentials) throws IOException, MojoExecutionException {
private AuthConfig extendedAuthentication(AuthConfig standardAuthConfig, String registry) throws IOException, MojoExecutionException {
EcrExtendedAuth ecr = new EcrExtendedAuth(log, registry);
if (ecr.isValidRegistry()) {
return ecr.extendedAuth(localCredentials);
if (ecr.isAwsRegistry()) {
return ecr.extendedAuth(standardAuthConfig);
}
return localCredentials;
return standardAuthConfig;
}

/**
Expand Down Expand Up @@ -192,21 +185,21 @@ private AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, S
// System properties docker.username and docker.password always take precedence
ret = getAuthConfigFromSystemProperties(lookupMode);
if (ret != null) {
log.debug("found credentials in system properties");
log.debug("AuthConfig: credentials from system properties");
return ret;
}

// Check for openshift authentication either from the plugin config or from system props
ret = getAuthConfigFromOpenShiftConfig(lookupMode,authConfigMap);
if (ret != null) {
log.debug("found openshift credentials");
log.debug("AuthConfig: OpenShift credentials");
return ret;
}

// Get configuration from global plugin config
ret = getAuthConfigFromPluginConfiguration(lookupMode,authConfigMap);
if (ret != null) {
log.debug("found credentials in plugin config");
log.debug("AuthConfig: credentials from plugin config");
return ret;
}
}
Expand All @@ -217,7 +210,7 @@ private AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, S
// Now lets lookup the registry & user from ~/.m2/setting.xml
ret = getAuthConfigFromSettings(settings, user, registry);
if (ret != null) {
log.debug("found credentials in ~/.m2/setting.xml");
log.debug("AuthConfig: credentials from ~/.m2/setting.xml");
return ret;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.fabric8.maven.docker.access.ecr;

import static org.junit.Assert.*;

import org.junit.Test;

import io.fabric8.maven.docker.access.AuthConfig;
Expand Down Expand Up @@ -39,12 +37,12 @@ public class EcrExtendedAuthTest {

@Test
public void testIsNotAws() {
assertFalse(new EcrExtendedAuth(logger, "jolokia").isValidRegistry());
assertFalse(new EcrExtendedAuth(logger, "jolokia").isAwsRegistry());
}

@Test
public void testIsAws() {
assertTrue(new EcrExtendedAuth(logger, "123456789012.dkr.ecr.eu-west-1.amazonaws.com").isValidRegistry());
assertTrue(new EcrExtendedAuth(logger, "123456789012.dkr.ecr.eu-west-1.amazonaws.com").isAwsRegistry());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @since 29.07.14
*/
@RunWith(JMockit.class)
public class AuthConfigFatoryTest {
public class AuthConfigFactoryTest {

public static final String ECR_NAME = "123456789012.dkr.ecr.bla.amazonaws.com";

Expand Down

0 comments on commit ea6c4cf

Please sign in to comment.