Search in sources :

Example 1 with AWSIotException

use of com.aws.greengrass.deployment.exceptions.AWSIotException in project aws-greengrass-nucleus by aws-greengrass.

the class CredentialRequestHandler method translateToAwsSdkFormat.

private byte[] translateToAwsSdkFormat(final String credentials) throws AWSIotException {
    try {
        JsonNode jsonNode = OBJECT_MAPPER.readTree(credentials).get(CREDENTIALS_UPSTREAM_STR);
        Map<String, String> response = new HashMap<>();
        response.put(ACCESS_KEY_DOWNSTREAM_STR, jsonNode.get(ACCESS_KEY_UPSTREAM_STR).asText());
        response.put(SECRET_ACCESS_DOWNSTREAM_STR, jsonNode.get(SECRET_ACCESS_UPSTREAM_STR).asText());
        response.put(SESSION_TOKEN_DOWNSTREAM_STR, jsonNode.get(SESSION_TOKEN_UPSTREAM_STR).asText());
        response.put(EXPIRATION_DOWNSTREAM_STR, jsonNode.get(EXPIRATION_UPSTREAM_STR).asText());
        return OBJECT_MAPPER.writeValueAsBytes(response);
    } catch (JsonProcessingException e) {
        LOGGER.error("Received malformed credential input", e);
        throw new AWSIotException(e);
    }
}
Also used : HashMap(java.util.HashMap) DefaultConcurrentHashMap(com.aws.greengrass.util.DefaultConcurrentHashMap) AWSIotException(com.aws.greengrass.deployment.exceptions.AWSIotException) JsonNode(com.fasterxml.jackson.databind.JsonNode) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Example 2 with AWSIotException

use of com.aws.greengrass.deployment.exceptions.AWSIotException 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)

Example 3 with AWSIotException

use of com.aws.greengrass.deployment.exceptions.AWSIotException in project aws-greengrass-nucleus by aws-greengrass.

the class IotCloudHelper method sendHttpRequest.

/**
 * Sends Http request to Iot Cloud.
 *
 * @param connManager underlying connection manager to use for sending requests
 * @param thingName   IoT Thing Name
 * @param path        Http url to query
 * @param verb        Http verb for the request
 * @param body        Http body for the request
 * @return Http response corresponding to http request for path
 * @throws AWSIotException when unable to send the request successfully
 */
public IotCloudResponse sendHttpRequest(final IotConnectionManager connManager, String thingName, final String path, final String verb, final byte[] body) throws AWSIotException {
    URI uri = null;
    try {
        uri = connManager.getURI();
    } catch (DeviceConfigurationException e) {
        throw new AWSIotException(e);
    }
    SdkHttpRequest.Builder innerRequestBuilder = SdkHttpRequest.builder().method(SdkHttpMethod.fromValue(verb));
    // If the path is actually a full URI, then treat it as such
    if (path.startsWith("https://")) {
        uri = URI.create(path);
        innerRequestBuilder.uri(uri);
    } else {
        innerRequestBuilder.uri(uri).encodedPath(path);
    }
    if (Utils.isNotEmpty(thingName)) {
        innerRequestBuilder.appendHeader(HTTP_HEADER_THING_NAME, thingName);
    }
    ExecutableHttpRequest request = connManager.getClient().prepareRequest(HttpExecuteRequest.builder().contentStreamProvider(body == null ? null : () -> new ByteArrayInputStream(body)).request(innerRequestBuilder.build()).build());
    BaseRetryableAccessor accessor = new BaseRetryableAccessor();
    CrashableSupplier<IotCloudResponse, AWSIotException> getHttpResponse = () -> getHttpResponse(request);
    return accessor.retry(RETRY_COUNT, BACKOFF_MILLIS, getHttpResponse, new HashSet<>(Collections.singletonList(AWSIotException.class)));
}
Also used : SdkHttpRequest(software.amazon.awssdk.http.SdkHttpRequest) AWSIotException(com.aws.greengrass.deployment.exceptions.AWSIotException) ByteArrayInputStream(java.io.ByteArrayInputStream) IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) BaseRetryableAccessor(com.aws.greengrass.util.BaseRetryableAccessor) DeviceConfigurationException(com.aws.greengrass.deployment.exceptions.DeviceConfigurationException) URI(java.net.URI) ExecutableHttpRequest(software.amazon.awssdk.http.ExecutableHttpRequest)

Example 4 with AWSIotException

use of com.aws.greengrass.deployment.exceptions.AWSIotException in project aws-greengrass-nucleus by aws-greengrass.

the class IotCloudHelper method getHttpResponse.

private IotCloudResponse getHttpResponse(ExecutableHttpRequest request) throws AWSIotException {
    final IotCloudResponse response = new IotCloudResponse();
    try {
        HttpExecuteResponse httpResponse = request.call();
        response.setStatusCode(httpResponse.httpResponse().statusCode());
        try (AbortableInputStream bodyStream = httpResponse.responseBody().orElseThrow(() -> new AWSIotException("No response body"))) {
            response.setResponseBody(IoUtils.toByteArray(bodyStream));
        }
    } catch (IOException e) {
        throw new AWSIotException("Unable to get response", e);
    }
    return response;
}
Also used : HttpExecuteResponse(software.amazon.awssdk.http.HttpExecuteResponse) AbortableInputStream(software.amazon.awssdk.http.AbortableInputStream) AWSIotException(com.aws.greengrass.deployment.exceptions.AWSIotException) IotCloudResponse(com.aws.greengrass.iot.model.IotCloudResponse) IOException(java.io.IOException)

Aggregations

AWSIotException (com.aws.greengrass.deployment.exceptions.AWSIotException)4 IotCloudResponse (com.aws.greengrass.iot.model.IotCloudResponse)3 DeviceConfigurationException (com.aws.greengrass.deployment.exceptions.DeviceConfigurationException)1 BaseRetryableAccessor (com.aws.greengrass.util.BaseRetryableAccessor)1 DefaultConcurrentHashMap (com.aws.greengrass.util.DefaultConcurrentHashMap)1 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 JsonNode (com.fasterxml.jackson.databind.JsonNode)1 ByteArrayInputStream (java.io.ByteArrayInputStream)1 IOException (java.io.IOException)1 URI (java.net.URI)1 Instant (java.time.Instant)1 HashMap (java.util.HashMap)1 AbortableInputStream (software.amazon.awssdk.http.AbortableInputStream)1 ExecutableHttpRequest (software.amazon.awssdk.http.ExecutableHttpRequest)1 HttpExecuteResponse (software.amazon.awssdk.http.HttpExecuteResponse)1 SdkHttpRequest (software.amazon.awssdk.http.SdkHttpRequest)1