
io.fabric8.maven.docker.util.AuthConfigFactory Maven / Gradle / Ivy
The newest version!
package io.fabric8.maven.docker.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import com.google.gson.JsonObject;
import java.lang.reflect.Method;
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.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
import com.google.common.net.UrlEscapers;
import com.google.gson.Gson;
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
*
* @author roland
* @since 29.07.14
*/
public class AuthConfigFactory {
// Whether to check for OpenShift authentication
private static final String AUTH_USE_OPENSHIFT_AUTH = "useOpenShiftAuth";
static final String DOCKER_LOGIN_DEFAULT_REGISTRY = "https://index.docker.io/v1/";
private final PlexusContainer container;
private Logger log;
private static final String[] DEFAULT_REGISTRIES = new String[]{
"docker.io", "index.docker.io", "registry.hub.docker.com"
};
/**
* Constructor which should be used during startup phase of a plugin
*
* @param container the container used for do decryption of passwords
*/
public AuthConfigFactory(PlexusContainer container) {
this.container = container;
}
public void setLog(Logger log) {
this.log = log;
}
/**
* Create an authentication config object which can be used for communication with a Docker registry
*
* The authentication information is looked up at various places (in this order):
*
*
* - From system properties
* - From the provided map which can contain key-value pairs
* - From the openshift settings in ~/.config/kube
* - From the Maven settings stored typically in ~/.m2/settings.xml
* - From the Docker settings stored in ~/.docker/config.json
*
*
* The following properties (prefix with 'docker.') and config key are evaluated:
*
*
* - username: User to authenticate
* - password: Password to authenticate. Can be encrypted
* - email: Optional EMail address which is send to the registry, too
*
*
* If the repository is in an aws ecr registry and skipExtendedAuth is not true, if found
* credentials are not from docker settings, they will be interpreted as iam credentials
* and exchanged for ecr credentials.
*
* @param isPush if true this AuthConfig is created for a push, if false it's for a pull
* @param skipExtendedAuth if true, do not execute extended authentication methods
* @param authConfig String-String Map holding configuration info from the plugin's configuration. Can be null
in
* which case the settings are consulted.
* @param settings the global Maven settings object
* @param user user to check for
* @param registry registry to use, might be null in which case a default registry is checked,
* @return the authentication configuration or null
if none could be found
*
* @throws MojoFailureException
*/
public AuthConfig createAuthConfig(boolean isPush, boolean skipExtendedAuth, Map authConfig, Settings settings, String user, String registry)
throws MojoExecutionException {
AuthConfig ret = createStandardAuthConfig(isPush, authConfig, settings, user, registry);
if (ret != null) {
if (registry == null || skipExtendedAuth) {
ret.setRegistry(registry);
return ret;
}
try {
ret= extendedAuthentication(ret, registry);
ret.setRegistry(registry);
return ret;
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
// Finally check ~/.docker/config.json
ret = getAuthConfigFromDockerConfig(registry);
if (ret != null) {
ret.setRegistry(registry);
log.debug("AuthConfig: credentials from ~/.docker/config.json");
return ret;
}
log.debug("AuthConfig: no credentials found");
return null;
}
/**
* Try various extended authentication method. Currently only supports amazon ECR
*
* @param standardAuthConfig The locally stored credentials.
* @param registry The registry to authenticated against.
* @return The given credentials, if registry does not need extended authentication;
* else, the credentials after authentication.
* @throws IOException
* @throws MojoExecutionException
*/
private AuthConfig extendedAuthentication(AuthConfig standardAuthConfig, String registry) throws IOException, MojoExecutionException {
EcrExtendedAuth ecr = new EcrExtendedAuth(log, registry);
if (ecr.isAwsRegistry()) {
return ecr.extendedAuth(standardAuthConfig);
}
return standardAuthConfig;
}
/**
* Create an authentication config object which can be used for communication with a Docker registry
*
* The authentication information is looked up at various places (in this order):
*
*
* - From system properties
* - From the provided map which can contain key-value pairs
* - From the openshift settings in ~/.config/kube
* - From the Maven settings stored typically in ~/.m2/settings.xml
*
*
* The following properties (prefix with 'docker.' or 'registry.') and config key are evaluated:
*
*
* - username: User to authenticate
* - password: Password to authenticate. Can be encrypted
* - email: Optional EMail address which is send to the registry, too
*
*
*
* @param isPush if true this AuthConfig is created for a push, if false it's for a pull
* @param authConfigMap String-String Map holding configuration info from the plugin's configuration. Can be null
in
* which case the settings are consulted.
* @param settings the global Maven settings object
* @param user user to check for
* @param registry registry to use, might be null in which case a default registry is checked,
* @return the authentication configuration or null
if none could be found
*
* @throws MojoFailureException
*/
private AuthConfig createStandardAuthConfig(boolean isPush, Map authConfigMap, Settings settings, String user, String registry)
throws MojoExecutionException {
AuthConfig ret;
// Check first for specific configuration based on direction (pull or push), then for a default value
for (LookupMode lookupMode : new LookupMode[] { getLookupMode(isPush), LookupMode.DEFAULT, LookupMode.REGISTRY}) {
// System properties docker.username and docker.password always take precedence
ret = getAuthConfigFromSystemProperties(lookupMode);
if (ret != null) {
log.debug("AuthConfig: credentials from system properties");
return ret;
}
// Check for openshift authentication either from the plugin config or from system props
if (lookupMode != LookupMode.REGISTRY) {
ret = getAuthConfigFromOpenShiftConfig(lookupMode, authConfigMap);
if (ret != null) {
log.debug("AuthConfig: OpenShift credentials");
return ret;
}
}
// Get configuration from global plugin config
ret = getAuthConfigFromPluginConfiguration(lookupMode, authConfigMap);
if (ret != null) {
log.debug("AuthConfig: credentials from plugin config");
return ret;
}
}
// ===================================================================
// These are lookups based on registry only, so the direction (push or pull) doesn't matter:
// Now lets lookup the registry & user from ~/.m2/setting.xml
ret = getAuthConfigFromSettings(settings, user, registry);
if (ret != null) {
log.debug("AuthConfig: credentials from ~/.m2/setting.xml");
return ret;
}
// 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");
return ret;
}
try {
ret = getAuthConfigFromEC2InstanceRole();
} catch (ConnectTimeoutException ex) {
log.debug("Connection timeout while retrieving instance meta-data, likely not an EC2 instance (%s)",
ex.getMessage());
} catch (IOException ex) {
// don't make that an error since it may fail if not run on an EC2 instance
log.warn("Error while retrieving EC2 instance credentials: %s", ex.getMessage());
}
if (ret != null) {
log.debug("AuthConfig: credentials from EC2 instance role");
return ret;
}
try {
ret = getAuthConfigFromTaskRole();
} 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
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
*/
private AuthConfig getAuthConfigFromAwsEnvironmentVariables() {
String accessKeyId = System.getenv("AWS_ACCESS_KEY_ID");
if (accessKeyId == null) {
log.debug("System environment not set for variable AWS_ACCESS_KEY_ID, no AWS credentials found");
return null;
}
String secretAccessKey = System.getenv("AWS_SECRET_ACCESS_KEY");
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 new AuthConfig(accessKeyId, secretAccessKey, "none", System.getenv("AWS_SESSION_TOKEN"));
}
// ===================================================================================================
// if the local credentials don't contain user and password, use EC2 instance
// role credentials
private AuthConfig getAuthConfigFromEC2InstanceRole() throws IOException {
log.debug("No user and password set for ECR, checking EC2 instance role");
try (CloseableHttpClient client = HttpClients.custom().useSystemProperties().build()) {
// we can set very low timeouts because the request returns almost instantly on
// an EC2 instance
// on a non-EC2 instance we can fail early
RequestConfig conf = RequestConfig.custom().setConnectionRequestTimeout(1000).setConnectTimeout(1000)
.setSocketTimeout(1000).build();
// get instance role - if available
HttpGet request = new HttpGet("http://169.254.169.254/latest/meta-data/iam/security-credentials");
request.setConfig(conf);
String instanceRole;
try (CloseableHttpResponse response = client.execute(request)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
// no instance role found
log.debug("No instance role found, return code was %d", response.getStatusLine().getStatusCode());
return null;
}
// read instance role
try (InputStream is = response.getEntity().getContent()) {
instanceRole = IOUtils.toString(is, StandardCharsets.UTF_8);
}
}
log.debug("Found instance role %s, getting temporary security credentials", instanceRole);
// get temporary credentials
request = new HttpGet("http://169.254.169.254/latest/meta-data/iam/security-credentials/"
+ UrlEscapers.urlPathSegmentEscaper().escape(instanceRole));
request.setConfig(conf);
return readAwsCredentials(client, request);
}
}
// if the local credentials don't contain user and password & is not a EC2 instance,
// use ECS|Fargate Task instance role credentials
private AuthConfig getAuthConfigFromTaskRole() throws IOException {
log.debug("No user and password set for ECR, checking ECS Task role");
URI uri = getMetadataEndpointForCredentials();
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);
}
}
private AuthConfig readAwsCredentials(CloseableHttpClient client, HttpGet request) 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 new AuthConfig(user, password, "none", token);
}
}
}
private URI getMetadataEndpointForCredentials() {
// get ECS task role - if available
String awsContainerCredentialsUri = System.getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI");
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;
}
String ecsMetadataEndpoint = System.getenv("ECS_METADATA_ENDPOINT");
if (ecsMetadataEndpoint == null) {
ecsMetadataEndpoint = "http://169.254.170.2";
}
try {
return new URI(ecsMetadataEndpoint + awsContainerCredentialsUri);
} catch (URISyntaxException e) {
log.warn("Failed to construct path to ECS metadata endpoint for credentials", e);
return null;
}
}
private AuthConfig getAuthConfigFromSystemProperties(LookupMode lookupMode) throws MojoExecutionException {
Properties props = System.getProperties();
String userKey = lookupMode.asSysProperty(AuthConfig.AUTH_USERNAME);
String passwordKey = lookupMode.asSysProperty(AuthConfig.AUTH_PASSWORD);
if (props.containsKey(userKey)) {
if (!props.containsKey(passwordKey)) {
throw new MojoExecutionException("No " + passwordKey + " provided for username " + props.getProperty(userKey));
}
return new AuthConfig(props.getProperty(userKey),
decrypt(props.getProperty(passwordKey)),
props.getProperty(lookupMode.asSysProperty(AuthConfig.AUTH_EMAIL)),
getAuthProperty(props, lookupMode));
} else {
return null;
}
}
private String getAuthProperty(Properties props, LookupMode lookupMode) {
String authProp = props.getProperty(lookupMode.asSysProperty(AuthConfig.AUTH_AUTH));
if (authProp != null) {
return authProp;
}
// Fallback is deprecated AUTH_AUTHTOKEN property
return props.getProperty(lookupMode.asSysProperty("authToken"));
}
private AuthConfig getAuthConfigFromOpenShiftConfig(LookupMode lookupMode, Map authConfigMap) throws MojoExecutionException {
Properties props = System.getProperties();
String useOpenAuthModeProp = lookupMode.asSysProperty(AUTH_USE_OPENSHIFT_AUTH);
// Check for system property
if (props.containsKey(useOpenAuthModeProp)) {
boolean useOpenShift = Boolean.valueOf(props.getProperty(useOpenAuthModeProp));
if (useOpenShift) {
return validateMandatoryOpenShiftLogin(parseOpenShiftConfig(), useOpenAuthModeProp);
} else {
return null;
}
}
// Check plugin config
Map mapToCheck = getAuthConfigMapToCheck(lookupMode,authConfigMap);
if (mapToCheck != null && mapToCheck.containsKey(AUTH_USE_OPENSHIFT_AUTH) &&
Boolean.valueOf((String) mapToCheck.get(AUTH_USE_OPENSHIFT_AUTH))) {
return validateMandatoryOpenShiftLogin(parseOpenShiftConfig(), useOpenAuthModeProp);
} else {
return null;
}
}
private AuthConfig getAuthConfigFromPluginConfiguration(LookupMode lookupMode, Map authConfig) throws MojoExecutionException {
Map mapToCheck = getAuthConfigMapToCheck(lookupMode,authConfig);
if (mapToCheck != null && mapToCheck.containsKey(AuthConfig.AUTH_USERNAME)) {
if (!mapToCheck.containsKey(AuthConfig.AUTH_PASSWORD)) {
throw new MojoExecutionException("No 'password' given while using in configuration for mode " + lookupMode);
}
Map cloneConfig = new HashMap<>(mapToCheck);
cloneConfig.put(AuthConfig.AUTH_PASSWORD, decrypt(cloneConfig.get(AuthConfig.AUTH_PASSWORD)));
return new AuthConfig(cloneConfig);
} else {
return null;
}
}
private AuthConfig getAuthConfigFromSettings(Settings settings, String user, String registry) throws MojoExecutionException {
Server defaultServer = null;
Server found;
for (Server server : settings.getServers()) {
String id = server.getId();
// Remember a default server without user as fallback for later
if (defaultServer == null) {
defaultServer = checkForServer(server, id, registry, null);
}
// Check for specific server with user part
found = checkForServer(server, id, registry, user);
if (found != null) {
return createAuthConfigFromServer(found);
}
}
return defaultServer != null ? createAuthConfigFromServer(defaultServer) : null;
}
private AuthConfig getAuthConfigFromDockerConfig(String registry) throws MojoExecutionException {
JsonObject dockerConfig = DockerFileUtil.readDockerConfig();
if (dockerConfig == null) {
return null;
}
String registryToLookup = registry != null ? registry : DOCKER_LOGIN_DEFAULT_REGISTRY;
if (dockerConfig.has("credHelpers") || dockerConfig.has("credsStore")) {
if (dockerConfig.has("credHelpers")) {
final JsonObject credHelpers = dockerConfig.getAsJsonObject("credHelpers");
if (credHelpers.has(registryToLookup)) {
return extractAuthConfigFromCredentialsHelper(registryToLookup, credHelpers.get(registryToLookup).getAsString());
}
}
if (dockerConfig.has("credsStore")) {
return extractAuthConfigFromCredentialsHelper(registryToLookup, dockerConfig.get("credsStore").getAsString());
}
}
if (dockerConfig.has("auths")) {
return extractAuthConfigFromDockerConfigAuths(registryToLookup, dockerConfig.getAsJsonObject("auths"));
}
return null;
}
private AuthConfig extractAuthConfigFromDockerConfigAuths(String registryToLookup, JsonObject auths) {
JsonObject credentials = getCredentialsNode(auths,registryToLookup);
if (credentials == null || !credentials.has("auth")) {
return null;
}
String auth = credentials.get("auth").getAsString();
String identityToken = credentials.has("identitytoken") ? credentials.get("identitytoken").getAsString() : null;
String email = credentials.has(AuthConfig.AUTH_EMAIL) && !credentials.get(AuthConfig.AUTH_EMAIL).isJsonNull() ? credentials.get(AuthConfig.AUTH_EMAIL).getAsString() : null;
return new AuthConfig(auth, email, identityToken);
}
private AuthConfig extractAuthConfigFromCredentialsHelper(String registryToLookup, String credConfig) throws MojoExecutionException {
CredentialHelperClient credentialHelper = new CredentialHelperClient(log, credConfig);
String version = credentialHelper.getVersion();
log.debug("AuthConfig: credentials from credential helper/store %s%s",
credentialHelper.getName(),
version != null ? " version " + version : "");
return credentialHelper.getAuthConfig(registryToLookup);
}
private JsonObject getCredentialsNode(JsonObject auths,String registryToLookup) {
if (auths.has(registryToLookup)) {
return auths.getAsJsonObject(registryToLookup);
}
String registryWithScheme = EnvUtil.ensureRegistryHttpUrl(registryToLookup);
if (auths.has(registryWithScheme)) {
return auths.getAsJsonObject(registryWithScheme);
}
return null;
}
// =======================================================================================================
private Map getAuthConfigMapToCheck(LookupMode lookupMode, Map authConfigMap) {
String configMapKey = lookupMode.getConfigMapKey();
if (configMapKey == null) {
return authConfigMap;
}
if (authConfigMap != null) {
return (Map) authConfigMap.get(configMapKey);
}
return null;
}
// Parse OpenShift config to get credentials, but return null if not found
private AuthConfig parseOpenShiftConfig() {
Map kubeConfig = DockerFileUtil.readKubeConfig();
if (kubeConfig == null) {
return null;
}
String currentContextName = (String) kubeConfig.get("current-context");
if (currentContextName == null) {
return null;
}
for (Map contextMap : (List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy