summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-01-28 11:42:45 +0100
committerMartin Polden <mpolden@mpolden.no>2019-01-28 11:47:49 +0100
commit412e2f151734e8903870273313b6be76c01247dc (patch)
treea934dbc9f92906d3ad259ee0c3c789230dee1e78 /node-admin
parent7ab7ef084c10547ad1e3456144d769c53849d7e0 (diff)
Apply trusted networks from ACL
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/Acl.java76
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java22
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPVersion.java19
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/AclTest.java78
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditorTest.java2
6 files changed, 143 insertions, 56 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/Acl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/Acl.java
index 16d1fd28441..050f1e39cc3 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/Acl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/Acl.java
@@ -5,7 +5,6 @@ import com.google.common.net.InetAddresses;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPVersion;
import java.net.InetAddress;
-
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -25,14 +24,21 @@ public class Acl {
private final Set<Node> trustedNodes;
private final Set<Integer> trustedPorts;
+ private final Set<String> trustedNetworks;
/**
- * @param trustedPorts Ports that hostname should trust
- * @param trustedNodes Other nodes that this hostname should trust
+ * @param trustedPorts Ports to trust
+ * @param trustedNodes Nodes to trust
+ * @param trustedNetworks Networks (in CIDR notation) to trust
*/
+ public Acl(Set<Integer> trustedPorts, Set<Node> trustedNodes, Set<String> trustedNetworks) {
+ this.trustedNodes = Set.copyOf(Objects.requireNonNull(trustedNodes, "trustedNodes must be non-null"));
+ this.trustedPorts = Set.copyOf(Objects.requireNonNull(trustedPorts, "trustedPorts must be non-null"));
+ this.trustedNetworks = Set.copyOf(Objects.requireNonNull(trustedNetworks, "trustedNetworks must be non-null"));
+ }
+
public Acl(Set<Integer> trustedPorts, Set<Node> trustedNodes) {
- this.trustedNodes = trustedNodes != null ? Collections.unmodifiableSet(trustedNodes) : Collections.emptySet();
- this.trustedPorts = trustedPorts != null ? Collections.unmodifiableSet(trustedPorts) : Collections.emptySet();
+ this(trustedPorts, trustedNodes, Collections.emptySet());
}
public List<String> toRules(IPVersion ipVersion) {
@@ -54,14 +60,21 @@ public class Acl {
// Allow trusted ports if any
String commaSeparatedPorts = trustedPorts.stream().map(i -> Integer.toString(i)).sorted().collect(Collectors.joining(","));
- if (!commaSeparatedPorts.isEmpty())
+ if (!commaSeparatedPorts.isEmpty()) {
rules.add("-A INPUT -p tcp -m multiport --dports " + commaSeparatedPorts + " -j ACCEPT");
+ }
// Allow traffic from trusted nodes
getTrustedNodes(ipVersion).stream()
- .map(node -> "-A INPUT -s " + node.inetAddressString() + ipVersion.singleHostCidr() + " -j ACCEPT")
- .sorted()
- .forEach(rules::add);
+ .map(node -> "-A INPUT -s " + node.inetAddressString() + ipVersion.singleHostCidr() + " -j ACCEPT")
+ .sorted()
+ .forEach(rules::add);
+
+ // Allow traffic from trusted networks
+ addressesOf(ipVersion, trustedNetworks).stream()
+ .map(network -> "-A INPUT -s " + network + " -j ACCEPT")
+ .sorted()
+ .forEach(rules::add);
// We reject instead of dropping to give us an easier time to figure out potential network issues
rules.add("-A INPUT -j REJECT --reject-with " + ipVersion.icmpPortUnreachable());
@@ -91,22 +104,30 @@ public class Acl {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- Acl that = (Acl) o;
- return Objects.equals(trustedPorts, that.trustedPorts) &&
- Objects.equals(trustedNodes, that.trustedNodes);
+ Acl acl = (Acl) o;
+ return trustedNodes.equals(acl.trustedNodes) &&
+ trustedPorts.equals(acl.trustedPorts) &&
+ trustedNetworks.equals(acl.trustedNetworks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(trustedNodes, trustedPorts, trustedNetworks);
}
@Override
public String toString() {
return "Acl{" +
- "trustedNodes=" + trustedNodes +
- ", trustedPorts=" + trustedPorts +
- '}';
+ "trustedNodes=" + trustedNodes +
+ ", trustedPorts=" + trustedPorts +
+ ", trustedNetworks=" + trustedNetworks +
+ '}';
}
- @Override
- public int hashCode() {
- return Objects.hash(trustedPorts, trustedNodes);
+ private static Set<String> addressesOf(IPVersion version, Set<String> addresses) {
+ return addresses.stream()
+ .filter(version::match)
+ .collect(Collectors.toUnmodifiableSet());
}
public static class Node {
@@ -158,14 +179,22 @@ public class Acl {
}
public static class Builder {
+
private final Set<Node> trustedNodes = new HashSet<>();
private final Set<Integer> trustedPorts = new HashSet<>();
+ private final Set<String> trustedNetworks = new HashSet<>();
public Builder() { }
public Builder(Acl acl) {
trustedNodes.addAll(acl.trustedNodes);
trustedPorts.addAll(acl.trustedPorts);
+ trustedNetworks.addAll(acl.trustedNetworks);
+ }
+
+ public Builder withTrustedNode(Node node) {
+ trustedNodes.add(node);
+ return this;
}
public Builder withTrustedNode(String hostname, String ipAddress) {
@@ -176,18 +205,19 @@ public class Acl {
return withTrustedNode(new Node(hostname, inetAddress));
}
- public Builder withTrustedNode(Node node) {
- trustedNodes.add(node);
+ public Builder withTrustedPorts(Integer... ports) {
+ trustedPorts.addAll(Arrays.asList(ports));
return this;
}
- public Builder withTrustedPorts(Integer... ports) {
- trustedPorts.addAll(Arrays.asList(ports));
+ public Builder withTrustedNetworks(Set<String> networks) {
+ trustedNetworks.addAll(networks);
return this;
}
public Acl build() {
- return new Acl(trustedPorts, trustedNodes);
+ return new Acl(trustedPorts, trustedNodes, trustedNetworks);
}
}
+
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
index 9c25687fae8..46608edf120 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java
@@ -76,8 +76,8 @@ public class RealNodeRepository implements NodeRepository {
@Override
public Map<String, Acl> getAcls(String hostName) {
try {
- final String path = String.format("/nodes/v2/acl/%s?children=true", hostName);
- final GetAclResponse response = configServerApi.get(path, GetAclResponse.class);
+ String path = String.format("/nodes/v2/acl/%s?children=true", hostName);
+ GetAclResponse response = configServerApi.get(path, GetAclResponse.class);
// Group ports by container hostname that trusts them
Map<String, Set<Integer>> trustedPorts = response.trustedPorts.stream()
@@ -93,14 +93,20 @@ public class RealNodeRepository implements NodeRepository {
node -> new Acl.Node(node.hostname, node.ipAddress),
Collectors.toSet())));
+ // Group trusted networks by container hostname that trusts them
+ Map<String, Set<String>> trustedNetworks = response.trustedNetworks.stream()
+ .collect(Collectors.groupingBy(GetAclResponse.Network::getTrustedBy,
+ Collectors.mapping(node -> node.network, Collectors.toSet())));
+
// For each hostname create an ACL
- return Stream.of(trustedNodes.keySet(), trustedPorts.keySet())
- .flatMap(Set::stream)
- .distinct()
- .collect(Collectors.toMap(
- Function.identity(),
- hostname -> new Acl(trustedPorts.get(hostname), trustedNodes.get(hostname))));
+ return Stream.of(trustedNodes.keySet(), trustedPorts.keySet(), trustedNetworks.keySet())
+ .flatMap(Set::stream)
+ .distinct()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ hostname -> new Acl(trustedPorts.get(hostname), trustedNodes.get(hostname),
+ trustedNetworks.get(hostname))));
} catch (HttpException.NotFoundException e) {
NODE_ADMIN_LOGGER.warning("Failed to fetch ACLs for " + hostName + " No ACL will be applied");
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPVersion.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPVersion.java
index 328043849fb..33498575e8a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPVersion.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPVersion.java
@@ -1,8 +1,12 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.network;
+import com.google.common.net.InetAddresses;
+
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Strong type IPv4 and IPv6 with common executables for ip related commands.
@@ -14,6 +18,8 @@ public enum IPVersion {
IPv6(6, "ip6tables", "ip -6", "ipv6-icmp", "/128", "icmp6-port-unreachable", "ip6tables-restore"),
IPv4(4, "iptables", "ip", "icmp", "/32", "icmp-port-unreachable", "iptables-restore");
+ private static Pattern cidrNotationPattern = Pattern.compile("/\\d+$");
+
IPVersion(int version, String iptablesCmd, String ipCmd,
String icmpProtocol, String singleHostCidr, String icmpPortUnreachable,
String iptablesRestore) {
@@ -59,7 +65,20 @@ public enum IPVersion {
return this == IPVersion.get(address);
}
+ public boolean match(String address) {
+ return this == IPVersion.get(address);
+ }
+
+ public static IPVersion get(String address) {
+ Matcher matcher = cidrNotationPattern.matcher(address);
+ if (matcher.find()) {
+ address = matcher.replaceFirst("");
+ }
+ return get(InetAddresses.forString(address));
+ }
+
public static IPVersion get(InetAddress address) {
return address instanceof Inet4Address ? IPv4 : IPv6;
}
+
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/AclTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/AclTest.java
index c68c9cf5f69..3010586e708 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/AclTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/AclTest.java
@@ -2,29 +2,35 @@
package com.yahoo.vespa.hosted.node.admin.configserver.noderepository;
import com.yahoo.vespa.hosted.node.admin.task.util.network.IPVersion;
-import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ * @author smorgrav
+ */
public class AclTest {
- private final Acl aclCommon = new Acl(
- createPortSet(1234, 453),
- createTrustedNodes("192.1.2.2", "fb00::1", "fe80::2", "fe80::3"));
+ private static final Acl aclCommon = new Acl(
+ Set.of(1234, 453),
+ testNodes("192.1.2.2", "fb00::1", "fe80::2", "fe80::3"),
+ Collections.emptySet());
- private final Acl aclNoPorts = new Acl(
+ private static final Acl aclWithoutPorts = new Acl(
Collections.emptySet(),
- createTrustedNodes("192.1.2.2", "fb00::1", "fe80::2"));
+ testNodes("192.1.2.2", "fb00::1", "fe80::2"),
+ Collections.emptySet());
@Test
public void no_trusted_ports() {
- String listRulesIpv4 = String.join("\n", aclNoPorts.toRules(IPVersion.IPv4));
- Assert.assertEquals(
+ String listRulesIpv4 = String.join("\n", aclWithoutPorts.toRules(IPVersion.IPv4));
+ assertEquals(
"-P INPUT ACCEPT\n" +
"-P FORWARD ACCEPT\n" +
"-P OUTPUT ACCEPT\n" +
@@ -37,9 +43,9 @@ public class AclTest {
}
@Test
- public void ipv4_list_rules() {
+ public void ipv4_rules() {
String listRulesIpv4 = String.join("\n", aclCommon.toRules(IPVersion.IPv4));
- Assert.assertEquals(
+ assertEquals(
"-P INPUT ACCEPT\n" +
"-P FORWARD ACCEPT\n" +
"-P OUTPUT ACCEPT\n" +
@@ -53,9 +59,9 @@ public class AclTest {
}
@Test
- public void ipv6_list_rules() {
+ public void ipv6_rules() {
String listRulesIpv6 = String.join("\n", aclCommon.toRules(IPVersion.IPv6));
- Assert.assertEquals(
+ assertEquals(
"-P INPUT ACCEPT\n" +
"-P FORWARD ACCEPT\n" +
"-P OUTPUT ACCEPT\n" +
@@ -70,23 +76,49 @@ public class AclTest {
}
@Test
- public void ipv6_rules_stable() {
+ public void ipv6_rules_stable_order() {
Acl aclCommonDifferentOrder = new Acl(
- createPortSet(453, 1234),
- createTrustedNodes("fe80::2", "192.1.2.2", "fb00::1", "fe80::3"));
+ Set.of(453, 1234),
+ testNodes("fe80::2", "192.1.2.2", "fb00::1", "fe80::3"),
+ Collections.emptySet());
for (IPVersion ipVersion: IPVersion.values()) {
- Assert.assertEquals(aclCommon.toRules(ipVersion), aclCommonDifferentOrder.toRules(ipVersion));
+ assertEquals(aclCommon.toRules(ipVersion), aclCommonDifferentOrder.toRules(ipVersion));
}
}
- private Set<Integer> createPortSet(Integer... ports) {
- return Stream.of(ports).collect(Collectors.toSet());
+ @Test
+ public void trusted_networks() {
+ Acl acl = new Acl(Set.of(4080), testNodes("127.0.0.1"), Set.of("10.0.0.0/24", "2001:db8::/32"));
+
+ assertEquals("-P INPUT ACCEPT\n" +
+ "-P FORWARD ACCEPT\n" +
+ "-P OUTPUT ACCEPT\n" +
+ "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n" +
+ "-A INPUT -i lo -j ACCEPT\n" +
+ "-A INPUT -p icmp -j ACCEPT\n" +
+ "-A INPUT -p tcp -m multiport --dports 4080 -j ACCEPT\n" +
+ "-A INPUT -s 127.0.0.1/32 -j ACCEPT\n" +
+ "-A INPUT -s 10.0.0.0/24 -j ACCEPT\n" +
+ "-A INPUT -j REJECT --reject-with icmp-port-unreachable",
+ String.join("\n", acl.toRules(IPVersion.IPv4)));
+
+ assertEquals("-P INPUT ACCEPT\n" +
+ "-P FORWARD ACCEPT\n" +
+ "-P OUTPUT ACCEPT\n" +
+ "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT\n" +
+ "-A INPUT -i lo -j ACCEPT\n" +
+ "-A INPUT -p ipv6-icmp -j ACCEPT\n" +
+ "-A INPUT -p tcp -m multiport --dports 4080 -j ACCEPT\n" +
+ "-A INPUT -s 2001:db8::/32 -j ACCEPT\n" +
+ "-A INPUT -j REJECT --reject-with icmp6-port-unreachable",
+ String.join("\n", acl.toRules(IPVersion.IPv6)));
}
- private Set<Acl.Node> createTrustedNodes(String... addresses) {
- return Arrays.stream(addresses)
- .map(ipAddress -> new Acl.Node("hostname", ipAddress))
- .collect(Collectors.toSet());
+ private static Set<Acl.Node> testNodes(String... address) {
+ return Arrays.stream(address)
+ .map(a -> new Acl.Node("hostname", a))
+ .collect(Collectors.toUnmodifiableSet());
}
+
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
index 07d3fab9534..65e8ebf0743 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/AclMaintainerTest.java
@@ -227,7 +227,7 @@ public class AclMaintainerTest {
.forEach(aclBuilder::withTrustedPorts);
Arrays.stream(addresses)
- .forEach(address -> aclBuilder.withTrustedNode("hostname", address));
+ .forEach(address -> aclBuilder.withTrustedNode(new Acl.Node("hostname", address)));
Map<String, Acl> map = new HashMap<>();
map.put(containerHostname, aclBuilder.build());
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditorTest.java
index 1b39014a9c6..abcba44bb7a 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/acl/IPTablesEditorTest.java
@@ -22,7 +22,7 @@ public class IPTablesEditorTest {
@Test
public void filter_set_wanted_rules() {
- Acl acl = new Acl.Builder().withTrustedPorts(22).withTrustedNode("hostname", "3001::1").build();
+ Acl acl = new Acl.Builder().withTrustedPorts(22).withTrustedNode(new Acl.Node("hostname", "3001::1")).build();
FilterTableLineEditor filterLineEditor = FilterTableLineEditor.from(acl, IPVersion.IPv6);
String currentFilterTable = "-P INPUT ACCEPT\n" +