diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-01-28 11:42:45 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-01-28 11:47:49 +0100 |
commit | 412e2f151734e8903870273313b6be76c01247dc (patch) | |
tree | a934dbc9f92906d3ad259ee0c3c789230dee1e78 /node-admin | |
parent | 7ab7ef084c10547ad1e3456144d769c53849d7e0 (diff) |
Apply trusted networks from ACL
Diffstat (limited to 'node-admin')
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" + |