Skip to content
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

feat: Support Workload Identity Federation on AWS ECS/Fargate #1374

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -109,14 +109,8 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont
"Unable to determine the AWS IAM role name. The credential source does not contain the"
+ " url field.");
}
String roleName = retrieveResource(awsCredentialSource.url, "IAM role", metadataRequestHeaders);

// Retrieve the AWS security credentials by calling the endpoint specified by the credential
// source.
String awsCredentials =
retrieveResource(
awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders);

String awsCredentials = retrieveCredentials(metadataRequestHeaders);
JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(awsCredentials);
GenericJson genericJson = parser.parseAndClose(GenericJson.class);

Expand Down Expand Up @@ -188,6 +182,36 @@ boolean shouldUseMetadataServer() {
|| !canRetrieveSecurityCredentialsFromEnvironment());
}

private boolean isECSEnvironment() {
return environmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != null
|| environmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI") != null;
}

private String ecsCredentialsUrl() {
return environmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != null
? "http://169.254.170.2"
+ environmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
: environmentProvider.getEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI");
}

private String retrieveCredentials(Map<String, Object> metadataRequestHeaders)
throws IOException {

if (isECSEnvironment()) {
// On ECS, credentials are available from a local container endpoint which is exposed in an
// environment variable.
return retrieveResource(ecsCredentialsUrl(), "credentials", metadataRequestHeaders);
} else {
String roleName =
retrieveResource(awsCredentialSource.url, "IAM role", metadataRequestHeaders);

// Retrieve the AWS security credentials by calling the endpoint specified by the credential
// source.
return retrieveResource(
awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders);
}
}

private String retrieveResource(String url, String resourceName, Map<String, Object> headers)
throws IOException {
return retrieveResource(url, resourceName, HttpMethods.GET, headers, /* content= */ null);
Expand Down
Expand Up @@ -66,6 +66,8 @@ public class AwsCredentialsTest extends BaseSerializationTest {
private static final String STS_URL = "https://sts.googleapis.com/v1/token";
private static final String AWS_CREDENTIALS_URL = "https://169.254.169.254";
private static final String AWS_CREDENTIALS_URL_WITH_ROLE = "https://169.254.169.254/roleName";
private static final String AWS_CREDENTIALS_URL_ON_ECS =
"http://169.254.170.2/v2/credentials/some-uuid";
private static final String AWS_REGION_URL = "https://169.254.169.254/region";
private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://169.254.169.254/imdsv2";
private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken";
Expand Down Expand Up @@ -843,6 +845,65 @@ public void getAwsSecurityCredentials_fromMetadataServer() throws IOException {
ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS);
}

@Test
public void getAwsSecurityCredentials_onECS_fromMetadataServerRelativeUrl() throws IOException {
TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
environmentProvider.setEnv(
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/some-uuid");

MockExternalAccountCredentialsTransportFactory transportFactory =
new MockExternalAccountCredentialsTransportFactory();

AwsCredentials awsCredential =
AwsCredentials.newBuilder(AWS_CREDENTIAL)
.setHttpTransportFactory(transportFactory)
.setCredentialSource(buildAwsCredentialSource(transportFactory))
.setEnvironmentProvider(environmentProvider)
.build();

AwsSecurityCredentials credentials =
awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);

assertEquals("accessKeyId", credentials.getAccessKeyId());
assertEquals("secretAccessKey", credentials.getSecretAccessKey());
assertEquals("token", credentials.getSessionToken());

List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
assertEquals(1, requests.size());

// Validate security credentials request (only one request is made on ECS).
ValidateRequest(requests.get(0), AWS_CREDENTIALS_URL_ON_ECS, EMPTY_STRING_HEADERS);
}

@Test
public void getAwsSecurityCredentials_onECS_fromMetadataServerFullUrl() throws IOException {
TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
environmentProvider.setEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI", AWS_CREDENTIALS_URL_ON_ECS);

MockExternalAccountCredentialsTransportFactory transportFactory =
new MockExternalAccountCredentialsTransportFactory();

AwsCredentials awsCredential =
AwsCredentials.newBuilder(AWS_CREDENTIAL)
.setHttpTransportFactory(transportFactory)
.setCredentialSource(buildAwsCredentialSource(transportFactory))
.setEnvironmentProvider(environmentProvider)
.build();

AwsSecurityCredentials credentials =
awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);

assertEquals("accessKeyId", credentials.getAccessKeyId());
assertEquals("secretAccessKey", credentials.getSecretAccessKey());
assertEquals("token", credentials.getSessionToken());

List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
assertEquals(1, requests.size());

// Validate security credentials request (only one request is made on ECS).
ValidateRequest(requests.get(0), AWS_CREDENTIALS_URL_ON_ECS, EMPTY_STRING_HEADERS);
}

@Test
public void getAwsSecurityCredentials_fromMetadataServer_noUrlProvided() {
MockExternalAccountCredentialsTransportFactory transportFactory =
Expand Down
Expand Up @@ -66,6 +66,9 @@ public class MockExternalAccountCredentialsTransport extends MockHttpTransport {
"https://www.googleapis.com/auth/cloud-platform";
private static final String ISSUED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
private static final String AWS_CREDENTIALS_URL = "https://169.254.169.254";

private static final String AWS_CREDENTIALS_URL_ON_ECS =
"http://169.254.170.2/v2/credentials/some-uuid";
private static final String AWS_REGION_URL = "https://169.254.169.254/region";
private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://169.254.169.254/imdsv2";
private static final String METADATA_SERVER_URL = "https://www.metadata.google.com";
Expand Down Expand Up @@ -150,6 +153,18 @@ public LowLevelHttpResponse execute() throws IOException {
.setContentType(Json.MEDIA_TYPE)
.setContent(response.toString());
}
if ((AWS_CREDENTIALS_URL_ON_ECS).equals(url)) {
GenericJson response = new GenericJson();
response.setFactory(JSON_FACTORY);
response.put("RoleArn", "arn:aws:iam::012345678912:role/ecs-task-role");
response.put("AccessKeyId", "accessKeyId");
response.put("SecretAccessKey", "secretAccessKey");
response.put("Token", "token");

return new MockLowLevelHttpResponse()
.setContentType(Json.MEDIA_TYPE)
.setContent(response.toString());
}

if (METADATA_SERVER_URL.equals(url)) {
String metadataRequestHeader = getFirstHeaderValue("Metadata-Flavor");
Expand Down