aboutsummaryrefslogtreecommitdiffstats
path: root/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node
diff options
context:
space:
mode:
authorHÃ¥kon Hallingstad <hakon.hallingstad@gmail.com>2023-07-24 10:57:11 +0200
committerGitHub <noreply@github.com>2023-07-24 10:57:11 +0200
commitbfe16ea26deee34e96b632e9e54344e0de79c3bf (patch)
tree3cd5850b2cbc2f6440c981741f10e59d4f7b30af /node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node
parentc5e9d550ea8d680b569db01648097c3e2bef3d14 (diff)
parent33d817902cec65e98d4c06bee068e589f746826c (diff)
Merge pull request #27632 from vespa-engine/hakonhall/exclude-private-ip-addresses-in-other-cloud-accounts-in-acls
Exclude private IP addresses in other cloud accounts in ACLs
Diffstat (limited to 'node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node')
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java7
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java83
2 files changed, 58 insertions, 32 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
index 1ff6d2b300d..c34b357b758 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/IP.java
@@ -119,7 +119,7 @@ public record IP() {
for (var other : sortedNodes) {
if (node.equals(other)) continue;
if (canAssignIpOf(other, node)) continue;
- Predicate<String> sharedIpSpace = other.cloudAccount().equals(node.cloudAccount()) ? __ -> true : IP::isPublic;
+ Predicate<String> sharedIpSpace = ip -> inSharedIpSpace(ip, other.cloudAccount(), node.cloudAccount());
var addresses = new HashSet<>(node.ipConfig().primary());
var otherAddresses = new HashSet<>(other.ipConfig().primary());
@@ -473,4 +473,9 @@ public record IP() {
return ! address.isLoopbackAddress() && ! address.isLinkLocalAddress() && ! address.isSiteLocalAddress();
}
+ /** Returns true if the IP address is in the IP space of both sourceCloudAccount and targetCloudAccount. */
+ public static boolean inSharedIpSpace(String ip, CloudAccount sourceCloudAccount, CloudAccount targetCloudAccount) {
+ return sourceCloudAccount.equals(targetCloudAccount) || isPublic(ip);
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java
index 843ba240ce9..14c4a63a500 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/NodeAcl.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.provision.node;
import com.google.common.collect.ImmutableSet;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.hosted.provision.Node;
@@ -11,12 +12,16 @@ import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancers;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
@@ -41,7 +46,7 @@ public record NodeAcl(Node node,
this.trustedUdpPorts = ImmutableSet.copyOf(Objects.requireNonNull(trustedUdpPorts, "trustedUdpPorts must be non-null"));
}
- public static NodeAcl from(Node node, NodeList allNodes, LoadBalancers loadBalancers, Zone zone) {
+ public static NodeAcl from(Node node, NodeList allNodes, LoadBalancers loadBalancers, Zone zone, boolean simplerAcl) {
Set<TrustedNode> trustedNodes = new TreeSet<>(Comparator.comparing(TrustedNode::hostname));
Set<Integer> trustedPorts = new LinkedHashSet<>();
Set<Integer> trustedUdpPorts = new LinkedHashSet<>();
@@ -53,12 +58,13 @@ public record NodeAcl(Node node,
// SSH opened (which is safe for 2 reasons: SSH daemon is not run inside containers, and NPT networks
// will (should) not forward port 22 traffic to container).
// - parent host (for health checks and metrics)
- // - nodes in same application
+ // - nodes in same application (Slobrok for tenant nodes, file distribution and ZK for config servers, etc),
+ // and parents if necessary due to NAT.
// - load balancers allocated to application
trustedPorts.add(22);
- allNodes.parentOf(node).map(TrustedNode::of).ifPresent(trustedNodes::add);
+ allNodes.parentOf(node).map(parent -> TrustedNode.of(parent, node.cloudAccount(), simplerAcl)).ifPresent(trustedNodes::add);
node.allocation().ifPresent(allocation -> {
- trustedNodes.addAll(TrustedNode.of(allNodes.owner(allocation.owner())));
+ trustedNodes.addAll(trustedNodesForChildrenMatching(node, allNodes, n -> n.allocation().map(Allocation::owner).equals(Optional.of(allocation.owner())), Set.of(), simplerAcl));
loadBalancers.list(allocation.owner()).asList()
.stream()
.map(LoadBalancer::instance)
@@ -72,19 +78,8 @@ public record NodeAcl(Node node,
// Tenant nodes in other states than ready, trust:
// - config servers
// - proxy nodes
- // - parents of the nodes in the same application: If some nodes are on a different IP version
- // or only a subset of them are dual-stacked, the communication between the nodes may be NAT-ed
- // via parent's IP address
- trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.config)));
- trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.proxy)));
- node.allocation().ifPresent(allocation -> trustedNodes.addAll(TrustedNode.of(allNodes.parentsOf(allNodes.owner(allocation.owner())))));
- if (node.state() == Node.State.ready) {
- // Tenant nodes in state ready, trust:
- // - All tenant nodes in zone. When a ready node is allocated to an application there's a brief
- // window where current ACLs have not yet been applied on the node. To avoid service disruption
- // during this window, ready tenant nodes trust all other tenant nodes
- trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.tenant)));
- }
+ trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.config), node.cloudAccount(), simplerAcl));
+ trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.proxy), node.cloudAccount(), simplerAcl));
}
case config -> {
// Config servers trust:
@@ -92,9 +87,7 @@ public record NodeAcl(Node node,
// - port 19070 (RPC) from all proxy nodes (and their hosts, in case traffic is NAT-ed via parent)
// - port 4443 from the world
// - udp port 51820 from the world
- trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.host, NodeType.tenant,
- NodeType.proxyhost, NodeType.proxy),
- RPC_PORTS));
+ trustedNodes.addAll(trustedNodesForChildrenMatching(node, allNodes, n -> EnumSet.of(NodeType.tenant, NodeType.proxy).contains(n.type()), RPC_PORTS, simplerAcl));
trustedPorts.add(4443);
if (zone.system().isPublic() && zone.cloud().allowEnclave()) {
trustedUdpPorts.add(WIREGUARD_PORT);
@@ -104,7 +97,7 @@ public record NodeAcl(Node node,
// Proxy nodes trust:
// - config servers
// - all connections from the world on 443 (production traffic) and 4443 (health checks)
- trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.config)));
+ trustedNodes.addAll(TrustedNode.of(allNodes.nodeType(NodeType.config), node.cloudAccount(), simplerAcl));
trustedPorts.add(443);
trustedPorts.add(4443);
}
@@ -121,26 +114,54 @@ public record NodeAcl(Node node,
return new NodeAcl(node, trustedNodes, trustedNetworks, trustedPorts, trustedUdpPorts);
}
+ /** Returns the set of children matching the selector, and their parent host if traffic from child may be NATed */
+ private static Set<TrustedNode> trustedNodesForChildrenMatching(Node node, NodeList allNodes, Predicate<Node> childNodeSelector,
+ Set<Integer> ports, boolean simplerAcl) {
+ if (node.type().isHost())
+ throw new IllegalArgumentException("Host nodes cannot have NAT parents");
+
+ boolean hasIp4 = node.ipConfig().primary().stream().anyMatch(IP::isV4);
+ boolean hasIp6 = node.ipConfig().primary().stream().anyMatch(IP::isV6);
+ return allNodes.stream()
+ .filter(n -> !n.type().isHost())
+ .filter(childNodeSelector)
+ .mapMulti((Node otherNode, Consumer<TrustedNode> consumer) -> {
+ consumer.accept(TrustedNode.of(otherNode, ports, node.cloudAccount(), simplerAcl));
+
+ // And parent host if traffic from otherNode may be NATed
+ if (hasIp4 && otherNode.ipConfig().primary().stream().noneMatch(IP::isV4) ||
+ hasIp6 && otherNode.ipConfig().primary().stream().noneMatch(IP::isV6)) {
+ consumer.accept(TrustedNode.of(allNodes.parentOf(otherNode).orElseThrow(), ports, node.cloudAccount(), simplerAcl));
+ }
+ })
+ .collect(Collectors.toSet());
+ }
+
public record TrustedNode(String hostname, NodeType type, Set<String> ipAddresses, Set<Integer> ports) {
- /** Trust given ports from node */
- public static TrustedNode of(Node node, Set<Integer> ports) {
- return new TrustedNode(node.hostname(), node.type(), node.ipConfig().primary(), ports);
+ /** Trust given ports from node, and primary IP addresses shared with given cloud account */
+ public static TrustedNode of(Node node, Set<Integer> ports, CloudAccount sourceCloudAccount, boolean simplerAcl) {
+ Set<String> ipAddresses = node.ipConfig()
+ .primary()
+ .stream()
+ .filter(ip -> !simplerAcl || IP.inSharedIpSpace(ip, sourceCloudAccount, node.cloudAccount()))
+ .collect(Collectors.toSet());
+ return new TrustedNode(node.hostname(), node.type(), ipAddresses, ports);
}
- /** Trust all ports from given node */
- public static TrustedNode of(Node node) {
- return of(node, Set.of());
+ /** The node in the given sourceCloudAccount should trust all ports from given node */
+ public static TrustedNode of(Node node, CloudAccount sourceCloudAccount, boolean simplerAcl) {
+ return of(node, Set.of(), sourceCloudAccount, simplerAcl);
}
- public static List<TrustedNode> of(Iterable<Node> nodes, Set<Integer> ports) {
+ public static List<TrustedNode> of(Iterable<Node> nodes, Set<Integer> ports, CloudAccount sourceCloudAccount, boolean simplerAcl) {
return StreamSupport.stream(nodes.spliterator(), false)
- .map(node -> TrustedNode.of(node, ports))
+ .map(node -> TrustedNode.of(node, ports, sourceCloudAccount, simplerAcl))
.toList();
}
- public static List<TrustedNode> of(Iterable<Node> nodes) {
- return of(nodes, Set.of());
+ public static List<TrustedNode> of(Iterable<Node> nodes, CloudAccount sourceCloudAccount, boolean simplerAcl) {
+ return of(nodes, Set.of(), sourceCloudAccount, simplerAcl);
}
}