This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new a7524061925 branch-4.2: [fix](load) Unify sensitive header redaction
in COPY and Stream Load logs #69621 (#69663)
a7524061925 is described below
commit a75240619257f08346b9801a449f54506b38d601
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 27 18:56:04 2026 +0800
branch-4.2: [fix](load) Unify sensitive header redaction in COPY and Stream
Load logs #69621 (#69663)
Cherry-picked from #69621
Co-authored-by: hui lai <[email protected]>
---
.../apache/doris/httpv2/rest/CopyIntoAction.java | 3 +-
.../org/apache/doris/httpv2/rest/LoadAction.java | 13 +---
.../apache/doris/httpv2/util/HttpHeaderUtils.java | 41 +++++++++++++
.../doris/httpv2/rest/CopyIntoActionTest.java | 70 ++++++++++++++++++++++
.../apache/doris/httpv2/rest/LoadActionTest.java | 14 +++--
.../doris/httpv2/util/HttpHeaderUtilsTest.java | 59 ++++++++++++++++++
6 files changed, 183 insertions(+), 17 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
index ea2c54e1976..1d43a05844c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/CopyIntoAction.java
@@ -33,6 +33,7 @@ import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
import org.apache.doris.httpv2.exception.UnauthorizedException;
import org.apache.doris.httpv2.rest.manager.HttpUtils;
import org.apache.doris.httpv2.util.ExecutionResultSet;
+import org.apache.doris.httpv2.util.HttpHeaderUtils;
import org.apache.doris.httpv2.util.StatementSubmitter;
import org.apache.doris.metric.MetricRepo;
import org.apache.doris.nereids.parser.NereidsParser;
@@ -118,7 +119,7 @@ public class CopyIntoAction extends RestBaseController {
}
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
- String value = request.getHeader(key);
+ String value =
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, key);
map.put(key, value);
}
return map;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
index f5d7139f5a1..08d36c159cd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/rest/LoadAction.java
@@ -32,6 +32,7 @@ import org.apache.doris.common.util.DebugPointUtil;
import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
import org.apache.doris.httpv2.entity.RestBaseResult;
import org.apache.doris.httpv2.exception.UnauthorizedException;
+import org.apache.doris.httpv2.util.HttpHeaderUtils;
import org.apache.doris.httpv2.util.StreamLoadRedirectDrainUtil;
import org.apache.doris.load.StreamLoadHandler;
import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -679,7 +680,7 @@ public class LoadAction extends RestBaseController {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
- String headerValue = isSensitiveHeader(headerName) ?
"***MASKED***" : request.getHeader(headerName);
+ String headerValue =
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, headerName);
headers.append(headerName).append(":").append(headerValue).append(", ");
}
return headers.toString();
@@ -759,16 +760,6 @@ public class LoadAction extends RestBaseController {
DRAIN
}
- private boolean isSensitiveHeader(String headerName) {
- return "Authorization".equalsIgnoreCase(headerName)
- || "Proxy-Authorization".equalsIgnoreCase(headerName)
- || "Cookie".equalsIgnoreCase(headerName)
- || "Set-Cookie".equalsIgnoreCase(headerName)
- || "token".equalsIgnoreCase(headerName)
- || "Auth-Token".equalsIgnoreCase(headerName)
- || "auth_code".equalsIgnoreCase(headerName);
- }
-
private Backend selectBackendForGroupCommit(String clusterName,
HttpServletRequest req, long tableId)
throws LoadException {
ConnectContext currentCtx = ConnectContext.get();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java
new file mode 100644
index 00000000000..b616cbb1473
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/httpv2/util/HttpHeaderUtils.java
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License 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 org.apache.doris.httpv2.util;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/** Shared rendering of HTTP request headers for logs. */
+public final class HttpHeaderUtils {
+ private HttpHeaderUtils() {
+ }
+
+ /** Mask credentials without reading or changing the header used for
authentication. */
+ public static String getRedactedHeaderValueForLogging(HttpServletRequest
request, String headerName) {
+ return isSensitiveHeader(headerName) ? "***MASKED***" :
request.getHeader(headerName);
+ }
+
+ private static boolean isSensitiveHeader(String headerName) {
+ return "Authorization".equalsIgnoreCase(headerName)
+ || "Proxy-Authorization".equalsIgnoreCase(headerName)
+ || "Cookie".equalsIgnoreCase(headerName)
+ || "Set-Cookie".equalsIgnoreCase(headerName)
+ || "token".equalsIgnoreCase(headerName)
+ || "Auth-Token".equalsIgnoreCase(headerName)
+ || "auth_code".equalsIgnoreCase(headerName);
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java
new file mode 100644
index 00000000000..c7887790d69
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/CopyIntoActionTest.java
@@ -0,0 +1,70 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License 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 org.apache.doris.httpv2.rest;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+public class CopyIntoActionTest {
+ @Test
+ public void testHeadersForUploadAndQueryLogsMaskCredentials() throws
Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ String[] sensitiveHeaders = {"aUtHoRiZaTiOn", "PROXY-AUTHORIZATION",
"cookie", "Set-Cookie",
+ "TOKEN", "aUtH-tOkEn", "AUTH_CODE"};
+ for (String value : new String[] {"Basic dXNlcjpzZWNyZXQ=", "Bearer
synthetic-token", "Basic malformed"}) {
+
Mockito.when(request.getHeaderNames()).thenReturn(Collections.enumeration(Arrays.asList(
+ "aUtHoRiZaTiOn", "PROXY-AUTHORIZATION", "cookie",
"Set-Cookie", "TOKEN", "aUtH-tOkEn",
+ "AUTH_CODE", "fileName", "label")));
+
Mockito.when(request.getHeader(Mockito.anyString())).thenReturn(value);
+ Mockito.when(request.getHeader("fileName")).thenReturn("data.csv");
+ Mockito.when(request.getHeader("label")).thenReturn("copy_label");
+
+ Map<String, String> headers = getHeadersInfo(request);
+
+ for (String name : sensitiveHeaders) {
+ Assertions.assertEquals("***MASKED***", headers.get(name));
+ }
+ Assertions.assertEquals("data.csv", headers.get("fileName"));
+ Assertions.assertEquals("copy_label", headers.get("label"));
+ Assertions.assertFalse(headers.toString().contains(value));
+ Assertions.assertEquals(value, request.getHeader("aUtHoRiZaTiOn"));
+ }
+ }
+
+ @Test
+ public void testHeadersUnavailable() throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Assertions.assertTrue(getHeadersInfo(request).isEmpty());
+ Mockito.when(request.getHeaderNames()).thenThrow(new
IllegalStateException("headers unavailable"));
+ Assertions.assertTrue(getHeadersInfo(request).isEmpty());
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, String> getHeadersInfo(HttpServletRequest request)
throws Exception {
+ Method method =
CopyIntoAction.class.getDeclaredMethod("getHeadersInfo",
HttpServletRequest.class);
+ method.setAccessible(true);
+ return (Map<String, String>) method.invoke(null, request);
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
index 93bd89fafd1..29f67e053fa 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/httpv2/rest/LoadActionTest.java
@@ -255,18 +255,22 @@ public class LoadActionTest {
LoadAction loadAction = new LoadAction();
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getHeaderNames()).thenReturn(Collections.enumeration(Arrays.asList(
- "Authorization", "Cookie", "Set-Cookie", "token", "label")));
+ "aUtHoRiZaTiOn", "PROXY-AUTHORIZATION", "Cookie",
"Set-Cookie", "token", "Auth-Token", "auth_code",
+ "label")));
+
Mockito.when(request.getHeader(Mockito.anyString())).thenReturn("synthetic-secret");
Mockito.when(request.getHeader("label")).thenReturn("load_label");
Method method = LoadAction.class.getDeclaredMethod("getAllHeaders",
HttpServletRequest.class);
method.setAccessible(true);
String headers = (String) method.invoke(loadAction, request);
- Assertions.assertTrue(headers.contains("Authorization:***MASKED***"));
- Assertions.assertTrue(headers.contains("Cookie:***MASKED***"));
- Assertions.assertTrue(headers.contains("Set-Cookie:***MASKED***"));
- Assertions.assertTrue(headers.contains("token:***MASKED***"));
+ for (String name : new String[] {"aUtHoRiZaTiOn",
"PROXY-AUTHORIZATION", "Cookie", "Set-Cookie",
+ "token", "Auth-Token", "auth_code"}) {
+ Assertions.assertTrue(headers.contains(name + ":***MASKED***"));
+ }
Assertions.assertTrue(headers.contains("label:load_label"));
+ Assertions.assertFalse(headers.contains("synthetic-secret"));
+ Assertions.assertEquals("synthetic-secret",
request.getHeader("aUtHoRiZaTiOn"));
}
@Test
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
new file mode 100644
index 00000000000..680c5b8407a
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/httpv2/util/HttpHeaderUtilsTest.java
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License 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 org.apache.doris.httpv2.util;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Locale;
+
+public class HttpHeaderUtilsTest {
+ @Test
+ public void testSensitiveHeadersAreMaskedRegardlessOfCase() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ for (String name : new String[] {"Authorization",
"Proxy-Authorization", "Cookie", "Set-Cookie",
+ "token", "Auth-Token", "auth_code", "aUtHoRiZaTiOn",
"pRoXy-AuThOrIzAtIoN"}) {
+ for (String spelling : new String[] {name,
name.toLowerCase(Locale.ROOT), name.toUpperCase(Locale.ROOT)}) {
+ Assertions.assertEquals("***MASKED***",
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, spelling));
+ }
+ }
+ Mockito.verifyNoInteractions(request);
+ }
+
+ @Test
+ public void testMaskingDoesNotChangeAuthenticationHeader() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ for (String value : new String[] {"Basic dXNlcjpzZWNyZXQ=", "Bearer
synthetic-token", "Basic malformed"}) {
+ Mockito.when(request.getHeader("Authorization")).thenReturn(value);
+ Assertions.assertEquals("***MASKED***",
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "Authorization"));
+ Assertions.assertEquals(value, request.getHeader("Authorization"));
+ }
+ }
+
+ @Test
+ public void testNonSensitiveHeadersKeepTheirValues() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.when(request.getHeader("label")).thenReturn("load_label");
+ Mockito.when(request.getHeader("fileName")).thenReturn("data.csv");
+ Assertions.assertEquals("load_label",
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "label"));
+ Assertions.assertEquals("data.csv",
HttpHeaderUtils.getRedactedHeaderValueForLogging(request, "fileName"));
+
Assertions.assertNull(HttpHeaderUtils.getRedactedHeaderValueForLogging(request,
"missing"));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]