Skip to content

Commit ef206c3

Browse files
bilaharithMehakmeet Singh
authored andcommitted
CDPD-19864. HADOOP-17311. ABFS: Logs should redact SAS signature (apache#2422)
Contributed by bilaharith. Change-Id: Iff0ed4303ac5ce41b62bfda8150ee983dafa40be
1 parent 1274baf commit ef206c3

File tree

6 files changed

+187
-19
lines changed

6 files changed

+187
-19
lines changed

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/contracts/exceptions/AbfsRestOperationException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ private static String formatMessage(final AbfsHttpOperation abfsHttpOperation) {
8787
"Operation failed: \"%1$s\", %2$s, HEAD, %3$s",
8888
abfsHttpOperation.getStatusDescription(),
8989
abfsHttpOperation.getStatusCode(),
90-
abfsHttpOperation.getUrl().toString());
90+
abfsHttpOperation.getSignatureMaskedUrl());
9191
}
9292

9393
return String.format(
9494
"Operation failed: \"%1$s\", %2$s, %3$s, %4$s, %5$s, \"%6$s\"",
9595
abfsHttpOperation.getStatusDescription(),
9696
abfsHttpOperation.getStatusCode(),
9797
abfsHttpOperation.getMethod(),
98-
abfsHttpOperation.getUrl().toString(),
98+
abfsHttpOperation.getSignatureMaskedUrl(),
9999
abfsHttpOperation.getStorageErrorCode(),
100100
// Remove break line to ensure the request id and timestamp can be shown in console.
101101
abfsHttpOperation.getStorageErrorMessage().replaceAll("\\n", " "));

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsHttpOperation.java

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
public class AbfsHttpOperation implements AbfsPerfLoggable {
5252
private static final Logger LOG = LoggerFactory.getLogger(AbfsHttpOperation.class);
5353

54+
public static final String SIGNATURE_QUERY_PARAM_KEY = "sig=";
55+
5456
private static final int CONNECT_TIMEOUT = 30 * 1000;
5557
private static final int READ_TIMEOUT = 30 * 1000;
5658

@@ -61,6 +63,8 @@ public class AbfsHttpOperation implements AbfsPerfLoggable {
6163

6264
private final String method;
6365
private final URL url;
66+
private String maskedUrl;
67+
private String maskedEncodedUrl;
6468

6569
private HttpURLConnection connection;
6670
private int statusCode;
@@ -100,8 +104,8 @@ public String getMethod() {
100104
return method;
101105
}
102106

103-
public URL getUrl() {
104-
return url;
107+
public String getHost() {
108+
return url.getHost();
105109
}
106110

107111
public int getStatusCode() {
@@ -147,7 +151,6 @@ public String getResponseHeader(String httpHeader) {
147151
// Returns a trace message for the request
148152
@Override
149153
public String toString() {
150-
final String urlStr = url.toString();
151154
final StringBuilder sb = new StringBuilder();
152155
sb.append(statusCode);
153156
sb.append(",");
@@ -171,19 +174,12 @@ public String toString() {
171174
sb.append(",");
172175
sb.append(method);
173176
sb.append(",");
174-
sb.append(urlStr);
177+
sb.append(getSignatureMaskedUrl());
175178
return sb.toString();
176179
}
177180

178181
// Returns a trace message for the ABFS API logging service to consume
179182
public String getLogString() {
180-
String urlStr = null;
181-
182-
try {
183-
urlStr = URLEncoder.encode(url.toString(), "UTF-8");
184-
} catch(UnsupportedEncodingException e) {
185-
urlStr = "https%3A%2F%2Ffailed%2Fto%2Fencode%2Furl";
186-
}
187183

188184
final StringBuilder sb = new StringBuilder();
189185
sb.append("s=")
@@ -211,7 +207,7 @@ public String getLogString() {
211207
.append(" m=")
212208
.append(method)
213209
.append(" u=")
214-
.append(urlStr);
210+
.append(getSignatureMaskedEncodedUrl());
215211

216212
return sb.toString();
217213
}
@@ -520,4 +516,42 @@ private void parseListFilesResponse(final InputStream stream) throws IOException
520516
private boolean isNullInputStream(InputStream stream) {
521517
return stream == null ? true : false;
522518
}
519+
520+
public static String getSignatureMaskedUrl(String url) {
521+
int qpStrIdx = url.indexOf('?' + SIGNATURE_QUERY_PARAM_KEY);
522+
if (qpStrIdx == -1) {
523+
qpStrIdx = url.indexOf('&' + SIGNATURE_QUERY_PARAM_KEY);
524+
}
525+
if (qpStrIdx == -1) {
526+
return url;
527+
}
528+
final int sigStartIdx = qpStrIdx + SIGNATURE_QUERY_PARAM_KEY.length() + 1;
529+
final int ampIdx = url.indexOf("&", sigStartIdx);
530+
final int sigEndIndex = (ampIdx != -1) ? ampIdx : url.length();
531+
String signature = url.substring(sigStartIdx, sigEndIndex);
532+
return url.replace(signature, "XXXX");
533+
}
534+
535+
public static String encodedUrlStr(String url) {
536+
try {
537+
return URLEncoder.encode(url, "UTF-8");
538+
} catch (UnsupportedEncodingException e) {
539+
return "https%3A%2F%2Ffailed%2Fto%2Fencode%2Furl";
540+
}
541+
}
542+
543+
public String getSignatureMaskedUrl() {
544+
if (this.maskedUrl == null) {
545+
this.maskedUrl = getSignatureMaskedUrl(this.url.toString());
546+
}
547+
return this.maskedUrl;
548+
}
549+
550+
public String getSignatureMaskedEncodedUrl() {
551+
if (this.maskedEncodedUrl == null) {
552+
this.maskedEncodedUrl = encodedUrlStr(getSignatureMaskedUrl());
553+
}
554+
return this.maskedEncodedUrl;
555+
}
556+
523557
}

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsIoUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public static void dumpHeadersToDebugLog(final String origin,
5858
if (key.contains("Cookie")) {
5959
values = "*cookie info*";
6060
}
61+
if (key.equals("sig")) {
62+
values = "XXXX";
63+
}
6164
LOG.debug(" {}={}",
6265
key,
6366
values);

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsRestOperation.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,18 @@ private boolean executeHttpOperation(final int retryCount) throws AzureBlobFileS
239239
incrementCounter(AbfsStatistic.BYTES_RECEIVED,
240240
httpOperation.getBytesReceived());
241241
}
242-
} catch (IOException ex) {
243-
if (ex instanceof UnknownHostException) {
244-
LOG.warn(String.format("Unknown host name: %s. Retrying to resolve the host name...", httpOperation.getUrl().getHost()));
242+
} catch (UnknownHostException ex) {
243+
String hostname = null;
244+
if (httpOperation != null) {
245+
hostname = httpOperation.getHost();
245246
}
246-
247+
LOG.warn("Unknown host name: %s. Retrying to resolve the host name...",
248+
hostname);
249+
if (!client.getRetryPolicy().shouldRetry(retryCount, -1)) {
250+
throw new InvalidAbfsRestOperationException(ex);
251+
}
252+
return false;
253+
} catch (IOException ex) {
247254
if (LOG.isDebugEnabled()) {
248255
if (httpOperation != null) {
249256
LOG.debug("HttpRequestFailure: " + httpOperation.toString(), ex);

hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestAzureBlobFileSystemDelegationSAS.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import java.util.List;
2626
import java.util.UUID;
2727

28-
import org.junit.Assert;
28+
import org.assertj.core.api.Assertions;
2929
import org.junit.Assume;
3030
import org.junit.Test;
3131
import org.slf4j.Logger;
@@ -39,6 +39,8 @@
3939
import org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants;
4040
import org.apache.hadoop.fs.azurebfs.constants.TestConfigurationKeys;
4141
import org.apache.hadoop.fs.azurebfs.extensions.MockDelegationSASTokenProvider;
42+
import org.apache.hadoop.fs.azurebfs.services.AbfsHttpOperation;
43+
import org.apache.hadoop.fs.azurebfs.services.AbfsRestOperation;
4244
import org.apache.hadoop.fs.azurebfs.services.AuthType;
4345
import org.apache.hadoop.fs.permission.AclEntry;
4446
import org.apache.hadoop.fs.permission.AclEntryScope;
@@ -53,6 +55,7 @@
5355
import static org.apache.hadoop.fs.permission.AclEntryScope.DEFAULT;
5456
import static org.apache.hadoop.fs.permission.AclEntryType.GROUP;
5557
import static org.apache.hadoop.fs.permission.AclEntryType.USER;
58+
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
5659

5760
/**
5861
* Test Perform Authorization Check operation
@@ -381,4 +384,30 @@ public void testProperties() throws Exception {
381384

382385
assertArrayEquals(propertyValue, fs.getXAttr(reqPath, propertyName));
383386
}
387+
388+
@Test
389+
public void testSignatureMask() throws Exception {
390+
final AzureBlobFileSystem fs = getFileSystem();
391+
String src = "/testABC/test.xt";
392+
fs.create(new Path(src));
393+
AbfsRestOperation abfsHttpRestOperation = fs.getAbfsClient()
394+
.renamePath(src, "/testABC" + "/abc.txt", null);
395+
AbfsHttpOperation result = abfsHttpRestOperation.getResult();
396+
String url = result.getSignatureMaskedUrl();
397+
String encodedUrl = result.getSignatureMaskedEncodedUrl();
398+
Assertions.assertThat(url.substring(url.indexOf("sig=")))
399+
.describedAs("Signature query param should be masked")
400+
.startsWith("sig=XXXX");
401+
Assertions.assertThat(encodedUrl.substring(encodedUrl.indexOf("sig%3D")))
402+
.describedAs("Signature query param should be masked")
403+
.startsWith("sig%3DXXXX");
404+
}
405+
406+
@Test
407+
public void testSignatureMaskOnExceptionMessage() throws Exception {
408+
intercept(IOException.class, "sig=XXXX",
409+
() -> getFileSystem().getAbfsClient()
410+
.renamePath("testABC/test.xt", "testABC/abc.txt", null));
411+
}
412+
384413
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
* <p>
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* <p>
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.fs.azurebfs.services;
20+
21+
import java.io.UnsupportedEncodingException;
22+
import java.net.MalformedURLException;
23+
import java.net.URLEncoder;
24+
25+
import org.assertj.core.api.Assertions;
26+
import org.junit.Test;
27+
28+
public class TestAbfsHttpOperation {
29+
30+
@Test
31+
public void testMaskingAndEncoding()
32+
throws MalformedURLException, UnsupportedEncodingException {
33+
testIfMaskAndEncodeSuccessful("Where sig is the only query param",
34+
"http://www.testurl.net?sig=abcd", "http://www.testurl.net?sig=XXXX");
35+
36+
testIfMaskAndEncodeSuccessful("Where sig is the first query param",
37+
"http://www.testurl.net?sig=abcd&abc=xyz",
38+
"http://www.testurl.net?sig=XXXX&abc=xyz");
39+
40+
testIfMaskAndEncodeSuccessful(
41+
"Where sig is neither first nor last query param",
42+
"http://www.testurl.net?lmn=abc&sig=abcd&abc=xyz",
43+
"http://www.testurl.net?lmn=abc&sig=XXXX&abc=xyz");
44+
45+
testIfMaskAndEncodeSuccessful("Where sig is the last query param",
46+
"http://www.testurl.net?abc=xyz&sig=abcd",
47+
"http://www.testurl.net?abc=xyz&sig=XXXX");
48+
49+
testIfMaskAndEncodeSuccessful("Where sig query param is not present",
50+
"http://www.testurl.net?abc=xyz", "http://www.testurl.net?abc=xyz");
51+
52+
testIfMaskAndEncodeSuccessful(
53+
"Where sig query param is not present but mysig",
54+
"http://www.testurl.net?abc=xyz&mysig=qwerty",
55+
"http://www.testurl.net?abc=xyz&mysig=qwerty");
56+
57+
testIfMaskAndEncodeSuccessful(
58+
"Where sig query param is not present but sigmy",
59+
"http://www.testurl.net?abc=xyz&sigmy=qwerty",
60+
"http://www.testurl.net?abc=xyz&sigmy=qwerty");
61+
62+
testIfMaskAndEncodeSuccessful(
63+
"Where sig query param is not present but a " + "value sig",
64+
"http://www.testurl.net?abc=xyz&mnop=sig",
65+
"http://www.testurl.net?abc=xyz&mnop=sig");
66+
67+
testIfMaskAndEncodeSuccessful(
68+
"Where sig query param is not present but a " + "value ends with sig",
69+
"http://www.testurl.net?abc=xyz&mnop=abcsig",
70+
"http://www.testurl.net?abc=xyz&mnop=abcsig");
71+
72+
testIfMaskAndEncodeSuccessful(
73+
"Where sig query param is not present but a " + "value starts with sig",
74+
"http://www.testurl.net?abc=xyz&mnop=sigabc",
75+
"http://www.testurl.net?abc=xyz&mnop=sigabc");
76+
}
77+
78+
private void testIfMaskAndEncodeSuccessful(final String scenario,
79+
final String url, final String expectedMaskedUrl)
80+
throws UnsupportedEncodingException {
81+
82+
Assertions.assertThat(AbfsHttpOperation.getSignatureMaskedUrl(url))
83+
.describedAs(url + " (" + scenario + ") after masking should be: "
84+
+ expectedMaskedUrl).isEqualTo(expectedMaskedUrl);
85+
86+
final String expectedMaskedEncodedUrl = URLEncoder
87+
.encode(expectedMaskedUrl, "UTF-8");
88+
Assertions.assertThat(AbfsHttpOperation.encodedUrlStr(expectedMaskedUrl))
89+
.describedAs(
90+
url + " (" + scenario + ") after masking and encoding should "
91+
+ "be: " + expectedMaskedEncodedUrl)
92+
.isEqualTo(expectedMaskedEncodedUrl);
93+
}
94+
95+
}

0 commit comments

Comments
 (0)