Skip to content

Commit 4ad583a

Browse files
authored
Merge pull request #447 from American-Cloud/fix_existing_egress_and_tags
Fix: egress rules & tags check existing
2 parents 7521b14 + 3a2dbec commit 4ad583a

File tree

9 files changed

+272
-99
lines changed

9 files changed

+272
-99
lines changed

api/v1beta1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta3/cloudstackisolatednetwork_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ type CloudStackIsolatedNetworkStatus struct {
7373
// Empty value means the network mode is NATTED, not ROUTED.
7474
RoutingMode string `json:"routingMode,omitempty"`
7575

76+
// Indicates whether the necessary firewall egress and routing rules for the isolated network have been applied successfully.
77+
FirewallRulesOpened bool `json:"firewallRulesOpened,omitempty"`
78+
7679
// Ready indicates the readiness of this provider resource.
7780
Ready bool `json:"ready"`
7881
}

config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ spec:
251251
description: CloudStackIsolatedNetworkStatus defines the observed state
252252
of CloudStackIsolatedNetwork
253253
properties:
254+
firewallRulesOpened:
255+
description: Firewall rules open status.
256+
type: boolean
254257
loadBalancerRuleID:
255258
description: The ID of the lb rule used to assign VMs to the lb.
256259
type: string

pkg/cloud/isolated_network.go

Lines changed: 127 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -154,54 +154,149 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
154154

155155
// OpenFirewallRules opens a CloudStack egress firewall for an isolated network.
156156
func (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

Comments
 (0)