Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CIDR ranges in ignore_hosts setting. #5099

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ dependencies {
implementation 'com.nimbusds:nimbus-jose-jwt:9.48'
implementation 'com.rfksystems:blake2b:2.0.0'
implementation 'com.password4j:password4j:1.8.2'
implementation "com.github.seancfoley:ipaddress:5.5.1"

// Action privileges: check tables and compact collections
implementation 'com.selectivem.collections:special-collections-complete:1.4.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@

import java.net.InetAddress;

import org.opensearch.security.support.WildcardMatcher;
import org.opensearch.security.support.HostAndCidrMatcher;
import org.opensearch.security.user.AuthCredentials;

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

WildcardMatcher getIgnoreHostsMatcher();
HostAndCidrMatcher getIgnoreHostsMatcher();

}
28 changes: 11 additions & 17 deletions src/main/java/org/opensearch/security/auth/BackendRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -66,7 +65,7 @@
import org.opensearch.security.http.XFFResolver;
import org.opensearch.security.securityconf.DynamicConfigModel;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.WildcardMatcher;
import org.opensearch.security.support.HostAndCidrMatcher;
import org.opensearch.security.user.AuthCredentials;
import org.opensearch.security.user.User;
import org.opensearch.threadpool.ThreadPool;
Expand All @@ -80,7 +79,7 @@

