当前位置: 首页 > 工具软件 > aws-vault > 使用案例 >

AWS Credentials

巫马玉堂
2023-12-01

在使用AWS Service的过程中,经常需要给AWS Service发http请求进行交互,比如对DynamoDB的CURD操作,S3上传或者下载文件等等。这些请求发送出去之后AWS会对请求中包含的Credentials进行验证,只有通过验证之后才会进行相应的操作,这就保证了安全性。

AWS Credentials

AWS Credentials包含以下四部分内容:

{
  "AccessKeyId" : "*************",
  "SecretAccessKey" : "*************************************",
  "Token" : "***************************************************************************",
  "Expiration" : "2021-11-12T10:51:36Z"
}

如何为AWS Java SDK提供AWS credentials

  • 使用默认的CredentialsProviderChain:DefaultAWSCredentialsProviderChain

    • 该方式时AWS官方推荐的方式

  • 使用某个具体的CredentialsProvider或者CredentialsProviderChain,也可以根据自己的需求创建自己的CredentialsProvider或者CredentialsProviderChain

  • 直接提供Credentials,Credentials可以是root账户的credentials,IAM用户的credentials,也可以是通过AWS STS服务获取到的temporary credentials

DefaultAWSCredentialsProviderChain

CredentialsProviderChain提供了四个CredentialsProvider:

  • EnvironmentVariableCredentialsProvider
    • 从操作系统环境变量中读取Credentials信息
      • AWS_ACCESS_KEY_ID
      • AWS_SECRET_ACCESS_KEY
      • AWS_SESSION_TOKEN
    • 不包含更新逻辑,,若token过期,需要自己主动对环境变量中的值进行更新
  • SystemPropertiesCredentialsProvider
    • 从java系统属性中读取Credentials信息
      • aws.accessKeyId
      • aws.secretKey
      • aws.sessionToken
    • 不包含更新逻辑,,若token过期,需要自己主动对java系统属性中的值进行更新
  • ProfileCredentialsProvider
    • 从~/.aws/credentials文件中读取Credentials信息
    • [default]
      aws_access_key_id = ***
      aws_secret_access_key = *************
      aws_session_token = ********************************
  • EC2ContainerCredentialsProviderWrapper
    • 如果设置了AWS_CONTAINER_CREDENTIALS_RELATIVE_URI或者AWS_CONTAINER_CREDENTIALS_FULL_URI环境变量,就使用Amazon EC2 container service分发给EC2的credentials
      • container credentials可以通过在EC2上向以下地址发送请求读取到:
      • http://169.254.170.2/${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}
      • ${AWS_CONTAINER_CREDENTIALS_FULL_URI}
    • 否则就使用Amazon EC2 metadata service分发的Instance profile credentials
      • Instance profile credentials可以通过在EC2上向以下地址发送请求读取到:
      • http://169.254.169.254/latest/meta-data/iam/security-credentials/${ec2_role}
package com.amazonaws.auth;

import com.amazonaws.auth.profile.ProfileCredentialsProvider;

/**
 * AWS credentials provider chain that looks for credentials in this order:
 * <ul>
 *   <li>Environment Variables -
 *      <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code>
 *      (RECOMMENDED since they are recognized by all the AWS SDKs and CLI except for .NET),
 *      or <code>AWS_ACCESS_KEY</code> and <code>AWS_SECRET_KEY</code> (only recognized by Java SDK)
 *   </li>
 *   <li>Java System Properties - aws.accessKeyId and aws.secretKey</li>
 *   <li>Credential profiles file at the default location (~/.aws/credentials) shared by all AWS SDKs and the AWS CLI</li>
 *   <li>Credentials delivered through the Amazon EC2 container service if AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" environment variable is set
 *   and security manager has permission to access the variable,</li>
 *   <li>Instance profile credentials delivered through the Amazon EC2 metadata service</li>
 * </ul>
 *
 * @see EnvironmentVariableCredentialsProvider
 * @see SystemPropertiesCredentialsProvider
 * @see ProfileCredentialsProvider
 * @see EC2ContainerCredentialsProviderWrapper
 */
