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);
}
}
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;
}
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)));
}
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;
}
Aggregations