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 4 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 "commons-net:commons-net:3.11.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 @@ -18,12 +18,19 @@
package org.opensearch.security.auth;

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

import org.apache.commons.net.util.SubnetUtils.SubnetInfo;

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

public interface AuthFailureListener {
List<String> getIgnoreHosts();

void onAuthFailure(InetAddress remoteAddress, AuthCredentials authCredentials, Object request);

WildcardMatcher getIgnoreHostsMatcher();

SubnetInfo getSubnetForCidr(String cidr);
}
30 changes: 13 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 @@ -76,11 +75,13 @@
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.opensearch.security.support.SecurityUtils.matchesHostNamePatterns;
import static org.opensearch.security.support.SecurityUtils.matchesIpAndCidrPatterns;
import static com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator.SAML_TYPE;

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 +697,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 +708,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
) {
WildcardMatcher 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 valuesToCheck.stream().anyMatch(hostMatcher);
}
return false;
return matchesHostNamePatterns(ignoreHostsMatcher, address, hostResolverMode)
|| matchesIpAndCidrPatterns(clientBlockRegistry, address);
}

private boolean isBlocked(String authBackend, String userName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.util.Collections;
import java.util.List;

import org.apache.commons.net.util.SubnetUtils;

import org.opensearch.common.settings.Settings;
import org.opensearch.security.auth.AuthFailureListener;
import org.opensearch.security.auth.blocking.ClientBlockRegistry;
Expand Down Expand Up @@ -50,6 +52,11 @@ public AbstractRateLimiter(Settings settings, Path configPath, Class<ClientIdTyp
);
}

@Override
public List<String> getIgnoreHosts() {
return ignoreHosts;
}

@Override
public abstract void onAuthFailure(InetAddress remoteAddress, AuthCredentials authCredentials, Object request);

Expand All @@ -66,6 +73,14 @@ public WildcardMatcher getIgnoreHostsMatcher() {
return hostMatcher;
}

@Override
public SubnetUtils.SubnetInfo getSubnetForCidr(String cidr) {
SubnetUtils utils = new SubnetUtils(cidr);
// Include network and broadcast addresses
utils.setInclusiveHostCount(true);
return utils.getInfo();
}

@Override
public boolean isBlocked(ClientIdType clientId) {
return clientBlockRegistry.isBlocked(clientId);
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/opensearch/security/support/SecurityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,24 @@

package org.opensearch.security.support;

import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.opensearch.common.settings.Settings;
import org.opensearch.security.auth.AuthFailureListener;
import org.opensearch.security.auth.blocking.ClientBlockRegistry;
import org.opensearch.security.tools.Hasher;

public final class SecurityUtils {
Expand All @@ -46,6 +54,7 @@
static final Pattern ENVBC_PATTERN = Pattern.compile("\\$\\{envbc" + ENV_PATTERN_SUFFIX);
static final Pattern ENVBASE64_PATTERN = Pattern.compile("\\$\\{envbase64" + ENV_PATTERN_SUFFIX);
public static Locale EN_Locale = forEN();
private static final Map<String, SubnetInfo> cidrCache = new ConcurrentHashMap<>();

private SecurityUtils() {}

Expand Down Expand Up @@ -140,4 +149,52 @@
return bc ? Hasher.hash(envVarValue.toCharArray(), settings) : envVarValue;
}
}

// This is used to match host names - for e.g. *.example.local to dev.example.local
public static boolean matchesHostNamePatterns(WildcardMatcher hostMatcher, InetAddress address, String hostResolverMode) {
if (address == null || hostMatcher == null) {
return false;
}

// Only proceed with hostname checks if hostResolverMode is configured
if (hostResolverMode != null
&& (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) {
try {
List<String> valuesToCheck = new ArrayList<>(List.of(address.getHostAddress()));
final String hostName = address.getHostName();
valuesToCheck.add(hostName);
return valuesToCheck.stream().anyMatch(hostMatcher);
} catch (Exception e) {
log.warn("Failed to resolve hostname for {}: {}", address.getHostAddress(), e.getMessage());
return false;

Check warning on line 169 in src/main/java/org/opensearch/security/support/SecurityUtils.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/support/SecurityUtils.java#L167-L169

Added lines #L167 - L169 were not covered by tests
}
}
return false;
}

// This is used to match IP addresses - for e.g. 192.168.0.1 to 192.168.0.0/16
public static boolean matchesIpAndCidrPatterns(ClientBlockRegistry<InetAddress> clientBlockRegistry, InetAddress address) {
if (address == null || clientBlockRegistry == null) {
return false;
}

AuthFailureListener authFailureListener = (AuthFailureListener) clientBlockRegistry;
final String hostAddress = address.getHostAddress();

return authFailureListener.getIgnoreHosts().stream().anyMatch(pattern -> {
// Handle CIDR patterns
if (pattern.indexOf('/') != -1) {
try {
SubnetInfo subnetInfo = cidrCache.computeIfAbsent(pattern, authFailureListener::getSubnetForCidr);
return subnetInfo.isInRange(hostAddress);
} catch (IllegalArgumentException e) {
log.warn("Invalid CIDR pattern: {}", pattern);
return false;
}
}

// Handle both direct IPs and wildcard IP patterns using WildcardMatcher
return authFailureListener.getIgnoreHostsMatcher().test(hostAddress);
});
}
}
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