Skip to content

Commit c4a2596

Browse files
committed
Log signed event for easier debugging
In case of future bugs related to event stream signing, this should help in debuggging to see the contents of the message in an easier to read format.
1 parent b349c1b commit c4a2596

File tree

2 files changed

+118
-1
lines changed

2 files changed

+118
-1
lines changed

core/auth/src/main/java/software/amazon/awssdk/auth/signer/internal/BaseEventStreamAsyncAws4Signer.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.nio.ByteBuffer;
2121
import java.nio.charset.StandardCharsets;
2222
import java.time.Instant;
23+
import java.util.Arrays;
2324
import java.util.HashMap;
25+
import java.util.Iterator;
2426
import java.util.Map;
2527
import java.util.Optional;
2628
import java.util.TreeMap;
@@ -143,7 +145,6 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
143145
/**
144146
* Calculate rolling signature
145147
*/
146-
147148
byte[] payload = byteBuffer.array();
148149
byte[] signatureBytes = signEventStream(priorSignature, signingKey, signingInstant, requestParams,
149150
nonSignatureHeaders, payload);
@@ -160,6 +161,13 @@ public ByteBuffer apply(ByteBuffer byteBuffer) {
160161
* Encode signed event to byte
161162
*/
162163
Message signedMessage = new Message(sortHeaders(headers), payload);
164+
165+
if (LOG.isLoggingLevelEnabled("trace")) {
166+
LOG.trace(() -> "Signed message: " + toDebugString(signedMessage, false));
167+
} else {
168+
LOG.debug(() -> "Signed message: " + toDebugString(signedMessage, true));
169+
}
170+
163171
return signedMessage.toByteBuffer();
164172
}
165173
};
@@ -259,4 +267,35 @@ public Optional<Long> contentLength() {
259267
return transformedRequestBody.contentLength();
260268
}
261269
}
270+
271+
static String toDebugString(Message m, boolean truncatePayload) {
272+
StringBuilder sb = new StringBuilder("Message = {headers={");
273+
Map<String, HeaderValue> headers = m.getHeaders();
274+
275+
Iterator<Map.Entry<String, HeaderValue>> headersIter = headers.entrySet().iterator();
276+
277+
while (headersIter.hasNext()) {
278+
Map.Entry<String, HeaderValue> h = headersIter.next();
279+
280+
sb.append(h.getKey()).append("={").append(h.getValue().toString()).append("}");
281+
282+
if (headersIter.hasNext()) {
283+
sb.append(", ");
284+
}
285+
}
286+
287+
sb.append("}, payload=");
288+
289+
byte[] payload = m.getPayload();
290+
291+
byte[] payloadToLog;
292+
if (truncatePayload) {
293+
// Would be nice if BinaryUtils.toHex() could take an array index range instead so we don't need to copy
294+
payloadToLog = Arrays.copyOf(payload, Math.min(32, payload.length));
295+
} else {
296+
payloadToLog = payload;
297+
}
298+
299+
return sb.append(BinaryUtils.toHex(payloadToLog)).append("}").toString();
300+
}
262301
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.auth.signer.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import java.util.Arrays;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
import java.util.Random;
23+
import org.junit.BeforeClass;
24+
import org.junit.Test;
25+
import software.amazon.awssdk.utils.BinaryUtils;
26+
import software.amazon.eventstream.HeaderValue;
27+
import software.amazon.eventstream.Message;
28+
29+
public class BaseEventStreamAsyncAws4SignerTest {
30+
private static Map<String, HeaderValue> headers;
31+
32+
@BeforeClass
33+
public static void setup() {
34+
headers = new LinkedHashMap<>();
35+
headers.put("header1", HeaderValue.fromInteger(42));
36+
headers.put("header2", HeaderValue.fromBoolean(false));
37+
headers.put("header3", HeaderValue.fromString("Hello world"));
38+
}
39+
40+
@Test
41+
public void toDebugString_emptyPayload_generatesCorrectString() {
42+
Message m = new Message(headers, new byte[0]);
43+
44+
assertThat(BaseEventStreamAsyncAws4Signer.toDebugString(m, false))
45+
.isEqualTo("Message = {headers={header1={42}, header2={false}, header3={\"Hello world\"}}, payload=}");
46+
}
47+
48+
@Test
49+
public void toDebugString_noHeaders_emptyPayload_generatesCorrectString() {
50+
Message m = new Message(new LinkedHashMap<>(), new byte[0]);
51+
52+
assertThat(BaseEventStreamAsyncAws4Signer.toDebugString(m, false))
53+
.isEqualTo("Message = {headers={}, payload=}");
54+
}
55+
56+
@Test
57+
public void toDebugString_largePayload_truncate_generatesCorrectString() {
58+
byte[] payload = new byte[128];
59+
new Random().nextBytes(payload);
60+
Message m = new Message(headers, payload);
61+
62+
byte[] first32 = Arrays.copyOf(payload, 32);
63+
String expectedPayloadString = BinaryUtils.toHex(first32);
64+
assertThat(BaseEventStreamAsyncAws4Signer.toDebugString(m, true))
65+
.isEqualTo("Message = {headers={header1={42}, header2={false}, header3={\"Hello world\"}}, payload=" + expectedPayloadString + "}");
66+
}
67+
68+
@Test
69+
public void toDebugString_largePayload_noTruncate_generatesCorrectString() {
70+
byte[] payload = new byte[128];
71+
new Random().nextBytes(payload);
72+
Message m = new Message(headers, payload);
73+
74+
String expectedPayloadString = BinaryUtils.toHex(payload);
75+
assertThat(BaseEventStreamAsyncAws4Signer.toDebugString(m, false))
76+
.isEqualTo("Message = {headers={header1={42}, header2={false}, header3={\"Hello world\"}}, payload=" + expectedPayloadString + "}");
77+
}
78+
}

0 commit comments

Comments
 (0)