/* * Copyright 2011-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. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * 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 static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import com.amazonaws.AmazonClientException; import com.amazonaws.internal.CredentialsEndpointProvider; import com.amazonaws.util.DateUtils; import com.amazonaws.util.IOUtils; import com.github.tomakehurst.wiremock.junit.WireMockRule; public class EC2CredentialsFetcherTest { @ClassRule public static WireMockRule mockServer = new WireMockRule(0); /** One minute (in milliseconds) */ private static final long ONE_MINUTE = 1000L * 60; /** Environment variable name for the AWS ECS Container credentials path */ private static final String CREDENTIALS_PATH = "/dummy/credentials/path"; private static String successResponse; private static String successResponseWithInvalidBody; @BeforeClass public static void setup() throws IOException { successResponse = IOUtils.toString(EC2CredentialsFetcherTest.class.getResourceAsStream("/resources/wiremock/successResponse.json")); successResponseWithInvalidBody = IOUtils.toString(EC2CredentialsFetcherTest.class.getResourceAsStream("/resources/wiremock/successResponseWithInvalidBody.json")); } /** Tests that the credentials provider reloads credentials appropriately */ @Test public void testNeedsToLoadCredentialsMethod() throws Exception { TestCredentialsProvider credentialsProvider = new TestCredentialsProvider(); // The provider should not refresh credentials when they aren't close to expiring and are recent stubForSuccessResonseWithCustomExpirationDate(200, DateUtils.formatISO8601Date(new Date(System.currentTimeMillis() + ONE_MINUTE * 60 * 24)).toString()); credentialsProvider.getCredentials(); assertFalse(credentialsProvider.needsToLoadCredentials()); // The provider should refresh credentials when they aren't close to expiring, but are more than an hour old stubForSuccessResonseWithCustomExpirationDate(200, DateUtils.formatISO8601Date(new Date(System.currentTimeMillis() + ONE_MINUTE * 16)).toString()); credentialsProvider.getCredentials(); credentialsProvider.setLastInstanceProfileCheck(new Date(System.currentTimeMillis() - ONE_MINUTE * 61)); assertTrue(credentialsProvider.needsToLoadCredentials()); // The provider should refresh credentials when they are close to expiring stubForSuccessResonseWithCustomExpirationDate(200, DateUtils.formatISO8601Date(new Date(System.currentTimeMillis() + ONE_MINUTE * 14)).toString()); credentialsProvider.getCredentials(); assertTrue(credentialsProvider.needsToLoadCredentials()); } /** * Test that loadCredentials returns proper credentials when response from client is in proper Json format. */ @Test public void testLoadCredentialsParsesJsonResponseProperly() { stubForSuccessResponseWithCustomBody(200, successResponse); TestCredentialsProvider credentialsProvider = new TestCredentialsProvider(); AWSSessionCredentials credentials = (AWSSessionCredentials) credentialsProvider.getCredentials(); assertEquals("ACCESS_KEY_ID", credentials.getAWSAccessKeyId()); assertEquals("SECRET_ACCESS_KEY", credentials.getAWSSecretKey()); assertEquals("TOKEN_TOKEN_TOKEN", credentials.getSessionToken()); } /** * Test that when credentials are null and response from client does not have access key/secret key, * throws AmazonClientException. */ @Test public void testLoadCredentialsThrowsAceWhenClientResponseDontHaveKeys() { // Stub for success response but without keys in the response body stubForSuccessResponseWithCustomBody(200, successResponseWithInvalidBody); TestCredentialsProvider credentialsProvider = new TestCredentialsProvider(); try { credentialsProvider.getCredentials(); fail("Expected an AmazonClientException"); } catch (AmazonClientException ace) { assertEquals("Unable to load credentials.", ace.getMessage()); } } /** * Tests how the credentials provider behaves when the * server is not running. */ @Test public void testNoMetadataService() throws Exception { stubForErrorResponse(); TestCredentialsProvider credentialsProvider = new TestCredentialsProvider(); // When there are no credentials, the provider should throw an exception if we can't connect try { credentialsProvider.getCredentials(); fail("Expected an AmazonClientException, but wasn't thrown"); } catch (AmazonClientException ace) { assertNotNull(ace.getMessage()); } // When there are valid credentials (but need to be refreshed) and the endpoint returns 404 status, // the provider should throw an exception. stubForSuccessResonseWithCustomExpirationDate(200, new Date(System.currentTimeMillis() + ONE_MINUTE * 4).toString()); credentialsProvider.getCredentials(); // loads the credentials that will be expired soon credentialsProvider.setLastInstanceProfileCheck(new Date(System.currentTimeMillis() - (ONE_MINUTE * 61))); stubForErrorResponse(); // Behaves as if server is unavailable. try { credentialsProvider.getCredentials(); fail("Expected an AmazonClientException, but wasn't thrown"); } catch (AmazonClientException ace) { assertNotNull(ace.getMessage()); } } private void stubForSuccessResponseWithCustomBody(int statusCode, String body) { stubFor( get(urlPathEqualTo(CREDENTIALS_PATH)) .willReturn(aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json") .withHeader("charset", "utf-8") .withBody(body))); } private void stubForSuccessResonseWithCustomExpirationDate(int statusCode, String expiration) { stubFor( get(urlPathEqualTo(CREDENTIALS_PATH)) .willReturn(aResponse() .withStatus(statusCode) .withHeader("Content-Type", "application/json") .withHeader("charset", "utf-8") .withBody("{\"AccessKeyId\":\"ACCESS_KEY_ID\",\"SecretAccessKey\":\"SECRET_ACCESS_KEY\"," + "\"Expiration\":\"" + expiration + "\"}"))); } private void stubForErrorResponse() { stubFor( get(urlPathEqualTo(CREDENTIALS_PATH)) .willReturn(aResponse() .withStatus(404) .withHeader("Content-Type", "application/json") .withHeader("charset", "utf-8") .withBody("{\"code\":\"404 Not Found\",\"message\":\"DetailedErrorMessage\"}"))); } private static class TestCredentialsProvider extends EC2CredentialsFetcher { public TestCredentialsProvider() { super(new TestCredentialsEndpointProvider("http://localhost:" + mockServer.port())); } public void setLastInstanceProfileCheck(Date lastInstanceProfileCheck) { this.lastInstanceProfileCheck = lastInstanceProfileCheck; } } /** * Dummy CredentialsPathProvider that overrides the endpoint * and connects to the WireMock server. */ private static class TestCredentialsEndpointProvider extends CredentialsEndpointProvider { private final String host; public TestCredentialsEndpointProvider(String host) { this.host = host; } @Override public URI getCredentialsEndpoint() throws URISyntaxException { return new URI(host + CREDENTIALS_PATH); } } }