Skip to content

Commit

Permalink
Implement SubnetValidator for blocked subnet matching
Browse files Browse the repository at this point in the history
Signed-off-by: David Schwilk <david.schwilk@bosch.io>
  • Loading branch information
DerSchwilk committed Nov 15, 2022
1 parent 03f8f0a commit 41a8794
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 40 deletions.
12 changes: 0 additions & 12 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
<sshd.version>2.9.0</sshd.version>
<eddsa.version>0.3.0</eddsa.version>
<lz4-java.version>1.8.0</lz4-java.version>
<spring-security-web.version>5.7.3</spring-security-web.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>

<!-- Keep these version consistent with akka-persistence-mongo.version's build.sbt -->
<mongo-java-driver.version>4.3.4</mongo-java-driver.version>
Expand Down Expand Up @@ -359,16 +357,6 @@
<artifactId>lz4-java</artifactId>
<version>${lz4-java.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security-web.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
</dependency>

<dependency>
<groupId>io.netty</groupId>
Expand Down
8 changes: 0 additions & 8 deletions connectivity/service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,6 @@ jmh-generator-annprocess). jmh-generator-annprocess overwrites the whole META-IN
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>

<!-- ### Testing ### -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.stream.Stream;

import org.eclipse.ditto.connectivity.service.config.ConnectivityConfig;
import org.springframework.security.web.util.matcher.IpAddressMatcher;

import akka.event.LoggingAdapter;

