Skip to content

Commit

Permalink
Fetch AWS Credentials from AWS SDK/Environment variables
Browse files Browse the repository at this point in the history
Add support in AuthConfigFactory to retrieve credentials from
different mechanisms for AWS.

Port of fabric8io/docker-maven-plugin#1311
Port of fabric8io/docker-maven-plugin#1310

Related to eclipse-jkube#702

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Jun 18, 2021
1 parent 32be156 commit 6e24f09
Show file tree
Hide file tree
Showing 8 changed files with 649 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Usage:
* Fix #705: JIB assembly works on Windows
* Fix #714: feat: Helm support for Golang expressions
* Port fabric8io/docker-maven-plugin#1318: Update ECR autorization token URL
* Port fabric8io/docker-maven-plugin#1311: Use AWS SDK to fetch AWS credentials
* Fix #710: Support DockerImage as output for Openshift builds

### 1.3.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Map;
Expand All @@ -35,34 +36,45 @@
@EqualsAndHashCode
public class AuthConfig {

public static final AuthConfig EMPTY_AUTH_CONFIG = new AuthConfig("", "", "", "");
public static final AuthConfig EMPTY_AUTH_CONFIG = new AuthConfig("", "", "", "", "");

private final String username;
private final String password;
private final String email;
private final String auth;
private final String identityToken;

private String authEncoded;

@Builder
public AuthConfig(String username, String password, String email, String auth) {
public AuthConfig(String username, String password, String email, String auth, String identityToken) {
this.username = username;
this.password = password;
this.email = email;
this.auth = auth;
this.identityToken = identityToken;
authEncoded = createAuthEncoded();
}

public AuthConfig(String username, String password, String email, String auth) {
this(username, password, email, auth, null);
}

public String toHeaderValue() {
return authEncoded;
}

private String createAuthEncoded() {
JsonObject ret = new JsonObject();
putNonNull(ret, "username", username);
putNonNull(ret, "password", password);
putNonNull(ret, "email", email);
putNonNull(ret, "auth", auth);
if(StringUtils.isNotBlank(identityToken)) {
putNonNull(ret, "identityToken", identityToken);
} else {
putNonNull(ret, "username", username);
putNonNull(ret, "password", password);
putNonNull(ret, "email", email);
putNonNull(ret, "auth", auth);
}

return encodeBase64ChunkedURLSafeString(ret.toString().getBytes(StandardCharsets.UTF_8));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jkube.kit.build.service.docker.auth.ecr.AwsSdkAuthConfigFactory;
import org.eclipse.jkube.kit.build.service.docker.auth.ecr.AwsSdkHelper;
import org.eclipse.jkube.kit.common.RegistryServerConfiguration;
import org.eclipse.jkube.kit.build.api.helper.DockerFileUtil;
import org.eclipse.jkube.kit.build.api.auth.AuthConfig;
Expand All @@ -35,11 +37,12 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -63,12 +66,18 @@ public class AuthConfigFactory {
private static final String DOCKER_LOGIN_DEFAULT_REGISTRY = "https://index.docker.io/v1/";

private final KitLogger log;
private final AwsSdkHelper awsSdkHelper;
private static final String[] DEFAULT_REGISTRIES = new String[]{
"docker.io", "index.docker.io", "registry.hub.docker.com"
};

public AuthConfigFactory(KitLogger log) {
this(log, new AwsSdkHelper());
}

AuthConfigFactory(KitLogger log, AwsSdkHelper awsSdkHelper) {
this.log = log;
this.awsSdkHelper = awsSdkHelper;
}

/**
Expand Down Expand Up @@ -111,7 +120,7 @@ public AuthConfigFactory(KitLogger log) {
public AuthConfig createAuthConfig(boolean isPush, boolean skipExtendedAuth, Map authConfig, List<RegistryServerConfiguration> settings, String user, String registry, UnaryOperator<String> passwordDecryptionMethod)
throws IOException {

AuthConfig ret = createStandardAuthConfig(isPush, authConfig, settings, user, registry, passwordDecryptionMethod, log);
AuthConfig ret = createStandardAuthConfig(isPush, authConfig, settings, user, registry, passwordDecryptionMethod, log, awsSdkHelper);
if (ret != null) {
if (registry == null || skipExtendedAuth) {
return ret;
Expand Down Expand Up @@ -185,7 +194,7 @@ private AuthConfig extendedAuthentication(AuthConfig standardAuthConfig, String
*
* @throws IOException any exception in case of fetching authConfig
*/
private static AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, List<RegistryServerConfiguration> settings, String user, String registry, UnaryOperator<String> passwordDecryptionMethod, KitLogger log)
private static AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, List<RegistryServerConfiguration> settings, String user, String registry, UnaryOperator<String> passwordDecryptionMethod, KitLogger log, AwsSdkHelper awsSdkHelper)
throws IOException {
AuthConfig ret;

Expand Down Expand Up @@ -225,6 +234,18 @@ private static AuthConfig createStandardAuthConfig(boolean isPush, Map authConfi

// check EC2 instance role if registry is ECR
if (EcrExtendedAuth.isAwsRegistry(registry)) {
ret = getAuthConfigViaAwsSdk(awsSdkHelper, log);
if (ret != null) {
log.debug("AuthConfig: AWS credentials from AWS SDK");
return ret;
}

ret = getAuthConfigFromAwsEnvironmentVariables(awsSdkHelper, log);
if (ret != null) {
log.debug("AuthConfig: AWS credentials from ENV variables");
return ret;
}

try {
ret = getAuthConfigFromEC2InstanceRole(log);
} catch (ConnectTimeoutException ex) {
Expand All @@ -238,6 +259,18 @@ private static AuthConfig createStandardAuthConfig(boolean isPush, Map authConfi
log.debug("AuthConfig: credentials from EC2 instance role");
return ret;
}
try {
ret = getAuthConfigFromTaskRole(awsSdkHelper, log);
} catch (ConnectTimeoutException ex) {
log.debug("Connection timeout while retrieving ECS meta-data, likely not an ECS instance (%s)",
ex.getMessage());
} catch (IOException ex) {
log.warn("Error while retrieving ECS Task role credentials: %s", ex.getMessage());
}
if (ret != null) {
log.debug("AuthConfig: credentials from ECS Task role");
return ret;
}
}

// No authentication found
Expand Down Expand Up @@ -433,6 +466,108 @@ private static JsonObject getCredentialsNode(JsonObject auths,String registryToL
return null;
}

// if the local credentials don't contain user and password & is not a EC2 instance,
// use ECS|Fargate Task instance role credentials
private static AuthConfig getAuthConfigFromTaskRole(AwsSdkHelper awsSdkHelper, KitLogger log) throws IOException {
log.debug("No user and password set for ECR, checking ECS Task role");
URI uri = getMetadataEndpointForCredentials(awsSdkHelper, log);
if (uri == null) {
return null;
}
// get temporary credentials
log.debug("Getting temporary security credentials from: %s", uri);
try (CloseableHttpClient client = HttpClients.custom().useSystemProperties().build()) {
RequestConfig conf =
RequestConfig.custom().setConnectionRequestTimeout(1000).setConnectTimeout(1000)
.setSocketTimeout(1000).build();
HttpGet request = new HttpGet(uri);
request.setConfig(conf);
return readAwsCredentials(client, request, log);
}
}


private static AuthConfig readAwsCredentials(CloseableHttpClient client, HttpGet request, KitLogger log) throws IOException {
try (CloseableHttpResponse response = client.execute(request)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
log.debug("No security credential found, return code was %d",
response.getStatusLine().getStatusCode());
// no instance role found
return null;
}

// read instance role
try (Reader r = new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)) {
JsonObject securityCredentials = new Gson().fromJson(r, JsonObject.class);

String user = securityCredentials.getAsJsonPrimitive("AccessKeyId").getAsString();
String password = securityCredentials.getAsJsonPrimitive("SecretAccessKey").getAsString();
String token = securityCredentials.getAsJsonPrimitive("Token").getAsString();

log.debug("Received temporary access key %s...", user.substring(0, 8));
return AuthConfig.builder()
.username(user)
.password(password)
.email("none")
.auth(token)
.build();
}
}
}

private static URI getMetadataEndpointForCredentials(AwsSdkHelper awsSdkHelper, KitLogger log) {
// get ECS task role - if available
String awsContainerCredentialsUri = awsSdkHelper.getAwsContainerCredentialsRelativeUri();
if (awsContainerCredentialsUri == null) {
log.debug("System environment not set for variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, no task role found");
return null;
}
if (awsContainerCredentialsUri.charAt(0) != '/') {
awsContainerCredentialsUri = "/" + awsContainerCredentialsUri;
}

try {
return new URI(awsSdkHelper.getEcsMetadataEndpoint() + awsContainerCredentialsUri);
} catch (URISyntaxException e) {
log.warn("Failed to construct path to ECS metadata endpoint for credentials", e);
return null;
}
}

private static AuthConfig getAuthConfigViaAwsSdk(AwsSdkHelper awsSdkHelper, KitLogger log) {
boolean credProviderPresent = awsSdkHelper.isDefaultAWSCredentialsProviderChainPresentInClassPath();
if (!credProviderPresent) {
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://www.eclipse.org/jkube/docs/kubernetes-maven-plugin#extended-authentication");
return null;
}
return new AwsSdkAuthConfigFactory(log, awsSdkHelper).createAuthConfig();
}

/**
* Try using the AWS credentials provided via ENV variables.
* See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
*/
private static AuthConfig getAuthConfigFromAwsEnvironmentVariables(AwsSdkHelper awsSdkHelper, KitLogger log) {
String accessKeyId = awsSdkHelper.getAwsAccessKeyIdEnvVar();
if (accessKeyId == null) {
log.debug("System environment not set for variable AWS_ACCESS_KEY_ID, no AWS credentials found");
return null;
}
String secretAccessKey = awsSdkHelper.getAwsSecretAccessKeyEnvVar();
if (secretAccessKey == null) {
log.warn("System environment set for variable AWS_ACCESS_KEY_ID, but NOT for variable AWS_SECRET_ACCESS_KEY!");
return null;
}
return AuthConfig.builder()
.username(accessKeyId)
.password(secretAccessKey)
.email("none")
.auth(awsSdkHelper.getAwsSessionTokenEnvVar())
.build();
}

// =======================================================================================================

private static Map<String, String> getAuthConfigMapToCheck(LookupMode lookupMode, Map<?, ?> authConfigMap) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.kit.build.service.docker.auth.ecr;

import org.eclipse.jkube.kit.build.api.auth.AuthConfig;
import org.eclipse.jkube.kit.common.KitLogger;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import static java.nio.charset.StandardCharsets.UTF_8;

public class AwsSdkAuthConfigFactory {

private final KitLogger log;
private AwsSdkHelper awsSdkHelper;

public AwsSdkAuthConfigFactory(KitLogger log, AwsSdkHelper awsSdkHelper) {
this.log = log;
this.awsSdkHelper = awsSdkHelper;
}

public AuthConfig createAuthConfig() {
try {
Object credentials = awsSdkHelper.getCredentialsFromDefaultAWSCredentialsProviderChain();
if (credentials == null) {
return null;
}

return AuthConfig.builder()
.username(awsSdkHelper.getAWSAccessKeyIdFromCredentials(credentials))
.password(awsSdkHelper.getAwsSecretKeyFromCredentials(credentials))
.email("none")
.auth(awsSdkHelper.getSessionTokenFromCrendentials(credentials))
.build();
} catch (Exception t) {
String issueTitle = null;
try {
issueTitle = URLEncoder.encode("Failed calling AWS SDK: " + t.getMessage(), UTF_8.name());
} catch (UnsupportedEncodingException ignore) {
}
log.warn("Failed to fetch AWS credentials: %s", t.getMessage());
if (t.getCause() != null) {
log.warn("Caused by: %s", t.getCause().getMessage());
}
log.warn("Please report a bug at https://github.com/eclipse/jkube/issues/new?%s",
issueTitle == null ? "" : "title=?" + issueTitle);
log.warn("%s", t);
return null;
}
}

}
Loading

0 comments on commit 6e24f09

Please sign in to comment.