/*
* Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.auth;
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 com.amazonaws.AmazonClientException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.amazonaws.SdkClientException;
import com.amazonaws.internal.CredentialsEndpointProvider;
import com.amazonaws.internal.EC2CredentialsUtils;
import com.amazonaws.util.EC2MetadataUtils;
/**
* Credentials provider implementation that loads credentials from the Amazon
* EC2 Instance Metadata Service.
*/
public class InstanceProfileCredentialsProvider implements AWSCredentialsProvider {
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,
*
* @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.
* @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());
shouldRefresh = eagerlyRefreshCredentialsAsync;
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);
} catch (Error e) {
handleError(e);
}
}
}, 0, ASYNC_REFRESH_INTERVAL_TIME_MINUTES, TimeUnit.MINUTES);
}
}
/**
* Returns a singleton {@link InstanceProfileCredentialsProvider} that does not refresh credentials
* asynchronously. Use {@link #InstanceProfileCredentialsProvider(boolean)} for the feature.
*/
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();
}
}
@Override
public AWSCredentials getCredentials() {
AWSCredentials creds = credentialsFetcher.getCredentials();
shouldRefresh = true;
return creds;
}
@Override
public void refresh() {
credentialsFetcher.refresh();
}
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]);
}
}
}