This is an automated email from the ASF dual-hosted git repository. xxyu pushed a commit to branch kylin5 in repository https://gitbox.apache.org/repos/asf/kylin.git
commit bcdbf5fb080da26b54908f2337f2f68105defc86 Author: ChenLiang.Lu <31469905+yab...@users.noreply.github.com> AuthorDate: Mon Jan 16 18:13:03 2023 +0800 KYLIN-5494 fix diag api security --- .../kylin/rest/controller/NBasicController.java | 30 ++++++++++++++++ .../kylin/rest/controller/NSystemController.java | 12 ++++++- .../rest/controller/NSystemControllerTest.java | 39 +++++++++++++++++++- .../kylin/rest/response/ServerExtInfoResponse.java | 42 ++++++++++++++++++++++ .../kylin/rest/controller/NQueryController.java | 8 ++++- .../rest/controller/NQueryControllerTest.java | 16 +++++++++ 6 files changed, 144 insertions(+), 3 deletions(-) diff --git a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java index 9f7c1df5cd..914338294a 100644 --- a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java +++ b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java @@ -48,6 +48,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -66,6 +67,7 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -635,4 +637,32 @@ public class NBasicController { throw new KylinException(REQUEST_PARAMETER_EMPTY_OR_VALUE_EMPTY, fieldName); } } + + public String decodeHost(String host) { + try { + if (StringUtils.isBlank(host) || host.startsWith("http://")) { + return host; + } + return new String(Base64.decodeBase64(host), Charset.defaultCharset()); + } catch (Exception e) { + logger.error("Failed to decode host, will use the original host name"); + } + return host; + } + + public String encodeHost(String host) { + try { + if (StringUtils.isBlank(host)) { + return host; + } + host = host.trim(); + if (!host.toLowerCase().startsWith("http")) { + host = "http://" + host; + } + return Base64.encodeBase64String(host.getBytes(Charset.defaultCharset())); + } catch (Exception e) { + logger.error("Failed to encode host, will use the original host name"); + } + return host; + } } diff --git a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java index 26c02418f6..b700cdd33e 100644 --- a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java +++ b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java @@ -52,6 +52,7 @@ import org.apache.kylin.rest.request.QueryDiagPackageRequest; import org.apache.kylin.rest.response.DiagStatusResponse; import org.apache.kylin.rest.response.EnvelopeResponse; import org.apache.kylin.rest.response.MaintenanceModeResponse; +import org.apache.kylin.rest.response.ServerExtInfoResponse; import org.apache.kylin.rest.response.ServerInfoResponse; import org.apache.kylin.rest.response.ServersResponse; import org.apache.kylin.rest.service.MaintenanceModeService; @@ -135,6 +136,7 @@ public class NSystemController extends NBasicController { public EnvelopeResponse<String> getRemoteDumpDiagPackage( @RequestParam(value = "host", required = false) String host, @RequestBody DiagPackageRequest diagPackageRequest, final HttpServletRequest request) throws Exception { + host = decodeHost(host); if (StringUtils.isNotBlank(diagPackageRequest.getJobId())) { diagPackageRequest.setStart(""); diagPackageRequest.setEnd(""); @@ -161,6 +163,7 @@ public class NSystemController extends NBasicController { @RequestParam(value = "host", required = false) String host, @RequestBody QueryDiagPackageRequest queryDiagPackageRequest, final HttpServletRequest request) throws Exception { + host = decodeHost(host); if (StringUtils.isEmpty(host)) { String uuid = systemService.dumpLocalQueryDiagPackage(queryDiagPackageRequest.getQueryId(), queryDiagPackageRequest.getProject()); @@ -195,6 +198,7 @@ public class NSystemController extends NBasicController { @RequestParam(value = "host", required = false) String host, @RequestParam(value = "id") String id, @RequestParam(value = "project", required = false) String project, final HttpServletRequest request) throws Exception { + host = decodeHost(host); if (StringUtils.isEmpty(host)) { return systemService.getExtractorStatus(id, project); } else { @@ -212,6 +216,7 @@ public class NSystemController extends NBasicController { public void remoteDownloadPackage(@RequestParam(value = "host", required = false) String host, @RequestParam(value = "id") String id, @RequestParam(value = "project", required = false) String project, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + host = decodeHost(host); if (StringUtils.isEmpty(host)) { setDownloadResponse(systemService.getDiagPackagePath(id, project), MediaType.APPLICATION_OCTET_STREAM_VALUE, response); @@ -229,6 +234,7 @@ public class NSystemController extends NBasicController { @ResponseBody public EnvelopeResponse<String> remoteStopPackage(@RequestParam(value = "host", required = false) String host, @RequestParam(value = "id") String id, final HttpServletRequest request) throws Exception { + host = decodeHost(host); if (StringUtils.isEmpty(host)) { systemService.stopDiagTask(id); return new EnvelopeResponse<>(CODE_SUCCESS, "", ""); @@ -271,7 +277,11 @@ public class NSystemController extends NBasicController { val servers = clusterManager.getServers(); response.setStatus(maintenanceModeService.getMaintenanceMode()); if (ext) { - response.setServers(servers); + response.setServers( + servers.stream().map(server -> + new ServerExtInfoResponse() + .setServer(server) + .setSecretName(encodeHost(server.getHost()))).collect(Collectors.toList())); } else { response.setServers(servers.stream().map(ServerInfoResponse::getHost).collect(Collectors.toList())); } diff --git a/src/common-server/src/test/java/org/apache/kylin/rest/controller/NSystemControllerTest.java b/src/common-server/src/test/java/org/apache/kylin/rest/controller/NSystemControllerTest.java index 24dd2819b5..6deb6ce481 100644 --- a/src/common-server/src/test/java/org/apache/kylin/rest/controller/NSystemControllerTest.java +++ b/src/common-server/src/test/java/org/apache/kylin/rest/controller/NSystemControllerTest.java @@ -22,17 +22,23 @@ import static org.apache.kylin.common.constant.HttpConstant.HTTP_VND_APACHE_KYLI import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Random; import org.apache.kylin.common.KylinConfigBase; import org.apache.kylin.common.util.JsonUtil; -import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.common.util.NLocalFileMetadataTestCase; import org.apache.kylin.junit.rule.TransactionExceptedException; +import org.apache.kylin.rest.cluster.ClusterManager; +import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.rest.request.DiagPackageRequest; import org.apache.kylin.rest.request.DiagProgressRequest; +import org.apache.kylin.rest.response.MaintenanceModeResponse; +import org.apache.kylin.rest.response.ServerInfoResponse; +import org.apache.kylin.rest.service.MaintenanceModeService; import org.apache.kylin.rest.service.SystemService; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -48,6 +54,8 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import com.google.common.collect.Lists; + public class NSystemControllerTest extends NLocalFileMetadataTestCase { private static final String APPLICATION_JSON = HTTP_VND_APACHE_KYLIN_JSON; @@ -62,6 +70,12 @@ public class NSystemControllerTest extends NLocalFileMetadataTestCase { @Rule public TransactionExceptedException thrown = TransactionExceptedException.none(); + @Mock + private ClusterManager clusterManager; + + @Mock + private MaintenanceModeService maintenanceModeService; + @Before public void setUp() { createTestMetadata(); @@ -173,6 +187,15 @@ public class NSystemControllerTest extends NLocalFileMetadataTestCase { .andExpect(MockMvcResultMatchers.status().isOk()); Mockito.verify(nSystemController).getRemotePackageStatus(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.any()); + NBasicController controller = new NBasicController(); + String encodeHost = controller.encodeHost("quickstart.cloudera:8088"); + String decodeHost = controller.decodeHost(encodeHost); + controller.encodeHost(""); + controller.decodeHost(""); + Assert.assertTrue(decodeHost.equalsIgnoreCase("http://quickstart.cloudera:8088")); + Assert.assertEquals( + "http://quickstart.cloudera:8088", + controller.decodeHost("http://quickstart.cloudera:8088")); } @Test @@ -272,4 +295,18 @@ public class NSystemControllerTest extends NLocalFileMetadataTestCase { .andExpect(MockMvcResultMatchers.status().isOk()); Mockito.verify(nSystemController).simulateInsertMeta(Mockito.anyInt(), Mockito.anyLong()); } + + @Test + public void testGetServer() throws Exception { + ServerInfoResponse response = new ServerInfoResponse(); + response.setHost("172.168.1.1"); + response.setMode("ALL"); + List<ServerInfoResponse> result = Lists.newArrayList(response); + Mockito.when(clusterManager.getServers()).thenReturn(result); + Mockito.when(maintenanceModeService.getMaintenanceMode()) + .thenReturn(new MaintenanceModeResponse(false, "")); + mockMvc.perform(MockMvcRequestBuilders.get("/api/system/servers") + .param("ext", "true").accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON))) + .andExpect(MockMvcResultMatchers.status().isOk()); + } } diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/response/ServerExtInfoResponse.java b/src/common-service/src/main/java/org/apache/kylin/rest/response/ServerExtInfoResponse.java new file mode 100644 index 0000000000..5214609b3c --- /dev/null +++ b/src/common-service/src/main/java/org/apache/kylin/rest/response/ServerExtInfoResponse.java @@ -0,0 +1,42 @@ +/* + * 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.kylin.rest.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ServerExtInfoResponse extends ServerInfoResponse { + + private String secretName; + + public ServerExtInfoResponse setServer(ServerInfoResponse response) { + this.setHost(response.getHost()); + this.setMode(response.getMode()); + return this; + } + + public ServerExtInfoResponse setSecretName(String secretName) { + this.secretName = secretName; + return this; + } +} diff --git a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java index 1c3ae4384b..35e2f9f86f 100644 --- a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java +++ b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java @@ -64,6 +64,7 @@ import org.apache.kylin.rest.request.SaveSqlRequest; import org.apache.kylin.rest.response.DataResult; import org.apache.kylin.rest.response.EnvelopeResponse; import org.apache.kylin.rest.response.SQLResponse; +import org.apache.kylin.rest.response.ServerExtInfoResponse; import org.apache.kylin.rest.service.QueryService; import org.apache.kylin.rest.util.AclEvaluate; import org.apache.kylin.common.persistence.transaction.StopQueryBroadcastEventNotifier; @@ -437,7 +438,12 @@ public class NQueryController extends NBasicController { public EnvelopeResponse<List> getServers( @RequestParam(value = "ext", required = false, defaultValue = "false") boolean ext) { if (ext) { - return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, clusterManager.getServers(), ""); + List<ServerExtInfoResponse> serverInfo = + clusterManager.getServers().stream().map(server -> + new ServerExtInfoResponse() + .setServer(server) + .setSecretName(encodeHost(server.getHost()))).collect(Collectors.toList()); + return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, serverInfo, ""); } else { return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, clusterManager.getServers().stream().map(ServerInfoResponse::getHost).collect(Collectors.toList()), diff --git a/src/query-server/src/test/java/org/apache/kylin/rest/controller/NQueryControllerTest.java b/src/query-server/src/test/java/org/apache/kylin/rest/controller/NQueryControllerTest.java index 4307b8168f..cd39e4dda6 100644 --- a/src/query-server/src/test/java/org/apache/kylin/rest/controller/NQueryControllerTest.java +++ b/src/query-server/src/test/java/org/apache/kylin/rest/controller/NQueryControllerTest.java @@ -40,11 +40,13 @@ import org.apache.kylin.metadata.query.NativeQueryRealization; import org.apache.kylin.metadata.query.QueryHistory; import org.apache.kylin.metadata.query.QueryHistoryInfo; import org.apache.kylin.metadata.query.QueryHistoryRequest; +import org.apache.kylin.rest.cluster.ClusterManager; import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.rest.model.Query; import org.apache.kylin.rest.request.PrepareSqlRequest; import org.apache.kylin.rest.request.SQLRequest; import org.apache.kylin.rest.request.SaveSqlRequest; +import org.apache.kylin.rest.response.ServerInfoResponse; import org.apache.kylin.rest.service.QueryCacheManager; import org.apache.kylin.rest.service.QueryHistoryService; import org.apache.kylin.rest.service.QueryService; @@ -85,6 +87,8 @@ public class NQueryControllerTest extends NLocalFileMetadataTestCase { private QueryCacheManager queryCacheManager; @InjectMocks private NQueryController nQueryController = Mockito.spy(new NQueryController()); + @Mock + private ClusterManager clusterManager; @Before public void setup() { @@ -588,4 +592,16 @@ public class NQueryControllerTest extends NLocalFileMetadataTestCase { .andExpect(MockMvcResultMatchers.jsonPath("$.data[1]").value("MODEL2")); Mockito.verify(nQueryController).getQueryHistoryModels(PROJECT, request.getFilterModelName(), 3); } + + @Test + public void testGetServers() throws Exception { + ServerInfoResponse response = new ServerInfoResponse(); + response.setHost("172.168.1.1"); + response.setMode("ALL"); + List<ServerInfoResponse> result = Lists.newArrayList(response); + Mockito.when(clusterManager.getServers()).thenReturn(result); + mockMvc.perform(MockMvcRequestBuilders.get("/api/query/servers") + .param("ext", "true").accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON))) + .andExpect(MockMvcResultMatchers.status().isOk()); + } }