-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathNeptuneNettyHttpSigV4Signer.java
214 lines (188 loc) · 9.97 KB
/
NeptuneNettyHttpSigV4Signer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*
* Copyright 2018 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.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. 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.neptune.auth;
import com.amazonaws.auth.AWSCredentialsProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.utils.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import org.apache.http.entity.StringEntity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.AUTHORIZATION;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.HOST;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_DATE;
import static software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerConstant.X_AMZ_SECURITY_TOKEN;
/**
* Signer for HTTP requests made via Netty clients {@link FullHttpRequest}s.
*/
public class NeptuneNettyHttpSigV4Signer extends NeptuneSigV4SignerBase<FullHttpRequest> {
/**
* Create a V4 Signer for Netty HTTP requests.
*
* @param regionName name of the region for which the request is signed
* @param v1AwsCredentialProvider the provider offering access to the credentials used for signing the request
* @throws NeptuneSigV4SignerException in case initialization fails
*/
public NeptuneNettyHttpSigV4Signer(final String regionName,
final AWSCredentialsProvider v1AwsCredentialProvider) throws NeptuneSigV4SignerException {
super(regionName, v1AwsCredentialProvider);
}
/**
* Create a V4 Signer for Netty HTTP requests.
*
* @param regionName name of the region for which the request is signed
* @param v1AwsCredentialProvider the provider offering access to the credentials used for signing the request
* @param serviceName name of the service name used to sign the requests. Defaults to neptune-db
* @throws NeptuneSigV4SignerException in case initialization fails
*/
public NeptuneNettyHttpSigV4Signer(final String regionName,
final AWSCredentialsProvider v1AwsCredentialProvider,
final String serviceName) throws NeptuneSigV4SignerException {
super(regionName, v1AwsCredentialProvider, serviceName);
}
/**
* Create a V4 Signer for Netty HTTP requests.
*
* @param regionName name of the region for which the request is signed
* @param awsCredentialsProvider the provider offering access to the credentials used for signing the request
* @throws NeptuneSigV4SignerException in case initialization fails
*/
public NeptuneNettyHttpSigV4Signer(final String regionName,
final AwsCredentialsProvider awsCredentialsProvider) throws NeptuneSigV4SignerException {
super(regionName, awsCredentialsProvider);
}
/**
* Create a V4 Signer for Netty HTTP requests.
*
* @param regionName name of the region for which the request is signed
* @param awsCredentialsProvider the provider offering access to the credentials used for signing the request
* @param serviceName name of the service name used to sign the requests. Defaults to neptune-db
* @throws NeptuneSigV4SignerException in case initialization fails
*/
public NeptuneNettyHttpSigV4Signer(final String regionName,
final AwsCredentialsProvider awsCredentialsProvider,
final String serviceName) throws NeptuneSigV4SignerException {
super(regionName, awsCredentialsProvider, serviceName);
}
@Override
protected SdkHttpFullRequest toSignableRequest(final FullHttpRequest request)
throws NeptuneSigV4SignerException {
// make sure the request is not null and contains the minimal required set of information
checkNotNull(request, "The request must not be null");
checkNotNull(request.uri(), "The request URI must not be null");
checkNotNull(request.method(), "The request method must not be null");
// convert the headers to the internal API format
final HttpHeaders headers = request.headers();
final Map<String, List<String>> headersInternal = new HashMap<>();
String hostName = "";
// we don't want to add the Host header as the Signer always adds the host header.
for (String header : headers.names()) {
// Skip adding the Host header as the signing process will add one.
if (!header.equalsIgnoreCase(HOST)) {
headersInternal.put(header, Arrays.asList(headers.get(header)));
} else {
hostName = headers.get(header);
}
}
// convert the parameters to the internal API format
final URI uri = URI.create(request.uri());
final String queryStr = uri.getQuery();
final Map<String, List<String>> parametersInternal = new HashMap<>(extractParametersFromQueryString(queryStr));
// carry over the entity (or an empty entity, if no entity is provided)
final InputStream content;
final ByteBuf contentBuffer = request.content();
boolean hasContent = false;
try {
if (contentBuffer != null && contentBuffer.isReadable()) {
hasContent = true;
contentBuffer.retain();
byte[] bytes = new byte[contentBuffer.readableBytes()];
contentBuffer.getBytes(contentBuffer.readerIndex(), bytes);
content = new ByteArrayInputStream(bytes);
} else {
content = new StringEntity("").getContent();
}
} catch (UnsupportedEncodingException e) {
throw new NeptuneSigV4SignerException("Encoding of the input string failed", e);
} catch (IOException e) {
throw new NeptuneSigV4SignerException("IOException while accessing entity content", e);
} finally {
if (hasContent) {
contentBuffer.release();
}
}
if (StringUtils.isEmpty(hostName)) {
// try to extract hostname from the uri since hostname was not provided in the header.
final String authority = uri.getAuthority();
if (authority == null) {
throw new NeptuneSigV4SignerException("Unable to identify host information,"
+ " either hostname should be provided in the uri or should be passed as a header");
}
hostName = authority;
}
// Gremlin websocket requests don't contain protocol information. Here, http:// doesn't have any consequence
// other than letting the signer work with a full valid uri. The protocol is not used anywhere in signing.
final URI endpointUri = URI.create("http://" + hostName);
return convertToSignableRequest(
request.method().name(),
endpointUri,
uri.getPath(),
headersInternal,
parametersInternal,
content);
}
@Override
protected void attachSignature(final FullHttpRequest request, final NeptuneSigV4Signature signature)
throws NeptuneSigV4SignerException {
// make sure the request is not null and contains the minimal required set of information
checkNotNull(signature, "The signature must not be null");
checkNotNull(signature.getHostHeader(), "The signed Host header must not be null");
checkNotNull(signature.getXAmzDateHeader(), "The signed X-AMZ-DATE header must not be null");
checkNotNull(signature.getAuthorizationHeader(), "The signed Authorization header must not be null");
final HttpHeaders headers = request.headers();
Optional<String> hostHeaderName = Optional.empty();
// Check if host header is present in the request headers.
for (String name: headers.names()) {
if (name.equalsIgnoreCase(HOST)) {
hostHeaderName = Optional.of(name);
break;
}
}
// Remove the host header from the request as we are going to add the host header from the signed request.
// This also ensures that the right header name is used.
hostHeaderName.ifPresent(name -> headers.remove(name));
request.headers().add(HOST, signature.getHostHeader());
request.headers().add(X_AMZ_DATE, signature.getXAmzDateHeader());
request.headers().add(AUTHORIZATION, signature.getAuthorizationHeader());
// https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
// For temporary security credentials, it requires an additional HTTP header
// or query string parameter for the security token. The name of the header
// or query string parameter is X-Amz-Security-Token, and the value is the session token.
if (!signature.getSessionToken().isEmpty()) {
request.headers().add(X_AMZ_SECURITY_TOKEN, signature.getSessionToken());
}
}
}