@@ -154,54 +154,149 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
154154
155155// OpenFirewallRules opens a CloudStack egress firewall for an isolated network.
156156func (c * client ) OpenFirewallRules (isoNet * infrav1.CloudStackIsolatedNetwork ) (retErr error ) {
157+ // Early return if VPC is present
158+ // Firewall rules are not opened for isolated networks within a VPC because VPCs have their own mechanisms for managing firewall rules.
157159 if isoNet .Spec .VPC != nil && isoNet .Spec .VPC .ID != "" {
158160 return nil
159161 }
160162
161- // If network's egress policy is true, then we don't need to open the firewall rules for all protocols
162- network , count , err := c .cs . Network . GetNetworkByID (isoNet .Spec .ID , cloudstack . WithProject ( c . user . Project . ID ) )
163+ // Get network details
164+ network , err := c .getNetwork (isoNet .Spec .ID )
163165 if err != nil {
164- return errors .Wrapf (err , "failed to get network by ID %s" , isoNet .Spec .ID )
165- }
166- if count == 0 {
167- return errors .Errorf ("no network found with ID %s" , isoNet .Spec .ID )
166+ return err
168167 }
168+
169+ // Early return if egress default policy is true
169170 if network .Egressdefaultpolicy {
171+ isoNet .Status .FirewallRulesOpened = true
170172 return nil
171173 }
172174
175+ // Check if all required firewall rules exist
176+ allRulesPresent , err := c .checkFirewallRules (isoNet )
177+ if err != nil {
178+ return err
179+ }
180+ if allRulesPresent {
181+ isoNet .Status .FirewallRulesOpened = true
182+ return nil
183+ }
184+
185+ // Reset status if rules are missing
186+ isoNet .Status .FirewallRulesOpened = false
187+
188+ // Create firewall rules for each protocol
173189 protocols := []string {NetworkProtocolTCP , NetworkProtocolUDP , NetworkProtocolICMP }
174190 for _ , proto := range protocols {
175- var err error
176- if isoNet .Status .RoutingMode != "" {
177- p := c .cs .Firewall .NewCreateRoutingFirewallRuleParams (isoNet .Spec .ID , proto )
178- if proto == "icmp" {
179- p .SetIcmptype (- 1 )
180- p .SetIcmpcode (- 1 )
181- }
182- _ , err = c .cs .Firewall .CreateRoutingFirewallRule (p )
183- } else {
184- p := c .cs .Firewall .NewCreateEgressFirewallRuleParams (isoNet .Spec .ID , proto )
191+ if err := c .createFirewallRule (isoNet , proto ); err != nil {
192+ c .customMetrics .EvaluateErrorAndIncrementAcsReconciliationErrorCounter (err )
193+ return errors .Wrapf (err , "failed creating firewall rule for network ID %s" , isoNet .Spec .ID )
194+ }
195+ }
196+
197+ // Mark firewall rules as opened if all rules were created successfully
198+ isoNet .Status .FirewallRulesOpened = true
199+ return nil
200+ }
201+
202+ // Helper function to check if all required firewall rules exist
203+ func (c * client ) checkFirewallRules (isoNet * infrav1.CloudStackIsolatedNetwork ) (bool , error ) {
204+ protocols := []string {NetworkProtocolTCP , NetworkProtocolUDP , NetworkProtocolICMP }
205+ p := c .cs .Firewall .NewListEgressFirewallRulesParams ()
206+ p .SetNetworkid (isoNet .Spec .ID )
207+ setIfNotEmpty (c .user .Project .ID , p .SetProjectid )
208+
209+ rules , err := c .cs .Firewall .ListEgressFirewallRules (p )
210+ if err != nil {
211+ return false , errors .Wrapf (err , "failed to list egress firewall rules for network ID %s" , isoNet .Spec .ID )
212+ }
185213
186- if proto == "icmp" {
187- p .SetIcmptype (- 1 )
188- p .SetIcmpcode (- 1 )
214+ // Create a map to track found protocols
215+ foundProtocols := make (map [string ]bool )
216+ for _ , proto := range protocols {
217+ foundProtocols [proto ] = false
218+ }
219+
220+ // Check each rule for matching protocol and parameters
221+ for _ , rule := range rules .EgressFirewallRules {
222+ if _ , exists := foundProtocols [rule .Protocol ]; exists {
223+ if rule .Protocol == "icmp" {
224+ // For ICMP, ensure icmptype and icmpcode are -1
225+ if rule .Icmptype == - 1 && rule .Icmpcode == - 1 {
226+ foundProtocols [rule .Protocol ] = true
227+ }
228+ } else {
229+ // For TCP/UDP, ensure no specific ports are set (all ports)
230+ if rule .Startport == 0 && rule .Endport == 0 {
231+ foundProtocols [rule .Protocol ] = true
232+ }
189233 }
234+ }
235+ }
236+
237+ // Return true only if all required protocols are found
238+ for _ , proto := range protocols {
239+ if ! foundProtocols [proto ] {
240+ return false , nil
241+ }
242+ }
243+ return true , nil
244+ }
245+
246+ // Helper function to get network details
247+ func (c * client ) getNetwork (networkID string ) (* cloudstack.Network , error ) {
248+ network , count , err := c .cs .Network .GetNetworkByID (networkID , cloudstack .WithProject (c .user .Project .ID ))
249+ if err != nil {
250+ return nil , errors .Wrapf (err , "failed to get network by ID %s" , networkID )
251+ }
252+ if count == 0 {
253+ return nil , errors .Errorf ("no network found with ID %s" , networkID )
254+ }
255+ return network , nil
256+ }
190257
191- _ , err = c .cs .Firewall .CreateEgressFirewallRule (p )
258+ // Helper function to create a firewall rule for a given protocol
259+ func (c * client ) createFirewallRule (isoNet * infrav1.CloudStackIsolatedNetwork , proto string ) error {
260+ if isoNet .Status .RoutingMode != "" {
261+ // Handle routing firewall rules
262+ p := c .cs .Firewall .NewCreateRoutingFirewallRuleParams (isoNet .Spec .ID , proto )
263+ if proto == "icmp" {
264+ p .SetIcmptype (- 1 )
265+ p .SetIcmpcode (- 1 )
192266 }
193- if err != nil &&
194- // Ignore errors regarding already existing fw rules for TCP/UDP for non-dynamic routing mode
195- ! strings .Contains (strings .ToLower (err .Error ()), "there is already" ) &&
196- ! strings .Contains (strings .ToLower (err .Error ()), "conflicts with rule" ) &&
197- // Ignore errors regarding already existing fw rule for ICMP
198- ! strings .Contains (strings .ToLower (err .Error ()), "new rule conflicts with existing rule" ) {
199- retErr = errors .Wrapf (
200- err , "failed creating egress firewall rule for network ID %s protocol %s" , isoNet .Spec .ID , proto )
267+ _ , err := c .cs .Firewall .CreateRoutingFirewallRule (p )
268+ if err != nil && ! c .isIgnorableFirewallRuleError (err ) {
269+ return errors .Wrapf (err , "failed creating routing firewall rule for network ID %s protocol %s" , isoNet .Spec .ID , proto )
201270 }
271+ return nil
202272 }
203- c .customMetrics .EvaluateErrorAndIncrementAcsReconciliationErrorCounter (retErr )
204- return retErr
273+
274+ // Handle egress firewall rules
275+ p := c .cs .Firewall .NewCreateEgressFirewallRuleParams (isoNet .Spec .ID , proto )
276+ if proto == "icmp" {
277+ p .SetIcmptype (- 1 )
278+ p .SetIcmpcode (- 1 )
279+ }
280+ _ , err := c .cs .Firewall .CreateEgressFirewallRule (p )
281+ if err != nil && ! c .isIgnorableFirewallRuleError (err ) {
282+ return errors .Wrapf (err , "failed creating egress firewall rule for network ID %s protocol %s" , isoNet .Spec .ID , proto )
283+ }
284+ return nil
285+ }
286+
287+ // Named constants for ignorable error substrings
288+ const (
289+ ErrAlreadyExists = "there is already"
290+ ErrRuleConflict = "conflicts with rule"
291+ ErrNewRuleConflict = "new rule conflicts with existing rule"
292+ )
293+
294+ // Helper function to check if an error is ignorable
295+ func (c * client ) isIgnorableFirewallRuleError (err error ) bool {
296+ errorMsg := strings .ToLower (err .Error ())
297+ return strings .Contains (errorMsg , ErrAlreadyExists ) ||
298+ strings .Contains (errorMsg , ErrRuleConflict ) ||
299+ strings .Contains (errorMsg , ErrNewRuleConflict )
205300}
206301
207302// GetPublicIP gets a public IP with ID for cluster endpoint.
@@ -300,9 +395,9 @@ func (c *client) GetOrCreateIsolatedNetwork(
300395 isoNet * infrav1.CloudStackIsolatedNetwork ,
301396 csCluster * infrav1.CloudStackCluster ,
302397) error {
303- // Get or create the isolated network itself and resolve details into passed custom resources.
304398 net := isoNet .Network ()
305- if err := c .ResolveNetwork (net ); err != nil { // Doesn't exist, create isolated network.
399+ err := c .ResolveNetwork (net )
400+ if err != nil {
306401 if err = c .CreateIsolatedNetwork (fd , isoNet ); err != nil {
307402 return errors .Wrap (err , "creating a new isolated network" )
308403 }
0 commit comments