This is an automated email from the ASF dual-hosted git repository.
andor pushed a commit to branch branch-3.9
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/branch-3.9 by this push:
new d22b17da6 ZOOKEEPER-4240: Add IPV6 support for ZooKeeper ACL
d22b17da6 is described below
commit d22b17da6355440a114d721620cad31c2488b420
Author: Abhishek Kothalikar <[email protected]>
AuthorDate: Tue Aug 19 23:29:36 2025 +0530
ZOOKEEPER-4240: Add IPV6 support for ZooKeeper ACL
Reviewers: anmolnar, anmolnar, kezhuw
Author: kabhishek4
Closes #2280 from kabhishek4/ZOOKEEPER-4240
---
.../server/auth/IPAuthenticationProvider.java | 104 ++++++++++++-
.../server/auth/IPAuthenticationProviderTest.java | 90 +++++++++++
.../java/org/apache/zookeeper/test/ACLTest.java | 164 +++++++++++++++++++++
3 files changed, 355 insertions(+), 3 deletions(-)
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
index 26c14a4e8..0b4ef9a70 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/IPAuthenticationProvider.java
@@ -22,15 +22,26 @@
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
+import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.ServerCnxn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class IPAuthenticationProvider implements AuthenticationProvider {
+ private static final Logger LOG =
LoggerFactory.getLogger(IPAuthenticationProvider.class);
public static final String X_FORWARDED_FOR_HEADER_NAME = "X-Forwarded-For";
public static final String USE_X_FORWARDED_FOR_KEY =
"zookeeper.IPAuthenticationProvider.usexforwardedfor";
+ private static final int IPV6_BYTE_LENGTH = 16; // IPv6 address is 128
bits = 16 bytes
+ private static final int IPV6_SEGMENT_COUNT = 8; // IPv6 address has 8
segments
+ private static final int IPV6_SEGMENT_BYTE_LENGTH = 2; // Each segment has
up to two bytes
+ private static final int IPV6_SEGMENT_HEX_LENGTH = 4; // Each segment has
up to 4 hex digits
+
+ private static final Pattern IPV6_PATTERN = Pattern.compile(":");
+ private static final Pattern IPV4_PATTERN = Pattern.compile("\\.");
public String getScheme() {
return "ip";
@@ -55,9 +66,14 @@ public List<Id> handleAuthentication(HttpServletRequest
request, byte[] authData
// This is a bit weird but we need to return the address and the number of
// bytes (to distinguish between IPv4 and IPv6
private byte[] addr2Bytes(String addr) {
- byte[] b = v4addr2Bytes(addr);
- // TODO Write the v6addr2Bytes
- return b;
+ if (IPV6_PATTERN.matcher(addr).find()) {
+ return v6addr2Bytes(addr);
+ } else if (IPV4_PATTERN.matcher(addr).find()) {
+ return v4addr2Bytes(addr);
+ } else {
+ LOG.warn("Input string does not resemble an IPv4 or IPv6 address:
{}", addr);
+ return null;
+ }
}
private byte[] v4addr2Bytes(String addr) {
@@ -81,6 +97,83 @@ private byte[] v4addr2Bytes(String addr) {
return b;
}
+ /**
+ * Validates an IPv6 address string and converts it into a byte array.
+ *
+ * @param ipv6Addr The IPv6 address string to validate.
+ * @return A byte array representing the IPv6 address if valid, or null if
the address
+ * is invalid or cannot be parsed.
+ */
+ static byte[] v6addr2Bytes(String ipv6Addr) {
+ try {
+ return parseV6addr(ipv6Addr);
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Fail to parse {} as IPv6 address: {}", ipv6Addr,
e.getMessage());
+ return null;
+ }
+ }
+
+ static byte[] parseV6addr(String ipv6Addr) {
+ // Split the address by "::" to handle zero compression, -1 to keep
trailing empty strings
+ String[] parts = ipv6Addr.split("::", -1);
+
+ String[] segments1 = new String[0];
+ String[] segments2 = new String[0];
+
+ // Case 1: No "::" (full address)
+ if (parts.length == 1) {
+ segments1 = parts[0].split(":", -1);
+ if (segments1.length != IPV6_SEGMENT_COUNT) {
+ String reason = "wrong number of segments";
+ throw new IllegalArgumentException(reason);
+ }
+ } else if (parts.length == 2) {
+ // Case 2: "::" is present
+ // Handle cases like "::1" or "1::"
+ if (!parts[0].isEmpty()) {
+ segments1 = parts[0].split(":", -1);
+ }
+ if (!parts[1].isEmpty()) {
+ segments2 = parts[1].split(":", -1);
+ }
+
+ // Check if the total number of explicit segments exceeds 8
+ if (segments1.length + segments2.length >= IPV6_SEGMENT_COUNT) {
+ String reason = "too many segments";
+ throw new IllegalArgumentException(reason);
+ }
+ } else {
+ // Case 3: Invalid number of parts after splitting by "::" (should
be 1 or 2)
+ String reason = "too many '::'";
+ throw new IllegalArgumentException(reason);
+ }
+
+ byte[] result = new byte[IPV6_BYTE_LENGTH];
+ // Process segments before "::"
+ parseV6Segment(result, 0, segments1);
+ // Process segments after "::"
+ parseV6Segment(result, IPV6_BYTE_LENGTH - segments2.length *
IPV6_SEGMENT_BYTE_LENGTH, segments2);
+
+ return result;
+ }
+
+ private static void parseV6Segment(byte[] addr, int i, String[] segments) {
+ for (String segment : segments) {
+ if (segment.isEmpty()) {
+ throw new IllegalArgumentException("empty segment");
+ } else if (segment.length() > IPV6_SEGMENT_HEX_LENGTH) {
+ throw new IllegalArgumentException("segment too long");
+ }
+ try {
+ int value = Integer.parseInt(segment, 16);
+ addr[i++] = (byte) ((value >> 8) & 0xFF);
+ addr[i++] = (byte) (value & 0xFF);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("invalid hexadecimal
characters in segment: " + segment);
+ }
+ }
+ }
+
private void mask(byte[] b, int bits) {
int start = bits / 8;
int startMask = (1 << (8 - (bits % 8))) - 1;
@@ -93,6 +186,7 @@ private void mask(byte[] b, int bits) {
}
public boolean matches(String id, String aclExpr) {
+ LOG.trace("id: '{}' aclExpr: {}", id, aclExpr);
String[] parts = aclExpr.split("/", 2);
byte[] aclAddr = addr2Bytes(parts[0]);
if (aclAddr == null) {
@@ -115,6 +209,10 @@ public boolean matches(String id, String aclExpr) {
return false;
}
mask(remoteAddr, bits);
+ // Check if id and acl expression are of different formats (ipv6 or
iv4) return false
+ if (remoteAddr.length != aclAddr.length) {
+ return false;
+ }
for (int i = 0; i < remoteAddr.length; i++) {
if (remoteAddr[i] != aclAddr[i]) {
return false;
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
index c1a1d1f52..1586cac39 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/auth/IPAuthenticationProviderTest.java
@@ -19,13 +19,23 @@
import static
org.apache.zookeeper.server.auth.IPAuthenticationProvider.USE_X_FORWARDED_FOR_KEY;
import static
org.apache.zookeeper.server.auth.IPAuthenticationProvider.X_FORWARDED_FOR_HEADER_NAME;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
public class IPAuthenticationProviderTest {
@@ -96,4 +106,84 @@ public void testGetClientIPAddressMissingXForwardedFor() {
// Assert
assertEquals("192.168.1.1", clientIp);
}
+
+ @Test
+ public void testParsingOfIPv6Address() {
+ //Full IPv6 address
+ String ipv6Full = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
+ byte[] expectedFull = {
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x85, (byte) 0xa3, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x8a, (byte) 0x2e,
+ (byte) 0x03, (byte) 0x70, (byte) 0x73, (byte) 0x34
+ };
+ byte[] actualFull = IPAuthenticationProvider.v6addr2Bytes(ipv6Full);
+ assertNotNull(actualFull, "Full IPv6 address should not return null");
+ assertArrayEquals(expectedFull, actualFull, "Full IPv6 address conversion
mismatch");
+
+ //Compressed IPv6 address (double colon)
+ String ipv6Compressed = "2001:db8::8a2e:370:7334";
+ byte[] expectedCompressed = {
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x8a, (byte) 0x2e,
+ (byte) 0x03, (byte) 0x70, (byte) 0x73, (byte) 0x34
+ };
+ byte[] actualCompressed =
IPAuthenticationProvider.v6addr2Bytes(ipv6Compressed);
+ assertNotNull(actualCompressed, "Compressed IPv6 address should not return
null");
+ assertArrayEquals(expectedCompressed, actualCompressed, "Compressed IPv6
address conversion mismatch");
+
+ //Shortened IPv6 address
+ String ipv6Shortened = "2001:db8::1";
+ byte[] expectedShortened = {
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+ };
+ byte[] actualShortened =
IPAuthenticationProvider.v6addr2Bytes(ipv6Shortened);
+ assertNotNull(actualShortened, "Shortened IPv6 address should not return
null");
+ assertArrayEquals(expectedShortened, actualShortened, "Shortened IPv6
address conversion mismatch");
+
+ //Loopback address
+ String ipv6Loopback = "::1";
+ byte[] expectedLoopback = {
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01
+ };
+ byte[] actualLoopback =
IPAuthenticationProvider.v6addr2Bytes(ipv6Loopback);
+ assertNotNull(actualLoopback, "Loopback IPv6 address should not return
null");
+ assertArrayEquals(expectedLoopback, actualLoopback, "Loopback IPv6 address
conversion mismatch");
+ }
+
+ private static Stream<Arguments> invalidIPv6Addresses() {
+ return Stream.of(
+ Arguments.of("1", "wrong number of segments"),
+ Arguments.of("1:2", "wrong number of segments"),
+ Arguments.of("1::2:", "empty segment"),
+ Arguments.of(":1::2:", "empty segment"),
+ Arguments.of("1:2:3:4:5:6:7:8:", "wrong number of segments"),
+ Arguments.of("1:2:3:4:5:6:7:8:9", "wrong number of segments"),
+ Arguments.of("1:2::3:4:5:6:7:8", "too many segments"),
+ Arguments.of("1::2::", "too many '::'"),
+ Arguments.of("1:abcdf::", "segment too long"),
+ Arguments.of("efgh::", "invalid hexadecimal characters in segment"),
+ Arguments.of("1:: ", "invalid hexadecimal characters in segment"),
+ Arguments.of(" 1::", "invalid hexadecimal characters in segment")
+ );
+ }
+
+ @ParameterizedTest(name = "address = \"{0}\"")
+ @MethodSource("invalidIPv6Addresses")
+ public void testParsingOfInvalidIPv6Address(String ipv6Address, String
expectedMessage) {
+ try {
+ IPAuthenticationProvider.parseV6addr(ipv6Address);
+ fail("expect failure");
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString(expectedMessage));
+ }
+ assertNull(IPAuthenticationProvider.v6addr2Bytes(ipv6Address));
+ }
}
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
index 965d99c4c..75e93752e 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ACLTest.java
@@ -23,11 +23,15 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
@@ -39,6 +43,7 @@
import org.apache.zookeeper.ZKTestCase;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
@@ -48,6 +53,7 @@
import org.apache.zookeeper.server.SyncRequestProcessor;
import org.apache.zookeeper.server.ZooKeeperServer;
import org.apache.zookeeper.server.auth.IPAuthenticationProvider;
+import org.apache.zookeeper.server.embedded.ZooKeeperServerEmbedded;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,6 +74,38 @@ public void testIPAuthenticationIsValidCIDR() throws
Exception {
assertFalse(prov.isValid("10.0.0.1/-1"), "testing netmask too low");
}
+ @Test
+ public void testIPAuthenticationIsValidIpv6CIDR() throws Exception {
+ IPAuthenticationProvider prov = new IPAuthenticationProvider();
+ assertTrue(prov.isValid("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
"full address no netmask");
+ assertTrue(prov.isValid("2001:db8:85a3::8a2e:370:7334"), "compressed
zeros");
+ assertTrue(prov.isValid("::1"), "loopback with compression");
+ assertTrue(prov.isValid("1::"), "Start with compression");
+ assertTrue(prov.isValid("2001:db8::/4"), "end with compression");
+ assertTrue(prov.isValid("0:0:0:0:0:0:0::/8"), "all zeros");
+ assertTrue(prov.isValid("2001:db8:85a3:0:0:0:0::/32"), "Explicit
zeros");
+ assertTrue(prov.isValid("1234:5678:9abc:def0:1234:5678:9abc:def0"),
"max hex value");
+
assertFalse(prov.isValid("2001:db8:85a3:0000:0000:8a2e:0370:7334:extra"), "too
many address segments");
+ assertFalse(prov.isValid("2001:db8:85a3:0000:0000:8a2e:0370"), "too
few address segments");
+ assertFalse(prov.isValid("2001:db8:85a3::8a2e::0370:7334"), "multiple
'::' not valid");
+ assertFalse(prov.isValid("2001:db8:85a3:G::8a2e:0370:7334"), "Invalid
hex character");
+ assertFalse(prov.isValid(""), "empty string");
+ assertFalse(prov.isValid("2001:db8:85a3:0:0:0:0:1:2"), "too many
segments post compression");
+ assertFalse(prov.isValid("2001:db8:85a3::8a2e:0370:7334:"), "trailing
colon");
+ assertFalse(prov.isValid(":2001:db8:85a3::8a2e:0370:7334"), "Leading
colon");
+ assertFalse(prov.isValid("::FFFF:192.168.1.1"), "IPv4-mapped");
+ assertTrue(prov.isValid("2001:db8:1234::/64"), "IPv6 address for
multiple clients");
+ }
+
+ @Test
+ public void testIPAuthenticationIsValidIpv6Mask() throws Exception {
+ IPAuthenticationProvider prov = new IPAuthenticationProvider();
+ assertTrue(prov.matches("2001:db8:1234::", "2001:db8:1234::/64"));
+ assertTrue(prov.matches("2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:0db8:85a3:0000:0000:8a2e:0370::/2"));
+ assertFalse(prov.matches("22001:db8:85a3:0:0:0:0::0",
"2001:db8:85a3:0:0:0:0::/32"));
+ assertFalse(prov.matches("2001:db8::/4", "2001:db8::/4"));
+ }
+
@Test
public void testNettyIpAuthDefault() throws Exception {
String HOSTPORT = "127.0.0.1:" + PortAssignment.unique();
@@ -101,6 +139,132 @@ public void testNettyIpAuthDefault() throws Exception {
}
}
+ @Test
+ public void testAuthWithIPV6Server(@TempDir File tmpDir) throws Exception {
+ Properties properties = new Properties();
+ properties.setProperty("clientPortAddress", "::1");
+ properties.setProperty("clientPort", "0");
+
+ ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded.builder()
+ .baseDir(tmpDir.toPath())
+ .configuration(properties)
+ .build();
+ server.start();
+
+ String connectionString = server.getConnectionString();
+ String port =
connectionString.substring(connectionString.lastIndexOf(':') + 1);
+
+ String hostport = String.format("[::1]:%s", port);
+ assertTrue(ClientBase.waitForServerUp(hostport, CONNECTION_TIMEOUT),
"waiting for server being up");
+
+ // given: ipv6 client
+ ZooKeeper zk = ClientBase.createZKClient(hostport);
+
+ // when: invalid ipv6 network acl
+ // then: InvalidACL
+ assertThrows(KeeperException.InvalidACLException.class, () ->
+ zk.create("/invalid-ipv6-network-acl", null,
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "::1/256"))),
CreateMode.PERSISTENT));
+
+ // given: ipv4 network acl
+ zk.create("/unmatched-ipv4-network-acl", null,
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "127.0.0.1/16"))),
CreateMode.PERSISTENT);
+ // when: access with v6 ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/unmatched-ipv4-network-acl", null, -1));
+
+ // given: prefix matched ipv4 acl
+ zk.create("/prefix-matched-ipv4-acl", null,
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "0.0.0.1/16"))),
CreateMode.PERSISTENT);
+ // when: access with v6 ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/prefix-matched-ipv4-acl", null, -1));
+
+ // given: ipv6 with network acl
+ zk.create("/ipv6-network-acl", null, Collections.singletonList(new
ACL(Perms.ALL, new Id("ip", "::1/64"))), CreateMode.PERSISTENT);
+ // when: access with valid ip
+ // then: ok
+ zk.setData("/ipv6-network-acl", null, -1);
+
+ // given: ipv6 acl
+ zk.create("/ipv6-acl", null, Collections.singletonList(new
ACL(Perms.ALL, new Id("ip", "::1"))), CreateMode.PERSISTENT);
+ // when: access with valid ip
+ // then: ok
+ zk.setData("/ipv6-acl", null, -1);
+
+ // given: mismatched ipv6 with network acl
+ zk.create("/mismatched-ipv6-network-acl", null,
Collections.singletonList(new ACL(Perms.ALL, new Id("ip", "0000:0001::/32"))),
CreateMode.PERSISTENT);
+ // when: access with invalid ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/mismatched-ipv6-network-acl", null, -1));
+
+ // given: mismatched ipv6 acl
+ zk.create("/mismatched-ipv6-acl", null, Collections.singletonList(new
ACL(Perms.ALL, new Id("ip", "::2"))), CreateMode.PERSISTENT);
+ // when: access with invalid ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/mismatched-ipv6-acl", null, -1));
+
+ server.close();
+ }
+
+ @Test
+ public void testAuthWithIPV4Server(@TempDir File tmpDir) throws Exception {
+ Properties properties = new Properties();
+ properties.setProperty("clientPortAddress", "127.0.0.1");
+ properties.setProperty("clientPort", "0");
+
+ ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded.builder()
+ .baseDir(tmpDir.toPath())
+ .configuration(properties)
+ .build();
+ server.start();
+
+ assertTrue(ClientBase.waitForServerUp(server.getConnectionString(),
CONNECTION_TIMEOUT), "waiting for server being up");
+
+ // given: ipv4 client
+ ZooKeeper zk = ClientBase.createZKClient(server.getConnectionString());
+
+ // when: invalid ipv4 network acl
+ // then: InvalidACL
+ assertThrows(KeeperException.InvalidACLException.class, () ->
+ zk.create("/invalid-ipv4-network-acl", new byte[]{},
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "127.0.0.1/64"))),
CreateMode.PERSISTENT));
+
+ // given: ipv6 acl
+ zk.create("/mismatched-ipv6-acl", new byte[]{}, Arrays.asList(new
ACL(Perms.ALL, new Id("ip", "::1"))), CreateMode.PERSISTENT);
+ // when: access with v4 ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/mismatched-ipv6-acl", null, -1));
+
+ // given: prefix matched ipv6 network acl
+ zk.create("/prefix-matched-ipv6-network-acl", new byte[]{},
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "7f::/16"))),
CreateMode.PERSISTENT);
+ // when: access with v4 ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/prefix-matched-ipv6-network-acl", null, -1));
+
+ // given: ipv4 with network acl
+ zk.create("/matched-ipv4-network-acl", new byte[]{}, Arrays.asList(new
ACL(Perms.ALL, new Id("ip", "127.0.0.1/16"))), CreateMode.PERSISTENT);
+ // when: access with valid ip
+ // then: ok
+ zk.setData("/matched-ipv4-network-acl", null, -1);
+
+ // given: matched ipv4 acl
+ zk.create("/matched-ipv4-acl", new byte[]{}, Arrays.asList(new
ACL(Perms.ALL, new Id("ip", "127.0.0.1"))), CreateMode.PERSISTENT);
+ // when: access with valid ip
+ // then: ok
+ zk.setData("/matched-ipv4-acl", null, -1);
+
+ // given: mismatched ipv4 network acl
+ zk.create("/mismatched-ipv4-network-acl", new byte[]{},
Arrays.asList(new ACL(Perms.ALL, new Id("ip", "192.168.0.2/16"))),
CreateMode.PERSISTENT);
+ // when: access with invalid ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/mismatched-ipv4-network-acl", null, -1));
+
+ // given: mismatched ipv4 acl
+ zk.create("/mismatched-ipv4-acl", new byte[]{}, Arrays.asList(new
ACL(Perms.ALL, new Id("ip", "127.0.0.2"))), CreateMode.PERSISTENT);
+ // when: access with invalid ip
+ // then: NoAuth
+ assertThrows(KeeperException.NoAuthException.class, () ->
zk.setData("/mismatched-ipv4-acl", null, -1));
+
+ server.close();
+ }
+
@Test
public void testDisconnectedAddAuth() throws Exception {
File tmpDir = ClientBase.createTmpDir();