public class DefaultAWSCredentialsProviderChain extends AWSCredentialsProviderChain {

    private static final DefaultAWSCredentialsProviderChain INSTANCE
        = new DefaultAWSCredentialsProviderChain();

    public DefaultAWSCredentialsProviderChain() {
        super(new EnvironmentVariableCredentialsProvider(),
              new SystemPropertiesCredentialsProvider(),
              new ProfileCredentialsProvider(),
              new EC2ContainerCredentialsProviderWrapper());
    }

    public static DefaultAWSCredentialsProviderChain getInstance() {
        return INSTANCE;
    }
}

AWSCredentialsProviderChain 

package com.amazonaws.auth;

import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.SdkClientException;

/**
 * {@link AWSCredentialsProvider} implementation that chains together multiple
 * credentials providers. When a caller first requests credentials from this provider,
 * it calls all the providers in the chain, in the original order specified,
 * until one can provide credentials, and then returns those credentials. If all
 * of the credential providers in the chain have been called, and none of them
 * can provide credentials, then this class will throw an exception indicated
 * that no credentials are available.
 * <p>
 * By default, this class will remember the first credentials provider in the chain
 * that was able to provide credentials, and will continue to use that provider when
 * credentials are requested in the future, instead of traversing the chain each time.
 * This behavior can be controlled through the {@link #setReuseLastProvider(boolean)} method.
 */
public class AWSCredentialsProviderChain implements AWSCredentialsProvider {

    private static final Log log = LogFactory.getLog(AWSCredentialsProviderChain.class);

    private final List<AWSCredentialsProvider> credentialsProviders =
            new LinkedList<AWSCredentialsProvider>();

    private boolean reuseLastProvider = true;
    private AWSCredentialsProvider lastUsedProvider;

    /**
     * Constructs a new AWSCredentialsProviderChain with the specified credential providers. When
     * credentials are requested from this provider, it will call each of these credential providers
     * in the same order specified here until one of them returns AWS security credentials.
     *
     * @param credentialsProviders
     *            The chain of credentials providers.
     */
    public AWSCredentialsProviderChain(List<? extends AWSCredentialsProvider> credentialsProviders) {
        if (credentialsProviders == null || credentialsProviders.size() == 0) {
            throw new IllegalArgumentException("No credential providers specified");
        }
        this.credentialsProviders.addAll(credentialsProviders);
    }

    /**
     * Constructs a new AWSCredentialsProviderChain with the specified credential providers. When
     * credentials are requested from this provider, it will call each of these credential providers
     * in the same order specified here until one of them returns AWS security credentials.
     *
     * @param credentialsProviders
     *            The chain of credentials providers.
     */
    public AWSCredentialsProviderChain(AWSCredentialsProvider... credentialsProviders) {
        if (credentialsProviders == null || credentialsProviders.length == 0) {
            throw new IllegalArgumentException("No credential providers specified");
        }

        for (AWSCredentialsProvider provider : credentialsProviders) {
            this.credentialsProviders.add(provider);
        }
    }

    /**
     * Returns true if this chain will reuse the last successful credentials
     * provider for future credentials requests, otherwise, false if it will
     * search through the chain each time.
     *
     * @return True if this chain will reuse the last successful credentials
     *         provider for future credentials requests.
     */
    public boolean getReuseLastProvider() {
        return reuseLastProvider;
    }

    /**
     * Enables or disables caching of the last successful credentials provider
     * in this chain. Reusing the last successful credentials provider will
     * typically return credentials faster than searching through the chain.
     *
     * @param b
     *            Whether to enable or disable reusing the last successful
     *            credentials provider for future credentials requests instead
     *            of searching through the whole chain.
     */
    public void setReuseLastProvider(boolean b) {
        this.reuseLastProvider = b;
    }

