Skip to content
Permalink
Browse files
Not allowed perform sensitive operations via gremlin (#176)
Implement #145

Change-Id: I9a590fe40d3b5a808b569ed0af8fd83214a2941a
  • Loading branch information
Linary authored and zhoney committed Jul 29, 2019
1 parent 3fcfce2 commit b01549a5799ad75ba1bfae5967b122d3a74b1510
Showing 30 changed files with 1,045 additions and 86 deletions.
@@ -5,7 +5,7 @@
<parent>
<artifactId>hugegraph</artifactId>
<groupId>com.baidu.hugegraph</groupId>
<version>0.10.0</version>
<version>0.10.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

@@ -86,7 +86,7 @@
</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Implementation-Version>0.40.0.0</Implementation-Version>
<Implementation-Version>0.42.0.0</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
@@ -52,7 +52,9 @@ public class API {

public static final String APPLICATION_JSON = MediaType.APPLICATION_JSON;
public static final String APPLICATION_JSON_WITH_CHARSET =
APPLICATION_JSON + ";charset=" + CHARSET;;
APPLICATION_JSON + ";charset=" + CHARSET;
public static final String JSON = MediaType.APPLICATION_JSON_TYPE
.getSubtype();

public static final String ACTION_APPEND = "append";
public static final String ACTION_ELIMINATE = "eliminate";
@@ -19,6 +19,8 @@

package com.baidu.hugegraph.api.filter;

import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.json.Json;
@@ -32,11 +34,13 @@
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.apache.commons.lang3.StringUtils;
import org.glassfish.hk2.api.MultiException;

import com.baidu.hugegraph.HugeException;
import com.baidu.hugegraph.config.HugeConfig;
import com.baidu.hugegraph.config.ServerOptions;
import com.baidu.hugegraph.exception.HugeGremlinException;
import com.baidu.hugegraph.exception.NotFoundException;

public class ExceptionFilter {
@@ -140,6 +144,21 @@ private boolean trace(int status) {
}
}

@Provider
public static class HugeGremlinExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<HugeGremlinException> {

@Override
public Response toResponse(HugeGremlinException exception) {
return Response.status(exception.statusCode())
.type(MediaType.APPLICATION_JSON)
.entity(formatGremlinException(exception,
this.trace()))
.build();
}
}

@Provider
public static class UnknownExceptionMapper extends TracedExceptionMapper
implements ExceptionMapper<Throwable> {
@@ -163,14 +182,14 @@ public static String formatException(Throwable exception) {

public static String formatException(Throwable exception, boolean trace) {
String clazz = exception.getClass().toString();
String msg = exception.getMessage() != null ?
exception.getMessage() : "";
String message = exception.getMessage() != null ?
exception.getMessage() : "";
String cause = exception.getCause() != null ?
exception.getCause().toString() : "";

JsonObjectBuilder json = Json.createObjectBuilder()
.add("exception", clazz)
.add("message", msg)
.add("message", message)
.add("cause", cause);

if (trace) {
@@ -183,4 +202,33 @@ public static String formatException(Throwable exception, boolean trace) {

return json.build().toString();
}

public static String formatGremlinException(HugeGremlinException exception,
boolean trace) {
Map<String, Object> map = exception.response();
String message = (String) map.get("message");
String exClassName = (String) map.get("Exception-Class");
@SuppressWarnings("unchecked")
List<String> exceptions = (List<String>) map.get("exceptions");
String stackTrace = (String) map.get("stackTrace");

message = message != null ? message : "";
exClassName = exClassName != null ? exClassName : "";
String cause = exceptions != null ? exceptions.toString() : "";

JsonObjectBuilder json = Json.createObjectBuilder()
.add("exception", exClassName)
.add("message", message)
.add("cause", cause);

if (trace && stackTrace != null) {
JsonArrayBuilder traces = Json.createArrayBuilder();
for (String part : StringUtils.split(stackTrace, '\n')) {
traces.add(part);
}
json.add("trace", traces);
}

return json.build().toString();
}
}
@@ -67,6 +67,17 @@ public void filter(ContainerRequestContext context) {
}

HugeConfig config = this.configProvider.get();

int maxWorkerThreads = config.get(ServerOptions.MAX_WORKER_THREADS);
WorkLoad load = this.loadProvider.get();
// There will be a thread doesn't work, dedicated to statistics
if (load.incrementAndGet() >= maxWorkerThreads) {
throw new ServiceUnavailableException(String.format(
"The server is too busy to process the request, " +
"you can config %s to adjust it or try again later",
ServerOptions.MAX_WORKER_THREADS.name()));
}

long minFreeMemory = config.get(ServerOptions.MIN_FREE_MEMORY);
long allocatedMem = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
@@ -81,16 +92,6 @@ public void filter(ContainerRequestContext context) {
presumableFreeMem, minFreeMemory,
ServerOptions.MIN_FREE_MEMORY.name()));
}

int maxWorkerThreads = config.get(ServerOptions.MAX_WORKER_THREADS);
WorkLoad load = this.loadProvider.get();
// There will be a thread doesn't work, dedicated to statistics
if (load.incrementAndGet() >= maxWorkerThreads) {
throw new ServiceUnavailableException(String.format(
"The server is too busy to process the request, " +
"you can config %s to adjust it or try again later",
ServerOptions.MAX_WORKER_THREADS.name()));
}
}

public static boolean isWhiteAPI(ContainerRequestContext context) {
@@ -19,29 +19,32 @@

package com.baidu.hugegraph.api.gremlin;

import java.util.Map;
import java.util.Set;

import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import com.baidu.hugegraph.api.API;
import com.baidu.hugegraph.api.filter.CompressInterceptor;
import com.baidu.hugegraph.api.filter.CompressInterceptor.Compress;
import com.baidu.hugegraph.config.HugeConfig;
import com.baidu.hugegraph.config.ServerOptions;
import com.baidu.hugegraph.exception.HugeGremlinException;
import com.baidu.hugegraph.metrics.MetricsUtil;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

@Path("gremlin")
@Singleton
@@ -52,44 +55,31 @@ public class GremlinAPI extends API {
private static final Histogram gremlinOutputHistogram =
MetricsUtil.registerHistogram(GremlinAPI.class, "gremlin-output");

private Client client = ClientBuilder.newClient();

private Response doGetRequest(String location, String auth, String query) {
String url = String.format("%s?%s", location, query);
Response r = this.client.target(url)
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.accept(MediaType.APPLICATION_JSON)
.acceptEncoding(CompressInterceptor.GZIP)
.get();
if (r.getMediaType() != null) {
// Append charset
assert MediaType.APPLICATION_JSON_TYPE.equals(r.getMediaType());
r.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE,
r.getMediaType().withCharset(CHARSET));
}
gremlinInputHistogram.update(query.length());
gremlinOutputHistogram.update(r.getLength());
return r;
}
private static final Set<String> FORBIDDEN_REQUEST_EXCEPTIONS =
ImmutableSet.of("java.lang.SecurityException");
private static final Set<String> BAD_REQUEST_EXCEPTIONS = ImmutableSet.of(
"java.lang.IllegalArgumentException",
"java.util.concurrent.TimeoutException",
"groovy.lang.",
"org.codehaus.",
"com.baidu.hugegraph."
);

private Response doPostRequest(String location, String auth, String req) {
Entity<?> body = Entity.entity(req, MediaType.APPLICATION_JSON);
Response r = this.client.target(location)
.request()
.header(HttpHeaders.AUTHORIZATION, auth)
.accept(MediaType.APPLICATION_JSON)
.acceptEncoding(CompressInterceptor.GZIP)
.post(body);
if (r.getMediaType() != null) {
// Append charset
assert MediaType.APPLICATION_JSON_TYPE.equals(r.getMediaType());
r.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE,
r.getMediaType().withCharset(CHARSET));
@Context
private javax.inject.Provider<HugeConfig> configProvider;

private GremlinClient client;

public GremlinClient client() {
if (this.client != null) {
return this.client;
}
gremlinInputHistogram.update(req.length());
gremlinOutputHistogram.update(r.getLength());
return r;
HugeConfig config = this.configProvider.get();
String url = config.get(ServerOptions.GREMLIN_SERVER_URL);
int timeout = config.get(ServerOptions.GREMLIN_SERVER_TIMEOUT) * 1000;
int maxRoutes = config.get(ServerOptions.GREMLIN_SERVER_MAX_ROUTE);
this.client = new GremlinClient(url, timeout, maxRoutes, maxRoutes);
return this.client;
}

@POST
@@ -106,9 +96,11 @@ public Response post(@Context HugeConfig conf,
// .build();
// Response.temporaryRedirect(UriBuilder.fromUri(location).build())
// .build();
String location = conf.get(ServerOptions.GREMLIN_SERVER_URL);
String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
return doPostRequest(location, auth, request);
Response response = this.client().doPostRequest(auth, request);
gremlinInputHistogram.update(request.length());
gremlinOutputHistogram.update(response.getLength());
return transformResponseIfNeeded(response);
}

@GET
@@ -118,9 +110,54 @@ public Response post(@Context HugeConfig conf,
public Response get(@Context HugeConfig conf,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
String location = conf.get(ServerOptions.GREMLIN_SERVER_URL);
String auth = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
String query = uriInfo.getRequestUri().getRawQuery();
return doGetRequest(location, auth, query);
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
Response response = this.client().doGetRequest(auth, params);
gremlinInputHistogram.update(query.length());
gremlinOutputHistogram.update(response.getLength());
return transformResponseIfNeeded(response);
}

private static Response transformResponseIfNeeded(Response response) {
MediaType mediaType = response.getMediaType();
if (mediaType != null) {
// Append charset
assert MediaType.APPLICATION_JSON_TYPE.equals(mediaType);
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE,
mediaType.withCharset(CHARSET));
}

Response.StatusType status = response.getStatusInfo();
if (status.getStatusCode() < 400) {
// No need to transform if normal response without error
return response;
}

if (mediaType == null || !JSON.equals(mediaType.getSubtype())) {
String message = response.readEntity(String.class);
throw new HugeGremlinException(status.getStatusCode(),
ImmutableMap.of("message", message));
}

@SuppressWarnings("unchecked")
Map<String, Object> map = response.readEntity(Map.class);
String exClassName = (String) map.get("Exception-Class");
if (FORBIDDEN_REQUEST_EXCEPTIONS.contains(exClassName)) {
status = Response.Status.FORBIDDEN;
} else if (matchBadRequestException(exClassName)) {
status = Response.Status.BAD_REQUEST;
}
throw new HugeGremlinException(status.getStatusCode(), map);
}

private static boolean matchBadRequestException(String exClass) {
if (exClass == null) {
return false;
}
if (BAD_REQUEST_EXCEPTIONS.contains(exClass)) {
return true;
}
return BAD_REQUEST_EXCEPTIONS.stream().anyMatch(exClass::startsWith);
}
}

0 comments on commit b01549a

Please sign in to comment.