Skip to content

Log unsuccessful attempts to get credentials from web identity tokens #88241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d2c3bdd
Log unsuccessful attempts to get credentials from web identity tokens…
arteam Jul 4, 2022
ce293dc
Add tests
arteam Jul 4, 2022
d0350bb
Remove redundant modifier
arteam Jul 4, 2022
1d5b0e6
Make lastUsedProvider volatile
arteam Jul 4, 2022
2926ec6
Use the correct signature for the warning logger
arteam Jul 4, 2022
547c3f1
Merge remote-tracking branch 'origin/master' into log-unsuccesful-aut…
arteam Jul 4, 2022
b0790a5
Update docs/changelog/88241.yaml
arteam Jul 4, 2022
3a7c704
Merge branch 'main' into log-unsuccesful-auth-attempts-as-warnings
elasticmachine Aug 3, 2022
e4e7d3f
Merge branch 'main' into log-unsuccesful-auth-attempts-as-warnings
elasticmachine Aug 17, 2022
251ba2e
Merge branch 'main' into log-unsuccesful-auth-attempts-as-warnings
elasticmachine Sep 5, 2022
d5a3480
Use logging credentials provider instead of the custom chain
arteam Sep 5, 2022
abe8f02
Add a test for refreshing LoggingCredentialsProvider
arteam Sep 5, 2022
8de85a3
Merge branch 'main' into log-unsuccesful-auth-attempts-as-warnings
elasticmachine Sep 6, 2022
f63064b
Update modules/repository-s3/src/main/java/org/elasticsearch/reposito…
arteam Sep 8, 2022
1dc05b5
Merge remote-tracking branch 'origin/main' into log-unsuccesful-auth-…
arteam Sep 8, 2022
7c74bca
Merge remote-tracking branch 'origin/log-unsuccesful-auth-attempts-as…
arteam Sep 8, 2022
f1e4279
Use expectThrows for catching exceptions
arteam Sep 8, 2022
56fdd04
Merge branch 'main' into log-unsuccesful-auth-attempts-as-warnings
elasticmachine Sep 8, 2022
020688c
Adjust test for the logging message supplier
arteam Sep 8, 2022
3da6efe
Adjust the refresh test for the logging message supplier
arteam Sep 8, 2022
e269f49
Update 88241.yaml
arteam Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/88241.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 88241
summary: Log unsuccessful attempts to get credentials from web identity tokens as
warnings
area: Allocation
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
package org.elasticsearch.repositories.s3;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSCredentialsProviderChain;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.AnonymousAWSCredentials;
import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper;
Expand Down Expand Up @@ -39,6 +39,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -223,7 +224,9 @@ static AWSCredentialsProvider buildCredentials(
if (webIdentityTokenCredentialsProvider.isActive()) {
logger.debug("Using a custom provider chain of Web Identity Token and instance profile credentials");
return new PrivilegedAWSCredentialsProvider(
new AWSCredentialsProviderChain(webIdentityTokenCredentialsProvider, new EC2ContainerCredentialsProviderWrapper())
new CustomAWSCredentialsProviderChain(
List.of(webIdentityTokenCredentialsProvider, new EC2ContainerCredentialsProviderWrapper())
)
);
} else {
logger.debug("Using instance profile credentials");
Expand Down Expand Up @@ -375,6 +378,45 @@ public void shutdown() throws IOException {
}
}

/**
* Customized {@link com.amazonaws.auth.AWSCredentialsProviderChain} that logs
* unsuccessful authentication attempts as warnings instead of debug messages
*/
static class CustomAWSCredentialsProviderChain implements AWSCredentialsProvider {

private final List<AWSCredentialsProvider> credentialsProviders;
private volatile AWSCredentialsProvider lastUsedProvider;

CustomAWSCredentialsProviderChain(List<AWSCredentialsProvider> credentialsProviders) {
this.credentialsProviders = List.copyOf(credentialsProviders);
}

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

for (AWSCredentialsProvider provider : credentialsProviders) {
try {
AWSCredentials credentials = provider.getCredentials();
if (credentials.getAWSAccessKeyId() != null && credentials.getAWSSecretKey() != null) {
lastUsedProvider = provider;
return credentials;
}
} catch (Exception e) {
LOGGER.warn("Unable to load credentials from " + provider, e);
}
}
throw new SdkClientException("Unable to load AWS credentials from any provider in the chain");
}

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

@FunctionalInterface
interface SystemEnvironment {
String getEnv(String name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,27 @@

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.SdkClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSCredentialsProviderChain;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper;

import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.junit.Assert;
import org.mockito.Mockito;

import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;

public class AwsS3ServiceImplTests extends ESTestCase {

Expand Down Expand Up @@ -60,12 +63,48 @@ public void testSupportsWebIdentityTokenCredentials() {
);
assertThat(credentialsProvider, instanceOf(S3Service.PrivilegedAWSCredentialsProvider.class));
var privilegedAWSCredentialsProvider = (S3Service.PrivilegedAWSCredentialsProvider) credentialsProvider;
assertThat(privilegedAWSCredentialsProvider.getCredentialsProvider(), instanceOf(AWSCredentialsProviderChain.class));
assertThat(
privilegedAWSCredentialsProvider.getCredentialsProvider(),
instanceOf(S3Service.CustomAWSCredentialsProviderChain.class)
);
AWSCredentials credentials = privilegedAWSCredentialsProvider.getCredentials();
assertEquals("sts_access_key_id", credentials.getAWSAccessKeyId());
assertEquals("sts_secret_key", credentials.getAWSSecretKey());
}

public void testCustomCredentialsProviderChainThrowsSdkErrorIfUnableToLoadCredentials() {
AWSCredentialsProvider provider1 = Mockito.mock(AWSCredentialsProvider.class);
AWSCredentialsProvider provider2 = Mockito.mock(AWSCredentialsProvider.class);
Mockito.when(provider1.getCredentials()).thenThrow(new IllegalStateException("credentialProvider1 is unavailable"));
Mockito.when(provider2.getCredentials()).thenThrow(new IllegalStateException("credentialProvider2 is unavailable"));
var credentialsProvider = new S3Service.CustomAWSCredentialsProviderChain(List.of(provider1, provider2));
try {
credentialsProvider.getCredentials();
Assert.fail("Shouldn't load any credentials");
} catch (SdkClientException e) {
assertThat(e.getMessage(), startsWith("Unable to load AWS credentials from any provider in the chain"));
}
}

public void testCustomCredentialsProviderChainReusesSuccessfulProvider() {
AWSCredentialsProvider provider1 = Mockito.mock(AWSCredentialsProvider.class);
AWSCredentialsProvider provider2 = Mockito.mock(AWSCredentialsProvider.class);
Mockito.when(provider1.getCredentials()).thenThrow(new IllegalStateException("credentialProvider1 is unavailable"));
Mockito.when(provider2.getCredentials()).thenReturn(new BasicAWSCredentials("sts_access_key_id", "sts_secret_key"));

var credentialsProviderChain = new S3Service.CustomAWSCredentialsProviderChain(List.of(provider1, provider2));
assertCredentials(credentialsProviderChain.getCredentials(), "sts_access_key_id", "sts_secret_key");
assertCredentials(credentialsProviderChain.getCredentials(), "sts_access_key_id", "sts_secret_key");

Mockito.verify(provider1, Mockito.times(1)).getCredentials();
Mockito.verify(provider2, Mockito.times(2)).getCredentials();
}

private void assertCredentials(AWSCredentials credentials, String accessKeyId, String secretKey) {
assertEquals(accessKeyId, credentials.getAWSAccessKeyId());
assertEquals(secretKey, credentials.getAWSSecretKey());
}

public void testAWSCredentialsFromKeystore() {
final MockSecureSettings secureSettings = new MockSecureSettings();
final String clientNamePrefix = "some_client_name_";
Expand Down