    @Override
    public AWSCredentials getCredentials() {
        if (reuseLastProvider && lastUsedProvider != null) {
            return lastUsedProvider.getCredentials();
        }

        List<String> exceptionMessages = null;
        for (AWSCredentialsProvider provider : credentialsProviders) {
            try {
                AWSCredentials credentials = provider.getCredentials();

                if (credentials.getAWSAccessKeyId() != null &&
                    credentials.getAWSSecretKey() != null) {
                    log.debug("Loading credentials from " + provider.toString());

                    lastUsedProvider = provider;
                    return credentials;
                }
            } catch (Exception e) {
                // Ignore any exceptions and move onto the next provider
                String message = provider + ": " + e.getMessage();
                log.debug("Unable to load credentials from " + message);
                if (exceptionMessages == null) {
                    exceptionMessages = new LinkedList<String>();
                }
                exceptionMessages.add(message);
            }
        }
        throw new SdkClientException("Unable to load AWS credentials from any provider in the chain: "
                                     + exceptionMessages);
    }

    @Override
    public void refresh() {
        for (AWSCredentialsProvider provider : credentialsProviders) {
            provider.refresh();
        }
    }
}

EnvironmentVariableCredentialsProvider 

package com.amazonaws.auth;

import static com.amazonaws.SDKGlobalConfiguration.ACCESS_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.ALTERNATE_ACCESS_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.ALTERNATE_SECRET_KEY_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.AWS_SESSION_TOKEN_ENV_VAR;
import static com.amazonaws.SDKGlobalConfiguration.SECRET_KEY_ENV_VAR;

import com.amazonaws.SdkClientException;
import com.amazonaws.util.StringUtils;

/**
 * {@link AWSCredentialsProvider} implementation that provides credentials by looking at the: <code>AWS_ACCESS_KEY_ID</code> (or
 * <code>AWS_ACCESS_KEY</code>) and <code>AWS_SECRET_KEY</code> (or <code>AWS_SECRET_ACCESS_KEY</code>) environment variables. If
 * the <code>AWS_SESSION_TOKEN</code> environment variable is also set then temporary credentials will be used.
 */
