From 332d5ebbbf3af38cb6429a0099bf70dfd29851be Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Wed, 25 Feb 2026 15:32:20 +0530 Subject: [PATCH 01/13] 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: [ From 1b4bfaca2b66b999e23713a39f5e954560c5f291 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Thu, 5 Mar 2026 11:53:16 +0530 Subject: [PATCH 02/13] UI changes --- .../com/cloud/network/vpc/VpcManagerImpl.java | 2 +- .../network/RoutedIpv4ManagerImpl.java | 3 +-- ui/src/views/network/PublicIpResource.vue | 25 +++++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 5b4ab908ca4..f498fefe5a0 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -332,7 +332,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpcChecker")); private List vpcElements = null; - private final List nonSupportedServices = Arrays.asList(Service.SecurityGroup, Service.Firewall); + private final List nonSupportedServices = Arrays.asList(Service.SecurityGroup); private final List supportedProviders = Arrays.asList(Provider.VPCVirtualRouter, Provider.NiciraNvp, Provider.InternalLbVm, Provider.Netscaler, Provider.JuniperContrailVpcRouter, Provider.Ovs, Provider.BigSwitchBcf, Provider.ConfigDrive, Provider.Nsx, Provider.Netris); 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 fbc5374066e..07f6346a878 100644 --- a/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/network/RoutedIpv4ManagerImpl.java @@ -995,8 +995,7 @@ public class RoutedIpv4ManagerImpl extends ComponentLifecycleBase implements Rou @Override public boolean isVirtualRouterGateway(NetworkOffering networkOffering) { return NetworkOffering.NetworkMode.ROUTED.equals(networkOffering.getNetworkMode()) - && networkOfferingServiceMapDao.canProviderSupportServiceInNetworkOffering(networkOffering.getId(), Service.Gateway, Provider.VirtualRouter) - || networkOfferingServiceMapDao.canProviderSupportServiceInNetworkOffering(networkOffering.getId(), Service.Gateway, Provider.VPCVirtualRouter); + && networkOfferingServiceMapDao.canProviderSupportServiceInNetworkOffering(networkOffering.getId(), Service.Gateway, Provider.VirtualRouter); } @Override diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 46ec6e66bd3..79b0da853d5 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -139,22 +139,37 @@ 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 => ['vpn', 'firewall'].includes(tab.name))) + let tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + if (this.resource.associatednetworkid) { + tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) + } + this.tabs = tabs return } - // VPC IPs with static nat have firewall + // VPC IPs with static nat have firewall (only if associatednetworkid present) if (this.resource.isstaticnat) { if (this.resource.virtualmachinetype === 'DomainRouter') { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) + let tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + if (this.resource.associatednetworkid) { + tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) + } + this.tabs = tabs } else { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'firewall')) + if (this.resource.associatednetworkid) { + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'firewall')) + } else { + this.tabs = this.defaultTabs + } } return } - // VPC IPs now have firewall support + // VPC IPs have all tabs, but firewall only if associatednetworkid present let tabs = this.$route.meta.tabs + if (!this.resource.associatednetworkid) { + tabs = tabs.filter(tab => tab.name !== 'firewall') + } const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { From 63d8160c269b8b61562e084508b994412f49097e Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Thu, 5 Mar 2026 15:33:39 +0530 Subject: [PATCH 03/13] more fixes --- .../configuration/ConfigurationManagerImpl.java | 2 ++ ui/src/views/network/PublicIpResource.vue | 17 ++--------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 2578d66e344..e0963bab78b 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -7227,6 +7227,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati forVpc = false; } else if (service == Service.NetworkACL) { forVpc = true; + } else if (service == Service.Firewall) { + forVpc = true; } } diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 79b0da853d5..f92270e576c 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -147,29 +147,16 @@ export default { return } - // VPC IPs with static nat have firewall (only if associatednetworkid present) + // VPC IPs with static nat have nothing if (this.resource.isstaticnat) { if (this.resource.virtualmachinetype === 'DomainRouter') { - let tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - if (this.resource.associatednetworkid) { - tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) - } - this.tabs = tabs - } else { - if (this.resource.associatednetworkid) { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'firewall')) - } else { - this.tabs = this.defaultTabs - } + this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) } return } // VPC IPs have all tabs, but firewall only if associatednetworkid present let tabs = this.$route.meta.tabs - if (!this.resource.associatednetworkid) { - tabs = tabs.filter(tab => tab.name !== 'firewall') - } const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { From b26e492561c05fbbfa3b37c0d3eb3afcf76736f5 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Thu, 7 May 2026 15:04:18 +0530 Subject: [PATCH 04/13] UI fix --- ui/src/views/network/PublicIpResource.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index f92270e576c..cbe73d60824 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -151,12 +151,17 @@ export default { if (this.resource.isstaticnat) { if (this.resource.virtualmachinetype === 'DomainRouter') { this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) + } else { + this.tabs = this.defaultTabs } return } // VPC IPs have all tabs, but firewall only if associatednetworkid present let tabs = this.$route.meta.tabs + if (!this.resource.associatednetworkid) { + tabs = tabs.filter(tab => tab.name !== 'firewall') + } const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { From 03c7756516cb5e75654ba98f66188eb18bff9990 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Mon, 11 May 2026 12:01:53 +0530 Subject: [PATCH 05/13] Firewall tab changes --- systemvm/debian/opt/cloud/bin/configure.py | 14 ++++++++++ systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 11 -------- ui/src/views/network/PublicIpResource.vue | 27 ++++++++++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index b2c0676c65d..a250229ba9e 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -703,12 +703,26 @@ class CsAcl(CsDataBag): self.add_routing_rules() return + fw_chains_created = set() for item in self.dbag: if item == "id": 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: + # For VPC firewall rules, create the PREROUTING jump and chain skeleton + # once per public IP before adding the individual rule + if self.config.is_vpc() and self.dbag[item].get("purpose") == "Firewall": + src_ip = self.dbag[item].get("src_ip") + if src_ip and src_ip not in fw_chains_created: + fw = self.config.get_fw() + fw.append(["mangle", "front", + "-A PREROUTING -d %s/32 -j FIREWALL_%s" % (src_ip, src_ip)]) + fw.append(["mangle", "front", + "-A FIREWALL_%s -m state --state RELATED,ESTABLISHED -j RETURN" % src_ip]) + fw.append(["mangle", "", + "-A FIREWALL_%s -j DROP" % src_ip]) + fw_chains_created.add(src_ip) self.AclIP(self.dbag[item], self.config).create() class CsIpv6Firewall(CsDataBag): diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index 1730cae9b32..cf48803cdfc 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -647,17 +647,6 @@ 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]) diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index cbe73d60824..340aec7119f 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -135,33 +135,27 @@ export default { return } if (this.resource && this.resource.vpcid) { - const vpc = await this.fetchVpc() +`` const vpc = await this.fetchVpc() // VPC IPs with source nat have only VPN when VPC offering conserve mode = false if (this.resource.issourcenat && vpc?.vpcofferingconservemode === false) { let tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - if (this.resource.associatednetworkid) { - tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) - } - this.tabs = tabs + this.tabs = this.addFirewallTab(tabs) return } - // VPC IPs with static nat have nothing + // VPC IPs with static nat keep existing VPN behavior and always show firewall if (this.resource.isstaticnat) { + let tabs = this.defaultTabs if (this.resource.virtualmachinetype === 'DomainRouter') { - this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => ['vpn', 'firewall'].includes(tab.name))) - } else { - this.tabs = this.defaultTabs + tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) } + this.tabs = this.addFirewallTab(tabs) return } - // VPC IPs have all tabs, but firewall only if associatednetworkid present + // VPC IPs have all tabs, and firewall should always be visible let tabs = this.$route.meta.tabs - if (!this.resource.associatednetworkid) { - tabs = tabs.filter(tab => tab.name !== 'firewall') - } const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { @@ -209,6 +203,13 @@ export default { fetchAction () { this.actions = this.$route.meta.actions || [] }, + addFirewallTab (tabs) { + const firewallTab = this.$route.meta.tabs.find(tab => tab.name === 'firewall') + if (!firewallTab || tabs.some(tab => tab.name === 'firewall')) { + return tabs + } + return tabs.concat(firewallTab) + }, fetchVpc () { if (!this.resource.vpcid) { return null From f8fb95f122c7253155ca534b0eff5723acb2c568 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Mon, 11 May 2026 17:01:38 +0530 Subject: [PATCH 06/13] Changes in mapping to the VPC and not the tier network --- .../network/firewall/FirewallManagerImpl.java | 7 ++++++- ui/src/views/network/PublicIpResource.vue | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java index 779d26d51f1..4584c201a11 100644 --- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java +++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java @@ -692,7 +692,12 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, case Ipv6Firewall: for (FirewallServiceProvider fwElement : _firewallElements) { Network.Provider provider = fwElement.getProvider(); - boolean isFwProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Firewall, provider); + boolean isFwProvider; + if (network.getVpcId() != null) { + isFwProvider = _vpcMgr.isProviderSupportServiceInVpc(network.getVpcId(), Service.Firewall, provider); + } else { + isFwProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Firewall, provider); + } if (!isFwProvider) { continue; } diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 340aec7119f..5a5ea99a003 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -135,12 +135,13 @@ export default { return } if (this.resource && this.resource.vpcid) { -`` const vpc = await this.fetchVpc() + const vpc = await this.fetchVpc() + const hasFirewallCapability = this.hasVpcFirewallCapability(vpc) // VPC IPs with source nat have only VPN when VPC offering conserve mode = false if (this.resource.issourcenat && vpc?.vpcofferingconservemode === false) { - let tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - this.tabs = this.addFirewallTab(tabs) + const tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) + this.tabs = hasFirewallCapability ? this.addFirewallTab(tabs) : tabs return } @@ -150,12 +151,15 @@ export default { if (this.resource.virtualmachinetype === 'DomainRouter') { tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) } - this.tabs = this.addFirewallTab(tabs) + this.tabs = hasFirewallCapability ? this.addFirewallTab(tabs) : tabs return } - // VPC IPs have all tabs, and firewall should always be visible + // VPC IPs have all tabs; firewall is shown only if VPC has firewall capability let tabs = this.$route.meta.tabs + if (!hasFirewallCapability) { + tabs = tabs.filter(tab => tab.name !== 'firewall') + } const network = await this.fetchNetwork() if (network && network.networkofferingconservemode) { @@ -210,6 +214,10 @@ export default { } return tabs.concat(firewallTab) }, + hasVpcFirewallCapability (vpc) { + const services = vpc?.service || [] + return Array.isArray(services) && services.some(service => (service?.name || '').toLowerCase() === 'firewall') + }, fetchVpc () { if (!this.resource.vpcid) { return null From 483e2ff60332735b2dc921a5e3d3d3c62176885b Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 13:47:57 +0530 Subject: [PATCH 07/13] Fix vpcid --- .../com/cloud/network/rules/FirewallRule.java | 4 +- .../firewall/CreateEgressFirewallRuleCmd.java | 2 +- .../user/firewall/CreateFirewallRuleCmd.java | 33 ++- .../firewall/CreatePortForwardingRuleCmd.java | 4 +- .../user/nat/CreateIpForwardingRuleCmd.java | 9 +- .../firewall/CreateFirewallRuleCmdTest.java | 29 ++ .../network/rules/StaticNatRuleImpl.java | 7 +- .../cloud/network/rules/FirewallRuleVO.java | 17 +- .../java/com/cloud/api/ApiResponseHelper.java | 9 +- .../cloud/network/IpAddressManagerImpl.java | 58 +++- .../com/cloud/network/NetworkModelImpl.java | 41 ++- .../element/VpcVirtualRouterElement.java | 8 +- .../network/firewall/FirewallManagerImpl.java | 273 ++++++++++++++++-- .../element/VpcVirtualRouterElementTest.java | 25 ++ .../network/firewall/FirewallManagerTest.java | 42 ++- 15 files changed, 503 insertions(+), 58 deletions(-) diff --git a/api/src/main/java/com/cloud/network/rules/FirewallRule.java b/api/src/main/java/com/cloud/network/rules/FirewallRule.java index 369c6aa57eb..38ba009163c 100644 --- a/api/src/main/java/com/cloud/network/rules/FirewallRule.java +++ b/api/src/main/java/com/cloud/network/rules/FirewallRule.java @@ -69,7 +69,9 @@ public interface FirewallRule extends ControlledEntity, Identity, InternalIdenti State getState(); - long getNetworkId(); + Long getNetworkId(); + + Long getVpcId(); Long getSourceIpAddressId(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java index 3fd571b7a47..e2c84ac85c7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java @@ -212,7 +212,7 @@ public class CreateEgressFirewallRuleCmd extends BaseAsyncCreateCmd implements F } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java index bc65126f33b..30dd1a2d015 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java @@ -223,13 +223,9 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal } @Override - public long getNetworkId() { - IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId()); - Long ntwkId = null; - - if (ip.getAssociatedWithNetworkId() != null) { - ntwkId = ip.getAssociatedWithNetworkId(); - } + public Long getNetworkId() { + IpAddress ip = getIp(); + Long ntwkId = isVpcIp(ip) ? getVpcNetworkIdForFirewallRule(ip) : getIsolatedNetworkIdForFirewallRule(ip); if (ntwkId == null) { throw new InvalidParameterValueException("Unable to create firewall rule for the IP address ID=" + ipAddressId + @@ -238,6 +234,12 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal return ntwkId; } + @Override + public Long getVpcId() { + IpAddress ip = getIp(); + return isVpcIp(ip) ? ip.getVpcId() : null; + } + @Override public long getEntityOwnerId() { Account account = CallContext.current().getCallingAccount(); @@ -300,7 +302,21 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal @Override public Long getSyncObjId() { - return getIp().getAssociatedWithNetworkId(); + Long syncObjId = getIp().getAssociatedWithNetworkId(); + return syncObjId != null ? syncObjId : getNetworkId(); + } + + private boolean isVpcIp(IpAddress ip) { + return ip.getVpcId() != null; + } + + private Long getIsolatedNetworkIdForFirewallRule(IpAddress ip) { + return ip.getAssociatedWithNetworkId(); + } + + private Long getVpcNetworkIdForFirewallRule(IpAddress ip) { + // VPC flow is independent from tier association; manager resolves execution network. + return ip.getNetworkId(); } private IpAddress getIp() { @@ -311,6 +327,7 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal return ip; } + @Override public Integer getIcmpCode() { if (icmpCode != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index 2bc5fc2ee68..66fc118395e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -176,7 +176,7 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P } } - private Long getVpcId() { + public Long getVpcId() { if (ipAddressId != null) { IpAddress ipAddr = _networkService.getIp(ipAddressId); if (ipAddr == null || !ipAddr.readyToUse()) { @@ -275,7 +275,7 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P } @Override - public long getNetworkId() { + public Long getNetworkId() { IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId()); Long ntwkId = _networkService.getPreferredNetworkIdForPublicIpRuleAssignment(ip, networkId); if (ntwkId == null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java index 7963dfe5c7d..98487ddeb19 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java @@ -229,8 +229,13 @@ public class CreateIpForwardingRuleCmd extends BaseAsyncCreateCmd implements Sta } @Override - public long getNetworkId() { - return -1; + public Long getNetworkId() { + return -1L; + } + + @Override + public Long getVpcId() { + return null; } @Override diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java index c905974b2be..504d3914b70 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java @@ -21,10 +21,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import com.cloud.network.IpAddress; +import com.cloud.network.NetworkService; +import com.cloud.utils.db.EntityManager; import org.apache.commons.collections.CollectionUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; @@ -33,6 +38,12 @@ import com.cloud.utils.net.NetUtils; @RunWith(MockitoJUnitRunner.class) public class CreateFirewallRuleCmdTest { + @Mock + private EntityManager entityManager; + + @Mock + private NetworkService networkService; + private void validateAllIp4Cidr(final CreateFirewallRuleCmd cmd) { Assert.assertTrue(CollectionUtils.isNotEmpty(cmd.getSourceCidrList())); Assert.assertEquals(1, cmd.getSourceCidrList().size()); @@ -88,4 +99,22 @@ public class CreateFirewallRuleCmdTest { Assert.assertEquals(2, cmd.getSourceCidrList().size()); Assert.assertEquals(cidr, cmd.getSourceCidrList().get(1)); } + + @Test + public void testGetNetworkIdVpcWithoutAssociatedNetworkUsesVpcFallbackAndSyncObjId() { + final CreateFirewallRuleCmd cmd = new CreateFirewallRuleCmd(); + final IpAddress ip = Mockito.mock(IpAddress.class); + + cmd._entityMgr = entityManager; + cmd._networkService = networkService; + ReflectionTestUtils.setField(cmd, "ipAddressId", 42L); + + Mockito.when(networkService.getIp(42L)).thenReturn(ip); + Mockito.when(ip.getAssociatedWithNetworkId()).thenReturn(null); + Mockito.when(ip.getVpcId()).thenReturn(100L); + Mockito.when(ip.getNetworkId()).thenReturn(2L); + + Assert.assertEquals(Long.valueOf(2L), cmd.getNetworkId()); + Assert.assertEquals(Long.valueOf(2L), cmd.getSyncObjId()); + } } diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java index 4d8270ca078..98060617602 100644 --- a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java +++ b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java @@ -80,10 +80,15 @@ public class StaticNatRuleImpl implements StaticNatRule { } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } + @Override + public Long getVpcId() { + return null; + } + @Override public long getId() { return id; diff --git a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java index 1dfdc5093a5..7c9c334a85e 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java @@ -85,6 +85,9 @@ public class FirewallRuleVO implements FirewallRule { @Column(name = "network_id") Long networkId; + @Column(name = "vpc_id") + Long vpcId; + @Column(name = "icmp_code") Integer icmpCode; @@ -190,10 +193,18 @@ public class FirewallRuleVO implements FirewallRule { } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } + public Long getVpcId() { + return vpcId; + } + + public void setVpcId(Long vpcId) { + this.vpcId = vpcId; + } + @Override public FirewallRuleType getType() { return type; @@ -207,7 +218,7 @@ public class FirewallRuleVO implements FirewallRule { uuid = UUID.randomUUID().toString(); } - public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, long networkId, long accountId, long domainId, + public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, Long networkId, long accountId, long domainId, Purpose purpose, List sourceCidrs, Integer icmpCode, Integer icmpType, Long related, TrafficType trafficType) { this.xId = xId; if (xId == null) { @@ -251,7 +262,7 @@ public class FirewallRuleVO implements FirewallRule { } - public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, long networkId, long accountId, long domainId, + public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, Long networkId, long accountId, long domainId, Purpose purpose, List sourceCidrs, List destCidrs, Integer icmpCode, Integer icmpType, Long related, TrafficType trafficType) { this(xId,ipAddressId, portStart, portEnd, protocol, networkId, accountId, domainId, purpose, sourceCidrs, icmpCode, icmpType, related, trafficType); this.destinationCidrs = destCidrs; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 11ac3d639e9..de7f5630d6c 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2952,8 +2952,13 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport { } } - Network network = ApiDBUtils.findNetworkById(fwRule.getNetworkId()); - response.setNetworkId(network.getUuid()); + Long networkId = fwRule.getNetworkId(); + if (networkId != null) { + Network network = ApiDBUtils.findNetworkById(networkId); + if (network != null) { + response.setNetworkId(network.getUuid()); + } + } FirewallRule.State state = fwRule.getState(); String stateToSet = state.toString(); diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 7f41a1a106c..ea757f867b3 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -656,28 +656,60 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage } boolean success = true; - Network network = _networksDao.findById(rules.get(0).getNetworkId()); - FirewallRuleVO.TrafficType trafficType = rules.get(0).getTrafficType(); + FirewallRule firstRule = rules.get(0); + Long networkId = firstRule.getNetworkId(); + Long vpcId = firstRule.getVpcId(); + FirewallRuleVO.TrafficType trafficType = firstRule.getTrafficType(); List publicIps = new ArrayList(); - if (!(rules.get(0).getPurpose() == FirewallRule.Purpose.Firewall && trafficType == FirewallRule.TrafficType.Egress)) { - // get the list of public ip's owned by the network - List userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null); - if (userIps != null && !userIps.isEmpty()) { - for (IPAddressVO userIp : userIps) { - PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId())); - publicIps.add(publicIp); + // For VPC firewall rules the networkId on the rule is null; resolve via VPC. + Network network; + if (networkId != null) { + network = _networksDao.findById(networkId); + } else if (vpcId != null) { + List vpcNetworks = _vpcMgr.getVpcNetworks(vpcId); + network = (vpcNetworks != null && !vpcNetworks.isEmpty()) ? _networksDao.findById(vpcNetworks.get(0).getId()) : null; + } else { + network = null; + } + + if (network == null) { + logger.warn("Unable to resolve network for firewall rules (networkId={}, vpcId={}); skipping IP association", networkId, vpcId); + } else if (!(firstRule.getPurpose() == FirewallRule.Purpose.Firewall && trafficType == FirewallRule.TrafficType.Egress)) { + // For VPC ingress rules, collect public IPs tied to the VPC rather than network association + if (vpcId != null && networkId == null) { + List vpcIps = _ipAddressDao.listByAssociatedVpc(vpcId, null); + if (vpcIps != null) { + for (IPAddressVO userIp : vpcIps) { + PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId())); + publicIps.add(publicIp); + } + } + } else { + // get the list of public ip's owned by the network + List userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null); + if (userIps != null && !userIps.isEmpty()) { + for (IPAddressVO userIp : userIps) { + PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId())); + publicIps.add(publicIp); + } } } } - // rules can not programmed unless IP is associated with network service provider, so run IP assoication for + + // rules can not programmed unless IP is associated with network service provider, so run IP association for // the network so as to ensure IP is associated before applying rules (in add state) - if (checkIfIpAssocRequired(network, false, publicIps)) { + if (network != null && checkIfIpAssocRequired(network, false, publicIps)) { applyIpAssociations(network, false, continueOnError, publicIps); } try { - applier.applyRules(network, purpose, rules); + if (network != null) { + applier.applyRules(network, purpose, rules); + } else { + logger.warn("Skipping applyRules: no network resolved for rules (vpcId={})", vpcId); + success = false; + } } catch (ResourceUnavailableException e) { if (!continueOnError) { throw e; @@ -688,7 +720,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage // if there are no active rules associated with a public IP, then public IP need not be associated with a provider. // This IPAssoc ensures, public IP is dis-associated after last active rule is revoked. - if (checkIfIpAssocRequired(network, true, publicIps)) { + if (network != null && checkIfIpAssocRequired(network, true, publicIps)) { applyIpAssociations(network, true, continueOnError, publicIps); } diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index 07305c0e641..3fe9363b4d5 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -104,9 +104,11 @@ import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.rules.dao.PortForwardingRulesDao; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcGatewayVO; +import com.cloud.network.vpc.VpcOfferingServiceMapVO; import com.cloud.network.vpc.dao.PrivateIpDao; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.network.vpc.dao.VpcGatewayDao; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offerings.NetworkOfferingServiceMapVO; @@ -183,6 +185,8 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi NetworkPermissionDao _networkPermissionDao; @Inject VpcDao vpcDao; + @Inject + VpcOfferingServiceMapDao _vpcOffSvcMapDao; private List networkElements; @@ -457,12 +461,16 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi // We only support one provider for one service now Map> serviceToProviders = getServiceProvidersMap(networkId); // Since IP already has service to bind with, the oldProvider can't be null - Set newProviders = serviceToProviders.get(service); + Set newProviders = getProvidersForServiceWithVpcFallback(serviceToProviders, service, publicIp.getVpcId()); if (newProviders == null || newProviders.isEmpty()) { throw new InvalidParameterValueException("There is no new provider for IP " + publicIp.getAddress() + " of service " + service.getName() + "!"); } Provider newProvider = (Provider)newProviders.toArray()[0]; - Set oldProviders = serviceToProviders.get(services.toArray()[0]); + Service existingService = (Service) services.toArray()[0]; + Set oldProviders = getProvidersForServiceWithVpcFallback(serviceToProviders, existingService, publicIp.getVpcId()); + if (oldProviders == null || oldProviders.isEmpty()) { + throw new InvalidParameterValueException("There is no existing provider for IP " + publicIp.getAddress() + " of service " + existingService.getName() + "!"); + } Provider oldProvider = (Provider)oldProviders.toArray()[0]; Network network = _networksDao.findById(networkId); NetworkElement oldElement = getElementImplementingProvider(oldProvider.getName()); @@ -477,6 +485,35 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi return true; } + private Set getProvidersForServiceWithVpcFallback(Map> serviceToProviders, Service service, Long vpcId) { + Set providers = serviceToProviders.get(service); + if (providers != null && !providers.isEmpty()) { + return providers; + } + + if (vpcId == null || service != Service.Firewall) { + return providers; + } + + Set vpcProviders = new HashSet(); + Vpc vpc = vpcDao.findById(vpcId); + if (vpc == null) { + return vpcProviders; + } + + List offeringProviders = _vpcOffSvcMapDao.listProvidersForServiceForVpcOffering(vpc.getVpcOfferingId(), Service.Firewall); + if (offeringProviders != null) { + for (VpcOfferingServiceMapVO offeringProvider : offeringProviders) { + Provider provider = Provider.getProvider(offeringProvider.getProvider()); + if (provider != null) { + vpcProviders.add(provider); + } + } + } + + return vpcProviders; + } + Map> getProviderServicesMap(long networkId) { Map> map = new HashMap>(); List nsms = _ntwkSrvcDao.getServicesInNetwork(networkId); 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 2b12c1a9f13..c1311c96eb7 100644 --- a/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java @@ -139,7 +139,13 @@ public class VpcVirtualRouterElement extends VirtualRouterElement implements Vpc return false; } } else { - if (!_networkMdl.isProviderSupportServiceInNetwork(network.getId(), service, getProvider())) { + boolean supportsService; + if (service == Service.Firewall) { + supportsService = _vpcMgr.isProviderSupportServiceInVpc(network.getVpcId(), service, getProvider()); + } else { + supportsService = _networkMdl.isProviderSupportServiceInNetwork(network.getId(), service, getProvider()); + } + if (!supportsService) { logger.trace("Element " + getProvider().getName() + " doesn't support service " + service.getName() + " in the network " + network); return false; } diff --git a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java index 4584c201a11..8f6539e5e59 100644 --- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java +++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java @@ -198,25 +198,62 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, if (sourceCidrs != null && !sourceCidrs.isEmpty()) Collections.replaceAll(sourceCidrs, "0.0.0.0/0", network.getCidr()); - return createFirewallRule(null, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), sourceCidrs, rule.getDestinationCidrList(), - rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay()); + return createFirewallRuleForNonVPC(null, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), sourceCidrs, + rule.getDestinationCidrList(), rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay()); } @Override @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", create = true) public FirewallRule createIngressFirewallRule(FirewallRule rule) throws NetworkRuleConflictException { - Account caller = CallContext.current().getCallingAccount(); + Account caller = CallContext.current().getCallingAccount(); Long sourceIpAddressId = rule.getSourceIpAddressId(); + IPAddressVO sourceIp = getSourceIpForIngressRule(sourceIpAddressId); - return createFirewallRule(sourceIpAddressId, caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), - rule.getSourceCidrList(), null, rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay()); + if (sourceIp.getVpcId() != null) { + return createIngressFirewallRuleForVpcIp(rule, caller, sourceIp); + } + return createIngressFirewallRuleForIsolatedIp(rule, caller, sourceIp); + } + + protected IPAddressVO getSourceIpForIngressRule(Long sourceIpAddressId) { + if (sourceIpAddressId == null) { + return null; + } + return _ipAddressDao.findById(sourceIpAddressId); + } + + protected FirewallRule createIngressFirewallRuleForIsolatedIp(FirewallRule rule, Account caller, IPAddressVO sourceIp) + throws NetworkRuleConflictException { + return createFirewallRuleForNonVPC(rule.getSourceIpAddressId(), caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), + rule.getProtocol(), rule.getSourceCidrList(), null, rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), + rule.getNetworkId(), rule.getTrafficType(), rule.isDisplay()); + } + + protected FirewallRule createIngressFirewallRuleForVpcIp(FirewallRule rule, Account caller, IPAddressVO sourceIp) + throws NetworkRuleConflictException { + Long vpcId = sourceIp != null ? sourceIp.getVpcId() : null; + return createFirewallRuleForVpc(rule.getSourceIpAddressId(), caller, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), + rule.getProtocol(), rule.getSourceCidrList(), null, rule.getIcmpCode(), rule.getIcmpType(), null, rule.getType(), + vpcId, rule.getTrafficType(), rule.isDisplay()); } //Destination CIDR capability is currently implemented for egress rules only. For others, the field is passed as null. @DB protected FirewallRule createFirewallRule(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, final String protocol, final List sourceCidrList, final List destCidrList, final Integer icmpCode, final Integer icmpType, final Long relatedRuleId, - final FirewallRule.FirewallRuleType type, final Long networkId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException { + final FirewallRule.FirewallRuleType type, final Long networkId, final Long vpcId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException { + if (vpcId != null) { + return createFirewallRuleForVpc(ipAddrId, caller, xId, portStart, portEnd, protocol, sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, + type, vpcId, trafficType, forDisplay); + } + return createFirewallRuleForNonVPC(ipAddrId, caller, xId, portStart, portEnd, protocol, sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, + type, networkId, trafficType, forDisplay); + } + + @DB + protected FirewallRule createFirewallRuleForNonVPC(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, final String protocol, + final List sourceCidrList, final List destCidrList, final Integer icmpCode, final Integer icmpType, final Long relatedRuleId, + final FirewallRule.FirewallRuleType type, final Long networkId, final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException { IPAddressVO ipAddress = null; try { // Validate ip address @@ -283,6 +320,158 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, } } + @DB + protected FirewallRule createFirewallRuleForVpc(final Long ipAddrId, Account caller, final String xId, final Integer portStart, final Integer portEnd, final String protocol, + final List sourceCidrList, final List destCidrList, final Integer icmpCode, final Integer icmpType, + final Long relatedRuleId, final FirewallRuleType type, final Long vpcId, + final FirewallRule.TrafficType trafficType, final Boolean forDisplay) throws NetworkRuleConflictException { + IPAddressVO ipAddress = null; + try { + Long resolvedVpcId = vpcId; + if (ipAddrId != null) { + ipAddress = _ipAddressDao.acquireInLockTable(ipAddrId); + if (ipAddress == null) { + throw new InvalidParameterValueException("Unable to create firewall rule; " + "couldn't locate IP address by id in the system"); + } + resolvedVpcId = resolvedVpcId != null ? resolvedVpcId : ipAddress.getVpcId(); + } + + if (resolvedVpcId == null) { + throw new InvalidParameterValueException("Unable to create VPC firewall rule; couldn't locate VPC id"); + } + + validateFirewallRuleForVpc(caller, ipAddress, portStart, portEnd, protocol, Purpose.Firewall, type, resolvedVpcId, trafficType); + + if (!protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (icmpCode != null || icmpType != null)) { + throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only"); + } + + if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) { + throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP"); + } + + Long accountId = null; + Long domainId = null; + + if (ipAddress != null) { + accountId = ipAddress.getAllocatedToAccountId(); + domainId = ipAddress.getAllocatedInDomainId(); + } else { + Vpc vpc = _vpcMgr.getActiveVpc(resolvedVpcId); + if (vpc == null) { + throw new InvalidParameterValueException("Unable to create VPC firewall rule; couldn't locate VPC by id=" + resolvedVpcId); + } + accountId = vpc.getAccountId(); + domainId = vpc.getDomainId(); + } + + final Long accountIdFinal = accountId; + final Long domainIdFinal = domainId; + final Long resolvedNetworkIdFinal = null; + final Long resolvedVpcIdFinal = resolvedVpcId; + return Transaction.execute((TransactionCallbackWithException) status -> { + FirewallRuleVO newRule = new FirewallRuleVO(xId, ipAddrId, portStart, portEnd, protocol.toLowerCase(), resolvedNetworkIdFinal, accountIdFinal, domainIdFinal, Purpose.Firewall, + sourceCidrList, destCidrList, icmpCode, icmpType, relatedRuleId, trafficType); + newRule.setVpcId(resolvedVpcIdFinal); + newRule.setType(type); + if (forDisplay != null) { + newRule.setDisplay(forDisplay); + } + newRule = _firewallDao.persist(newRule); + + if (type == FirewallRuleType.User) + detectRulesConflict(newRule); + + if (!_firewallDao.setStateToAdd(newRule)) { + throw new CloudRuntimeException("Unable to update the state to add for " + newRule); + } + CallContext.current().setEventDetails("Rule ID: " + newRule.getUuid()); + CallContext.current().putContextParameter(FirewallRule.class, newRule.getId()); + + return newRule; + }); + } finally { + if (ipAddrId != null) { + _ipAddressDao.releaseFromLockTable(ipAddrId); + } + } + } + + protected void validateFirewallRuleForVpc(Account caller, IPAddressVO ipAddress, Integer portStart, Integer portEnd, String proto, Purpose purpose, + FirewallRuleType type, Long vpcId, FirewallRule.TrafficType trafficType) { + if (portStart != null && !NetUtils.isValidPort(portStart)) { + throw new InvalidParameterValueException("publicPort is an invalid value: " + portStart); + } + if (portEnd != null && !NetUtils.isValidPort(portEnd)) { + throw new InvalidParameterValueException("Public port range is an invalid value: " + portEnd); + } + if (portStart != null && portEnd != null && portStart > portEnd) { + throw new InvalidParameterValueException("Start port can't be bigger than end port"); + } + + if (ipAddress == null && type == FirewallRuleType.System) { + return; + } + + if (vpcId == null) { + throw new InvalidParameterValueException("Unable to retrieve VPC id to validate the rule"); + } + + if (ipAddress != null) { + _accountMgr.checkAccess(caller, null, true, ipAddress); + } + + Vpc vpc = _vpcMgr.getActiveVpc(vpcId); + if (vpc == null) { + throw new InvalidParameterValueException("Unable to retrieve VPC to validate the rule by id=" + vpcId); + } + + Map caps = null; + if (purpose == Purpose.Firewall) { + caps = getFirewallServiceCapabilitiesForVpc(vpcId); + } + + if (caps != null) { + String supportedTrafficTypes = null; + if (purpose == FirewallRule.Purpose.Firewall) { + supportedTrafficTypes = caps.get(Capability.SupportedTrafficDirection).toLowerCase(); + } + + String supportedProtocols; + if (purpose == FirewallRule.Purpose.Firewall && trafficType == FirewallRule.TrafficType.Egress) { + supportedProtocols = caps.get(Capability.SupportedEgressProtocols).toLowerCase(); + } else { + supportedProtocols = caps.get(Capability.SupportedProtocols).toLowerCase(); + } + + if (!supportedProtocols.contains(proto.toLowerCase())) { + throw new InvalidParameterValueException("Protocol " + proto + " is not supported in VPC " + vpcId); + } else if (proto.equalsIgnoreCase(NetUtils.ICMP_PROTO) && purpose != Purpose.Firewall) { + throw new InvalidParameterValueException("Protocol " + proto + " is currently supported only for rules with purpose " + Purpose.Firewall); + } else if (purpose == Purpose.Firewall && !supportedTrafficTypes.contains(trafficType.toString().toLowerCase())) { + throw new InvalidParameterValueException(String.format("Traffic Type %s is currently supported by Firewall in VPC %s", trafficType, vpc.getUuid())); + } + } + } + + protected Map getFirewallServiceCapabilitiesForVpc(Long vpcId) { + for (FirewallServiceProvider fwElement : _firewallElements) { + Network.Provider provider = fwElement.getProvider(); + if (_vpcMgr.isProviderSupportServiceInVpc(vpcId, Service.Firewall, provider)) { + Map> capabilities = fwElement.getCapabilities(); + if (capabilities != null && capabilities.get(Service.Firewall) != null) { + return capabilities.get(Service.Firewall); + } + } + } + return null; + } + + protected Long resolveIsolatedFirewallRuleNetworkId(IPAddressVO ipAddress, Long networkId) { + _networkModel.checkIpForService(ipAddress, Service.Firewall, networkId); + return ipAddress.getAssociatedWithNetworkId(); + } + @Override public Pair, Integer> listFirewallRules(IListFirewallRulesCmd cmd) { Long ipId = cmd.getIpAddressId(); @@ -399,9 +588,16 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, assert (rules.size() >= 1); } - NetworkVO newRuleNetwork = getNewRuleNetwork(newRule); - boolean newRuleIsOnVpcNetwork = newRuleNetwork.getVpcId() != null; - boolean vpcConserveModeEnabled = _vpcMgr.isNetworkOnVpcEnabledConserveMode(newRuleNetwork); + Long newRuleVpcId = newRule.getVpcId(); + boolean newRuleIsVpc = newRuleVpcId != null; + NetworkVO newRuleNetwork = null; + boolean newRuleIsOnVpcNetwork = false; + boolean vpcConserveModeEnabled = false; + if (!newRuleIsVpc) { + newRuleNetwork = getNewRuleNetwork(newRule); + newRuleIsOnVpcNetwork = newRuleNetwork.getVpcId() != null; + vpcConserveModeEnabled = newRuleIsOnVpcNetwork && _vpcMgr.isNetworkOnVpcEnabledConserveMode(newRuleNetwork); + } for (FirewallRuleVO rule : rules) { if (rule.getId() == newRule.getId()) { @@ -452,8 +648,8 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, // Checking if the rule applied is to the same network that is passed in the rule. // (except for VPCs with conserve mode = true) - if ((!newRuleIsOnVpcNetwork || !vpcConserveModeEnabled) - && rule.getNetworkId() != newRule.getNetworkId() && rule.getState() != State.Revoke) { + if (!newRuleIsVpc && (!newRuleIsOnVpcNetwork || !vpcConserveModeEnabled) + && !Objects.equals(rule.getNetworkId(), newRule.getNetworkId()) && rule.getState() != State.Revoke) { String errMsg = String.format("New rule is for a different network than what's specified in rule %s", rule.getXid()); if (newRuleIsOnVpcNetwork) { Vpc vpc = _vpcMgr.getActiveVpc(newRuleNetwork.getVpcId()); @@ -575,11 +771,9 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, } if (ipAddress != null) { - if (ipAddress.getAssociatedWithNetworkId() == null) { - throw new InvalidParameterValueException("Unable to create firewall rule ; ip with specified id is not associated with any network"); - } else { - networkId = ipAddress.getAssociatedWithNetworkId(); - } + networkId = isVpcIpAddress(ipAddress) + ? validateFirewallRuleForVpcIp(ipAddress, networkId) + : validateFirewallRuleForIsolatedIp(ipAddress); // Validate ip address _accountMgr.checkAccess(caller, null, true, ipAddress); @@ -610,7 +804,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, if (routedIpv4Manager.isVirtualRouterGateway(network)) { throw new CloudRuntimeException("Unable to create routing firewall rule. Please use routing firewall API instead."); } - caps = _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Firewall); + caps = getFirewallServiceCapabilities(network); } if (caps != null) { @@ -637,6 +831,41 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, } + protected boolean isVpcIpAddress(IPAddressVO ipAddress) { + return ipAddress.getVpcId() != null; + } + + protected Long validateFirewallRuleForIsolatedIp(IPAddressVO ipAddress) { + if (ipAddress.getAssociatedWithNetworkId() == null) { + throw new InvalidParameterValueException("Unable to create firewall rule ; ip with specified id is not associated with any network"); + } + return ipAddress.getAssociatedWithNetworkId(); + } + + protected Long validateFirewallRuleForVpcIp(IPAddressVO ipAddress, Long networkId) { + if (networkId == null) { + throw new InvalidParameterValueException("Unable to retrieve network id to validate the rule"); + } + return networkId; + } + + protected Map getFirewallServiceCapabilities(Network network) { + if (network.getVpcId() == null) { + return _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Firewall); + } + + for (FirewallServiceProvider fwElement : _firewallElements) { + Network.Provider provider = fwElement.getProvider(); + if (_vpcMgr.isProviderSupportServiceInVpc(network.getVpcId(), Service.Firewall, provider)) { + Map> capabilities = fwElement.getCapabilities(); + if (capabilities != null && capabilities.get(Service.Firewall) != null) { + return capabilities.get(Service.Firewall); + } + } + } + return _networkModel.getNetworkServiceCapabilities(network.getId(), Service.Firewall); + } + @Override public boolean applyRules(List rules, boolean continueOnError, boolean updateRulesInDB) throws ResourceUnavailableException { boolean success = true; @@ -784,8 +1013,10 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, for (FirewallRuleVO rule : rules) { // validate rule - for NSX - long networkId = rule.getNetworkId(); - validateNsxConstraints(networkId, rule); + Long networkId = rule.getNetworkId(); + if (networkId != null) { + validateNsxConstraints(networkId, rule); + } // load cidrs if any rule.setSourceCidrList(_firewallCidrsDao.getSourceCidrs(rule.getId())); rule.setDestinationCidrsList(_firewallDcidrsDao.getDestCidrs(rule.getId())); @@ -1045,7 +1276,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, List oneCidr = new ArrayList(); oneCidr.add(NetUtils.ALL_IP4_CIDRS); return createFirewallRule(ipAddrId, caller, null, startPort, endPort, protocol, oneCidr, null, icmpCode, icmpType, relatedRuleId, FirewallRule.FirewallRuleType.User, - networkId, FirewallRule.TrafficType.Ingress, true); + networkId, null, FirewallRule.TrafficType.Ingress, true); } @Override @@ -1160,7 +1391,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, _firewallDao.loadSourceCidrs(rule); } createFirewallRule(ip.getId(), acct, rule.getXid(), rule.getSourcePortStart(), rule.getSourcePortEnd(), rule.getProtocol(), rule.getSourceCidrList(),null, - rule.getIcmpCode(), rule.getIcmpType(), rule.getRelated(), FirewallRuleType.System, rule.getNetworkId(), rule.getTrafficType(), true); + rule.getIcmpCode(), rule.getIcmpType(), rule.getRelated(), FirewallRuleType.System, rule.getNetworkId(), rule.getVpcId(), rule.getTrafficType(), true); } catch (Exception e) { logger.debug("Failed to add system wide firewall rule, due to:" + e.toString()); } diff --git a/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java b/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java index 20ddb39d943..ede1feb7428 100644 --- a/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java +++ b/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java @@ -19,10 +19,13 @@ package com.cloud.network.element; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; import com.cloud.network.RemoteAccessVpn; import com.cloud.network.VpnUser; import com.cloud.network.router.VpcVirtualNetworkApplianceManagerImpl; import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.utils.db.EntityManager; import com.cloud.vm.DomainRouterVO; @@ -43,7 +46,9 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,6 +65,12 @@ public class VpcVirtualRouterElementTest { @Mock EntityManager _entityMgr; + @Mock + NetworkModel _networkMdl; + + @Mock + VpcManager _vpcMgr; + @Mock NetworkTopologyContext networkTopologyContext; @@ -188,4 +199,18 @@ public class VpcVirtualRouterElementTest { verify(remoteAccessVpn, times(1)).getVpcId(); } + + @Test + public void testCanHandleFirewallUsesVpcCapability() { + final Network network = Mockito.mock(Network.class); + + when(_networkMdl.getPhysicalNetworkId(network)).thenReturn(1L); + when(network.getVpcId()).thenReturn(100L); + when(_networkMdl.isProviderEnabledInPhysicalNetwork(1L, Network.Provider.VPCVirtualRouter.getName())).thenReturn(true); + when(_vpcMgr.isProviderSupportServiceInVpc(100L, Network.Service.Firewall, Network.Provider.VPCVirtualRouter)).thenReturn(true); + + assertTrue(vpcVirtualRouterElement.canHandle(network, Network.Service.Firewall)); + verify(_vpcMgr).isProviderSupportServiceInVpc(100L, Network.Service.Firewall, Network.Provider.VPCVirtualRouter); + verify(_networkMdl, never()).isProviderSupportServiceInNetwork(network.getId(), Network.Service.Firewall, Network.Provider.VPCVirtualRouter); + } } diff --git a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java index bacef85479a..e570880545e 100644 --- a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java +++ b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java @@ -21,23 +21,29 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Service; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkRuleApplier; import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.element.FirewallServiceProvider; import com.cloud.network.element.VirtualRouterElement; import com.cloud.network.element.VpcVirtualRouterElement; import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRule.FirewallRuleType; import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcManager; +import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.DomainManager; import com.cloud.utils.component.ComponentContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -53,7 +59,9 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -79,6 +87,8 @@ public class FirewallManagerTest { @Mock IpAddressManager _ipAddrMgr; @Mock + RoutedIpv4Manager routedIpv4Manager; + @Mock FirewallRulesDao _firewallDao; @Mock NetworkDao _networkDao; @@ -115,7 +125,7 @@ public class FirewallManagerTest { } private FirewallRule createFirewallRule(int startPort, int endPort, Purpose purpose) { - return new FirewallRuleVO("xid", 1L, startPort, endPort, "TCP", 2, 3, 4, purpose, new ArrayList<>(), + return new FirewallRuleVO("xid", 1L, startPort, endPort, "TCP", 2L, 3, 4, purpose, new ArrayList<>(), new ArrayList<>(), 5, 6, null, FirewallRule.TrafficType.Ingress); } @@ -332,4 +342,34 @@ public class FirewallManagerTest { Assert.assertFalse(result); } + + @Test + public void testValidateFirewallRuleVpcWithoutAssociatedNetworkUsesVpcCapabilities() { + final Account caller = Mockito.mock(Account.class); + final IPAddressVO ipAddress = Mockito.mock(IPAddressVO.class); + final NetworkVO network = Mockito.mock(NetworkVO.class); + final FirewallServiceProvider firewallServiceProvider = Mockito.mock(FirewallServiceProvider.class); + final Map firewallCaps = new HashMap<>(); + final Map> capabilities = new HashMap<>(); + + firewallCaps.put(Capability.SupportedTrafficDirection, "ingress, egress"); + firewallCaps.put(Capability.SupportedProtocols, "tcp,udp,icmp"); + firewallCaps.put(Capability.SupportedEgressProtocols, "tcp,udp,icmp"); + capabilities.put(Service.Firewall, firewallCaps); + + when(ipAddress.getAssociatedWithNetworkId()).thenReturn(null); + when(ipAddress.getVpcId()).thenReturn(10L); + when(_networkModel.getNetwork(2L)).thenReturn(network); + when(network.getVpcId()).thenReturn(10L); + when(network.getDataCenterId()).thenReturn(1L); + when(routedIpv4Manager.isVirtualRouterGateway(network)).thenReturn(false); + when(firewallServiceProvider.getProvider()).thenReturn(Network.Provider.VPCVirtualRouter); + when(firewallServiceProvider.getCapabilities()).thenReturn(capabilities); + when(_vpcMgr.isProviderSupportServiceInVpc(10L, Service.Firewall, Network.Provider.VPCVirtualRouter)).thenReturn(true); + _firewallMgr._firewallElements = List.of(firewallServiceProvider); + + _firewallMgr.validateFirewallRule(caller, ipAddress, 80, 80, "tcp", Purpose.Firewall, FirewallRuleType.User, 2L, FirewallRule.TrafficType.Ingress); + + verify(_networkModel, Mockito.never()).getNetworkServiceCapabilities(Mockito.anyLong(), Mockito.eq(Service.Firewall)); + } } From 8fec093110efc7145fbcf223a512a2c1c9799d3b Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 15:29:30 +0530 Subject: [PATCH 08/13] Fix delete firewall flow --- .../network/firewall/FirewallManagerImpl.java | 2 +- systemvm/debian/opt/cloud/bin/configure.py | 51 +++++++++++++++++++ ui/src/views/network/PublicIpResource.vue | 14 +++-- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java index 8f6539e5e59..4557de4de4b 100644 --- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java +++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java @@ -894,7 +894,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, success = false; } else { removeRule(rule); - if (rule.getSourceIpAddressId() != null) { + if (rule.getSourceIpAddressId() != null && rule.getVpcId() == null) { //if the rule is the last one for the ip address assigned to VPC, unassign it from the network _vpcMgr.unassignIPFromVpcNetwork(rule.getSourceIpAddressId(), rule.getNetworkId()); } diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index a250229ba9e..84c327cfa29 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -703,6 +703,7 @@ class CsAcl(CsDataBag): self.add_routing_rules() return + desired_firewall_ips = self._get_desired_vpc_firewall_ips() fw_chains_created = set() for item in self.dbag: if item == "id": @@ -725,6 +726,56 @@ class CsAcl(CsDataBag): fw_chains_created.add(src_ip) self.AclIP(self.dbag[item], self.config).create() + if self.config.is_vpc(): + self._cleanup_removed_vpc_firewall_chains(desired_firewall_ips) + + def _get_desired_vpc_firewall_ips(self): + desired_firewall_ips = set() + if not self.config.is_vpc(): + return desired_firewall_ips + + for item in self.dbag: + if item == "id": + continue + rule = self.dbag[item] + if rule.get("purpose") == "Firewall": + src_ip = rule.get("src_ip") + if src_ip: + desired_firewall_ips.add(src_ip) + return desired_firewall_ips + + def _cleanup_removed_vpc_firewall_chains(self, desired_firewall_ips): + """Delete FIREWALL_ chain only when no firewall rule remains for that VPC public IP.""" + try: + mangle_save = CsHelper.execute("iptables-save -t mangle") + existing_firewall_ips = [] + for line in mangle_save: + if line.startswith(":FIREWALL_"): + chain = line.split(" ")[0][1:] + existing_firewall_ips.append(chain.replace("FIREWALL_", "", 1)) + + for src_ip in existing_firewall_ips: + if src_ip in desired_firewall_ips: + continue + self._delete_vpc_firewall_chain(src_ip) + except Exception as e: + logging.debug("Failed VPC firewall chain cleanup: %s", e) + + def _delete_vpc_firewall_chain(self, src_ip): + chain = "FIREWALL_%s" % src_ip + try: + prerouting_rules = CsHelper.execute("iptables -t mangle -S PREROUTING") + for rule in prerouting_rules: + if ("-d %s/32" % src_ip) in rule and ("-j %s" % chain) in rule: + delete_rule = rule.replace("-A PREROUTING", "-D PREROUTING", 1) + CsHelper.execute2("iptables -t mangle %s" % delete_rule, False) + + CsHelper.execute2("iptables -t mangle -F %s" % chain, False) + CsHelper.execute2("iptables -t mangle -X %s" % chain, False) + logging.info("Deleted VPC firewall chain %s as last firewall rule was removed", chain) + except Exception as e: + logging.debug("Failed deleting VPC firewall chain %s: %s", chain, e) + class CsIpv6Firewall(CsDataBag): """ Deal with IPv6 Firewall diff --git a/ui/src/views/network/PublicIpResource.vue b/ui/src/views/network/PublicIpResource.vue index 5a5ea99a003..0f77cb52937 100644 --- a/ui/src/views/network/PublicIpResource.vue +++ b/ui/src/views/network/PublicIpResource.vue @@ -147,11 +147,15 @@ export default { // VPC IPs with static nat keep existing VPN behavior and always show firewall if (this.resource.isstaticnat) { - let tabs = this.defaultTabs - if (this.resource.virtualmachinetype === 'DomainRouter') { - tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) - } - this.tabs = hasFirewallCapability ? this.addFirewallTab(tabs) : tabs + const tabs = this.addFirewallTab(this.$route.meta.tabs).map(tab => { + if (tab.name !== 'firewall') { + return tab + } + const staticNatFirewallTab = { ...tab } + delete staticNatFirewallTab.networkServiceFilter + return staticNatFirewallTab + }) + this.tabs = tabs return } From 662356fd307faa5e1274164d90819310b580f6fd Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 16:01:50 +0530 Subject: [PATCH 09/13] remove default offering changes --- .../upgrade/dao/Upgrade42210to42300.java | 64 ------------------- 1 file changed, 64 deletions(-) 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 a8e5365e5c1..393f2039950 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,8 +17,6 @@ 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; @@ -53,7 +51,6 @@ public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgr @Override public void performDataMigration(Connection conn) { unhideJsInterpretationEnabled(conn); - updateVpcDefaultOfferingsWithFirewallService(conn); } protected void unhideJsInterpretationEnabled(Connection conn) { @@ -92,65 +89,4 @@ 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); - } - } } From dffd525708b8621319b24c66c74d5ec7e821cc24 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 16:04:47 +0530 Subject: [PATCH 10/13] Fix unit test --- .../kubernetes/cluster/KubernetesClusterManagerImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java index 71949459c86..1fab5420c3c 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImplTest.java @@ -155,7 +155,7 @@ public class KubernetesClusterManagerImplTest { } private FirewallRuleVO createRule(int startPort, int endPort) { - FirewallRuleVO rule = new FirewallRuleVO(null, null, startPort, endPort, "tcp", 1, 1, 1, FirewallRule.Purpose.Firewall, List.of("0.0.0.0/0"), null, null, null, FirewallRule.TrafficType.Ingress); + FirewallRuleVO rule = new FirewallRuleVO(null, null, startPort, endPort, "tcp", 1L, 1, 1, FirewallRule.Purpose.Firewall, List.of("0.0.0.0/0"), null, null, null, FirewallRule.TrafficType.Ingress); return rule; } From b7e560ebe6d8791e7728eb6ba093f7d4e4992709 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 16:13:27 +0530 Subject: [PATCH 11/13] Fix whitespace --- systemvm/debian/opt/cloud/bin/configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index 84c327cfa29..5d4085b091c 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -707,7 +707,7 @@ class CsAcl(CsDataBag): fw_chains_created = set() for item in self.dbag: if item == "id": - continue + 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: From 7fec4f5b395fc07df48b345ca5d7fb8dc500f39a Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 16:18:08 +0530 Subject: [PATCH 12/13] remove Unnecessary Stubbing --- .../java/com/cloud/network/firewall/FirewallManagerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java index e570880545e..c33af926e81 100644 --- a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java +++ b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java @@ -357,11 +357,9 @@ public class FirewallManagerTest { firewallCaps.put(Capability.SupportedEgressProtocols, "tcp,udp,icmp"); capabilities.put(Service.Firewall, firewallCaps); - when(ipAddress.getAssociatedWithNetworkId()).thenReturn(null); when(ipAddress.getVpcId()).thenReturn(10L); when(_networkModel.getNetwork(2L)).thenReturn(network); when(network.getVpcId()).thenReturn(10L); - when(network.getDataCenterId()).thenReturn(1L); when(routedIpv4Manager.isVirtualRouterGateway(network)).thenReturn(false); when(firewallServiceProvider.getProvider()).thenReturn(Network.Provider.VPCVirtualRouter); when(firewallServiceProvider.getCapabilities()).thenReturn(capabilities); From c3b397d6424a0897a12ab3cb9db6325a68ff968a Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 12 May 2026 17:21:00 +0530 Subject: [PATCH 13/13] more unit test fixes --- .../java/com/cloud/network/resource/PaloAltoResourceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/network-elements/palo-alto/src/test/java/com/cloud/network/resource/PaloAltoResourceTest.java b/plugins/network-elements/palo-alto/src/test/java/com/cloud/network/resource/PaloAltoResourceTest.java index 58f962f1372..ae8520e005a 100644 --- a/plugins/network-elements/palo-alto/src/test/java/com/cloud/network/resource/PaloAltoResourceTest.java +++ b/plugins/network-elements/palo-alto/src/test/java/com/cloud/network/resource/PaloAltoResourceTest.java @@ -290,7 +290,7 @@ public class PaloAltoResourceTest { List rules = new ArrayList(); List cidrList = new ArrayList(); cidrList.add("0.0.0.0/0"); - FirewallRuleVO activeVO = new FirewallRuleVO(null, null, 80, 80, "tcp", 1, 1, 1, Purpose.Firewall, cidrList, null, null, null, FirewallRule.TrafficType.Egress); + FirewallRuleVO activeVO = new FirewallRuleVO(null, null, 80, 80, "tcp", 1L, 1, 1, Purpose.Firewall, cidrList, null, null, null, FirewallRule.TrafficType.Egress); FirewallRuleTO active = new FirewallRuleTO(activeVO, Long.toString(vlanId), null, Purpose.Firewall, FirewallRule.TrafficType.Egress); rules.add(active); @@ -319,7 +319,7 @@ public class PaloAltoResourceTest { long vlanId = 3954; List rules = new ArrayList(); - FirewallRuleVO revokedVO = new FirewallRuleVO(null, null, 80, 80, "tcp", 1, 1, 1, Purpose.Firewall, null, null, null, null, FirewallRule.TrafficType.Egress); + FirewallRuleVO revokedVO = new FirewallRuleVO(null, null, 80, 80, "tcp", 1L, 1, 1, Purpose.Firewall, null, null, null, null, FirewallRule.TrafficType.Egress); revokedVO.setState(State.Revoke); FirewallRuleTO revoked = new FirewallRuleTO(revokedVO, Long.toString(vlanId), null, Purpose.Firewall, FirewallRule.TrafficType.Egress); rules.add(revoked);