feat: support White IP List (#2299)

tips:
- this feat works when auth mode was set.
- this feat works when white ip status was enabled.

because now PD is unavailable,just use java list; when pd ready , we can checkout pd.
This commit is contained in:
SunnyBoy-WYH 2023-10-02 13:25:45 +08:00 committed by imbajin
parent b116fc8a21
commit 4bb48814bb
6 changed files with 280 additions and 27 deletions

View File

@ -17,14 +17,38 @@
package org.apache.hugegraph.api.filter;
import static org.apache.hugegraph.config.ServerOptions.WHITE_IP_STATUS;
import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
import org.apache.hugegraph.auth.HugeAuthenticator;
import org.apache.hugegraph.auth.HugeAuthenticator.RequiredPerm;
import org.apache.hugegraph.auth.HugeAuthenticator.RolePerm;
import org.apache.hugegraph.auth.HugeAuthenticator.User;
import org.apache.hugegraph.auth.RolePermission;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticationException;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.utils.Charsets;
import org.slf4j.Logger;
import com.alipay.remoting.util.StringUtils;
import com.google.common.collect.ImmutableList;
import jakarta.annotation.Priority;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
@ -35,23 +59,6 @@ import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.Provider;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticationException;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.utils.Charsets;
import org.slf4j.Logger;
import org.apache.hugegraph.auth.HugeAuthenticator;
import org.apache.hugegraph.auth.HugeAuthenticator.RequiredPerm;
import org.apache.hugegraph.auth.HugeAuthenticator.RolePerm;
import org.apache.hugegraph.auth.HugeAuthenticator.User;
import org.apache.hugegraph.auth.RolePermission;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import com.google.common.collect.ImmutableList;
@Provider
@PreMatching
@ -68,12 +75,20 @@ public class AuthenticationFilter implements ContainerRequestFilter {
"versions"
);
private static String whiteIpStatus;
private static final String STRING_WHITE_IP_LIST = "whiteiplist";
private static final String STRING_ENABLE = "enable";
@Context
private jakarta.inject.Provider<GraphManager> managerProvider;
@Context
private jakarta.inject.Provider<Request> requestProvider;
@Context
private jakarta.inject.Provider<HugeConfig> configProvider;
@Override
public void filter(ContainerRequestContext context) throws IOException {
if (AuthenticationFilter.isWhiteAPI(context)) {
@ -102,6 +117,26 @@ public class AuthenticationFilter implements ContainerRequestFilter {
path = request.getRequestURI();
}
// Check whiteIp
if (whiteIpStatus == null) {
whiteIpStatus = this.configProvider.get().get(WHITE_IP_STATUS);
}
if (Objects.equals(whiteIpStatus, STRING_ENABLE) && request != null) {
peer = request.getRemoteAddr() + ":" + request.getRemotePort();
path = request.getRequestURI();
String remoteIp = request.getRemoteAddr();
Set<String> whiteIpList = manager.authManager().listWhiteIPs();
boolean whiteIpEnabled = manager.authManager().getWhiteIpStatus();
if (!path.contains(STRING_WHITE_IP_LIST) && whiteIpEnabled &&
!whiteIpList.contains(remoteIp)) {
throw new ForbiddenException(
String.format("Remote ip '%s' is not permitted",
remoteIp));
}
}
Map<String, String> credentials = new HashMap<>();
// Extract authentication credentials
String auth = context.getHeaderString(HttpHeaders.AUTHORIZATION);

View File

@ -0,0 +1,155 @@
/*
* 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.hugegraph.api.profile;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.hugegraph.api.API;
import org.apache.hugegraph.api.filter.StatusFilter;
import org.apache.hugegraph.auth.AuthManager;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableMap;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Singleton;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
@Path("whiteiplist")
@Singleton
public class WhiteIpListAPI extends API {
private static final Logger LOG = Log.logger(WhiteIpListAPI.class);
@GET
@Timed
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed("admin")
public Map<String, Object> list(@Context GraphManager manager) {
LOG.debug("List white ips");
AuthManager authManager = manager.authManager();
Set<String> whiteIpList = authManager.listWhiteIPs();
return ImmutableMap.of("whiteIpList", whiteIpList);
}
@POST
@Timed
@StatusFilter.Status(StatusFilter.Status.ACCEPTED)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed("admin")
public Map<String, Object> updateWhiteIPs(@Context GraphManager manager, Map<String, Object> actionMap) {
E.checkArgument(actionMap != null,
"Missing argument: actionMap");
Set<String> whiteIpList = manager.authManager().listWhiteIPs();
Object ipListRaw = actionMap.get("ips");
E.checkArgument(ipListRaw instanceof List,
"Invalid ips type '%s', must be list", ipListRaw.getClass());
List<String> ipList = (List<String>) ipListRaw;
Object actionRaw = actionMap.get("action");
E.checkArgument(actionRaw != null,
"Missing argument: action");
E.checkArgument(actionRaw instanceof String,
"Invalid action type '%s', must be string",
actionRaw.getClass());
String action = (String) actionRaw;
E.checkArgument(StringUtils.isNotEmpty(action),
"Missing argument: action");
Set<String> existedIPs = new HashSet<>();
Set<String> loadedIPs = new HashSet<>();
Set<String> illegalIPs = new HashSet<>();
Map<String, Object> result = new HashMap<>();
for (String ip : ipList) {
if (whiteIpList.contains(ip)) {
existedIPs.add(ip);
continue;
}
if ("load".equals(action)) {
boolean rightIp = checkIp(ip) ? loadedIPs.add(ip) : illegalIPs.add(ip);
}
}
switch (action) {
case "load":
LOG.debug("Load to white ip list");
result.put("existed_ips", existedIPs);
result.put("added_ips", loadedIPs);
if (!illegalIPs.isEmpty()) {
result.put("illegal_ips", illegalIPs);
}
whiteIpList.addAll(loadedIPs);
break;
case "remove":
LOG.debug("Remove from white ip list");
result.put("removed_ips", existedIPs);
result.put("non_existed_ips", loadedIPs);
whiteIpList.removeAll(existedIPs);
break;
default:
throw new AssertionError(String.format("Invalid action '%s', " +
"supported action is " +
"'load' or 'remove'",
action));
}
manager.authManager().setWhiteIPs(whiteIpList);
return result;
}
@PUT
@Timed
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed("admin")
public Map<String, Object> updateStatus(@Context GraphManager manager, @QueryParam("status") String status) {
LOG.debug("Enable or disable white ip list");
E.checkArgument("true".equals(status) ||
"false".equals(status),
"Invalid status, valid status is 'true' or 'false'");
boolean open = Boolean.parseBoolean(status);
manager.authManager().enabledWhiteIpList(open);
Map<String, Object> map = new HashMap<>();
map.put("WhiteIpListOpen", open);
return map;
}
private boolean checkIp(String ipStr) {
String ip = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
+ "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(00?\\d|1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
Pattern pattern = Pattern.compile(ip);
Matcher matcher = pattern.matcher(ipStr);
return matcher.matches();
}
}

View File

@ -1600,6 +1600,26 @@ public final class HugeGraphAuthProxy implements HugeGraph {
}
}
@Override
public Set<String> listWhiteIPs() {
return this.authManager.listWhiteIPs();
}
@Override
public void setWhiteIPs(Set<String> whiteIpList) {
this.authManager.setWhiteIPs(whiteIpList);
}
@Override
public boolean getWhiteIpStatus() {
return this.authManager.getWhiteIpStatus();
}
@Override
public void enabledWhiteIpList(boolean status) {
this.authManager.enabledWhiteIpList(status);
}
@Override
public String loginUser(String username, String password) {
try {

View File

@ -264,4 +264,12 @@ public class ServerOptions extends OptionHolder {
disallowEmpty(),
true
);
}
public static final ConfigOption<String> WHITE_IP_STATUS =
new ConfigOption<>(
"white_ip.status",
"The status of whether enable white ip.",
disallowEmpty(),
"disable"
);
}

View File

@ -126,4 +126,12 @@ public interface AuthManager {
UserWithRole validateUser(String username, String password);
UserWithRole validateUser(String token);
Set<String> listWhiteIPs();
void setWhiteIPs(Set<String> whiteIpList);
boolean getWhiteIpStatus();
void enabledWhiteIpList(boolean status);
}

View File

@ -27,31 +27,30 @@ import java.util.concurrent.Callable;
import javax.security.sasl.AuthenticationException;
import jakarta.ws.rs.ForbiddenException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraphParams;
import org.apache.hugegraph.auth.HugeUser.P;
import org.apache.hugegraph.auth.SchemaDefine.AuthElement;
import org.apache.hugegraph.backend.cache.Cache;
import org.apache.hugegraph.backend.cache.CacheManager;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.backend.id.IdGenerator;
import org.apache.hugegraph.config.AuthOptions;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.type.define.Directions;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.LockUtil;
import org.apache.hugegraph.util.Log;
import org.apache.hugegraph.util.StringEncoding;
import org.slf4j.Logger;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.HugeGraphParams;
import org.apache.hugegraph.auth.HugeUser.P;
import org.apache.hugegraph.auth.SchemaDefine.AuthElement;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.jsonwebtoken.Claims;
import jakarta.ws.rs.ForbiddenException;
public class StandardAuthManager implements AuthManager {
@ -77,6 +76,10 @@ public class StandardAuthManager implements AuthManager {
private final TokenGenerator tokenGenerator;
private final long tokenExpire;
private Set<String> ipWhiteList;
private Boolean ipWhiteListEnabled;
public StandardAuthManager(HugeGraphParams graph) {
E.checkNotNull(graph, "graph");
HugeConfig config = graph.configuration();
@ -104,6 +107,10 @@ public class StandardAuthManager implements AuthManager {
HugeAccess::fromEdge);
this.tokenGenerator = new TokenGenerator(config);
this.ipWhiteList = new HashSet<>();
this.ipWhiteListEnabled = false;
}
private <V> Cache<Id, V> cache(String prefix, long capacity,
@ -689,6 +696,26 @@ public class StandardAuthManager implements AuthManager {
return new UserWithRole(user.id(), username, this.rolePermission(user));
}
@Override
public Set<String> listWhiteIPs() {
return ipWhiteList;
}
@Override
public void setWhiteIPs(Set<String> ipWhiteList) {
this.ipWhiteList = ipWhiteList;
}
@Override
public boolean getWhiteIpStatus() {
return this.ipWhiteListEnabled;
}
@Override
public void enabledWhiteIpList(boolean status) {
this.ipWhiteListEnabled = status;
}
/**
* Maybe can define an proxy class to choose forward or call local
*/