From 332d5ebbbf3af38cb6429a0099bf70dfd29851be Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Wed, 25 Feb 2026 15:32:20 +0530 Subject: [PATCH] Support Firewall for public IPs in VPC --- .../orchestration/NetworkOrchestrator.java | 1 + .../upgrade/dao/Upgrade42210to42300.java | 64 +++++++++++++++++++ .../cluster/KubernetesClusterManagerImpl.java | 3 +- .../ConfigurationManagerImpl.java | 2 +- .../element/VpcVirtualRouterElement.java | 4 -- .../network/RoutedIpv4ManagerImpl.java | 3 +- systemvm/debian/opt/cloud/bin/configure.py | 4 +- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 12 ++++ ui/src/views/network/PublicIpResource.vue | 16 +++-- ui/src/views/offering/AddVpcOffering.vue | 6 ++ 10 files changed, 97 insertions(+), 18 deletions(-) diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 7d455e7d6dc..a845281039b 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -561,6 +561,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra defaultVPCOffProviders.put(Service.StaticNat, defaultProviders); defaultVPCOffProviders.put(Service.PortForwarding, defaultProviders); defaultVPCOffProviders.put(Service.Vpn, defaultProviders); + defaultVPCOffProviders.put(Service.Firewall, defaultProviders); Transaction.execute(new TransactionCallbackNoReturn() { @Override diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java index 393f2039950..a8e5365e5c1 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java @@ -17,6 +17,8 @@ package com.cloud.upgrade.dao; import java.io.InputStream; +import java.util.Arrays; +import java.util.List; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -51,6 +53,7 @@ public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgr @Override public void performDataMigration(Connection conn) { unhideJsInterpretationEnabled(conn); + updateVpcDefaultOfferingsWithFirewallService(conn); } protected void unhideJsInterpretationEnabled(Connection conn) { @@ -89,4 +92,65 @@ public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgr logger.warn("Error while decrypting configuration 'js.interpretation.enabled'. The configuration may already be decrypted."); } } + + private void updateVpcDefaultOfferingsWithFirewallService(Connection conn) { + logger.debug("Updating default VPC offerings to add Firewall service with VpcVirtualRouter provider"); + + final List defaultVpcOfferingUniqueNames = Arrays.asList( + "DefaultIsolatedNetworkOfferingForVpcNetworks", + "DefaultIsolatedNetworkOfferingForVpcNetworksNoLB", + "DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB", + "DefaultNATNSXNetworkOfferingForVpc", + "DefaultRoutedNSXNetworkOfferingForVpc", + "DefaultNATNSXNetworkOfferingForVpcWithInternalLB", + "DefaultRoutedNetrisNetworkOfferingForVpc", + "DefaultNATNetrisNetworkOfferingForVpc", + "DefaultNSXVPCNetworkOfferingforKubernetesService" + ); + + try { + for (String uniqueName : defaultVpcOfferingUniqueNames) { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`network_offerings` WHERE unique_name = ?"); + pstmt.setString(1, uniqueName); + + ResultSet rs = pstmt.executeQuery(); + if (!rs.next()) { + continue; + } + + long offeringId = rs.getLong(1); + rs.close(); + pstmt.close(); + + // Insert into ntwk_offering_service_map (if not exists) + pstmt = conn.prepareStatement("INSERT INTO `cloud`.`ntwk_offering_service_map` " + + "(network_offering_id, service, provider, created) " + + "VALUES (?, 'Firewall', 'VpcVirtualRouter', now())"); + pstmt.setLong(1, offeringId); + pstmt.executeUpdate(); + pstmt.close(); + + // Update existing networks (ntwk_service_map) + pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`networks` WHERE network_offering_id = ?"); + pstmt.setLong(1, offeringId); + + rs = pstmt.executeQuery(); + while (rs.next()) { + long networkId = rs.getLong(1); + PreparedStatement insertService = conn.prepareStatement("INSERT INTO `cloud`.`ntwk_service_map` " + + "(network_id, service, provider, created) " + + "VALUES (?, 'Firewall', 'VpcVirtualRouter', now())"); + insertService.setLong(1, networkId); + insertService.executeUpdate(); + insertService.close(); + } + + rs.close(); + pstmt.close(); + } + + } catch (SQLException e) { + logger.warn("Exception while updating VPC default offerings with Firewall service: " + e.getMessage(), e); + } + } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index fea20eb124f..aa5ddf0cd00 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -2986,9 +2986,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne defaultKubernetesServiceNetworkOfferingProviders.put(Service.UserData, provider); if (forVpc) { defaultKubernetesServiceNetworkOfferingProviders.put(Service.NetworkACL, forNsx ? Network.Provider.Nsx : provider); - } else { - defaultKubernetesServiceNetworkOfferingProviders.put(Service.Firewall, forNsx ? Network.Provider.Nsx : provider); } + defaultKubernetesServiceNetworkOfferingProviders.put(Service.Firewall, forNsx ? Network.Provider.Nsx : provider); defaultKubernetesServiceNetworkOfferingProviders.put(Service.Lb, forNsx ? Network.Provider.Nsx : provider); defaultKubernetesServiceNetworkOfferingProviders.put(Service.SourceNat, forNsx ? Network.Provider.Nsx : provider); defaultKubernetesServiceNetworkOfferingProviders.put(Service.StaticNat, forNsx ? Network.Provider.Nsx : provider); diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 6da5dda967d..2578d66e344 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -7223,7 +7223,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } if (forVpc == null) { - if (service == Service.SecurityGroup || service == Service.Firewall) { + if (service == Service.SecurityGroup) { forVpc = false; } else if (service == Service.NetworkACL) { forVpc = true; diff --git a/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java index f393ef8a129..2b12c1a9f13 100644 --- a/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java @@ -412,10 +412,6 @@ public class VpcVirtualRouterElement extends VirtualRouterElement implements Vpc vpnCapabilities.putAll(capabilities.get(Service.Vpn)); vpnCapabilities.put(Capability.VpnTypes, "s2svpn"); capabilities.put(Service.Vpn, vpnCapabilities); - - // remove firewall capability - capabilities.remove(Service.Firewall); - // add network ACL capability final Map networkACLCapabilities = new HashMap(); networkACLCapabilities.put(Capability.SupportedProtocols, "tcp,udp,icmp"); diff --git a/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java b/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java index a03db4d4a24..fbc5374066e 100644 --- a/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java @@ -989,8 +989,7 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou @Override public boolean isVirtualRouterGateway(Network network) { return isRoutedNetwork(network) - && (networkServiceMapDao.canProviderSupportServiceInNetwork(network.getId(), Service.Gateway, Provider.VirtualRouter)) - || networkServiceMapDao.canProviderSupportServiceInNetwork(network.getId(), Service.Gateway, Provider.VPCVirtualRouter); + && (networkServiceMapDao.canProviderSupportServiceInNetwork(network.getId(), Service.Gateway, Provider.VirtualRouter)); } @Override diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index bf48be66694..b2c0676c65d 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -705,8 +705,8 @@ class CsAcl(CsDataBag): for item in self.dbag: if item == "id": - continue - if self.config.is_vpc(): + continue + if self.config.is_vpc() and not ("purpose" in self.dbag[item] and self.dbag[item]["purpose"] == "Firewall"): self.AclDevice(self.dbag[item], self.config).create() else: self.AclIP(self.dbag[item], self.config).create() diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index bde4a976e31..1730cae9b32 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -647,6 +647,18 @@ class CsIP: (self.address['network'], self.address['network'])]) if self.get_type() in ["public"]: + # Add PREROUTING firewall chain jump for public IP + self.fw.append(["mangle", "front", + "-A PREROUTING " + + "-d %s/32 -j FIREWALL_%s" % (self.address['public_ip'], self.address['public_ip'])]) + + # Add the firewall chain with default DROP policy + self.fw.append(["mangle", "front", + "-A FIREWALL_%s " % self.address['public_ip'] + + "-m state --state RELATED,ESTABLISHED -j RETURN"]) + self.fw.append(["mangle", "", + "-A FIREWALL_%s -j DROP" % self.address['public_ip']]) + self.fw.append( ["mangle", "", "-A FORWARD -j VPN_STATS_%s" % self.dev]) self.fw.append( diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 0540e7f292a..46ec6e66bd3 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -139,20 +139,22 @@ export default { // VPC IPs with source nat have only VPN when VPC offering conserve mode = false if (this.resource.issourcenat && vpc?.vpcofferingconservemode === false) { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) return } - // VPC IPs with static nat have nothing + // VPC IPs with static nat have firewall if (this.resource.isstaticnat) { if (this.resource.virtualmachinetype === 'DomainRouter') { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) + } else { + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'firewall')) } return } - // VPC IPs don't have firewall - let tabs = this.$route.meta.tabs.filter(tab => tab.name !== 'firewall') + // VPC IPs now have firewall support + let tabs = this.$route.meta.tabs const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { @@ -168,12 +170,12 @@ export default { this.portFWRuleCount = await this.fetchPortFWRule() this.loadBalancerRuleCount = await this.fetchLoadBalancerRule() - // VPC IPs with PF only have PF + // VPC IPs with PF only have PF (and firewall) if (this.portFWRuleCount > 0) { tabs = tabs.filter(tab => tab.name !== 'loadbalancing') } - // VPC IPs with LB rules only have LB + // VPC IPs with LB rules only have LB (and firewall) if (this.loadBalancerRuleCount > 0) { tabs = tabs.filter(tab => tab.name !== 'portforwarding') } diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue index 509109c91c2..39b12ed81cf 100644 --- a/ui/src/views/offering/AddVpcOffering.vue +++ b/ui/src/views/offering/AddVpcOffering.vue @@ -534,6 +534,12 @@ export default { { name: 'ConfigDrive' } ] }) + services.push({ + name: 'Firewall', + provider: [ + { name: 'VpcVirtualRouter' } + ] + }) services.push({ name: 'Lb', provider: [