Search in sources :

Example 1 with IotCloudResponse

use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.

the class CredentialRequestHandlerTest method GIVEN_4xx_response_code_WHEN_called_handle_THEN_expire_in_2_minutes.

@Test
@SuppressWarnings("PMD.CloseResource")
void GIVEN_4xx_response_code_WHEN_called_handle_THEN_expire_in_2_minutes() throws Exception {
    byte[] response = {};
    IotCloudResponse mockResponse = new IotCloudResponse(response, 400);
    when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
    when(mockAuthNHandler.doAuthentication(anyString())).thenReturn("ServiceA");
    when(mockAuthZHandler.isAuthorized(any(), any())).thenReturn(true);
    CredentialRequestHandler handler = setupHandler();
    handler.handle(mockExchange);
    int expectedStatus = 400;
    byte[] expectedResponse = String.format("TES responded with status code: %d. Caching response. ", expectedStatus).getBytes();
    // expire in 2 minutes
    handler.getAwsCredentials();
    Instant expirationTime = Instant.now().plus(Duration.ofMinutes(CLOUD_4XX_ERROR_CACHE_IN_MIN));
    Clock mockClock = Clock.fixed(expirationTime, ZoneId.of("UTC"));
    handler.setClock(mockClock);
    handler.getAwsCredentials();
    verify(mockExchange, times(1)).sendResponseHeaders(expectedStatus, expectedResponse.length);
    verify(mockStream, times(1)).write(expectedResponse);
    mockStream.close();
}
Also used : IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) Instant(java.time.Instant) Clock(java.time.Clock) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 2 with IotCloudResponse

use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.

the class CredentialRequestHandlerTest method GIVEN_credential_handler_WHEN_called_handle_THEN_caches_creds.

@Test
@SuppressWarnings("PMD.CloseResource")
void GIVEN_credential_handler_WHEN_called_handle_THEN_caches_creds() throws Exception {
    // Expiry time in the past will give 500 error, no caching
    Instant expirationTime = Instant.now().minus(Duration.ofMinutes(1));
    String responseStr = String.format(RESPONSE_STR, expirationTime.toString());
    IotCloudResponse mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
    when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
    when(mockAuthNHandler.doAuthentication(anyString())).thenReturn("ServiceA");
    when(mockAuthZHandler.isAuthorized(any(), any())).thenReturn(true);
    CredentialRequestHandler handler = setupHandler();
    handler.handle(mockExchange);
    byte[] expectedResponse = ("TES responded with expired credentials: " + responseStr).getBytes();
    int expectedStatus = 500;
    verify(mockCloudHelper, times(1)).sendHttpRequest(any(), any(), any(), any(), any());
    verify(mockExchange, times(1)).sendResponseHeaders(expectedStatus, expectedResponse.length);
    verify(mockStream, times(1)).write(expectedResponse);
    // Expiry time in recent future won't give error but there wil be no caching
    expirationTime = Instant.now().plus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN - 1));
    responseStr = String.format(RESPONSE_STR, expirationTime.toString());
    mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
    when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
    handler.handle(mockExchange);
    verify(mockCloudHelper, times(2)).sendHttpRequest(any(), any(), any(), any(), any());
    // Expiry time in future will result in credentials being cached
    expirationTime = Instant.now().plus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN + 1));
    responseStr = String.format(RESPONSE_STR, expirationTime.toString());
    mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
    when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
    handler.handle(mockExchange);
    verify(mockCloudHelper, times(3)).sendHttpRequest(any(), any(), any(), any(), any());
    // Credentials were cached
    handler.handle(mockExchange);
    verify(mockCloudHelper, times(3)).sendHttpRequest(any(), any(), any(), any(), any());
    // Cached credentials expired
    Clock mockClock = Clock.fixed(expirationTime, ZoneId.of("UTC"));
    handler.setClock(mockClock);
    handler.handle(mockExchange);
    verify(mockCloudHelper, times(4)).sendHttpRequest(any(), any(), any(), any(), any());
    mockStream.close();
}
Also used : IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) Instant(java.time.Instant) Matchers.containsString(org.hamcrest.Matchers.containsString) ArgumentMatchers.anyString(org.mockito.ArgumentMatchers.anyString) Clock(java.time.Clock) Test(org.junit.jupiter.api.Test) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 3 with IotCloudResponse

use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.

