Skip to content

Commit aa22ca8

Browse files
authored
Add support for CIDR ranges in ignore_hosts setting. (#5099)
Signed-off-by: shikharj05 <8859327+shikharj05@users.noreply.github.com>
1 parent 9e970e2 commit aa22ca8

File tree

10 files changed

+410
-114
lines changed

10 files changed

+410
-114
lines changed

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ dependencies {
589589
implementation 'com.nimbusds:nimbus-jose-jwt:9.48'
590590
implementation 'com.rfksystems:blake2b:2.0.0'
591591
implementation 'com.password4j:password4j:1.8.2'
592+
implementation "com.github.seancfoley:ipaddress:5.5.1"
592593

593594
// Action privileges: check tables and compact collections
594595
implementation 'com.selectivem.collections:special-collections-complete:1.4.0'

src/main/java/org/opensearch/security/auth/AuthFailureListener.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919

2020
import java.net.InetAddress;
2121

22-
import org.opensearch.security.support.WildcardMatcher;
22+
import org.opensearch.security.support.HostAndCidrMatcher;
2323
import org.opensearch.security.user.AuthCredentials;
2424

2525
public interface AuthFailureListener {
2626
void onAuthFailure(InetAddress remoteAddress, AuthCredentials authCredentials, Object request);
2727

28-
WildcardMatcher getIgnoreHostsMatcher();
28+
HostAndCidrMatcher getIgnoreHostsMatcher();
29+
2930
}

src/main/java/org/opensearch/security/auth/BackendRegistry.java

+11-17
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import java.net.InetAddress;
3030
import java.net.InetSocketAddress;
31-
import java.util.ArrayList;
3231
import java.util.Collection;
3332
import java.util.Collections;
3433
import java.util.HashSet;
@@ -66,7 +65,7 @@
6665
import org.opensearch.security.http.XFFResolver;
6766
import org.opensearch.security.securityconf.DynamicConfigModel;
6867
import org.opensearch.security.support.ConfigConstants;
69-
import org.opensearch.security.support.WildcardMatcher;
68+
import org.opensearch.security.support.HostAndCidrMatcher;
7069
import org.opensearch.security.user.AuthCredentials;
7170
import org.opensearch.security.user.User;
7271
import org.opensearch.threadpool.ThreadPool;
@@ -80,7 +79,7 @@
8079

8180
public class BackendRegistry {
8281

83-
protected final Logger log = LogManager.getLogger(this.getClass());
82+
protected static final Logger log = LogManager.getLogger(BackendRegistry.class);
8483
private SortedSet<AuthDomain> restAuthDomains;
8584
private Set<AuthorizationBackend> restAuthorizers;
8685

@@ -696,8 +695,7 @@ private boolean isBlocked(InetAddress address) {
696695
}
697696

698697
for (ClientBlockRegistry<InetAddress> clientBlockRegistry : ipClientBlockRegistries) {
699-
WildcardMatcher ignoreHostsMatcher = ((AuthFailureListener) clientBlockRegistry).getIgnoreHostsMatcher();
700-
if (matchesHostPatterns(ignoreHostsMatcher, address, hostResolverMode)) {
698+
if (matchesIgnoreHostPatterns(clientBlockRegistry, address, hostResolverMode)) {
701699
return false;
702700
}
703701
if (clientBlockRegistry.isBlocked(address)) {
@@ -708,21 +706,17 @@ private boolean isBlocked(InetAddress address) {
708706
return false;
709707
}
710708

711-
public static boolean matchesHostPatterns(WildcardMatcher hostMatcher, InetAddress address, String hostResolverMode) {
712-
if (hostMatcher == null) {
709+
private static boolean matchesIgnoreHostPatterns(
710+
ClientBlockRegistry<InetAddress> clientBlockRegistry,
711+
InetAddress address,
712+
String hostResolverMode
713+
) {
714+
HostAndCidrMatcher ignoreHostsMatcher = ((AuthFailureListener) clientBlockRegistry).getIgnoreHostsMatcher();
715+
if (ignoreHostsMatcher == null || address == null) {
713716
return false;
714717
}
715-
if (address != null) {
716-
List<String> valuesToCheck = new ArrayList<>(List.of(address.getHostAddress()));
717-
if (hostResolverMode != null
718-
&& (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) {
719-
final String hostName = address.getHostName();
720-
valuesToCheck.add(hostName);
721-
}
718+
return ignoreHostsMatcher.matches(address, hostResolverMode);
722719

723-
return valuesToCheck.stream().anyMatch(hostMatcher);
724-
}
725-
return false;
726720
}
727721

728722
private boolean isBlocked(String authBackend, String userName) {

src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@
2626
import org.opensearch.security.auth.AuthFailureListener;
2727
import org.opensearch.security.auth.blocking.ClientBlockRegistry;
2828
import org.opensearch.security.auth.blocking.HeapBasedClientBlockRegistry;
29-
import org.opensearch.security.support.WildcardMatcher;
29+
import org.opensearch.security.support.HostAndCidrMatcher;
3030
import org.opensearch.security.user.AuthCredentials;
3131
import org.opensearch.security.util.ratetracking.RateTracker;
3232

3333
public abstract class AbstractRateLimiter<ClientIdType> implements AuthFailureListener, ClientBlockRegistry<ClientIdType> {
3434
protected final ClientBlockRegistry<ClientIdType> clientBlockRegistry;
3535
protected final RateTracker<ClientIdType> rateTracker;
3636
protected final List<String> ignoreHosts;
37-
private WildcardMatcher ignoreHostMatcher;
37+
private HostAndCidrMatcher ignoreHostMatcher;
3838

3939
public AbstractRateLimiter(Settings settings, Path configPath, Class<ClientIdType> clientIdType) {
4040
this.ignoreHosts = settings.getAsList("ignore_hosts", Collections.emptyList());
@@ -54,13 +54,13 @@ public AbstractRateLimiter(Settings settings, Path configPath, Class<ClientIdTyp
5454
public abstract void onAuthFailure(InetAddress remoteAddress, AuthCredentials authCredentials, Object request);
5555

5656
@Override
57-
public WildcardMatcher getIgnoreHostsMatcher() {
57+
public HostAndCidrMatcher getIgnoreHostsMatcher() {
5858
if (this.ignoreHostMatcher != null) {
5959
return this.ignoreHostMatcher;
6060
}
61-
WildcardMatcher hostMatcher = WildcardMatcher.NONE;
61+
HostAndCidrMatcher hostMatcher = new HostAndCidrMatcher(Collections.emptyList());
6262
if (this.ignoreHosts != null && !this.ignoreHosts.isEmpty()) {
63-
hostMatcher = WildcardMatcher.from(this.ignoreHosts);
63+
hostMatcher = new HostAndCidrMatcher(this.ignoreHosts);
6464
}
6565
this.ignoreHostMatcher = hostMatcher;
6666
return hostMatcher;

src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.opensearch.security.securityconf.impl.v7.RoleV7;
5353
import org.opensearch.security.securityconf.impl.v7.TenantV7;
5454
import org.opensearch.security.support.ConfigConstants;
55+
import org.opensearch.security.support.HostResolverMode;
5556
import org.opensearch.security.support.WildcardMatcher;
5657
import org.opensearch.security.user.User;
5758

@@ -330,15 +331,16 @@ private Set<String> map(final User user, final TransportAddress caller) {
330331
}
331332

332333
if (caller.address() != null
333-
&& (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) {
334+
&& (hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME.getValue())
335+
|| hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME_LOOKUP.getValue()))) {
334336
final String hostName = caller.address().getHostString();
335337

336338
for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) {
337339
securityRoles.addAll(hosts.get(p));
338340
}
339341
}
340342

341-
if (caller.address() != null && hostResolverMode.equalsIgnoreCase("ip-hostname-lookup")) {
343+
if (caller.address() != null && hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME_LOOKUP.getValue())) {
342344

343345
final String resolvedHostName = caller.address().getHostName();
344346

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.support;
13+
14+
import java.net.InetAddress;
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
18+
import org.apache.logging.log4j.LogManager;
19+
import org.apache.logging.log4j.Logger;
20+
21+
import inet.ipaddr.IPAddressString;
22+
23+
/**
24+
* A utility class that performs matching of IP addresses against hostname patterns and CIDR ranges.
25+
* This matcher supports both wildcard hostname patterns (e.g., *.example.com) and CIDR notation (e.g., 192.168.1.0/24).
26+
*/
27+
public class HostAndCidrMatcher {
28+
protected final Logger log = LogManager.getLogger(HostAndCidrMatcher.class);
29+
private final WildcardMatcher hostMatcher;
30+
private final List<IPAddressString> cidrMatchers;
31+
32+
/**
33+
* Constructs a new matcher with the specified host patterns.
34+
*
35+
* @param hostPatterns A list of patterns that can include both hostname wildcards
36+
* (e.g., *.example.com) and CIDR ranges (e.g., 192.168.1.0/24).
37+
* Must not be null.
38+
* @throws IllegalArgumentException if hostPatterns is null
39+
*/
40+
public HostAndCidrMatcher(List<String> hostPatterns) {
41+
if (hostPatterns == null) {
42+
throw new IllegalArgumentException("Host patterns cannot be null");
43+
}
44+
45+
this.hostMatcher = WildcardMatcher.from(hostPatterns);
46+
this.cidrMatchers = hostPatterns.stream().map(IPAddressString::new).filter(IPAddressString::isIPAddress).toList();
47+
}
48+
49+
/**
50+
* Checks if the provided IP address matches any of the configured CIDR ranges.
51+
*
52+
* @param address The IP address to check. Can be either IPv4 or IPv6.
53+
* @return true if the address matches any configured CIDR range, false otherwise
54+
* or if the address is null
55+
*/
56+
public boolean matchesCidr(InetAddress address) {
57+
if (address == null || cidrMatchers == null) {
58+
return false;
59+
}
60+
61+
IPAddressString addressString = new IPAddressString(address.getHostAddress());
62+
return cidrMatchers.stream().anyMatch(cidrAddress -> cidrAddress.contains(addressString));
63+
}
64+
65+
/**
66+
* Checks if the provided IP address matches any of the configured hostname patterns.
67+
* This method can perform DNS lookups depending on the hostResolverMode.
68+
*
69+
* @param address The IP address to check
70+
* @param hostResolverMode The resolution mode. Must be one of {@link HostResolverMode} to enable hostname matching
71+
* @return true if the address matches any configured hostname pattern, false otherwise,
72+
* if the address is null, or if the resolver mode is invalid
73+
* @implNote This method may perform DNS lookups which could impact performance
74+
*/
75+
public boolean matchesHostname(InetAddress address, String hostResolverMode) {
76+
if (address == null || hostMatcher == null) {
77+
return false;
78+
}
79+
80+
List<String> valuesToCheck = new ArrayList<>(List.of(address.getHostAddress()));
81+
if (hostResolverMode != null
82+
&& (hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME.getValue())
83+
|| hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME_LOOKUP.getValue()))) {
84+
try {
85+
final String hostName = address.getHostName(); // potential blocking call
86+
valuesToCheck.add(hostName);
87+
} catch (Exception e) {
88+
log.warn("Failed to resolve hostname for {}: {}", address.getHostAddress(), e.getMessage());
89+
return false;
90+
}
91+
}
92+
return valuesToCheck.stream().anyMatch(hostMatcher);
93+
}
94+
95+
/**
96+
* Checks if the provided IP address matches either hostname patterns or CIDR ranges.
97+
*
98+
* @param address The IP address to check
99+
* @param hostResolverMode The resolution mode for hostname matching
100+
* @return true if the address matches either hostname patterns or CIDR ranges,
101+
* false otherwise
102+
*/
103+
public boolean matches(InetAddress address, String hostResolverMode) {
104+
return matchesHostname(address, hostResolverMode) || matchesCidr(address);
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.support;
13+
14+
public enum HostResolverMode {
15+
IP_HOSTNAME("ip-hostname"),
16+
IP_HOSTNAME_LOOKUP("ip-hostname-lookup");
17+
18+
private final String value;
19+
20+
HostResolverMode(String value) {
21+
this.value = value;
22+
}
23+
24+
public String getValue() {
25+
return value;
26+
}
27+
}

src/test/java/org/opensearch/security/UtilTests.java

-88
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,11 @@
2626

2727
package org.opensearch.security;
2828

29-
import java.net.InetAddress;
30-
import java.net.UnknownHostException;
31-
import java.util.List;
3229
import java.util.Map;
3330

3431
import org.junit.Test;
3532

3633
import org.opensearch.common.settings.Settings;
37-
import org.opensearch.security.auth.BackendRegistry;
3834
import org.opensearch.security.hasher.PasswordHasher;
3935
import org.opensearch.security.hasher.PasswordHasherFactory;
4036
import org.opensearch.security.support.ConfigConstants;
@@ -226,88 +222,4 @@ public void testNoEnvReplace() {
226222
);
227223
}
228224
}
229-
230-
@Test
231-
public void testHostMatching() throws UnknownHostException {
232-
assertThat(BackendRegistry.matchesHostPatterns(null, null, "ip-only"), is(false));
233-
assertThat(BackendRegistry.matchesHostPatterns(null, null, null), is(false));
234-
assertThat(BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), null, "ip-only"), is(false));
235-
assertThat(BackendRegistry.matchesHostPatterns(null, InetAddress.getByName("127.0.0.1"), "ip-only"), is(false));
236-
assertThat(
237-
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), InetAddress.getByName("127.0.0.1"), "ip-only"),
238-
is(true)
239-
);
240-
assertThat(
241-
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.*")), InetAddress.getByName("127.0.0.1"), "ip-only"),
242-
is(true)
243-
);
244-
assertThat(
245-
BackendRegistry.matchesHostPatterns(
246-
WildcardMatcher.from(List.of("127.0.0.1")),
247-
InetAddress.getByName("localhost"),
248-
"ip-hostname"
249-
),
250-
is(true)
251-
);
252-
assertThat(
253-
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), InetAddress.getByName("localhost"), "ip-only"),
254-
is(true)
255-
);
256-
assertThat(
257-
BackendRegistry.matchesHostPatterns(
258-
WildcardMatcher.from(List.of("127.0.0.1")),
259-
InetAddress.getByName("localhost"),
260-
"ip-hostname"
261-
),
262-
is(true)
263-
);
264-
assertThat(
265-
BackendRegistry.matchesHostPatterns(
266-
WildcardMatcher.from(List.of("127.0.0.1")),
267-
InetAddress.getByName("example.org"),
268-
"ip-hostname"
269-
),
270-
is(false)
271-
);
272-
assertThat(
273-
BackendRegistry.matchesHostPatterns(
274-
WildcardMatcher.from(List.of("example.org")),
275-
InetAddress.getByName("example.org"),
276-
"ip-hostname"
277-
),
278-
is(true)
279-
);
280-
assertThat(
281-
BackendRegistry.matchesHostPatterns(
282-
WildcardMatcher.from(List.of("example.org")),
283-
InetAddress.getByName("example.org"),
284-
"ip-only"
285-
),
286-
is(false)
287-
);
288-
assertThat(
289-
BackendRegistry.matchesHostPatterns(
290-
WildcardMatcher.from(List.of("*example.org")),
291-
InetAddress.getByName("example.org"),
292-
"ip-hostname"
293-
),
294-
is(true)
295-
);
296-
assertThat(
297-
BackendRegistry.matchesHostPatterns(
298-
WildcardMatcher.from(List.of("example.*")),
299-
InetAddress.getByName("example.org"),
300-
"ip-hostname"
301-
),
302-
is(true)
303-
);
304-
assertThat(
305-
BackendRegistry.matchesHostPatterns(
306-
WildcardMatcher.from(List.of("opensearch.org")),
307-
InetAddress.getByName("example.org"),
308-
"ip-hostname"
309-
),
310-
is(false)
311-
);
312-
}
313225
}

0 commit comments

Comments
 (0)