Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ protected void deleteKubernetesClusterIsolatedNetworkRules(Network network, List
if (firewallRule == null) {
logMessage(Level.WARN, "Firewall rule for SSH access can't be removed", null);
}
removeEtcdFirewallRules(publicIp);
try {
removePortForwardingRules(publicIp, network, owner, removedVmIds);
} catch (ResourceUnavailableException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import com.cloud.hypervisor.Hypervisor;
import com.cloud.kubernetes.cluster.KubernetesCluster;
import com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl;
import com.cloud.kubernetes.cluster.KubernetesClusterService;
import com.cloud.kubernetes.cluster.KubernetesClusterVO;
import com.cloud.network.IpAddress;
import com.cloud.network.Network;
Expand Down Expand Up @@ -544,20 +545,45 @@ protected FirewallRule removeApiFirewallRule(final IpAddress publicIp) {
}

protected FirewallRule removeSshFirewallRule(final IpAddress publicIp, final long networkId) {
FirewallRule rule = null;
FirewallRuleVO rule = null;
List<FirewallRuleVO> firewallRules = firewallRulesDao.listByIpPurposeProtocolAndNotRevoked(publicIp.getId(), FirewallRule.Purpose.Firewall, NetUtils.TCP_PROTO);
for (FirewallRuleVO firewallRule : firewallRules) {
PortForwardingRuleVO pfRule = portForwardingRulesDao.findByNetworkAndPorts(networkId, firewallRule.getSourcePortStart(), firewallRule.getSourcePortEnd());
if (Objects.equals(firewallRule.getSourcePortStart(), CLUSTER_NODES_DEFAULT_START_SSH_PORT) || (Objects.nonNull(pfRule) && pfRule.getDestinationPortStart() == DEFAULT_SSH_PORT) ) {
if (Objects.equals(firewallRule.getSourcePortStart(), CLUSTER_NODES_DEFAULT_START_SSH_PORT)) {
rule = firewallRule;
firewallService.revokeIngressFwRule(firewallRule.getId(), true);
logger.debug("The SSH firewall rule {} with the id {} was revoked", firewallRule.getName(), firewallRule.getId());
break;
}
if (rule == null && firewallRule.getSourcePortStart() != null
&& firewallRule.getSourcePortStart() < KubernetesClusterService.KubernetesEtcdNodeStartPort.value()) {
PortForwardingRuleVO pfRule = portForwardingRulesDao.findByNetworkAndPorts(networkId, firewallRule.getSourcePortStart(), firewallRule.getSourcePortEnd());
if (Objects.nonNull(pfRule) && pfRule.getDestinationPortStart() == DEFAULT_SSH_PORT) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the 2222 rule was deleted by hand, can this still pick an etcd rule and wipe the etcd forwarding again? the caller already falls back to a safe range when nothing is found

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. You are right that with the 2222 range rule gone, the fallback matches the first rule forwarding to 22, and the etcd rules at 50000+ do, so it could grab one and the scale path would then wipe 2222..50000. Since the caller already bounds its cleanup safely on a null return, I capped the fallback below KubernetesEtcdNodeStartPort so it never crosses into the etcd range, with a test for the no-node-rule case. Pushed in 75edb36.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the cap below the etcd start port in 75edb36 covers it

rule = firewallRule;
}
}
}
if (rule != null) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on destroy in a network the user keeps, should the etcd rules from 50000 up go too? they stay open and the next cluster with etcd nodes would clash on those ports

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the etcd firewall rules at 50000+ were not being revoked on destroy and would linger in a network the user keeps, clashing with the next etcd cluster on those ports. Fixed in the same push: the destroy path now revokes the etcd ingress rules alongside the node and API rules, with a test. 75edb36.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revoking the etcd rules on destroy in 75edb36 covers it

firewallService.revokeIngressFwRule(rule.getId(), true);
logger.debug("The SSH firewall rule {} with the id {} was revoked", rule.getName(), rule.getId());
}
return rule;
}

protected void removeEtcdFirewallRules(final IpAddress publicIp) {
Long etcdNodeCount = kubernetesCluster.getEtcdNodeCount();
if (etcdNodeCount == null || etcdNodeCount <= 0) {
return;
}
int etcdStartPort = KubernetesClusterService.KubernetesEtcdNodeStartPort.value();
int etcdEndPort = etcdStartPort + etcdNodeCount.intValue() - 1;
List<FirewallRuleVO> firewallRules = firewallRulesDao.listByIpPurposeProtocolAndNotRevoked(publicIp.getId(), FirewallRule.Purpose.Firewall, NetUtils.TCP_PROTO);
for (FirewallRuleVO firewallRule : firewallRules) {
Integer startPort = firewallRule.getSourcePortStart();
if (startPort != null && startPort >= etcdStartPort && startPort <= etcdEndPort) {
firewallService.revokeIngressFwRule(firewallRule.getId(), true);
logger.debug("The etcd SSH firewall rule {} with the id {} was revoked", firewallRule.getName(), firewallRule.getId());
}
}
}

protected void removePortForwardingRules(final IpAddress publicIp, final Network network, final Account account, final List<Long> removedVMIds) throws ResourceUnavailableException {
if (!CollectionUtils.isEmpty(removedVMIds)) {
List<PortForwardingRuleVO> pfRules = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@

import com.cloud.kubernetes.cluster.KubernetesCluster;
import com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl;
import com.cloud.kubernetes.cluster.KubernetesClusterService;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
import com.cloud.kubernetes.version.dao.KubernetesSupportedVersionDao;
import com.cloud.network.IpAddress;
import com.cloud.network.dao.FirewallRulesDao;
import com.cloud.network.firewall.FirewallService;
import com.cloud.network.rules.FirewallRule;
import com.cloud.network.rules.FirewallRuleVO;
import com.cloud.network.rules.PortForwardingRuleVO;
import com.cloud.network.rules.dao.PortForwardingRulesDao;
import com.cloud.utils.net.NetUtils;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -51,6 +61,18 @@ public class KubernetesClusterResourceModifierActionWorkerTest {
@Mock
private KubernetesCluster kubernetesClusterMock;

@Mock
private IpAddress publicIpMock;

@Mock
private FirewallRulesDao firewallRulesDaoMock;

@Mock
private PortForwardingRulesDao portForwardingRulesDaoMock;

@Mock
private FirewallService firewallServiceMock;

private KubernetesClusterResourceModifierActionWorker kubernetesClusterResourceModifierActionWorker;

@Before
Expand Down Expand Up @@ -135,4 +157,99 @@ public void getKubernetesClusterNodeNamePrefixTestNormalizedPrefixShouldNotStart
Mockito.when(kubernetesClusterMock.getName()).thenReturn(originalPrefix);
Assert.assertEquals(expectedPrefix, kubernetesClusterResourceModifierActionWorker.getKubernetesClusterNodeNamePrefix());
}

private static final long NETWORK_ID = 10L;

private void mockSshFirewallRules(FirewallRuleVO... rules) {
kubernetesClusterResourceModifierActionWorker.firewallRulesDao = firewallRulesDaoMock;
kubernetesClusterResourceModifierActionWorker.portForwardingRulesDao = portForwardingRulesDaoMock;
kubernetesClusterResourceModifierActionWorker.firewallService = firewallServiceMock;
Mockito.when(publicIpMock.getId()).thenReturn(1L);
Mockito.when(firewallRulesDaoMock.listByIpPurposeProtocolAndNotRevoked(1L, FirewallRule.Purpose.Firewall, NetUtils.TCP_PROTO)).thenReturn(Arrays.asList(rules));
}

private FirewallRuleVO mockSshForwardedFirewallRule(int port) {
FirewallRuleVO rule = Mockito.mock(FirewallRuleVO.class);
Mockito.when(rule.getSourcePortStart()).thenReturn(port);
Mockito.when(rule.getSourcePortEnd()).thenReturn(port);
PortForwardingRuleVO pfRule = Mockito.mock(PortForwardingRuleVO.class);
Mockito.when(pfRule.getDestinationPortStart()).thenReturn(KubernetesClusterActionWorker.DEFAULT_SSH_PORT);
Mockito.when(portForwardingRulesDaoMock.findByNetworkAndPorts(NETWORK_ID, port, port)).thenReturn(pfRule);
return rule;
}

@Test
public void removeSshFirewallRuleTestPrefersNodesRuleOverEtcdRuleListedFirst() {
FirewallRuleVO etcdRule = mockSshForwardedFirewallRule(50000);
FirewallRuleVO nodesRule = Mockito.mock(FirewallRuleVO.class);
Mockito.when(nodesRule.getSourcePortStart()).thenReturn(KubernetesClusterActionWorker.CLUSTER_NODES_DEFAULT_START_SSH_PORT);
Mockito.when(nodesRule.getId()).thenReturn(11L);
mockSshFirewallRules(etcdRule, nodesRule);

Assert.assertSame(nodesRule, kubernetesClusterResourceModifierActionWorker.removeSshFirewallRule(publicIpMock, NETWORK_ID));
Mockito.verify(firewallServiceMock).revokeIngressFwRule(11L, true);
Mockito.verifyNoMoreInteractions(firewallServiceMock);
}

@Test
public void removeSshFirewallRuleTestFallsBackToSshForwardedRule() {
FirewallRuleVO externalNodeRule = mockSshForwardedFirewallRule(2225);
Mockito.when(externalNodeRule.getId()).thenReturn(12L);
mockSshFirewallRules(externalNodeRule);

Assert.assertSame(externalNodeRule, kubernetesClusterResourceModifierActionWorker.removeSshFirewallRule(publicIpMock, NETWORK_ID));
Mockito.verify(firewallServiceMock).revokeIngressFwRule(12L, true);
}

@Test
public void removeSshFirewallRuleTestReturnsNullWhenNoSshRule() {
FirewallRuleVO apiRule = Mockito.mock(FirewallRuleVO.class);
Mockito.when(apiRule.getSourcePortStart()).thenReturn(KubernetesClusterActionWorker.CLUSTER_API_PORT);
Mockito.when(apiRule.getSourcePortEnd()).thenReturn(KubernetesClusterActionWorker.CLUSTER_API_PORT);
mockSshFirewallRules(apiRule);

Assert.assertNull(kubernetesClusterResourceModifierActionWorker.removeSshFirewallRule(publicIpMock, NETWORK_ID));
Mockito.verifyNoMoreInteractions(firewallServiceMock);
}

@Test
public void removeSshFirewallRuleTestDoesNotPickEtcdRuleWhenNodeRuleMissing() {
FirewallRuleVO etcdRule = Mockito.mock(FirewallRuleVO.class);
Mockito.when(etcdRule.getSourcePortStart()).thenReturn(KubernetesClusterService.KubernetesEtcdNodeStartPort.value());
mockSshFirewallRules(etcdRule);

Assert.assertNull(kubernetesClusterResourceModifierActionWorker.removeSshFirewallRule(publicIpMock, NETWORK_ID));
Mockito.verify(firewallServiceMock, Mockito.never()).revokeIngressFwRule(Mockito.anyLong(), Mockito.anyBoolean());
}

@Test
public void removeEtcdFirewallRulesTestRevokesOnlyEtcdRangeRules() {
int etcdStart = KubernetesClusterService.KubernetesEtcdNodeStartPort.value();
Mockito.when(kubernetesClusterMock.getEtcdNodeCount()).thenReturn(2L);

FirewallRuleVO nodeRule = Mockito.mock(FirewallRuleVO.class);
Mockito.when(nodeRule.getSourcePortStart()).thenReturn(KubernetesClusterActionWorker.CLUSTER_NODES_DEFAULT_START_SSH_PORT);
FirewallRuleVO etcdRuleOne = Mockito.mock(FirewallRuleVO.class);
Mockito.when(etcdRuleOne.getSourcePortStart()).thenReturn(etcdStart);
Mockito.when(etcdRuleOne.getId()).thenReturn(21L);
FirewallRuleVO etcdRuleTwo = Mockito.mock(FirewallRuleVO.class);
Mockito.when(etcdRuleTwo.getSourcePortStart()).thenReturn(etcdStart + 1);
Mockito.when(etcdRuleTwo.getId()).thenReturn(22L);
mockSshFirewallRules(nodeRule, etcdRuleOne, etcdRuleTwo);

kubernetesClusterResourceModifierActionWorker.removeEtcdFirewallRules(publicIpMock);

Mockito.verify(firewallServiceMock).revokeIngressFwRule(21L, true);
Mockito.verify(firewallServiceMock).revokeIngressFwRule(22L, true);
Mockito.verifyNoMoreInteractions(firewallServiceMock);
}

@Test
public void removeEtcdFirewallRulesTestNoOpWhenClusterHasNoEtcdNodes() {
Mockito.when(kubernetesClusterMock.getEtcdNodeCount()).thenReturn(null);

kubernetesClusterResourceModifierActionWorker.removeEtcdFirewallRules(publicIpMock);

Mockito.verifyNoInteractions(firewallServiceMock, firewallRulesDaoMock);
}
}