the class IotCloudHelperTest method GIVEN_error_code_once_WHEN_send_request_called_THEN_retry_and_success.

@Test
void GIVEN_error_code_once_WHEN_send_request_called_THEN_retry_and_success() throws Exception {
    when(mockConnectionManager.getClient()).thenReturn(mockClient);
    when(mockConnectionManager.getURI()).thenReturn(HOST);
    ExecutableHttpRequest requestMock = mock(ExecutableHttpRequest.class);
    when(requestMock.call()).thenThrow(IOException.class).thenReturn(HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(STATUS_CODE).build()).responseBody(AbortableInputStream.create(new ByteArrayInputStream(CLOUD_RESPONSE))).build());
    doReturn(requestMock).when(mockClient).prepareRequest(any());
    IotCloudHelper cloudHelper = new IotCloudHelper();
    final IotCloudResponse response = cloudHelper.sendHttpRequest(mockConnectionManager, null, IOT_CREDENTIALS_PATH, CredentialRequestHandler.IOT_CREDENTIALS_HTTP_VERB, null);
    assertArrayEquals(CLOUD_RESPONSE, response.getResponseBody());
    assertEquals(STATUS_CODE, response.getStatusCode());
}
Also used : IotCloudHelper(com.aws.greengrass.iot.IotCloudHelper) ByteArrayInputStream(java.io.ByteArrayInputStream) IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) IOException(java.io.IOException) ExecutableHttpRequest(software.amazon.awssdk.http.ExecutableHttpRequest) Test(org.junit.jupiter.api.Test)

Example 4 with IotCloudResponse

use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.

the class IotCloudHelperTest method GIVEN_valid_creds_WHEN_send_request_called_THEN_success.

@Test
void GIVEN_valid_creds_WHEN_send_request_called_THEN_success() throws Exception {
    when(mockConnectionManager.getClient()).thenReturn(mockClient);
    when(mockConnectionManager.getURI()).thenReturn(HOST);
    ExecutableHttpRequest requestMock = mock(ExecutableHttpRequest.class);
    when(requestMock.call()).thenReturn(HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(STATUS_CODE).build()).responseBody(AbortableInputStream.create(new ByteArrayInputStream(CLOUD_RESPONSE))).build());
    doReturn(requestMock).when(mockClient).prepareRequest(any());
    IotCloudHelper cloudHelper = new IotCloudHelper();
    final IotCloudResponse response = cloudHelper.sendHttpRequest(mockConnectionManager, null, IOT_CREDENTIALS_PATH, CredentialRequestHandler.IOT_CREDENTIALS_HTTP_VERB, null);
    assertArrayEquals(CLOUD_RESPONSE, response.getResponseBody());
    assertEquals(STATUS_CODE, response.getStatusCode());
}
Also used : IotCloudHelper(com.aws.greengrass.iot.IotCloudHelper) ByteArrayInputStream(java.io.ByteArrayInputStream) IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) ExecutableHttpRequest(software.amazon.awssdk.http.ExecutableHttpRequest) Test(org.junit.jupiter.api.Test)

Example 5 with IotCloudResponse

use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.

the class CredentialRequestHandler method getCredentialsBypassCache.

/**
 * API to get credentials while bypassing the caching layer.
 *
 * @return credentials
 */