public class BackendRegistry {

protected final Logger log = LogManager.getLogger(this.getClass());
protected static final Logger log = LogManager.getLogger(BackendRegistry.class);
private SortedSet<AuthDomain> restAuthDomains;
private Set<AuthorizationBackend> restAuthorizers;

Expand Down Expand Up @@ -696,8 +695,7 @@ private boolean isBlocked(InetAddress address) {
}

for (ClientBlockRegistry<InetAddress> clientBlockRegistry : ipClientBlockRegistries) {
WildcardMatcher ignoreHostsMatcher = ((AuthFailureListener) clientBlockRegistry).getIgnoreHostsMatcher();
if (matchesHostPatterns(ignoreHostsMatcher, address, hostResolverMode)) {
if (matchesIgnoreHostPatterns(clientBlockRegistry, address, hostResolverMode)) {
return false;
}
if (clientBlockRegistry.isBlocked(address)) {
Expand All @@ -708,21 +706,17 @@ private boolean isBlocked(InetAddress address) {
return false;
}

public static boolean matchesHostPatterns(WildcardMatcher hostMatcher, InetAddress address, String hostResolverMode) {
if (hostMatcher == null) {
private static boolean matchesIgnoreHostPatterns(
ClientBlockRegistry<InetAddress> clientBlockRegistry,
InetAddress address,
String hostResolverMode
) {
HostAndCidrMatcher ignoreHostsMatcher = ((AuthFailureListener) clientBlockRegistry).getIgnoreHostsMatcher();
if (ignoreHostsMatcher == null || address == null) {
return false;
}
if (address != null) {
List<String> valuesToCheck = new ArrayList<>(List.of(address.getHostAddress()));
if (hostResolverMode != null
&& (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) {
final String hostName = address.getHostName();
valuesToCheck.add(hostName);
}
return ignoreHostsMatcher.matches(address, hostResolverMode);

return valuesToCheck.stream().anyMatch(hostMatcher);
}
return false;
}

private boolean isBlocked(String authBackend, String userName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
import org.opensearch.security.auth.AuthFailureListener;
import org.opensearch.security.auth.blocking.ClientBlockRegistry;
import org.opensearch.security.auth.blocking.HeapBasedClientBlockRegistry;
import org.opensearch.security.support.WildcardMatcher;
import org.opensearch.security.support.HostAndCidrMatcher;
import org.opensearch.security.user.AuthCredentials;
import org.opensearch.security.util.ratetracking.RateTracker;

public abstract class AbstractRateLimiter<ClientIdType> implements AuthFailureListener, ClientBlockRegistry<ClientIdType> {
protected final ClientBlockRegistry<ClientIdType> clientBlockRegistry;
protected final RateTracker<ClientIdType> rateTracker;
protected final List<String> ignoreHosts;
private WildcardMatcher ignoreHostMatcher;
private HostAndCidrMatcher ignoreHostMatcher;

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

@Override
public WildcardMatcher getIgnoreHostsMatcher() {
public HostAndCidrMatcher getIgnoreHostsMatcher() {
if (this.ignoreHostMatcher != null) {
return this.ignoreHostMatcher;
}
WildcardMatcher hostMatcher = WildcardMatcher.NONE;
HostAndCidrMatcher hostMatcher = new HostAndCidrMatcher(Collections.emptyList());
if (this.ignoreHosts != null && !this.ignoreHosts.isEmpty()) {
hostMatcher = WildcardMatcher.from(this.ignoreHosts);
hostMatcher = new HostAndCidrMatcher(this.ignoreHosts);
}
this.ignoreHostMatcher = hostMatcher;
return hostMatcher;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.opensearch.security.securityconf.impl.v7.RoleV7;
import org.opensearch.security.securityconf.impl.v7.TenantV7;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.HostResolverMode;
import org.opensearch.security.support.WildcardMatcher;
import org.opensearch.security.user.User;

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

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

for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) {
securityRoles.addAll(hosts.get(p));
}
}

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.support;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import inet.ipaddr.IPAddressString;

/**
* A utility class that performs matching of IP addresses against hostname patterns and CIDR ranges.
* This matcher supports both wildcard hostname patterns (e.g., *.example.com) and CIDR notation (e.g., 192.168.1.0/24).
*/
public class HostAndCidrMatcher {
protected final Logger log = LogManager.getLogger(HostAndCidrMatcher.class);
private final WildcardMatcher hostMatcher;
private final List<IPAddressString> cidrMatchers;

/**
* Constructs a new matcher with the specified host patterns.
*
* @param hostPatterns A list of patterns that can include both hostname wildcards
* (e.g., *.example.com) and CIDR ranges (e.g., 192.168.1.0/24).
* Must not be null.
* @throws IllegalArgumentException if hostPatterns is null
*/
public HostAndCidrMatcher(List<String> hostPatterns) {
if (hostPatterns == null) {
throw new IllegalArgumentException("Host patterns cannot be null");
}

this.hostMatcher = WildcardMatcher.from(hostPatterns);
this.cidrMatchers = hostPatterns.stream().map(IPAddressString::new).filter(IPAddressString::isIPAddress).toList();
}

/**
* Checks if the provided IP address matches any of the configured CIDR ranges.
*
* @param address The IP address to check. Can be either IPv4 or IPv6.
* @return true if the address matches any configured CIDR range, false otherwise
* or if the address is null
*/
public boolean matchesCidr(InetAddress address) {
if (address == null || cidrMatchers == null) {
return false;
}

IPAddressString addressString = new IPAddressString(address.getHostAddress());
return cidrMatchers.stream().anyMatch(cidrAddress -> cidrAddress.contains(addressString));
}

/**
* Checks if the provided IP address matches any of the configured hostname patterns.
* This method can perform DNS lookups depending on the hostResolverMode.
*
* @param address The IP address to check
* @param hostResolverMode The resolution mode. Must be one of {@link HostResolverMode} to enable hostname matching
* @return true if the address matches any configured hostname pattern, false otherwise,
* if the address is null, or if the resolver mode is invalid
* @implNote This method may perform DNS lookups which could impact performance
*/
public boolean matchesHostname(InetAddress address, String hostResolverMode) {
if (address == null || hostMatcher == null) {
return false;
}

List<String> valuesToCheck = new ArrayList<>(List.of(address.getHostAddress()));
if (hostResolverMode != null
&& (hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME.getValue())
|| hostResolverMode.equalsIgnoreCase(HostResolverMode.IP_HOSTNAME_LOOKUP.getValue()))) {
try {
final String hostName = address.getHostName(); // potential blocking call
valuesToCheck.add(hostName);
} catch (Exception e) {
log.warn("Failed to resolve hostname for {}: {}", address.getHostAddress(), e.getMessage());
return false;

Check warning on line 89 in src/main/java/org/opensearch/security/support/HostAndCidrMatcher.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/support/HostAndCidrMatcher.java#L87-L89

Added lines #L87 - L89 were not covered by tests
}
}
return valuesToCheck.stream().anyMatch(hostMatcher);
}

/**
* Checks if the provided IP address matches either hostname patterns or CIDR ranges.
*
* @param address The IP address to check
* @param hostResolverMode The resolution mode for hostname matching
* @return true if the address matches either hostname patterns or CIDR ranges,
* false otherwise
*/
public boolean matches(InetAddress address, String hostResolverMode) {
return matchesHostname(address, hostResolverMode) || matchesCidr(address);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.support;

public enum HostResolverMode {
IP_HOSTNAME("ip-hostname"),
IP_HOSTNAME_LOOKUP("ip-hostname-lookup");

private final String value;

HostResolverMode(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
88 changes: 0 additions & 88 deletions src/test/java/org/opensearch/security/UtilTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@

package org.opensearch.security;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;

import org.junit.Test;

import org.opensearch.common.settings.Settings;
import org.opensearch.security.auth.BackendRegistry;
import org.opensearch.security.hasher.PasswordHasher;
import org.opensearch.security.hasher.PasswordHasherFactory;
import org.opensearch.security.support.ConfigConstants;
Expand Down Expand Up @@ -226,88 +222,4 @@ public void testNoEnvReplace() {
);
}
}

@Test
public void testHostMatching() throws UnknownHostException {
assertThat(BackendRegistry.matchesHostPatterns(null, null, "ip-only"), is(false));
assertThat(BackendRegistry.matchesHostPatterns(null, null, null), is(false));
assertThat(BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), null, "ip-only"), is(false));
assertThat(BackendRegistry.matchesHostPatterns(null, InetAddress.getByName("127.0.0.1"), "ip-only"), is(false));
assertThat(
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), InetAddress.getByName("127.0.0.1"), "ip-only"),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.*")), InetAddress.getByName("127.0.0.1"), "ip-only"),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("127.0.0.1")),
InetAddress.getByName("localhost"),
"ip-hostname"
),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(WildcardMatcher.from(List.of("127.0.0.1")), InetAddress.getByName("localhost"), "ip-only"),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("127.0.0.1")),
InetAddress.getByName("localhost"),
"ip-hostname"
),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("127.0.0.1")),
InetAddress.getByName("example.org"),
"ip-hostname"
),
is(false)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("example.org")),
InetAddress.getByName("example.org"),
"ip-hostname"
),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("example.org")),
InetAddress.getByName("example.org"),
"ip-only"
),
is(false)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("*example.org")),
InetAddress.getByName("example.org"),
"ip-hostname"
),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("example.*")),
InetAddress.getByName("example.org"),
"ip-hostname"
),
is(true)
);
assertThat(
BackendRegistry.matchesHostPatterns(
WildcardMatcher.from(List.of("opensearch.org")),
InetAddress.getByName("example.org"),
"ip-hostname"
),
is(false)
);
}
}
Loading
Loading