3403 Security enhancement for /api/admin/config (#3484) * KYLIN-1664 add whitelist for non-admin kylin properties
* 3403 KYLIN-1664 add another api for getting public configurations Project: http://git-wip-us.apache.org/repos/asf/kylin/repo Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/4629d848 Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/4629d848 Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/4629d848 Branch: refs/heads/master Commit: 4629d8483914e29f4bb50cfcc6ba09b064187a38 Parents: af15062 Author: Shaofeng Shi <shaofeng...@gmail.com> Authored: Tue Dec 12 15:37:31 2017 +0800 Committer: Zhixiong Chen <c...@apache.org> Committed: Tue Dec 12 15:37:31 2017 +0800 ---------------------------------------------------------------------- .../org/apache/kylin/common/KylinConfig.java | 29 ++++++++++++++++---- .../apache/kylin/common/KylinConfigBase.java | 22 ++++++++++++++- .../apache/kylin/query/ITKylinQueryTest.java | 1 + .../kylin/rest/controller/AdminController.java | 27 +++++++++++++++--- .../kylin/rest/controller/BasicController.java | 19 +++++++++++++ .../apache/kylin/rest/service/AdminService.java | 13 +++------ server/src/main/resources/kylinSecurity.xml | 1 + .../hbase/cube/v2/CubeHBaseEndpointRPC.java | 2 +- 8 files changed, 94 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/core-common/src/main/java/org/apache/kylin/common/KylinConfig.java ---------------------------------------------------------------------- diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfig.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfig.java index c638ec6..c662a16 100644 --- a/core-common/src/main/java/org/apache/kylin/common/KylinConfig.java +++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfig.java @@ -30,6 +30,7 @@ import java.lang.reflect.Method; import java.net.URL; import java.nio.ByteOrder; import java.nio.charset.Charset; +import java.util.Collection; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -439,11 +440,9 @@ public class KylinConfig extends KylinConfigBase { return copy; } - public String exportToString() throws IOException { - Properties allProps = getAllProperties(); - OrderedProperties orderedProperties = KylinConfig.buildSiteOrderedProps(); - - final StringBuilder sb = new StringBuilder(); + public String exportAllToString() throws IOException { + final Properties allProps = getProperties(null); + final OrderedProperties orderedProperties = KylinConfig.buildSiteOrderedProps(); for (Map.Entry<Object, Object> entry : allProps.entrySet()) { String key = entry.getKey().toString(); @@ -454,10 +453,30 @@ public class KylinConfig extends KylinConfigBase { orderedProperties.setProperty(key, value); } } + + final StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : orderedProperties.entrySet()) { sb.append(entry.getKey() + "=" + entry.getValue()).append('\n'); } return sb.toString(); + + } + + public String exportToString(Collection<String> propertyKeys) throws IOException { + Properties filteredProps = getProperties(propertyKeys); + OrderedProperties orderedProperties = KylinConfig.buildSiteOrderedProps(); + + for (String key : propertyKeys) { + if (!filteredProps.containsKey(key)) { + filteredProps.put(key, orderedProperties.getProperty(key, "")); + } + } + + final StringBuilder sb = new StringBuilder(); + for (Map.Entry<Object, Object> entry : filteredProps.entrySet()) { + sb.append(entry.getKey() + "=" + entry.getValue()).append('\n'); + } + return sb.toString(); } public void exportToFile(File file) throws IOException { http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java ---------------------------------------------------------------------- diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java index 1302247..8572fa3 100644 --- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java +++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java @@ -21,6 +21,7 @@ package org.apache.kylin.common; import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -125,12 +126,23 @@ abstract public class KylinConfigBase implements Serializable { } protected Properties getAllProperties() { + return getProperties(null); + } + + /** + * + * @param propertyKeys the collection of the properties; if null will return all properties + * @return + */ + protected Properties getProperties(Collection<String> propertyKeys) { Map<String, String> envMap = System.getenv(); StrSubstitutor sub = new StrSubstitutor(envMap); Properties properties = new Properties(); for (Entry<Object, Object> entry : this.properties.entrySet()) { - properties.put(entry.getKey(), sub.replace((String) entry.getValue())); + if (propertyKeys == null || propertyKeys.contains(entry.getKey())) { + properties.put(entry.getKey(), sub.replace((String) entry.getValue())); + } } return properties; } @@ -1386,4 +1398,12 @@ abstract public class KylinConfigBase implements Serializable { public boolean isHtraceTracingEveryQuery() { return Boolean.valueOf(getOptional("kylin.htrace.trace-every-query", "false")); } + + public String getPropertiesWhiteList() { + return getOptional("kylin.web.properties.whitelist", + "kylin.web.timezone,kylin.query.cache-enabled,kylin.env,kylin.web.hive-limit,kylin.storage.default,kylin.engine.default,kylin.web.link-hadoop,kylin.web.link-diagnostic," + + "kylin.web.contact-mail,kylin.web.help.length,kylin.web.help.0,kylin.web.help.1,kylin.web.help.2,kylin.web.help.3," + + "kylin.web.help,kylin.web.hide-measures,kylin.web.link-streaming-guide,kylin.server.external-acl-provider,kylin.security.profile," + + "kylin.htrace.show-gui-trace-toggle"); + } } http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/kylin-it/src/test/java/org/apache/kylin/query/ITKylinQueryTest.java ---------------------------------------------------------------------- diff --git a/kylin-it/src/test/java/org/apache/kylin/query/ITKylinQueryTest.java b/kylin-it/src/test/java/org/apache/kylin/query/ITKylinQueryTest.java index 60633c9..c468a9f 100644 --- a/kylin-it/src/test/java/org/apache/kylin/query/ITKylinQueryTest.java +++ b/kylin-it/src/test/java/org/apache/kylin/query/ITKylinQueryTest.java @@ -406,4 +406,5 @@ public class ITKylinQueryTest extends KylinTestBase { public void testPercentileQuery() throws Exception { batchExecuteQuery(getQueryFolderPrefix() + "src/test/resources/query/sql_percentile"); } + } http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/server-base/src/main/java/org/apache/kylin/rest/controller/AdminController.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/AdminController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/AdminController.java index eec9fa5..bb71190 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/controller/AdminController.java +++ b/server-base/src/main/java/org/apache/kylin/rest/controller/AdminController.java @@ -19,8 +19,12 @@ package org.apache.kylin.rest.controller; import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import com.google.common.collect.Lists; import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.lang3.StringUtils; import org.apache.kylin.common.KylinConfig; import org.apache.kylin.rest.msg.Message; import org.apache.kylin.rest.msg.MsgPicker; @@ -41,8 +45,6 @@ import org.springframework.web.bind.annotation.ResponseBody; /** * Admin Controller is defined as Restful API entrance for UI. * - * @author jianliu - * */ @Controller @RequestMapping(value = "/admin") @@ -75,14 +77,31 @@ public class AdminController extends BasicController { @RequestMapping(value = "/config", method = { RequestMethod.GET }, produces = { "application/json" }) @ResponseBody public GeneralResponse getConfig() throws IOException { - String config = adminService.exportToString(); + String config = KylinConfig.getInstanceFromEnv().exportAllToString(); + GeneralResponse configRes = new GeneralResponse(); + configRes.put("config", config); + + return configRes; + } + @RequestMapping(value = "/public_config", method = { RequestMethod.GET }, produces = { "application/json" }) + @ResponseBody + public GeneralResponse getPublicConfig() throws IOException { + final String whiteListProperties = KylinConfig.getInstanceFromEnv().getPropertiesWhiteList(); + + Collection<String> propertyKeys = Lists.newArrayList(); + if (StringUtils.isNotEmpty(whiteListProperties)) { + propertyKeys.addAll(Arrays.asList(whiteListProperties.split(","))); + } + + final String config = KylinConfig.getInstanceFromEnv().exportToString(propertyKeys); GeneralResponse configRes = new GeneralResponse(); configRes.put("config", config); return configRes; } + @RequestMapping(value = "/metrics/cubes", method = { RequestMethod.GET }, produces = { "application/json" }) @ResponseBody public MetricsResponse cubeMetrics(MetricsRequest request) { @@ -97,7 +116,7 @@ public class AdminController extends BasicController { @RequestMapping(value = "/config", method = { RequestMethod.PUT }, produces = { "application/json" }) public void updateKylinConfig(@RequestBody UpdateConfigRequest updateConfigRequest) { - KylinConfig.getInstanceFromEnv().setProperty(updateConfigRequest.getKey(), updateConfigRequest.getValue()); + adminService.updateConfig(updateConfigRequest.getKey(), updateConfigRequest.getValue()); } public void setAdminService(AdminService adminService) { http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/server-base/src/main/java/org/apache/kylin/rest/controller/BasicController.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/BasicController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/BasicController.java index b0454fd..b77309e 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/controller/BasicController.java +++ b/server-base/src/main/java/org/apache/kylin/rest/controller/BasicController.java @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.kylin.rest.constant.Constant; import org.apache.kylin.rest.exception.BadRequestException; import org.apache.kylin.rest.exception.ForbiddenException; import org.apache.kylin.rest.exception.InternalErrorException; @@ -40,6 +41,9 @@ import org.apache.kylin.rest.response.ErrorResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -116,4 +120,19 @@ public class BasicController { throw new InternalErrorException("Failed to download file: " + e.getMessage(), e); } } + + public boolean isAdmin() { + boolean isAdmin = false; + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + for (GrantedAuthority auth : authentication.getAuthorities()) { + if (auth.getAuthority().equals(Constant.ROLE_ADMIN)) { + isAdmin = true; + break; + } + } + } + return isAdmin; + } + } http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/server-base/src/main/java/org/apache/kylin/rest/service/AdminService.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/AdminService.java b/server-base/src/main/java/org/apache/kylin/rest/service/AdminService.java index ae004cf..f98a1d4 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/AdminService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/AdminService.java @@ -19,7 +19,6 @@ package org.apache.kylin.rest.service; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.TreeMap; @@ -36,7 +35,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** - * @author jianliu */ @Component("adminService") public class AdminService extends BasicService { @@ -77,14 +75,11 @@ public class AdminService extends BasicService { return content; } - /** - * Get Java config info as String - */ - // @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN) // this is a critical security issue, see KYLIN-1664 - public String exportToString() throws IOException { - logger.debug("Get Kylin Runtime Config"); + @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN) + public void updateConfig(String key, String value) { + logger.debug("Update Kylin Runtime Config, key=" + key + ", value=" + value); - return KylinConfig.getInstanceFromEnv().exportToString(); + KylinConfig.getInstanceFromEnv().setProperty(key, value); } @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN) http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/server/src/main/resources/kylinSecurity.xml ---------------------------------------------------------------------- diff --git a/server/src/main/resources/kylinSecurity.xml b/server/src/main/resources/kylinSecurity.xml index bc97d06..f2c5dbf 100644 --- a/server/src/main/resources/kylinSecurity.xml +++ b/server/src/main/resources/kylinSecurity.xml @@ -245,6 +245,7 @@ <scr:intercept-url pattern="/api/models*/**" access="isAuthenticated()"/> <scr:intercept-url pattern="/api/streaming*/**" access="isAuthenticated()"/> <scr:intercept-url pattern="/api/job*/**" access="isAuthenticated()"/> + <scr:intercept-url pattern="/api/admin/public_config" access="permitAll"/> <scr:intercept-url pattern="/api/projects" access="permitAll"/> <scr:intercept-url pattern="/api/admin*/**" access="hasRole('ROLE_ADMIN')"/> <scr:intercept-url pattern="/api/**" access="isAuthenticated()"/> http://git-wip-us.apache.org/repos/asf/kylin/blob/4629d848/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v2/CubeHBaseEndpointRPC.java ---------------------------------------------------------------------- diff --git a/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v2/CubeHBaseEndpointRPC.java b/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v2/CubeHBaseEndpointRPC.java index 4ac237b..b7bf677 100644 --- a/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v2/CubeHBaseEndpointRPC.java +++ b/storage-hbase/src/main/java/org/apache/kylin/storage/hbase/cube/v2/CubeHBaseEndpointRPC.java @@ -156,7 +156,7 @@ public class CubeHBaseEndpointRPC extends CubeHBaseRPC { builder.addHbaseColumnsToGT(intList); } builder.setRowkeyPreambleSize(cubeSeg.getRowKeyPreambleSize()); - builder.setKylinProperties(kylinConfig.exportToString()); + builder.setKylinProperties(kylinConfig.exportAllToString()); final String queryId = queryContext.getQueryId(); if (queryId != null) { builder.setQueryId(queryId);