Expand All @@ -35,7 +34,7 @@ final class DefaultHostValidator implements HostValidator {

private final Collection<String> allowedHostnames;
private final Collection<InetAddress> blockedAddresses;
private final Collection<IpAddressMatcher> blockedSubnets;
private final Collection<String> blockedSubnets;
private final AddressResolver resolver;
private final Pattern hostRegexPattern;

Expand Down Expand Up @@ -63,7 +62,7 @@ final class DefaultHostValidator implements HostValidator {
final Collection<String> blockedHostnames = connectivityConfig.getConnectionConfig().getBlockedHostnames();
this.blockedAddresses = calculateBlockedAddresses(blockedHostnames, loggingAdapter);
final Collection<String> blockedSubnetsList = connectivityConfig.getConnectionConfig().getBlockedSubnets();
this.blockedSubnets = calculateBlockedSubnets(blockedSubnetsList, loggingAdapter);
this.blockedSubnets = filterEmptyBlockedSubnets(blockedSubnetsList);
final var regex = connectivityConfig.getConnectionConfig().getBlockedHostRegex();
this.hostRegexPattern = Pattern.compile(regex);
}
Expand Down Expand Up @@ -117,8 +116,8 @@ private HostValidationResult validateInetAddressesAndSubnets(final String host)
// host is contained in the block-list --> block
return HostValidationResult.blocked(host);
}
for (final IpAddressMatcher subnet : blockedSubnets) {
if (subnet.matches(requestAddress.getHostAddress())) {
for (final String subnet : blockedSubnets) {
if (SubnetValidator.matches(subnet, requestAddress.getHostAddress())) {
// ip is contained in the blocked-subnet --> block
return HostValidationResult.blocked(host, "the hostname resides in a blocked subnet.");
}
Expand Down Expand Up @@ -157,26 +156,15 @@ private Collection<InetAddress> calculateBlockedAddresses(final Collection<Strin
}

/**
* Calculate blocked subnets from cidr range strings that should not be accessed.
* Filters out empty blocked subnets.
*
* @param blockedSubnets blocked subnets.
* @param log the logger.
* @return info of blocked subnets.
* @return the blocked subnets.
*/
private Collection<IpAddressMatcher> calculateBlockedSubnets(final Collection<String> blockedSubnets,
final LoggingAdapter log) {
private Collection<String> filterEmptyBlockedSubnets(final Collection<String> blockedSubnets) {

return blockedSubnets.stream()
.filter(blockedSubnet -> !blockedSubnet.isEmpty())
.flatMap(blockedSubnet -> {
try {
return Stream.of(new IpAddressMatcher(blockedSubnet));
} catch (final IllegalArgumentException e) {
log.error(e, "Could not create subnet info during building blocked subnets set: <{}>",
blockedSubnet);
return Stream.empty();
}
})
.collect(Collectors.toSet());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.connectivity.service.messaging.validation;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;

final class SubnetValidator {

private SubnetValidator() {
throw new AssertionError();
}

/**
* Validates that the given input address matches the matcher address. This method respects either IPv4 or IPv6
* addresses including subnet notation.
*
* @param matcher the address to be matched.
* @param input the input which is validates.
* @return if the input address matches the matcher address.
*/
static boolean matches(final String matcher, final String input) {
final InetAddress inputAddress = parseAddress(input);
final int subnetMask = getNetworkMask(matcher);
final InetAddress matcherAddress = getMatcherAddress(matcher, subnetMask);
if (!matchesIpFamily(matcherAddress, inputAddress)) {
return false;
} else if (subnetMask < 0) {
return inputAddress.equals(matcherAddress);
} else {
return matchesWithSubnetMask(matcherAddress.getAddress(), inputAddress.getAddress(), subnetMask);
}
}

private static boolean matchesIpFamily(final InetAddress matcher, final InetAddress input) {
if (matcher instanceof Inet4Address) {
return input instanceof Inet4Address;
} else if (matcher instanceof Inet6Address) {
return input instanceof Inet6Address;
} else {
throw new IllegalArgumentException(String.format("IP address %s is in no known IP family.", matcher));
}
}

private static InetAddress parseAddress(final String address) {
try {
return InetAddress.getByName(address);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(String.format("Address %s has unknown host.", address), e);
}
}

private static int getNetworkMask(final String address) {
final int result;
if (hasSubnetMask(address)) {
final String[] addressAndMask = address.split("/");
result = Integer.parseInt(addressAndMask[1]);
} else {
result = -1;
}
return result;
}

@SuppressWarnings("java:S2692")
private static boolean hasSubnetMask(final String address) {
return address.indexOf("/") > 0;
}

private static InetAddress getMatcherAddress(final String address, final int subnetMask) {
final String intermediateIpAddress;
if (hasSubnetMask(address)) {
final String[] addressAndMask = address.split("/");
intermediateIpAddress = addressAndMask[0];
} else {
intermediateIpAddress = address;
}
final InetAddress parsedAddress = parseAddress(intermediateIpAddress);
if (!(parsedAddress.getAddress().length * 8 >= subnetMask)) {
throw new IllegalArgumentException(String.format("IP address %s " +
"does not match subnet mask with length: %d", address, subnetMask));
} else {
return parsedAddress;
}
}

private static boolean matchesWithSubnetMask(final byte[] matcher, final byte[] input, final int subnetMask) {
final int nMaskFullBytes = subnetMask / 8;
final byte finalByte = (byte) ('\uff00' >> (subnetMask & 7));

for (int i = 0; i < nMaskFullBytes; ++i) {
if (input[i] != matcher[i]) {
return false;
}
}
if (finalByte != 0) {
return (input[nMaskFullBytes] & finalByte) == (matcher[nMaskFullBytes] & finalByte);
} else {
return true;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void setUp() {
loggingAdapter = mock(LoggingAdapter.class);

when(connectionConfig.getBlockedHostnames()).thenReturn(List.of("localhost"));
when(connectionConfig.getBlockedSubnets()).thenReturn(List.of("11.1.0.0/16","169.254.0.0/16"));
when(connectionConfig.getBlockedSubnets()).thenReturn(List.of("11.1.0.0/16", "169.254.0.0/16"));
when(connectionConfig.getBlockedHostRegex()).thenReturn("");
}

Expand Down

0 comments on commit 41a8794

Please sign in to comment.