Skip to content

Commit 5adf14c

Browse files
authored
fix(pd): resolve hostname entries in IpAuthHandler allowlist (#2962)
- Resolve allowlist hostnames to IPs using InetAddress.getAllByName - Add refresh() to update resolved IPs when Raft peer list changes - Wire refresh into RaftEngine.changePeerList() - Add IpAuthHandlerTest covering hostname resolution, refresh behavior, and failure cases
1 parent 1f40f05 commit 5adf14c

9 files changed

Lines changed: 374 additions & 9 deletions

File tree

hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/RaftEngine.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@
2323
import java.util.HashSet;
2424
import java.util.List;
2525
import java.util.Objects;
26+
import java.util.Set;
2627
import java.util.concurrent.CompletableFuture;
2728
import java.util.concurrent.CountDownLatch;
2829
import java.util.concurrent.ExecutionException;
30+
import java.util.concurrent.TimeUnit;
2931
import java.util.concurrent.atomic.AtomicReference;
3032
import java.util.stream.Collectors;
3133

@@ -40,7 +42,6 @@
4042
import com.alipay.sofa.jraft.JRaftUtils;
4143
import com.alipay.sofa.jraft.Node;
4244
import com.alipay.sofa.jraft.RaftGroupService;
43-
import com.alipay.sofa.jraft.ReplicatorGroup;
4445
import com.alipay.sofa.jraft.Status;
4546
import com.alipay.sofa.jraft.conf.Configuration;
4647
import com.alipay.sofa.jraft.core.Replicator;
@@ -54,7 +55,6 @@
5455
import com.alipay.sofa.jraft.rpc.RpcServer;
5556
import com.alipay.sofa.jraft.rpc.impl.BoltRpcServer;
5657
import com.alipay.sofa.jraft.util.Endpoint;
57-
import com.alipay.sofa.jraft.util.ThreadId;
5858
import com.alipay.sofa.jraft.util.internal.ThrowUtil;
5959

6060
import io.netty.channel.ChannelHandler;
@@ -313,23 +313,66 @@ public List<Metapb.Member> getMembers() throws ExecutionException, InterruptedEx
313313

314314
public Status changePeerList(String peerList) {
315315
AtomicReference<Status> result = new AtomicReference<>();
316+
Configuration newPeers = new Configuration();
316317
try {
317318
String[] peers = peerList.split(",", -1);
318319
if ((peers.length & 1) != 1) {
319320
throw new PDException(-1, "the number of peer list must be odd.");
320321
}
321-
Configuration newPeers = new Configuration();
322322
newPeers.parse(peerList);
323323
CountDownLatch latch = new CountDownLatch(1);
324324
this.raftNode.changePeers(newPeers, status -> {
325-
result.set(status);
325+
// Use compareAndSet so a late callback does not overwrite a timeout status
326+
result.compareAndSet(null, status);
327+
// Refresh inside callback so it fires even if caller already timed out
328+
// Note: changePeerList() uses Configuration.parse() which only supports
329+
// plain comma-separated peer addresses with no learner syntax.
330+
// getLearners() will always be empty here. Learner support is handled
331+
// in PDService.updatePdRaft() which uses PeerUtil.parseConfig()
332+
// and supports the /learner suffix.
333+
if (status != null && status.isOk()) {
334+
IpAuthHandler handler = IpAuthHandler.getInstance();
335+
if (handler != null) {
336+
Set<String> newIps = newPeers.getPeers()
337+
.stream()
338+
.map(PeerId::getIp)
339+
.collect(Collectors.toSet());
340+
handler.refresh(newIps);
341+
log.info("IpAuthHandler refreshed after peer list change to: {}",
342+
peerList);
343+
} else {
344+
log.warn("IpAuthHandler not initialized, skipping refresh for "
345+
+ "peer list: {}", peerList);
346+
}
347+
}
326348
latch.countDown();
327349
});
328-
latch.await();
350+
// Use 3x configured RPC timeout — bare await() would block forever if
351+
// the callback is never invoked (e.g. node not started / RPC failure)
352+
boolean completed = latch.await(3L * config.getRpcTimeout(),
353+
TimeUnit.MILLISECONDS);
354+
if (!completed && result.get() == null) {
355+
Status timeoutStatus = new Status(RaftError.EINTERNAL,
356+
"changePeerList timed out after %d ms",
357+
3L * config.getRpcTimeout());
358+
if (!result.compareAndSet(null, timeoutStatus)) {
359+
// Callback arrived just before us — keep its result
360+
timeoutStatus = null;
361+
}
362+
if (timeoutStatus != null) {
363+
log.error("changePeerList to {} timed out after {} ms",
364+
peerList, 3L * config.getRpcTimeout());
365+
}
366+
}
367+
} catch (InterruptedException e) {
368+
Thread.currentThread().interrupt();
369+
result.set(new Status(RaftError.EINTERNAL, "changePeerList interrupted"));
370+
log.error("changePeerList to {} was interrupted", peerList, e);
329371
} catch (Exception e) {
330372
log.error("failed to changePeerList to {},{}", peerList, e);
331373
result.set(new Status(-1, e.getMessage()));
332374
}
375+
333376
return result.get();
334377
}
335378

@@ -366,7 +409,8 @@ private boolean peerEquals(PeerId p1, PeerId p2) {
366409
if (p1 == null || p2 == null) {
367410
return false;
368411
}
369-
return Objects.equals(p1.getIp(), p2.getIp()) && Objects.equals(p1.getPort(), p2.getPort());
412+
return Objects.equals(p1.getIp(), p2.getIp()) &&
413+
Objects.equals(p1.getPort(), p2.getPort());
370414
}
371415

372416
private Replicator.State getReplicatorState(PeerId peerId) {

hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/raft/auth/IpAuthHandler.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
package org.apache.hugegraph.pd.raft.auth;
1919

20+
import java.net.InetAddress;
2021
import java.net.InetSocketAddress;
22+
import java.net.UnknownHostException;
2123
import java.util.Collections;
24+
import java.util.HashSet;
2225
import java.util.Set;
2326

2427
import io.netty.channel.ChannelDuplexHandler;
@@ -30,11 +33,11 @@
3033
@ChannelHandler.Sharable
3134
public class IpAuthHandler extends ChannelDuplexHandler {
3235

33-
private final Set<String> allowedIps;
36+
private volatile Set<String> resolvedIps;
3437
private static volatile IpAuthHandler instance;
3538

3639
private IpAuthHandler(Set<String> allowedIps) {
37-
this.allowedIps = Collections.unmodifiableSet(allowedIps);
40+
this.resolvedIps = resolveAll(allowedIps);
3841
}
3942

4043
public static IpAuthHandler getInstance(Set<String> allowedIps) {
@@ -48,6 +51,25 @@ public static IpAuthHandler getInstance(Set<String> allowedIps) {
4851
return instance;
4952
}
5053

54+
/**
55+
* Returns the existing singleton instance, or null if not yet initialized.
56+
* Should only be called after getInstance(Set) has been called during startup.
57+
*/
58+
public static IpAuthHandler getInstance() {
59+
return instance;
60+
}
61+
62+
/**
63+
* Refreshes the resolved IP allowlist from a new set of hostnames or IPs.
64+
* Should be called when the Raft peer list changes via RaftEngine#changePeerList().
65+
* Note: DNS-only changes (e.g. container restart with new IP, same hostname)
66+
* are not automatically detected and still require a process restart.
67+
*/
68+
public void refresh(Set<String> newAllowedIps) {
69+
this.resolvedIps = resolveAll(newAllowedIps);
70+
log.info("IpAuthHandler allowlist refreshed, resolved {} entries", resolvedIps.size());
71+
}
72+
5173
@Override
5274
public void channelActive(ChannelHandlerContext ctx) throws Exception {
5375
String clientIp = getClientIp(ctx);
@@ -65,7 +87,25 @@ private static String getClientIp(ChannelHandlerContext ctx) {
6587
}
6688

6789
private boolean isIpAllowed(String ip) {
68-
return allowedIps.isEmpty() || allowedIps.contains(ip);
90+
Set<String> resolved = this.resolvedIps;
91+
// Empty allowlist means no restriction is configured — allow all
92+
return resolved.isEmpty() || resolved.contains(ip);
93+
}
94+
95+
private static Set<String> resolveAll(Set<String> entries) {
96+
Set<String> result = new HashSet<>(entries);
97+
98+
for (String entry : entries) {
99+
try {
100+
for (InetAddress addr : InetAddress.getAllByName(entry)) {
101+
result.add(addr.getHostAddress());
102+
}
103+
} catch (UnknownHostException e) {
104+
log.warn("Could not resolve allowlist entry '{}': {}", entry, e.getMessage());
105+
}
106+
}
107+
108+
return Collections.unmodifiableSet(result);
69109
}
70110

71111
@Override

hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/rest/MemberAPI.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ public RestApiResponse getMembers() throws InterruptedException, ExecutionExcept
113113
* @return Returns a JSON string containing the modification results
114114
* @throws Exception If an exception occurs during request processing, service invocation, or Peer list modification, it is captured and returned as the JSON representation of the exception
115115
*/
116+
// TODO: this endpoint has no authentication check — any caller with network
117+
// access to the management port can trigger a peer list change.
118+
// Wire authentication here as part of the planned auth refactor.
116119
@PostMapping(value = "/members/change", consumes = MediaType.APPLICATION_JSON_VALUE,
117120
produces = MediaType.APPLICATION_JSON_VALUE)
118121
@ResponseBody

hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/service/PDService.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Objects;
28+
import java.util.Set;
2829
import java.util.concurrent.ConcurrentHashMap;
2930
import java.util.concurrent.ExecutionException;
3031
import java.util.concurrent.TimeUnit;
@@ -85,6 +86,7 @@
8586
import org.apache.hugegraph.pd.raft.PeerUtil;
8687
import org.apache.hugegraph.pd.raft.RaftEngine;
8788
import org.apache.hugegraph.pd.raft.RaftStateListener;
89+
import org.apache.hugegraph.pd.raft.auth.IpAuthHandler;
8890
import org.apache.hugegraph.pd.util.grpc.StreamObserverUtil;
8991
import org.apache.hugegraph.pd.watch.PDWatchSubject;
9092
import org.lognet.springboot.grpc.GRpcService;
@@ -1735,6 +1737,17 @@ public void updatePdRaft(Pdpb.UpdatePdRaftRequest request,
17351737
node.changePeers(config, status -> {
17361738
if (status.isOk()) {
17371739
log.info("updatePdRaft, change peers success");
1740+
// Refresh IpAuthHandler so newly added peers are not blocked
1741+
IpAuthHandler handler = IpAuthHandler.getInstance();
1742+
if (handler != null) {
1743+
Set<String> newIps = new HashSet<>();
1744+
config.getPeers().forEach(p -> newIps.add(p.getIp()));
1745+
config.getLearners().forEach(p -> newIps.add(p.getIp()));
1746+
handler.refresh(newIps);
1747+
log.info("IpAuthHandler refreshed after updatePdRaft peer change");
1748+
} else {
1749+
log.warn("IpAuthHandler not initialized, skipping refresh");
1750+
}
17381751
} else {
17391752
log.error("changePeers status: {}, msg:{}, code: {}, raft error:{}",
17401753
status, status.getErrorMsg(), status.getCode(),

hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/service/interceptor/Authentication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ protected <T> T authenticate(String authority, String token, Function<String, T>
7777
}
7878

7979
String name = info.substring(0, delim);
80+
// TODO: password validation is skipped — only service name is checked against
81+
// innerModules. Full credential validation should be added as part of the auth refactor.
8082
//String pwd = info.substring(delim + 1);
8183
if (innerModules.contains(name)) {
8284
return call.get();

hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/util/grpc/GRpcServerConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public void configure(ServerBuilder<?> serverBuilder) {
4040
HgExecutorUtil.createExecutor(EXECUTOR_NAME, poolGrpc.getCore(), poolGrpc.getMax(),
4141
poolGrpc.getQueue()));
4242
serverBuilder.maxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE);
43+
// TODO: GrpcAuthentication is instantiated as a Spring bean but never registered
44+
// here — add serverBuilder.intercept(grpcAuthentication) once auth is refactored.
4345
}
4446

4547
}

hugegraph-pd/hg-pd-test/src/main/java/org/apache/hugegraph/pd/core/PDCoreSuiteTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import org.apache.hugegraph.pd.core.meta.MetadataKeyHelperTest;
2121
import org.apache.hugegraph.pd.core.store.HgKVStoreImplTest;
22+
import org.apache.hugegraph.pd.raft.IpAuthHandlerTest;
23+
import org.apache.hugegraph.pd.raft.RaftEngineIpAuthIntegrationTest;
2224
import org.junit.runner.RunWith;
2325
import org.junit.runners.Suite;
2426

@@ -36,6 +38,8 @@
3638
StoreMonitorDataServiceTest.class,
3739
StoreServiceTest.class,
3840
TaskScheduleServiceTest.class,
41+
IpAuthHandlerTest.class,
42+
RaftEngineIpAuthIntegrationTest.class,
3943
// StoreNodeServiceTest.class,
4044
})
4145
@Slf4j

0 commit comments

Comments
 (0)