private byte[] getCredentialsBypassCache() {
    byte[] response;
    LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Got request for credentials, querying iot");
    TESCache cacheEntry = tesCache.get(iotCredentialsPath);
    // Use the future in order to prevent multiple concurrent requests for the same information.
    // If a request is already underway then it should simply wait on the existing future instead of making a
    // parallel call to the cloud.
    CompletableFuture<Void> future;
    synchronized (cacheEntry) {
        future = cacheEntry.future.get();
        if (future == null || future.isDone()) {
            future = new CompletableFuture<>();
            tesCache.get(iotCredentialsPath).future.set(future);
        }
    }
    Instant newExpiry = tesCache.get(iotCredentialsPath).expiry;
    try {
        final IotCloudResponse cloudResponse = iotCloudHelper.sendHttpRequest(iotConnectionManager, thingName, iotCredentialsPath, IOT_CREDENTIALS_HTTP_VERB, null);
        final String credentials = cloudResponse.toString();
        final int cloudResponseCode = cloudResponse.getStatusCode();
        LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).kv("statusCode", cloudResponseCode).log("Received response from cloud: {}", cloudResponseCode == 200 ? "response code 200, not logging credentials" : credentials);
        if (cloudResponseCode == 0) {
            // Client errors should expire immediately
            String responseString = "Failed to get credentials from TES";
            response = responseString.getBytes(StandardCharsets.UTF_8);
            newExpiry = Instant.now(clock);
            tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
        } else if (cloudResponseCode == HttpURLConnection.HTTP_OK) {
            // Get response successfully, cache credentials according to expiry in response
            try {
                response = translateToAwsSdkFormat(credentials);
                String expiryString = parseExpiryFromResponse(credentials);
                Instant expiry = Instant.parse(expiryString);
                if (expiry.isBefore(Instant.now(clock))) {
                    String responseString = "TES responded with expired credentials: " + credentials;
                    response = responseString.getBytes(StandardCharsets.UTF_8);
                    tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
                    LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Unable to cache expired credentials which expired at {}", expiry);
                } else {
                    newExpiry = expiry.minus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN));
                    tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_OK;
                    if (newExpiry.isBefore(Instant.now(clock))) {
                        LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Can't cache credentials as new credentials {} will " + "expire in less than {} minutes", expiry, TIME_BEFORE_CACHE_EXPIRE_IN_MIN);
                    } else {
                        LOGGER.atInfo().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Received IAM credentials that will be cached until {}", newExpiry);
                    }
                }
            } catch (AWSIotException e) {
                String responseString = "Bad TES response: " + credentials;
                response = responseString.getBytes(StandardCharsets.UTF_8);
                tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
                LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Unable to parse response body", e);
            }
        } else {
            // Cloud errors should be cached
            String responseString = String.format("TES responded with status code: %d. Caching response. %s", cloudResponseCode, credentials);
            response = responseString.getBytes(StandardCharsets.UTF_8);
            newExpiry = getExpiryPolicyForErr(cloudResponseCode);
            tesCache.get(iotCredentialsPath).responseCode = cloudResponseCode;
            LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log(responseString);
        }
        tesCache.get(iotCredentialsPath).expiry = newExpiry;
        tesCache.get(iotCredentialsPath).credentials = response;
    } catch (AWSIotException e) {
        // Http connection error should expire immediately
        String responseString = "Failed to get connection";
        response = responseString.getBytes(StandardCharsets.UTF_8);
        newExpiry = Instant.now(clock);
        tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
        tesCache.get(iotCredentialsPath).expiry = newExpiry;
        tesCache.get(iotCredentialsPath).credentials = response;
        LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Encountered error while fetching credentials", e);
    } finally {
        synchronized (cacheEntry) {
            // Complete the future to notify listeners that we're done.
            // Clear the future so that any new requests trigger an updated request instead of
            // pulling from the cache when the cached credentials are invalid
            CompletableFuture<Void> oldFuture = tesCache.get(iotCredentialsPath).future.getAndSet(null);
            if (oldFuture != null && !oldFuture.isDone()) {
                oldFuture.complete(null);
            }
        }
    }
    return response;
}
Also used : AWSIotException(com.aws.greengrass.deployment.exceptions.AWSIotException) IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) Instant(java.time.Instant)

Aggregations

IotCloudResponse (com.aws.greengrass.iot.model.IotCloudResponse)11 Test (org.junit.jupiter.api.Test)8 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)6 Instant (java.time.Instant)5 Clock (java.time.Clock)4 AWSIotException (com.aws.greengrass.deployment.exceptions.AWSIotException)3 ByteArrayInputStream (java.io.ByteArrayInputStream)3 Matchers.containsString (org.hamcrest.Matchers.containsString)3 ArgumentMatchers.anyString (org.mockito.ArgumentMatchers.anyString)3 ExecutableHttpRequest (software.amazon.awssdk.http.ExecutableHttpRequest)3 IotCloudHelper (com.aws.greengrass.iot.IotCloudHelper)2 IOException (java.io.IOException)2 DeviceConfigurationException (com.aws.greengrass.deployment.exceptions.DeviceConfigurationException)1 BaseRetryableAccessor (com.aws.greengrass.util.BaseRetryableAccessor)1 URI (java.net.URI)1 AbortableInputStream (software.amazon.awssdk.http.AbortableInputStream)1 HttpExecuteResponse (software.amazon.awssdk.http.HttpExecuteResponse)1 SdkHttpRequest (software.amazon.awssdk.http.SdkHttpRequest)1