|
@@ -21,37 +21,69 @@ package org.apache.hadoop.fs.s3a.auth;
|
|
|
import java.io.Closeable;
|
|
|
import java.io.IOException;
|
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
|
|
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
|
|
import software.amazon.awssdk.auth.credentials.ContainerCredentialsProvider;
|
|
|
+import software.amazon.awssdk.auth.credentials.HttpCredentialsProvider;
|
|
|
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
|
|
|
import software.amazon.awssdk.core.exception.SdkClientException;
|
|
|
|
|
|
import org.apache.hadoop.classification.InterfaceAudience;
|
|
|
import org.apache.hadoop.classification.InterfaceStability;
|
|
|
|
|
|
+import static org.apache.hadoop.fs.s3a.impl.ErrorTranslation.maybeExtractIOException;
|
|
|
+
|
|
|
/**
|
|
|
* This is an IAM credential provider which wraps
|
|
|
* an {@code ContainerCredentialsProvider}
|
|
|
* to provide credentials when the S3A connector is instantiated on AWS EC2
|
|
|
* or the AWS container services.
|
|
|
* <p>
|
|
|
- * When it fails to authenticate, it raises a
|
|
|
- * {@link NoAwsCredentialsException} which can be recognized by retry handlers
|
|
|
+ * The provider is initialized with async credential refresh enabled to be less
|
|
|
+ * brittle against transient network issues.
|
|
|
+ * <p>
|
|
|
+ * If the ContainerCredentialsProvider fails to authenticate, then an instance of
|
|
|
+ * {@link InstanceProfileCredentialsProvider} is created and attemped to
|
|
|
+ * be used instead, again with async credential refresh enabled.
|
|
|
+ * <p>
|
|
|
+ * If both credential providers fail, a {@link NoAwsCredentialsException}
|
|
|
+ * is thrown, which can be recognized by retry handlers
|
|
|
* as a non-recoverable failure.
|
|
|
* <p>
|
|
|
* It is implicitly public; marked evolving as we can change its semantics.
|
|
|
- *
|
|
|
*/
|
|
|
@InterfaceAudience.Public
|
|
|
@InterfaceStability.Evolving
|
|
|
public class IAMInstanceCredentialsProvider
|
|
|
implements AwsCredentialsProvider, Closeable {
|
|
|
|
|
|
- private final AwsCredentialsProvider containerCredentialsProvider =
|
|
|
- ContainerCredentialsProvider.builder().build();
|
|
|
+ private static final Logger LOG =
|
|
|
+ LoggerFactory.getLogger(IAMInstanceCredentialsProvider.class);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The credentials provider.
|
|
|
+ * Initially a container credentials provider, but if that fails
|
|
|
+ * fall back to the instance profile provider.
|
|
|
+ */
|
|
|
+ private HttpCredentialsProvider iamCredentialsProvider;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the container credentials provider in use?
|
|
|
+ */
|
|
|
+ private boolean isContainerCredentialsProvider;
|
|
|
|
|
|
+ /**
|
|
|
+ * Constructor.
|
|
|
+ * Build credentials provider with async refresh,
|
|
|
+ * mark {@link #isContainerCredentialsProvider} as true.
|
|
|
+ */
|
|
|
public IAMInstanceCredentialsProvider() {
|
|
|
+ isContainerCredentialsProvider = true;
|
|
|
+ iamCredentialsProvider = ContainerCredentialsProvider.builder()
|
|
|
+ .asyncCredentialUpdateEnabled(true)
|
|
|
+ .build();
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -65,9 +97,16 @@ public class IAMInstanceCredentialsProvider
|
|
|
try {
|
|
|
return getCredentials();
|
|
|
} catch (SdkClientException e) {
|
|
|
+
|
|
|
+ // if the exception contains an IOE, extract it
|
|
|
+ // so its type is the immediate cause of this new exception.
|
|
|
+ Throwable t = e;
|
|
|
+ final IOException ioe = maybeExtractIOException("IAM endpoint", e);
|
|
|
+ if (ioe != null) {
|
|
|
+ t = ioe;
|
|
|
+ }
|
|
|
throw new NoAwsCredentialsException("IAMInstanceCredentialsProvider",
|
|
|
- e.getMessage(),
|
|
|
- e);
|
|
|
+ e.getMessage(), t);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -78,23 +117,52 @@ public class IAMInstanceCredentialsProvider
|
|
|
*
|
|
|
* @return credentials
|
|
|
*/
|
|
|
- private AwsCredentials getCredentials() {
|
|
|
+ private synchronized AwsCredentials getCredentials() {
|
|
|
try {
|
|
|
- return containerCredentialsProvider.resolveCredentials();
|
|
|
+ return iamCredentialsProvider.resolveCredentials();
|
|
|
} catch (SdkClientException e) {
|
|
|
- return InstanceProfileCredentialsProvider.create().resolveCredentials();
|
|
|
+ LOG.debug("Failed to get credentials from container provider,", e);
|
|
|
+ if (isContainerCredentialsProvider) {
|
|
|
+ // create instance profile provider
|
|
|
+ LOG.debug("Switching to instance provider", e);
|
|
|
+
|
|
|
+ // close it to shut down any thread
|
|
|
+ iamCredentialsProvider.close();
|
|
|
+ isContainerCredentialsProvider = false;
|
|
|
+ iamCredentialsProvider = InstanceProfileCredentialsProvider.builder()
|
|
|
+ .asyncCredentialUpdateEnabled(true)
|
|
|
+ .build();
|
|
|
+ return iamCredentialsProvider.resolveCredentials();
|
|
|
+ } else {
|
|
|
+ // already using instance profile provider, so fail
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Is this a container credentials provider?
|
|
|
+ * @return true if the container credentials provider is in use;
|
|
|
+ * false for InstanceProfileCredentialsProvider
|
|
|
+ */
|
|
|
+ public boolean isContainerCredentialsProvider() {
|
|
|
+ return isContainerCredentialsProvider;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
- public void close() throws IOException {
|
|
|
- // no-op.
|
|
|
+ public synchronized void close() throws IOException {
|
|
|
+ // this be true but just for safety...
|
|
|
+ if (iamCredentialsProvider != null) {
|
|
|
+ iamCredentialsProvider.close();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String toString() {
|
|
|
return "IAMInstanceCredentialsProvider{" +
|
|
|
- "containerCredentialsProvider=" + containerCredentialsProvider +
|
|
|
+ "credentialsProvider=" + iamCredentialsProvider +
|
|
|
+ ", isContainerCredentialsProvider=" + isContainerCredentialsProvider +
|
|
|
'}';
|
|
|
}
|
|
|
}
|