-
-
Save danielprinz/edd82f8bde7d66c3293ba0b20f395892 to your computer and use it in GitHub Desktop.
import static org.apache.http.protocol.HttpCoreContext.HTTP_TARGET_HOST; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.net.URI; | |
import java.net.URISyntaxException; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.TreeMap; | |
import org.apache.http.Header; | |
import org.apache.http.HttpEntityEnclosingRequest; | |
import org.apache.http.HttpException; | |
import org.apache.http.HttpHost; | |
import org.apache.http.HttpRequest; | |
import org.apache.http.HttpRequestInterceptor; | |
import org.apache.http.NameValuePair; | |
import org.apache.http.client.utils.URIBuilder; | |
import org.apache.http.entity.BasicHttpEntity; | |
import org.apache.http.message.BasicHeader; | |
import org.apache.http.protocol.HttpContext; | |
import lombok.RequiredArgsConstructor; | |
import software.amazon.awssdk.auth.signer.Aws4Signer; | |
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams; | |
import software.amazon.awssdk.http.SdkHttpFullRequest; | |
import software.amazon.awssdk.http.SdkHttpMethod; | |
/** | |
* AWS SDK v2 version of: https://github.com/awslabs/aws-request-signing-apache-interceptor/blob/master/src/main/java/com/amazonaws/http/AWSRequestSigningApacheInterceptor.java | |
*/ | |
@RequiredArgsConstructor | |
public class AWSRequestSigningInterceptor implements HttpRequestInterceptor { | |
private final Aws4Signer signer; | |
private final Aws4SignerParams params; | |
@Override | |
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { | |
URIBuilder uriBuilder; | |
try { | |
uriBuilder = new URIBuilder(request.getRequestLine().getUri()); | |
} catch (URISyntaxException e) { | |
throw new IOException("Invalid URI" , e); | |
} | |
final SdkHttpFullRequest.Builder signableRequestBuilder = SdkHttpFullRequest.builder(); | |
final HttpHost host = (HttpHost) context.getAttribute(HTTP_TARGET_HOST); | |
if (host != null) { | |
signableRequestBuilder.uri(URI.create(host.toURI())); | |
} | |
final SdkHttpMethod httpMethod = | |
SdkHttpMethod.fromValue(request.getRequestLine().getMethod()); | |
signableRequestBuilder.method(httpMethod); | |
try { | |
signableRequestBuilder.encodedPath(uriBuilder.build().getRawPath()); | |
} catch (URISyntaxException e) { | |
throw new IOException("Invalid URI" , e); | |
} | |
if (request instanceof HttpEntityEnclosingRequest) { | |
HttpEntityEnclosingRequest httpEntityEnclosingRequest = | |
(HttpEntityEnclosingRequest) request; | |
if (httpEntityEnclosingRequest.getEntity() != null) { | |
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |
httpEntityEnclosingRequest.getEntity().writeTo(outputStream); | |
signableRequestBuilder.contentStreamProvider(() -> new ByteArrayInputStream(outputStream.toByteArray())); | |
} | |
} | |
// Append Parameters and Headers | |
nvpToMapParams(uriBuilder.getQueryParams()).forEach(signableRequestBuilder::appendRawQueryParameter); | |
headerArrayToMap(request.getAllHeaders()).forEach(signableRequestBuilder::appendHeader); | |
// Sign it | |
final SdkHttpFullRequest signedRequest = signer.sign(signableRequestBuilder.build(), params); | |
// Now copy everything back | |
request.setHeaders(mapToHeaderArray(signedRequest.headers())); | |
if (request instanceof HttpEntityEnclosingRequest) { | |
HttpEntityEnclosingRequest httpEntityEnclosingRequest = | |
(HttpEntityEnclosingRequest) request; | |
if (httpEntityEnclosingRequest.getEntity() != null) { | |
BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); | |
if (signedRequest.contentStreamProvider().isPresent()) { | |
basicHttpEntity.setContent(signedRequest.contentStreamProvider().get().newStream()); | |
} else { | |
throw new RuntimeException("Empty content stream was not expected!"); | |
} | |
httpEntityEnclosingRequest.setEntity(basicHttpEntity); | |
} | |
} | |
} | |
/** | |
* | |
* @param params list of HTTP query params as NameValuePairs | |
* @return a Multimap of HTTP query params | |
*/ | |
private static Map<String, String> nvpToMapParams(final List<NameValuePair> params) { | |
Map<String, String> parameterMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); | |
for (NameValuePair nvp : params) { | |
parameterMap.putIfAbsent(nvp.getName(), nvp.getValue()); | |
} | |
return parameterMap; | |
} | |
/** | |
* @param headers modeled Header objects | |
* @return a Map of header entries | |
*/ | |
private static Map<String, String> headerArrayToMap(final Header[] headers) { | |
Map<String, String> headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); | |
for (Header header : headers) { | |
if (!skipHeader(header)) { | |
headersMap.put(header.getName(), header.getValue()); | |
} | |
} | |
return headersMap; | |
} | |
/** | |
* @param header header line to check | |
* @return true if the given header should be excluded when signing | |
*/ | |
private static boolean skipHeader(final Header header) { | |
return ("content-length".equalsIgnoreCase(header.getName()) | |
&& "0".equals(header.getValue())) // Strip Content-Length: 0 | |
|| "host".equalsIgnoreCase(header.getName()); // Host comes from endpoint | |
} | |
/** | |
* @param mapHeaders Map of header entries | |
* @return modeled Header objects | |
*/ | |
private static Header[] mapToHeaderArray(final Map<String, List<String>> mapHeaders) { | |
Header[] headers = new Header[mapHeaders.size()]; | |
int i = 0; | |
for (Map.Entry<String, List<String>> headerEntry : mapHeaders.entrySet()) { | |
for (String value : headerEntry.getValue()) { | |
headers[i++] = new BasicHeader(headerEntry.getKey(), value); | |
} | |
} | |
return headers; | |
} | |
} |
I see below error when there is token(aws_session_token) expires
status line [HTTP/1.1 403 Forbidden]\n{"message":"The security token included in the request is expired"}\n\tat org.elasticsearch.client.RestClient.convertResponse(RestClient.java:302)\n\tat org.elasticsearch.client.RestClient.performRequest(RestClient.java:272)\n\tat org.elasticsearch.client.RestClient.performRequest(RestClient.java:246)\n\tat org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1613)\n\t...
@cuddihyge
Please anyone help me in resolving this
Thanks
AK
Hello @reinouts!
Feel free to use and adapt it. I would say the MIT License is appropriate.Kind regards
Hi @danielprinz ,
Could please help me in understanding the DefaultCredentialsProvider.create().resolveCredentials() code snippet ?
Will it reads all three keys(aws_access_key_id,aws_secret_access_key and aws_session_token) ?
if yes then when token (aws_session_token) is got expired then how the SDK 2 gonna refresh it in AWSRequestSigningInterceptor.Java?
if no then what are all the keys that i going to read ?
This will fail with an OutOfBounds exception when there are headers with multiple values.
private static Header[] mapToHeaderArray(final Map<String, List<String>> mapHeaders) {
Header[] headers = new Header[mapHeaders.size()];
int i = 0;
for (Map.Entry<String, List<String>> headerEntry : mapHeaders.entrySet()) {
for (String value : headerEntry.getValue()) {
headers[i++] = new BasicHeader(headerEntry.getKey(), value);
}
}
return headers;
}
You are also caching the signing params, which contain the AWS credentials. After some time (~ 10 minutes), all requests will to ES will fail because the token has expired.
not working when enabled compression
I've been trying to fix support with compression enabled, see https://github.com/dblock/opensearch-java-client-demo for a repro that does decompress gzip content and sets the correct headers, but that still doesn't work. I've engaged AWS SDK support at AWS.
I've been trying to fix support with compression enabled, see https://github.com/dblock/opensearch-java-client-demo for a repro that does decompress gzip content and sets the correct headers, but that still doesn't work. I've engaged AWS SDK support at AWS.
Great, thank you.
Please head over to https://github.com/acm19/aws-request-signing-apache-interceptor. We have everything working except chunked request signing.
Hi @danielprinz ,
Could please help me in understanding the DefaultCredentialsProvider.create().resolveCredentials() code snippet ?
Will it reads all three keys(aws_access_key_id,aws_secret_access_key and aws_session_token) ?
if yes then when token (aws_session_token) is got expired then how the SDK 2 gonna refresh it in AWSRequestSigningInterceptor.Java?
if no then what are all the keys that i going to read ?