diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AuthenticationFilter.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AuthenticationFilter.java index f534a0ac9a..464e695fef 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AuthenticationFilter.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AuthenticationFilter.java @@ -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.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 managerProvider; @Context private jakarta.inject.Provider requestProvider; + @Context + private jakarta.inject.Provider configProvider; + @Override public void filter(ContainerRequestContext context) throws IOException { if (AuthenticationFilter.isWhiteAPI(context)) { @@ -102,6 +117,26 @@ protected User authenticate(ContainerRequestContext context) { 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 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 credentials = new HashMap<>(); // Extract authentication credentials String auth = context.getHeaderString(HttpHeaders.AUTHORIZATION); diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/WhiteIpListAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/WhiteIpListAPI.java new file mode 100644 index 0000000000..7503e13822 --- /dev/null +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/WhiteIpListAPI.java @@ -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 list(@Context GraphManager manager) { + LOG.debug("List white ips"); + AuthManager authManager = manager.authManager(); + Set 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 updateWhiteIPs(@Context GraphManager manager, Map actionMap) { + E.checkArgument(actionMap != null, + "Missing argument: actionMap"); + Set whiteIpList = manager.authManager().listWhiteIPs(); + Object ipListRaw = actionMap.get("ips"); + E.checkArgument(ipListRaw instanceof List, + "Invalid ips type '%s', must be list", ipListRaw.getClass()); + List ipList = (List) 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 existedIPs = new HashSet<>(); + Set loadedIPs = new HashSet<>(); + Set illegalIPs = new HashSet<>(); + Map 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 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 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(); + } +} diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java index 04cfac30d7..2435e2667a 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java @@ -1568,6 +1568,26 @@ public UserWithRole validateUser(String token) { } } + @Override + public Set listWhiteIPs() { + return this.authManager.listWhiteIPs(); + } + + @Override + public void setWhiteIPs(Set 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 { diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java index e66b593568..6e41ae87c0 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java @@ -264,4 +264,12 @@ public static synchronized ServerOptions instance() { disallowEmpty(), true ); -} \ No newline at end of file + + public static final ConfigOption WHITE_IP_STATUS = + new ConfigOption<>( + "white_ip.status", + "The status of whether enable white ip.", + disallowEmpty(), + "disable" + ); +} diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java index 2dba7c7a15..908eed01f1 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java @@ -126,4 +126,12 @@ public interface AuthManager { UserWithRole validateUser(String username, String password); UserWithRole validateUser(String token); + + Set listWhiteIPs(); + + void setWhiteIPs(Set whiteIpList); + + boolean getWhiteIpStatus(); + + void enabledWhiteIpList(boolean status); } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java index 910f19cdc5..123c8e9ffd 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java @@ -27,31 +27,30 @@ 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 ipWhiteList; + + private Boolean ipWhiteListEnabled; + public StandardAuthManager(HugeGraphParams graph) { E.checkNotNull(graph, "graph"); HugeConfig config = graph.configuration(); @@ -104,6 +107,10 @@ public StandardAuthManager(HugeGraphParams graph) { HugeAccess::fromEdge); this.tokenGenerator = new TokenGenerator(config); + + this.ipWhiteList = new HashSet<>(); + + this.ipWhiteListEnabled = false; } private Cache cache(String prefix, long capacity, @@ -689,6 +696,26 @@ public UserWithRole validateUser(String token) { return new UserWithRole(user.id(), username, this.rolePermission(user)); } + @Override + public Set listWhiteIPs() { + return ipWhiteList; + } + + @Override + public void setWhiteIPs(Set 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 */