From 8be9f16d08777d9c6368dac7b200a1fefa9515a7 Mon Sep 17 00:00:00 2001 From: liningrui Date: Tue, 6 Nov 2018 23:00:45 +0800 Subject: [PATCH] Not allowed perform sensitive operations via gremlin Implement #145 Change-Id: I9a590fe40d3b5a808b569ed0af8fd83214a2941a --- .../hugegraph/api/gremlin/GremlinAPI.java | 50 ++++++- .../exception/SecurityException.java | 20 +++ .../security/HugeSecurityManager.java | 129 ++++++++++++++++++ .../assembly/static/bin/hugegraph-server.sh | 6 +- .../src/assembly/static/conf/hugegraph.policy | 6 + .../baidu/hugegraph/api/GremlinApiTest.java | 2 +- 6 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 hugegraph-core/src/main/java/com/baidu/hugegraph/exception/SecurityException.java create mode 100644 hugegraph-core/src/main/java/com/baidu/hugegraph/security/HugeSecurityManager.java create mode 100644 hugegraph-dist/src/assembly/static/conf/hugegraph.policy diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java index 361f334b02..163eeea4ae 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/gremlin/GremlinAPI.java @@ -19,7 +19,12 @@ package com.baidu.hugegraph.api.gremlin; +import java.util.List; +import java.util.Map; +import java.util.Set; + import javax.inject.Singleton; +import javax.json.Json; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -42,6 +47,7 @@ import com.baidu.hugegraph.metrics.MetricsUtil; import com.codahale.metrics.Histogram; import com.codahale.metrics.annotation.Timed; +import com.google.common.collect.ImmutableSet; @Path("gremlin") @Singleton @@ -52,6 +58,10 @@ public class GremlinAPI extends API { private static final Histogram gremlinOutputHistogram = MetricsUtil.registerHistogram(GremlinAPI.class, "gremlin-output"); + private static final Set INNER_EXCEPTIONS = ImmutableSet.of( + "java.lang.SecurityException" + ); + private Client client = ClientBuilder.newClient(); private Response doGetRequest(String location, String auth, String query) { @@ -108,7 +118,8 @@ public Response post(@Context HugeConfig conf, // .build(); String location = conf.get(ServerOptions.GREMLIN_SERVER_URL); String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); - return doPostRequest(location, auth, request); + Response response = doPostRequest(location, auth, request); + return transformResponseIfNeed(response); } @GET @@ -121,6 +132,41 @@ public Response get(@Context HugeConfig conf, String location = conf.get(ServerOptions.GREMLIN_SERVER_URL); String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION); String query = uriInfo.getRequestUri().getRawQuery(); - return doGetRequest(location, auth, query); + Response response = doGetRequest(location, auth, query); + return transformResponseIfNeed(response); + } + + private static Response transformResponseIfNeed(Response response) { + int code = response.getStatusInfo().getStatusCode(); + // No need to transform + if (code != Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) { + return response; + } + + @SuppressWarnings("unchecked") + Map map = response.readEntity(Map.class); + String message = (String) map.get("message"); + String exClassName = (String) map.get("Exception-Class"); + @SuppressWarnings("unchecked") + List exceptions = (List) map.get("exceptions"); + if (message == null || exClassName == null || exceptions == null) { + throw new IllegalStateException(String.format( + "Invalid response for inner exception, should contains " + + "Exception-Class, but got %s", map)); + } + if (INNER_EXCEPTIONS.contains(exClassName)) { + String cause = !exceptions.isEmpty() ? exceptions.get(0) : ""; + String json = Json.createObjectBuilder() + .add("exception", exClassName) + .add("message", message) + .add("cause", cause) + .build().toString(); + return Response.status(400) + .type(MediaType.APPLICATION_JSON) + .entity(json) + .build(); + } else { + return response; + } } } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/exception/SecurityException.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/exception/SecurityException.java new file mode 100644 index 0000000000..46fcc7fb14 --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/exception/SecurityException.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 Baidu, Inc. All Rights Reserved. + */ + +package com.baidu.hugegraph.exception; + +import com.baidu.hugegraph.HugeException; + +public class SecurityException extends HugeException { + + private static final long serialVersionUID = -1427924451828873200L; + + public SecurityException(String message) { + super(message); + } + + public SecurityException(String message, Object... args) { + super(message, args); + } +} diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/security/HugeSecurityManager.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/security/HugeSecurityManager.java new file mode 100644 index 0000000000..70ab445d8b --- /dev/null +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/security/HugeSecurityManager.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 Baidu, Inc. All Rights Reserved. + */ + +package com.baidu.hugegraph.security; + +import java.io.FileDescriptor; +import java.security.Permission; + +import org.slf4j.Logger; + +import com.baidu.hugegraph.util.Log; + +public class HugeSecurityManager extends SecurityManager { + + private static final Logger LOG = Log.logger(HugeSecurityManager.class); + + private static final String GremlinExecutor_Class = + "org.apache.tinkerpop.gremlin.groovy.engine.ScriptEngines"; + + @Override + public void checkPermission(Permission permission) { + // allow anything. + } + + @Override + public void checkPermission(Permission permission, Object context) { + // allow anything. + } + + @Override + public void checkAccess(ThreadGroup g) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to modify thread via gremlin"); + } else { + super.checkAccess(g); + } + } + + @Override + public void checkExit(int status) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to call System.exit() via gremlin"); + } else { + super.checkExit(status); + } + } + + @Override + public void checkRead(FileDescriptor fd) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to read file via gremlin"); + } else { + super.checkRead(fd); + } + } + +// @Override +// public void checkRead(String file) { +// if (this.callFromGremlin()) { +// throw new SecurityException("Not allowed to read file via gremlin"); +// } else { +// super.checkRead(file); +// } +// } +// +// @Override +// public void checkRead(String file, Object context) { +// if (this.callFromGremlin()) { +// throw new SecurityException("Not allowed to read file via gremlin"); +// } else { +// super.checkRead(file, context); +// } +// } + + @Override + public void checkWrite(FileDescriptor fd) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to write file via gremlin"); + } else { + super.checkWrite(fd); + } + } + + @Override + public void checkWrite(String file) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to write file via gremlin"); + } else { + super.checkWrite(file); + } + } + + @Override + public void checkAccept(String host, int port) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to accept connect via gremlin"); + } else { + super.checkAccept(host, port); + } + } + + @Override + public void checkConnect(String host, int port) { + if (this.callFromGremlin()) { + throw new SecurityException( + "Not allowed to connect socket via gremlin"); + } else { + super.checkConnect(host, port); + } + } + + private boolean callFromGremlin() { + StackTraceElement elements[] = Thread.currentThread().getStackTrace(); + for (StackTraceElement element : elements) { + String className = element.getClassName(); + if (GremlinExecutor_Class.equals(className)) { + return true; + } + } + return false; + } +} diff --git a/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index c3232b0d6b..9812f81ba3 100755 --- a/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -19,6 +19,7 @@ PLUGINS="$TOP/plugins" LOG="$TOP/logs" OUTPUT=${LOG}/hugegraph-server.log +export HUGEGRAPH_HOME="$TOP" . ${BIN}/util.sh ensure_path_writable $LOG @@ -79,6 +80,9 @@ fi # Execute the application and return its exit code ARGS="conf/gremlin-server.yaml conf/rest-server.properties" -exec ${JAVA} -Dname="HugeGraphServer" -Dlog4j.configurationFile="${CONF}/log4j2.xml" \ +# Turn on security check +exec ${JAVA} -Dname="HugeGraphServer" \ +-Djava.security.manager="com.baidu.hugegraph.security.HugeSecurityManager" \ +-Dlog4j.configurationFile="${CONF}/log4j2.xml" \ ${JAVA_OPTIONS} -cp ${CLASSPATH}: com.baidu.hugegraph.dist.HugeGraphServer ${ARGS} \ >> ${OUTPUT} 2>&1 diff --git a/hugegraph-dist/src/assembly/static/conf/hugegraph.policy b/hugegraph-dist/src/assembly/static/conf/hugegraph.policy new file mode 100644 index 0000000000..d25a71a9f6 --- /dev/null +++ b/hugegraph-dist/src/assembly/static/conf/hugegraph.policy @@ -0,0 +1,6 @@ +// Standard extensions get all permissions by default + +// 目录要使用变量 +grant codeBase "file:/Users/liningrui/IdeaProjects/baidu/xbu-data/hugegraph/hugegraph-0.8.0/hugegraph-core-0.8.0.jar" { + permission java.security.AllPermission; +}; \ No newline at end of file diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/api/GremlinApiTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/api/GremlinApiTest.java index 2fcd6f81d2..3e83056c9d 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/api/GremlinApiTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/api/GremlinApiTest.java @@ -23,9 +23,9 @@ import javax.ws.rs.core.Response; -import org.junit.Assert; import org.junit.Test; +import com.baidu.hugegraph.testutil.Assert; import com.google.common.collect.ImmutableMap; public class GremlinApiTest extends BaseApiTest {