public class EnvironmentVariableCredentialsProvider implements AWSCredentialsProvider {
    @Override
    public AWSCredentials getCredentials() {
        String accessKey = System.getenv(ACCESS_KEY_ENV_VAR);
        if (accessKey == null) {
            accessKey = System.getenv(ALTERNATE_ACCESS_KEY_ENV_VAR);
        }

        String secretKey = System.getenv(SECRET_KEY_ENV_VAR);
        if (secretKey == null) {
            secretKey = System.getenv(ALTERNATE_SECRET_KEY_ENV_VAR);
        }

        accessKey = StringUtils.trim(accessKey);
        secretKey = StringUtils.trim(secretKey);
        String sessionToken = StringUtils.trim(System.getenv(AWS_SESSION_TOKEN_ENV_VAR));

        if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey)) {

            throw new SdkClientException(
                    "Unable to load AWS credentials from environment variables " +
                    "(" + ACCESS_KEY_ENV_VAR + " (or " + ALTERNATE_ACCESS_KEY_ENV_VAR + ") and " +
                    SECRET_KEY_ENV_VAR + " (or " + ALTERNATE_SECRET_KEY_ENV_VAR + "))");
        }

        return sessionToken == null ?
                new BasicAWSCredentials(accessKey, secretKey)
                :
                new BasicSessionCredentials(accessKey, secretKey, sessionToken);
    }

    @Override
    public void refresh() {
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

SystemPropertiesCredentialsProvider

package com.amazonaws.auth;

import static com.amazonaws.SDKGlobalConfiguration.ACCESS_KEY_SYSTEM_PROPERTY;
import static com.amazonaws.SDKGlobalConfiguration.SECRET_KEY_SYSTEM_PROPERTY;
import static com.amazonaws.SDKGlobalConfiguration.SESSION_TOKEN_SYSTEM_PROPERTY;

import com.amazonaws.SdkClientException;
import com.amazonaws.util.StringUtils;

/**
 * {@link AWSCredentialsProvider} implementation that provides credentials by
 * looking at the <code>aws.accessKeyId</code> and <code>aws.secretKey</code>
 * Java system properties.
 */
public class SystemPropertiesCredentialsProvider implements AWSCredentialsProvider {

    @Override
    public AWSCredentials getCredentials() {
        String accessKey = StringUtils.trim(System.getProperty(ACCESS_KEY_SYSTEM_PROPERTY));
        String secretKey = StringUtils.trim(System.getProperty(SECRET_KEY_SYSTEM_PROPERTY));
        String sessionToken = StringUtils.trim(System.getProperty(SESSION_TOKEN_SYSTEM_PROPERTY));

        if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey)) {
            throw new SdkClientException(
                    "Unable to load AWS credentials from Java system "
                    + "properties (" + ACCESS_KEY_SYSTEM_PROPERTY + " and "
                    + SECRET_KEY_SYSTEM_PROPERTY + ")");
        }
        if (StringUtils.isNullOrEmpty(sessionToken)) {
            return new BasicAWSCredentials(accessKey, secretKey);
        } else {
            return new BasicSessionCredentials(accessKey, secretKey, sessionToken);
        }
    }

    @Override
    public void refresh() {
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

ProfileCredentialsProvider

package com.amazonaws.auth.profile;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.profile.internal.AwsProfileNameLoader;

import java.util.concurrent.Semaphore;

/**
 * Credentials provider based on AWS configuration profiles. This provider vends AWSCredentials from
 * the profile configuration file for the default profile, or for a specific, named profile. <p> AWS
 * credential profiles allow you to share multiple sets of AWS security credentials between
 * different tools like the AWS SDK for Java and the AWS CLI. <p> See
 * http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
 *
 * @see ProfilesConfigFile
 */
public class ProfileCredentialsProvider implements AWSCredentialsProvider {

    /**
     * Default refresh interval
     */
    private static final long DEFAULT_REFRESH_INTERVAL_NANOS = 5 * 60 * 1000 * 1000 * 1000L;

    /**
     * Default force reload interval
     */
    private static final long DEFAULT_FORCE_RELOAD_INTERVAL_NANOS =
            2 * DEFAULT_REFRESH_INTERVAL_NANOS;

    /**
     * The credential profiles file from which this provider loads the security credentials. Lazily
     * loaded by the double-check idiom.
     */
    private volatile ProfilesConfigFile profilesConfigFile;

    /**
     * When the profiles file was last refreshed.
     */
    private volatile long lastRefreshed;

    /**
     * The name of the credential profile
     */
    private final String profileName;

    /**
     * Used to have only one thread block on refresh, for applications making at least one call
     * every REFRESH_INTERVAL_NANOS.
     */
    private final Semaphore refreshSemaphore = new Semaphore(1);

    /**
     * Refresh interval. Defaults to {@link #DEFAULT_REFRESH_INTERVAL_NANOS}
     */
    private long refreshIntervalNanos = DEFAULT_REFRESH_INTERVAL_NANOS;

    /**
     * Force reload interval. Defaults to {@link #DEFAULT_FORCE_RELOAD_INTERVAL_NANOS}
     */
    private long refreshForceIntervalNanos = DEFAULT_FORCE_RELOAD_INTERVAL_NANOS;

    /**
     * Creates a new profile credentials provider that returns the AWS security credentials
     * configured for the default profile. Loading the credential file is deferred until the
     * getCredentials() method is called.
     */
    public ProfileCredentialsProvider() {
        this(null);
    }

    /**
     * Creates a new profile credentials provider that returns the AWS security credentials
     * configured for the named profile. Loading the credential file is deferred until the
     * getCredentials() method is called.
     *
     * @param profileName The name of a local configuration profile.
     */
    public ProfileCredentialsProvider(String profileName) {
        this((ProfilesConfigFile) null, profileName);
    }

    /**
     * Creates a new profile credentials provider that returns the AWS security credentials for the
     * specified profiles configuration file and profile name.
     *
     * @param profilesConfigFilePath The file path where the profile configuration file is located.
     * @param profileName            The name of a configuration profile in the specified
     *                               configuration file.
     */
    public ProfileCredentialsProvider(String profilesConfigFilePath, String profileName) {
        this(new ProfilesConfigFile(profilesConfigFilePath), profileName);
    }

    /**
     * Creates a new profile credentials provider that returns the AWS security credentials for the
     * specified profiles configuration file and profile name.
     *
     * @param profilesConfigFile The profile configuration file containing the profiles used by this
     *                           credentials provider or null to defer load to first use.
     * @param profileName        The name of a configuration profile in the specified configuration
     *                           file.
     */
    public ProfileCredentialsProvider(ProfilesConfigFile profilesConfigFile, String profileName) {
        this.profilesConfigFile = profilesConfigFile;
        if (this.profilesConfigFile != null) {
            this.lastRefreshed = System.nanoTime();
        }
        if (profileName == null) {
            this.profileName = AwsProfileNameLoader.INSTANCE.loadProfileName();
        } else {
            this.profileName = profileName;
        }
    }

    @Override
    public AWSCredentials getCredentials() {
        if (profilesConfigFile == null) {
            synchronized (this) {
                if (profilesConfigFile == null) {
                    profilesConfigFile = new ProfilesConfigFile();
                    lastRefreshed = System.nanoTime();
                }
            }
        }

        // Periodically check if the file on disk has been modified
        // since we last read it.
        //
        // For active applications, only have one thread block.
        // For applications that use this method in bursts, ensure the
        // credentials are never too stale.
        long now = System.nanoTime();
        long age = now - lastRefreshed;
        if (age > refreshForceIntervalNanos) {
            refresh();
        } else if (age > refreshIntervalNanos) {
            if (refreshSemaphore.tryAcquire()) {
                try {
                    refresh();
                } finally {
                    refreshSemaphore.release();
                }
            }
        }

        return profilesConfigFile.getCredentials(profileName);
    }

    @Override
    public void refresh() {
        if (profilesConfigFile != null) {
            profilesConfigFile.refresh();
            lastRefreshed = System.nanoTime();
        }
    }

    /**
     * Gets the refresh interval in nanoseconds.
     *
     * @return nanoseconds
     */
    public long getRefreshIntervalNanos() {
        return refreshIntervalNanos;
    }

    /**
     * Sets the refresh interval in nanoseconds.
     *
     * @param refreshIntervalNanos nanoseconds
     */
    public void setRefreshIntervalNanos(long refreshIntervalNanos) {
        this.refreshIntervalNanos = refreshIntervalNanos;
    }

    /**
     * Gets the forced refresh interval in nanoseconds.
     *
     * @return nanoseconds
     */
    public long getRefreshForceIntervalNanos() {
        return refreshForceIntervalNanos;
    }

    /**
     * Sets the forced refresh interval in nanoseconds.
     */
    public void setRefreshForceIntervalNanos(long refreshForceIntervalNanos) {
        this.refreshForceIntervalNanos = refreshForceIntervalNanos;
    }
}

EC2ContainerCredentialsProviderWrapper

package com.amazonaws.auth;

import static com.amazonaws.auth.ContainerCredentialsProvider.CONTAINER_CREDENTIALS_FULL_URI;
import static com.amazonaws.auth.ContainerCredentialsProvider.ECS_CONTAINER_CREDENTIALS_PATH;

import com.amazonaws.auth.ContainerCredentialsProvider.ECSCredentialsEndpointProvider;
import com.amazonaws.auth.ContainerCredentialsProvider.FullUriCredentialsEndpointProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * {@link AWSCredentialsProvider} that loads credentials from an Amazon Container (e.g. EC2)
 *
 * Credentials are solved in the following order:
 * <ol>
 *     <li>
 *         If environment variable "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" is
 *         set (typically on EC2) it is used to hit the metadata service at the following endpoint: http://169.254.170.2
 *     </li>
 *     <li>
 *         If environment variable "AWS_CONTAINER_CREDENTIALS_FULL_URI" is
 *         set it is used to hit a metadata service at that URI. <br/> Optionally an authorization token can be included
 *         in the "Authorization" header of the request by setting the "AWS_CONTAINER_AUTHORIZATION_TOKEN" environment variable.
 *     </li>
 *     <li>
 *         If neither of the above environment variables are specified credentials are attempted to be loaded from Amazon EC2
 *         Instance Metadata Service using the {@link InstanceProfileCredentialsProvider}.
 *     </li>
 * </ol>
 */
public class EC2ContainerCredentialsProviderWrapper implements AWSCredentialsProvider {

    private static final Log LOG = LogFactory.getLog(EC2ContainerCredentialsProviderWrapper.class);

    private final AWSCredentialsProvider provider;

    public EC2ContainerCredentialsProviderWrapper() {
        provider = initializeProvider();
    }

    private AWSCredentialsProvider initializeProvider() {
        try {
            if (System.getenv(ECS_CONTAINER_CREDENTIALS_PATH) != null) {
                return new ContainerCredentialsProvider(new ECSCredentialsEndpointProvider());
            }
            if (System.getenv(CONTAINER_CREDENTIALS_FULL_URI) != null) {
                return new ContainerCredentialsProvider(new FullUriCredentialsEndpointProvider());
            }
            return InstanceProfileCredentialsProvider.getInstance();
        } catch (SecurityException securityException) {
            LOG.debug("Security manager did not allow access to the ECS credentials environment variable " + ECS_CONTAINER_CREDENTIALS_PATH +
                "or the container full URI environment variable " + CONTAINER_CREDENTIALS_FULL_URI
                        + ". Please provide access to this environment variable if you want to load credentials from ECS Container.");
            return InstanceProfileCredentialsProvider.getInstance();
        }
    }

    @Override
    public AWSCredentials getCredentials() {
        return provider.getCredentials();
    }

    @Override
    public void refresh() {
       provider.refresh();
    }
}

ContainerCredentialsProvider 

package com.amazonaws.auth;

import com.amazonaws.SdkClientException;
import com.amazonaws.internal.CredentialsEndpointProvider;
import com.amazonaws.retry.internal.CredentialsEndpointRetryPolicy;
import com.amazonaws.util.CollectionUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * {@link AWSCredentialsProvider} implementation that loads credentials
 * from an Amazon Elastic Container.
 * </p>
 * <p>
 * By default, the URI path is retrieved from the environment variable
 * "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" in the container's environment.
 * </p>
 */
public class ContainerCredentialsProvider implements AWSCredentialsProvider {

    /** Environment variable to get the Amazon ECS credentials resource path. */
    static final String ECS_CONTAINER_CREDENTIALS_PATH = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";

    /** Environment variable to get the full URI for a credentials path */
    static final String CONTAINER_CREDENTIALS_FULL_URI = "AWS_CONTAINER_CREDENTIALS_FULL_URI";

    static final String CONTAINER_AUTHORIZATION_TOKEN = "AWS_CONTAINER_AUTHORIZATION_TOKEN";

    private static final Set<String> ALLOWED_FULL_URI_HOSTS = allowedHosts();

    /** Default endpoint to retreive the Amazon ECS Credentials. */
    private static final String ECS_CREDENTIALS_ENDPOINT = "http://169.254.170.2";

    private final EC2CredentialsFetcher credentialsFetcher;

    /**
     * @deprecated use {@link #ContainerCredentialsProvider(CredentialsEndpointProvider)}
     */
    @Deprecated
    public ContainerCredentialsProvider() {
        this(new ECSCredentialsEndpointProvider());
    }

    public ContainerCredentialsProvider(CredentialsEndpointProvider credentialsEndpointProvider) {
        this.credentialsFetcher = new EC2CredentialsFetcher(credentialsEndpointProvider);
    }

    @Override
    public AWSCredentials getCredentials() {
        return credentialsFetcher.getCredentials();
    }

    @Override
    public void refresh() {
        credentialsFetcher.refresh();
    }

    public Date getCredentialsExpiration() {
        return credentialsFetcher.getCredentialsExpiration();
    }


    static class ECSCredentialsEndpointProvider extends CredentialsEndpointProvider {
        @Override
        public URI getCredentialsEndpoint() throws URISyntaxException {
            String path = System.getenv(ECS_CONTAINER_CREDENTIALS_PATH);
            if (path == null) {
                throw new SdkClientException(
                        "The environment variable " + ECS_CONTAINER_CREDENTIALS_PATH + " is empty");
            }

            return new URI(ECS_CREDENTIALS_ENDPOINT + path);
        }
        @Override
        public CredentialsEndpointRetryPolicy getRetryPolicy() {
            return ContainerCredentialsRetryPolicy.getInstance();
        }

    }

    /**
     * A URI resolver that uses environment variable {@value CONTAINER_CREDENTIALS_FULL_URI} as the URI
     * for the metadata service.
     * Optionally an authorization token can be provided using the {@value CONTAINER_AUTHORIZATION_TOKEN} environment variable.
     */
    static class FullUriCredentialsEndpointProvider extends CredentialsEndpointProvider {

        @Override
        public URI getCredentialsEndpoint() throws URISyntaxException {
            String fullUri = System.getenv(CONTAINER_CREDENTIALS_FULL_URI);
            if (fullUri == null || fullUri.length() == 0) {
                throw new SdkClientException("The environment variable " + CONTAINER_CREDENTIALS_FULL_URI + " is empty");
            }

            URI uri = new URI(fullUri);

            if (!ALLOWED_FULL_URI_HOSTS.contains(uri.getHost())) {
                throw new SdkClientException("The full URI (" + uri + ") contained withing environment variable " +
                    CONTAINER_CREDENTIALS_FULL_URI + " has an invalid host. Host can only be one of [" +
                    CollectionUtils.join(ALLOWED_FULL_URI_HOSTS, ", ") + "]");
            }

            return uri;
        }

        @Override
        public Map<String, String> getHeaders() {
            if (System.getenv(CONTAINER_AUTHORIZATION_TOKEN) != null) {
                return Collections.singletonMap("Authorization", System.getenv(CONTAINER_AUTHORIZATION_TOKEN));
            }
            return new HashMap<String, String>();
        }
    }

    private static Set<String> allowedHosts() {
        HashSet<String> hosts = new HashSet<String>();
        hosts.add("127.0.0.1");
        hosts.add("localhost");
        return Collections.unmodifiableSet(hosts);
    }

}

InstanceProfileCredentialsProvider

package com.amazonaws.auth;

import com.amazonaws.AmazonClientException;
import com.amazonaws.SDKGlobalConfiguration;
import com.amazonaws.SdkClientException;
import com.amazonaws.internal.CredentialsEndpointProvider;
import com.amazonaws.internal.EC2CredentialsUtils;
import com.amazonaws.util.EC2MetadataUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Credentials provider implementation that loads credentials from the Amazon EC2 Instance Metadata Service.
 *
 * <p>When using {@link InstanceProfileCredentialsProvider} with asynchronous refreshing it is
 * <b>strongly</b> recommended to explicitly call {@link #close()} to release the async thread.</p>
 */
public class InstanceProfileCredentialsProvider implements AWSCredentialsProvider, Closeable {

    private static final Log LOG = LogFactory.getLog(InstanceProfileCredentialsProvider.class);

    /**
     * The wait time, after which the background thread initiates a refresh to
     * load latest credentials if needed.
     */
    private static final int ASYNC_REFRESH_INTERVAL_TIME_MINUTES = 1;

    /**
     * The default InstanceProfileCredentialsProvider that can be shared by
     * multiple CredentialsProvider instance threads to shrink the amount of
     * requests to EC2 metadata service.
     */
    private static final InstanceProfileCredentialsProvider INSTANCE = new InstanceProfileCredentialsProvider();

    private final EC2CredentialsFetcher credentialsFetcher;

    /**
     * The executor service used for refreshing the credentials in the
     * background.
     */
    private volatile ScheduledExecutorService executor;

    private volatile boolean shouldRefresh = false;

    /**
     * @deprecated for the singleton method {@link #getInstance()}.
     */
    @Deprecated
    public InstanceProfileCredentialsProvider() {
        this(false);
    }

    /**
     * Spins up a new thread to refresh the credentials asynchronously if
     * refreshCredentialsAsync is set to true, otherwise the credentials will be
     * refreshed from the instance metadata service synchronously,
     *
     * <p>It is <b>strongly</b> recommended to reuse instances of this credentials provider, especially
     * when async refreshing is used since a background thread is created.</p>
     *
     * @param refreshCredentialsAsync
     *            true if credentials needs to be refreshed asynchronously else
     *            false.
     */
    public InstanceProfileCredentialsProvider(boolean refreshCredentialsAsync) {
        this(refreshCredentialsAsync, true);
    }

    /**
     * Spins up a new thread to refresh the credentials asynchronously.
     *
     * <p>It is <b>strongly</b> recommended to reuse instances of this credentials provider, especially
     * when async refreshing is used since a background thread is created.</p>
     *
     * @param eagerlyRefreshCredentialsAsync
     *            when set to false will not attempt to refresh credentials asynchronously
     *            until after a call has been made to {@link #getCredentials()} - ensures that
     *            {@link EC2CredentialsFetcher#getCredentials()} is only hit when this CredentialProvider is actually required
     */
    public static InstanceProfileCredentialsProvider createAsyncRefreshingProvider(final boolean eagerlyRefreshCredentialsAsync) {
        return new InstanceProfileCredentialsProvider(true, eagerlyRefreshCredentialsAsync);
    }

    private InstanceProfileCredentialsProvider(boolean refreshCredentialsAsync, final boolean eagerlyRefreshCredentialsAsync) {

        credentialsFetcher = new EC2CredentialsFetcher(new InstanceMetadataCredentialsEndpointProvider());

        if (!SDKGlobalConfiguration.isEc2MetadataDisabled()) {
            if (refreshCredentialsAsync) {
                executor = Executors.newScheduledThreadPool(1);
                executor.scheduleWithFixedDelay(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            if (shouldRefresh) credentialsFetcher.getCredentials();
                        } catch (AmazonClientException ace) {
                            handleError(ace);
                        } catch (RuntimeException re) {
                            handleError(re);
                        }
                    }
                }, 0, ASYNC_REFRESH_INTERVAL_TIME_MINUTES, TimeUnit.MINUTES);
            }
        }
    }

    /**
     * Returns a singleton {@link InstanceProfileCredentialsProvider} that does not refresh credentials asynchronously.
     *
     * <p>
     * See {@link #InstanceProfileCredentialsProvider(boolean)} or {@link #createAsyncRefreshingProvider(boolean)} for
     * asynchronous credentials refreshing.
     * </p>
     */
    public static InstanceProfileCredentialsProvider getInstance() {
        return INSTANCE;
    }

    private void handleError(Throwable t) {
        refresh();
        LOG.error(t.getMessage(), t);
    }

    @Override
    protected void finalize() throws Throwable {
        if (executor != null) {
            executor.shutdownNow();
        }
    }


    /**
     * {@inheritDoc}
     *
     * @throws AmazonClientException if {@link SDKGlobalConfiguration#isEc2MetadataDisabled()} is true
     */
    @Override
    public AWSCredentials getCredentials() {
        if (SDKGlobalConfiguration.isEc2MetadataDisabled()) {
            throw new AmazonClientException("AWS_EC2_METADATA_DISABLED is set to true, not loading credentials from EC2 Instance "
                                         + "Metadata service");
        }
        AWSCredentials creds = credentialsFetcher.getCredentials();
        shouldRefresh = true;
        return creds;
    }

    @Override
    public void refresh() {
        if (credentialsFetcher != null) {
            credentialsFetcher.refresh();
        }
    }

    @Override
    public void close() throws IOException {
        if (executor != null) {
            executor.shutdownNow();
            executor = null;
        }
    }

    private static class InstanceMetadataCredentialsEndpointProvider extends CredentialsEndpointProvider {
        @Override
        public URI getCredentialsEndpoint() throws URISyntaxException, IOException {
            String host = EC2MetadataUtils.getHostAddressForEC2MetadataService();

            String securityCredentialsList = EC2CredentialsUtils.getInstance().readResource(new URI(host + EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE));
            String[] securityCredentials = securityCredentialsList.trim().split("\n");
            if (securityCredentials.length == 0) {
                throw new SdkClientException("Unable to load credentials path");
            }

            return new URI(host + EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE + securityCredentials[0]);
        }
    }
}

相关阅读

相关文章

相关问答