Skip to content

Commit fcc425e

Browse files
committed
Validate region/service in DynamicAwsCredentials (elastic#125671)
Following on from elastic#125559, we can validate the region and service name in tests that use `DynamicAwsCredentials` too.
1 parent c220dc5 commit fcc425e

File tree

8 files changed

+84
-25
lines changed

8 files changed

+84
-25
lines changed

modules/repository-s3/src/javaRestTest/java/org/elasticsearch/repositories/s3/RepositoryS3EcsCredentialsRestIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class RepositoryS3EcsCredentialsRestIT extends AbstractRepositoryS3RestTe
3535
private static final String BASE_PATH = PREFIX + "base_path";
3636
private static final String CLIENT = "ecs_credentials_client";
3737

38-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
38+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials("*", "s3");
3939

4040
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
4141
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V1).newCredentialsConsumer(dynamicCredentials::addValidCredentials)

modules/repository-s3/src/javaRestTest/java/org/elasticsearch/repositories/s3/RepositoryS3ImdsV1CredentialsRestIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class RepositoryS3ImdsV1CredentialsRestIT extends AbstractRepositoryS3Res
3333
private static final String BASE_PATH = PREFIX + "base_path";
3434
private static final String CLIENT = "imdsv1_credentials_client";
3535

36-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
36+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials("*", "s3");
3737

3838
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
3939
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V1).newCredentialsConsumer(dynamicCredentials::addValidCredentials)

modules/repository-s3/src/javaRestTest/java/org/elasticsearch/repositories/s3/RepositoryS3ImdsV2CredentialsRestIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class RepositoryS3ImdsV2CredentialsRestIT extends AbstractRepositoryS3Res
3333
private static final String BASE_PATH = PREFIX + "base_path";
3434
private static final String CLIENT = "imdsv2_credentials_client";
3535

36-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
36+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials("*", "s3");
3737

3838
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
3939
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V2).newCredentialsConsumer(dynamicCredentials::addValidCredentials)

modules/repository-s3/src/javaRestTest/java/org/elasticsearch/repositories/s3/RepositoryS3StsCredentialsRestIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class RepositoryS3StsCredentialsRestIT extends AbstractRepositoryS3RestTe
3232
private static final String BASE_PATH = PREFIX + "base_path";
3333
private static final String CLIENT = "sts_credentials_client";
3434

35-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
35+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials("*", "s3");
3636

3737
private static final S3HttpFixture s3HttpFixture = new S3HttpFixture(true, BUCKET, BASE_PATH, dynamicCredentials::isAuthorized);
3838

plugins/discovery-ec2/src/javaRestTest/java/org/elasticsearch/discovery/ec2/DiscoveryEc2EcsCredentialsIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626

2727
public class DiscoveryEc2EcsCredentialsIT extends DiscoveryEc2ClusterFormationTestCase {
2828

29-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
30-
3129
private static final String PREFIX = getIdentifierPrefix("DiscoveryEc2EcsCredentialsIT");
3230
private static final String REGION = PREFIX + "-region";
3331
private static final String CREDENTIALS_ENDPOINT = "/ecs_credentials_endpoint_" + PREFIX;
3432

33+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials(REGION, "ec2");
34+
3535
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
3636
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V1).newCredentialsConsumer(dynamicCredentials::addValidCredentials)
3737
.alternativeCredentialsEndpoints(Set.of(CREDENTIALS_ENDPOINT))

plugins/discovery-ec2/src/javaRestTest/java/org/elasticsearch/discovery/ec2/DiscoveryEc2InstanceProfileIT.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,27 @@
1515
import fixture.aws.imds.Ec2ImdsServiceBuilder;
1616
import fixture.aws.imds.Ec2ImdsVersion;
1717

18+
import org.elasticsearch.common.util.LazyInitializable;
1819
import org.elasticsearch.discovery.DiscoveryModule;
20+
import org.elasticsearch.test.ESTestCase;
1921
import org.elasticsearch.test.cluster.ElasticsearchCluster;
2022
import org.junit.ClassRule;
2123
import org.junit.rules.RuleChain;
2224
import org.junit.rules.TestRule;
2325

2426
import java.util.List;
27+
import java.util.function.Supplier;
2528

2629
public class DiscoveryEc2InstanceProfileIT extends DiscoveryEc2ClusterFormationTestCase {
2730

28-
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials();
31+
// Lazy-initialized so we can generate it randomly, which is not possible in static context.
32+
private static final Supplier<String> regionSupplier = new LazyInitializable<>(ESTestCase::randomIdentifier)::getOrCompute;
33+
34+
private static final DynamicAwsCredentials dynamicCredentials = new DynamicAwsCredentials(regionSupplier, "ec2");
2935

3036
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
3137
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V2).instanceIdentityDocument(
32-
(builder, params) -> builder.field("region", randomIdentifier())
38+
(builder, params) -> builder.field("region", regionSupplier.get())
3339
).newCredentialsConsumer(dynamicCredentials::addValidCredentials)
3440
);
3541

