Skip to content

Commit e8ac826

Browse files
committed
CKS: open the cluster public IP on VPC tiers when the VPC has the Firewall service
Since #12706 a VPC offering can provide the Firewall service for public IPs. The VPC virtual router then drops ingress traffic to a public IP that has no firewall rule. CKS only created network ACL items, port forwarding and the API load balancer rule on VPC tiers, so the management server could not reach the nodes (SSH 2222+, API 6443), the control node could not reach its own API through the public IP, and cluster creation failed after the start timeout. When the VPC virtual router provides the Firewall service of the cluster's VPC (the check that sets vpc_firewall_enabled on the VR), CKS now creates the same ingress firewall rules as on isolated networks on create (including etcd nodes), scale and node add/remove. In a VPC the firewall rules are created after the load balancing and port forwarding rules: applying firewall rules on a VPC public IP does not associate the IP with the VR, and if they come first the IP is never associated. Firewall rules on VPC public IPs have no network_id, so per-node rules are looked up by public IP and ports. On delete the rules go with the release of the cluster public IP. Isolated networks, VPCs without the Firewall service and the NSX/Netris paths are unchanged.
1 parent a7be64a commit e8ac826

8 files changed

Lines changed: 270 additions & 24 deletions

File tree

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.cloud.kubernetes.cluster.utils.KubernetesClusterUtil;
5252
import com.cloud.network.firewall.FirewallService;
5353
import com.cloud.network.rules.FirewallRule;
54+
import com.cloud.network.rules.FirewallRuleVO;
5455
import com.cloud.network.rules.PortForwardingRuleVO;
5556
import com.cloud.network.rules.RulesService;
5657
import com.cloud.network.rules.dao.PortForwardingRulesDao;
@@ -59,6 +60,7 @@
5960
import com.cloud.utils.component.ComponentContext;
6061
import com.cloud.utils.db.TransactionCallbackWithException;
6162
import com.cloud.utils.net.Ip;
63+
import com.cloud.utils.net.NetUtils;
6264
import com.cloud.vm.Nic;
6365
import com.cloud.vm.NicVO;
6466
import com.cloud.vm.VirtualMachine;
@@ -107,10 +109,12 @@
107109
import com.cloud.network.Network;
108110
import com.cloud.network.NetworkModel;
109111
import com.cloud.network.NetworkService;
112+
import com.cloud.network.dao.FirewallRulesDao;
110113
import com.cloud.network.dao.IPAddressDao;
111114
import com.cloud.network.dao.IPAddressVO;
112115
import com.cloud.network.dao.NetworkDao;
113116
import com.cloud.network.vpc.VpcService;
117+
import com.cloud.network.vpc.dao.VpcServiceMapDao;
114118
import com.cloud.projects.ProjectService;
115119
import com.cloud.service.dao.ServiceOfferingDao;
116120
import com.cloud.storage.Storage;
@@ -214,12 +218,16 @@ public class KubernetesClusterActionWorker {
214218
@Inject
215219
public VpcService vpcService;
216220
@Inject
221+
protected VpcServiceMapDao vpcServiceMapDao;
222+
@Inject
217223
public PortForwardingRulesDao portForwardingRulesDao;
218224
@Inject
219225
protected RulesService rulesService;
220226
@Inject
221227
protected FirewallService firewallService;
222228
@Inject
229+
protected FirewallRulesDao firewallRulesDao;
230+
@Inject
223231
private NicDao nicDao;
224232
@Inject
225233
protected AffinityGroupDao affinityGroupDao;
@@ -1113,6 +1121,49 @@ protected String updateKubeConfigWithRegistryDetails(String k8sConfig) {
11131121
return k8sConfig;
11141122
}
11151123

1124+
/**
1125+
* When the VPC virtual router provides the Firewall service of a VPC, it drops ingress traffic to
1126+
* public IPs that has no firewall rule (in addition to the network ACL of each tier). The ports
1127+
* used by CKS must then be opened on the cluster public IP, as for isolated networks. This is the
1128+
* same check that sets vpc_firewall_enabled for the VPC virtual router.
1129+
*
1130+
* @param network the network of the Kubernetes cluster
1131+
* @return true if the network is a VPC tier and the VPC virtual router provides the Firewall service
1132+
*/
1133+
protected boolean isVpcFirewallEnabled(Network network) {
1134+
if (network == null || network.getVpcId() == null) {
1135+
return false;
1136+
}
1137+
return vpcServiceMapDao.canProviderSupportServiceInVpc(network.getVpcId(), Network.Service.Firewall, Network.Provider.VPCVirtualRouter);
1138+
}
1139+
1140+
/**
1141+
* @param network the network of the Kubernetes cluster
1142+
* @return true if ingress firewall rules are needed on the cluster public IP: always for isolated
1143+
* networks, and for VPC tiers when the VPC uses the Firewall service
1144+
*/
1145+
protected boolean isPublicIpFirewallRequired(Network network) {
1146+
return network.getVpcId() == null || isVpcFirewallEnabled(network);
1147+
}
1148+
1149+
/**
1150+
* Finds the ingress firewall rule for the given public port range of the cluster. Firewall rules on a
1151+
* public IP of a VPC are stored with the VPC and without a network, so they are looked up by the IP.
1152+
*
1153+
* @return the rule, or null if none exists
1154+
*/
1155+
protected FirewallRuleVO findIngressFirewallRuleForPorts(Network network, Long publicIpId, int startPort, int endPort) {
1156+
if (network.getVpcId() == null) {
1157+
return firewallRulesDao.findByNetworkIdAndPorts(network.getId(), startPort, endPort);
1158+
}
1159+
if (publicIpId == null) {
1160+
return null;
1161+
}
1162+
List<FirewallRuleVO> rules = firewallRulesDao.listByIpPurposePortsProtocolAndNotRevoked(publicIpId, startPort, endPort,
1163+
NetUtils.TCP_PROTO, FirewallRule.Purpose.Firewall);
1164+
return CollectionUtils.isEmpty(rules) ? null : rules.get(0);
1165+
}
1166+
11161167
public Map<Long, Integer> addFirewallRulesForNodes(IpAddress publicIp, int size) throws ManagementServerException {
11171168
Map<Long, Integer> vmIdPortMap = new HashMap<>();
11181169
CallContext.register(CallContext.current(), null);

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterAddWorker.java‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import com.cloud.kubernetes.cluster.utils.KubernetesClusterUtil;
3232
import com.cloud.network.IpAddress;
3333
import com.cloud.network.Network;
34-
import com.cloud.network.dao.FirewallRulesDao;
3534
import com.cloud.network.rules.FirewallRuleVO;
3635
import com.cloud.network.rules.PortForwardingRuleVO;
3736
import com.cloud.service.ServiceOfferingVO;
@@ -49,7 +48,6 @@
4948
import org.apache.commons.codec.binary.Base64;
5049
import org.apache.logging.log4j.Level;
5150

52-
import javax.inject.Inject;
5351
import java.io.File;
5452
import java.io.IOException;
5553
import java.lang.reflect.Field;
@@ -60,8 +58,6 @@
6058

6159
public class KubernetesClusterAddWorker extends KubernetesClusterActionWorker {
6260

63-
@Inject
64-
private FirewallRulesDao firewallRulesDao;
6561
private long addNodeTimeoutTime;
6662

6763
List<Long> finalNodeIds = new ArrayList<>();
@@ -229,9 +225,13 @@ private Pair<Boolean, Integer> validateAndSetupNode(Network network, IpAddress p
229225
provisionFirewallRules(publicIp, owner, sshStartPort, sshStartPort);
230226
}
231227
provisionPublicIpPortForwardingRule(publicIp, network, account, nodeId, sshStartPort, DEFAULT_SSH_PORT);
228+
if (isVpcFirewallEnabled(network)) {
229+
// In a VPC the firewall rule follows the port forwarding rule, see setupKubernetesClusterVpcTierRules
230+
provisionFirewallRules(publicIp, owner, sshStartPort, sshStartPort);
231+
}
232232
boolean isCompatible = validateNodeCompatibility(publicIp, nodeId, sshStartPort);
233233
if (!isCompatible) {
234-
revertNetworkRules(network, nodeId, sshStartPort);
234+
revertNetworkRules(network, publicIp, nodeId, sshStartPort);
235235
return new Pair<>(false, nodeIndex);
236236
}
237237

@@ -249,13 +249,13 @@ private Pair<Boolean, Integer> validateAndSetupNode(Network network, IpAddress p
249249
InsufficientCapacityException | IllegalAccessException e) {
250250
logger.error(String.format("Failed to activate API port forwarding rules for the Kubernetes cluster : %s", kubernetesCluster.getName()));
251251
// remove added Firewall and PF rules
252-
revertNetworkRules(network, nodeId, sshStartPort);
252+
revertNetworkRules(network, publicIp, nodeId, sshStartPort);
253253
return new Pair<>( false, nodeIndex);
254254
} catch (Exception e) {
255255
String errMsg = String.format("Unexpected exception while trying to add the external node %s to the Kubernetes cluster %s: %s",
256256
nodeId, kubernetesCluster.getName(), e.getMessage());
257257
logger.error(errMsg, e);
258-
revertNetworkRules(network, nodeId, sshStartPort);
258+
revertNetworkRules(network, publicIp, nodeId, sshStartPort);
259259
throw new CloudRuntimeException(e);
260260
}
261261
return new Pair<>(true, ++nodeIndex);
@@ -310,12 +310,14 @@ private void cleanupCloudInitSemFolder(UserVm userVm, IpAddress publicIp, File p
310310
}
311311
}
312312

313-
private void revertNetworkRules(Network network, long vmId, int port) {
313+
private void revertNetworkRules(Network network, IpAddress publicIp, long vmId, int port) {
314314
logger.debug(String.format("Reverting network rules for VM ID %s on network %s", vmId, network.getName()));
315-
FirewallRuleVO ruleVO = firewallRulesDao.findByNetworkIdAndPorts(network.getId(), port, port);
316-
if (Objects.isNull(network.getVpcId())) {
317-
logger.debug(String.format("Removing firewall rule %s", ruleVO.getId()));
318-
firewallService.revokeIngressFirewallRule(ruleVO.getId(), true);
315+
if (isPublicIpFirewallRequired(network)) {
316+
FirewallRuleVO ruleVO = findIngressFirewallRuleForPorts(network, publicIp.getId(), port, port);
317+
if (ruleVO != null) {
318+
logger.debug(String.format("Removing firewall rule %s", ruleVO.getId()));
319+
firewallService.revokeIngressFirewallRule(ruleVO.getId(), true);
320+
}
319321
}
320322
List<PortForwardingRuleVO> pfRules = portForwardingRulesDao.listByVm(vmId);
321323
for (PortForwardingRuleVO pfRule : pfRules) {

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterRemoveWorker.java‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import com.cloud.kubernetes.cluster.KubernetesClusterVO;
2727
import com.cloud.network.IpAddress;
2828
import com.cloud.network.Network;
29-
import com.cloud.network.dao.FirewallRulesDao;
3029
import com.cloud.network.rules.FirewallRuleVO;
3130
import com.cloud.network.rules.PortForwardingRuleVO;
3231
import com.cloud.service.ServiceOfferingVO;
@@ -37,7 +36,6 @@
3736
import org.apache.cloudstack.api.ApiCommandResourceType;
3837
import org.apache.cloudstack.context.CallContext;
3938

40-
import javax.inject.Inject;
4139
import java.io.File;
4240
import java.util.ArrayList;
4341
import java.util.List;
@@ -47,9 +45,6 @@
4745

4846
public class KubernetesClusterRemoveWorker extends KubernetesClusterActionWorker {
4947

50-
@Inject
51-
private FirewallRulesDao firewallRulesDao;
52-
5348
private long removeNodeTimeoutTime;
5449

5550
public KubernetesClusterRemoveWorker(KubernetesCluster kubernetesCluster, KubernetesClusterManagerImpl clusterManager) {
@@ -128,9 +123,13 @@ protected boolean removeNodePortForwardingRules(Long nodeId, Network network, Us
128123
for (PortForwardingRuleVO pfRule : pfRules) {
129124
try {
130125
result &= rulesService.revokePortForwardingRule(pfRule.getId(), true);
131-
if (Objects.isNull(network.getVpcId())) {
132-
FirewallRuleVO ruleVO = firewallRulesDao.findByNetworkIdAndPorts(network.getId(), pfRule.getSourcePortStart(), pfRule.getSourcePortEnd());
133-
result &= firewallService.revokeIngressFirewallRule(ruleVO.getId(), true);
126+
if (isPublicIpFirewallRequired(network)) {
127+
FirewallRuleVO ruleVO = findIngressFirewallRuleForPorts(network, pfRule.getSourceIpAddressId(), pfRule.getSourcePortStart(), pfRule.getSourcePortEnd());
128+
if (ruleVO != null) {
129+
result &= firewallService.revokeIngressFirewallRule(ruleVO.getId(), true);
130+
} else {
131+
logger.warn("Firewall rule for port {} of node {} not found, it may have been removed manually", pfRule.getSourcePortStart(), vm.getName());
132+
}
134133
}
135134
} catch (Exception e) {
136135
String err = String.format("Failed to cleanup network rules for node %s, due to: %s", vm.getName(), e.getMessage());

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@
8282
import com.cloud.kubernetes.cluster.KubernetesClusterVO;
8383
import com.cloud.network.IpAddress;
8484
import com.cloud.network.Network;
85-
import com.cloud.network.dao.FirewallRulesDao;
8685
import com.cloud.network.dao.LoadBalancerDao;
8786
import com.cloud.network.dao.LoadBalancerVO;
8887
import com.cloud.network.lb.LoadBalancingRulesService;
@@ -130,8 +129,6 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
130129
@Inject
131130
protected HostDao hostDao;
132131
@Inject
133-
protected FirewallRulesDao firewallRulesDao;
134-
@Inject
135132
protected NetworkACLService networkACLService;
136133
@Inject
137134
protected NetworkACLItemDao networkACLItemDao;
@@ -832,6 +829,14 @@ protected void setupKubernetesClusterVpcTierRules(IpAddress publicIp, Network ne
832829
} catch (ResourceUnavailableException | NetworkRuleConflictException e) {
833830
throw new ManagementServerException(String.format("Failed to activate SSH port forwarding rules for the Kubernetes cluster : %s", kubernetesCluster.getName()), e);
834831
}
832+
833+
// The VPC firewall drops traffic to the public IP unless a firewall rule allows it, open the
834+
// API and SSH ports as for isolated networks. Create them after the load balancing and port
835+
// forwarding rules: applying firewall rules on a VPC public IP does not associate the IP with
836+
// the VPC virtual router, the first load balancing or port forwarding rule does.
837+
if (isVpcFirewallEnabled(network)) {
838+
createFirewallRules(publicIp, clusterVMIds, true);
839+
}
835840
}
836841

837842

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ private void scaleKubernetesClusterVpcTierRules(final List<Long> clusterVMIds) t
173173
if (publicIp == null) {
174174
throw new ManagementServerException(String.format("No public IP addresses found for VPC tier : %s, Kubernetes cluster : %s", network.getName(), kubernetesCluster.getName()));
175175
}
176+
boolean vpcFirewallEnabled = isVpcFirewallEnabled(network);
177+
if (vpcFirewallEnabled) {
178+
// Remove the existing SSH firewall rule, it is recreated below for the new node count
179+
FirewallRule firewallRule = removeSshFirewallRule(publicIp, network.getId());
180+
if (firewallRule == null) {
181+
logger.warn("SSH firewall rule not found for Kubernetes cluster: {}. It may have been manually deleted or modified.", kubernetesCluster.getName());
182+
}
183+
}
176184
try {
177185
removePortForwardingRules(publicIp, network, owner, CLUSTER_NODES_DEFAULT_START_SSH_PORT, CLUSTER_NODES_DEFAULT_START_SSH_PORT + clusterVMIds.size() - 1);
178186
} catch (ResourceUnavailableException e) {
@@ -184,6 +192,10 @@ private void scaleKubernetesClusterVpcTierRules(final List<Long> clusterVMIds) t
184192
} catch (ResourceUnavailableException | NetworkRuleConflictException e) {
185193
throw new ManagementServerException(String.format("Failed to activate SSH port forwarding rules for the Kubernetes cluster : %s", kubernetesCluster.getName()), e);
186194
}
195+
if (vpcFirewallEnabled) {
196+
// After the port forwarding rules, as when the cluster is created
197+
addFirewallRulesForNodes(publicIp, clusterVMIds.size());
198+
}
187199
}
188200

189201
/**

‎plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,14 @@ protected void setupKubernetesEtcdNetworkRules(List<UserVm> etcdVms, Network net
691691
throw new ManagementServerException(String.format("Failed to provision firewall rules for etcd nodes for the Kubernetes cluster : %s", kubernetesCluster.getName()), e);
692692
}
693693
provisionPublicIpPortForwardingRule(publicIp, network, owner, etcdVmIds.get(i), etcdStartPort, DEFAULT_SSH_PORT);
694+
if (isVpcFirewallEnabled(network)) {
695+
// In a VPC the firewall rule follows the port forwarding rule, see setupKubernetesClusterVpcTierRules
696+
try {
697+
provisionFirewallRules(publicIp, owner, etcdStartPort, etcdStartPort);
698+
} catch (NoSuchFieldException | IllegalAccessException | ResourceUnavailableException | NetworkRuleConflictException e) {
699+
throw new ManagementServerException(String.format("Failed to provision firewall rules for etcd nodes for the Kubernetes cluster : %s", kubernetesCluster.getName()), e);
700+
}
701+
}
694702
}
695703
}
696704

0 commit comments

Comments
 (0)