test/fixtures/aws-fixture-utils/src/main/java/fixture/aws/AwsCredentialsUtils.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,34 @@ public static BiPredicate<String, String> fixedAccessKey(String accessKey, Strin
3939
* @param region the name of the AWS region used to sign the request, or {@code *} to skip validation of the region parameter
4040
*/
4141
public static BiPredicate<String, String> mutableAccessKey(Supplier<String> accessKeySupplier, String region, String serviceName) {
42-
return (authorizationHeader, sessionTokenHeader) -> {
43-
if (authorizationHeader == null) {
44-
return false;
45-
}
42+
return (authorizationHeader, sessionTokenHeader) -> authorizationHeader != null
43+
&& isValidAwsV4SignedAuthorizationHeader(accessKeySupplier.get(), region, serviceName, authorizationHeader);
44+
}
4645

47-
final var accessKey = accessKeySupplier.get();
48-
final var expectedPrefix = "AWS4-HMAC-SHA256 Credential=" + accessKey + "/";
49-
if (authorizationHeader.startsWith(expectedPrefix) == false) {
50-
return false;
51-
}
46+
/**
47+
* @return whether the given value is a valid AWS-v4-signed authorization header that matches the given access key, region, and service
48+
* name.
49+
* @see <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html">AWS v4 Signatures</a>
50+
* @param region the name of the AWS region used to sign the request, or {@code *} to skip validation of the region parameter
51+
*/
52+
public static boolean isValidAwsV4SignedAuthorizationHeader(
53+
String accessKey,
54+
String region,
55+
String serviceName,
56+
String authorizationHeader
57+
) {
58+
final var expectedPrefix = "AWS4-HMAC-SHA256 Credential=" + accessKey + "/";
59+
if (authorizationHeader.startsWith(expectedPrefix) == false) {
60+
return false;
61+
}
5262

53-
if (region.equals("*")) {
54-
// skip region validation; TODO eliminate this when region is fixed in all tests
55-
return authorizationHeader.contains("/" + serviceName + "/aws4_request, ");
56-
}
63+
if (region.equals("*")) {
64+
// skip region validation; TODO eliminate this when region is fixed in all tests
65+
return authorizationHeader.contains("/" + serviceName + "/aws4_request, ");
66+
}
5767

58-
final var remainder = authorizationHeader.substring(expectedPrefix.length() + "YYYYMMDD".length() /* skip over date field */);
59-
return remainder.startsWith("/" + region + "/" + serviceName + "/aws4_request, ");
60-
};
68+
final var remainder = authorizationHeader.substring(expectedPrefix.length() + "YYYYMMDD".length() /* skip over date field */);
69+
return remainder.startsWith("/" + region + "/" + serviceName + "/aws4_request, ");
6170
}
6271

6372
/**

test/fixtures/aws-fixture-utils/src/main/java/fixture/aws/DynamicAwsCredentials.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,63 @@
1414
import java.util.Map;
1515
import java.util.Objects;
1616
import java.util.Set;
17+
import java.util.function.Supplier;
1718

1819
/**
1920
* Allows dynamic creation of access-key/session-token credentials for accessing AWS services such as S3. Typically there's one service
2021
* (e.g. IMDS or STS) which creates credentials dynamically and registers them here using {@link #addValidCredentials}, and then the
2122
* fixture uses {@link #isAuthorized} to validate the credentials it receives corresponds with some previously-generated credentials.
2223
*/
2324
public class DynamicAwsCredentials {
25+
26+
/**
27+
* Extra validation that requests are signed using the correct region. Lazy so it can be randomly generated after initialization, since
28+
* randomness is not available in static context.
29+
*/
30+
private final Supplier<String> expectedRegionSupplier;
31+
32+
/**
33+
* Extra validation that requests are directed to the correct service.
34+
*/
35+
private final String expectedServiceName;
36+
37+
/**
38+
* The set of access keys for each session token registered with {@link #addValidCredentials}. It's this way round because the session
39+
* token is a separate header so it's easier to extract.
40+
*/
2441
private final Map<String, Set<String>> validCredentialsMap = ConcurrentCollections.newConcurrentMap();
2542

43+
/**
44+
* @param expectedRegion The region to use for validating the authorization header, or {@code *} to skip this validation.
45+
* @param expectedServiceName The service name that should appear in the authorization header.
46+
*/
47+
public DynamicAwsCredentials(String expectedRegion, String expectedServiceName) {
48+
this(() -> expectedRegion, expectedServiceName);
49+
}
50+
51+
/**
52+
* @param expectedRegionSupplier Supplies the region to use for validating the authorization header, or {@code *} to skip this
53+
* validation.
54+
* @param expectedServiceName The service name that should appear in the authorization header.
55+
*/
56+
public DynamicAwsCredentials(Supplier<String> expectedRegionSupplier, String expectedServiceName) {
57+
this.expectedRegionSupplier = expectedRegionSupplier;
58+
this.expectedServiceName = expectedServiceName;
59+
}
60+
2661
public boolean isAuthorized(String authorizationHeader, String sessionTokenHeader) {
2762
return authorizationHeader != null
2863
&& sessionTokenHeader != null
29-
&& validCredentialsMap.getOrDefault(sessionTokenHeader, Set.of()).stream().anyMatch(authorizationHeader::contains);
64+
&& validCredentialsMap.getOrDefault(sessionTokenHeader, Set.of())
65+
.stream()
66+
.anyMatch(
67+
validAccessKey -> AwsCredentialsUtils.isValidAwsV4SignedAuthorizationHeader(
68+
validAccessKey,
69+
expectedRegionSupplier.get(),
70+
expectedServiceName,
71+
authorizationHeader
72+
)
73+
);
3074
}
3175

3276
public void addValidCredentials(String accessKey, String sessionToken) {

0 commit comments

Comments
 (0)