Skip to content

Commit a315d9e

Browse files
authored
Add support for NetworkACLs for LB on VPC networks (apache#69)
* Add support for NetworkACLs for LB on VPC networks * set source cudr list * set source cudr list * update image * update image * logging changes * add more logs * add logs + fetch network id from public ip for vpc * remove debug logs * revert docker image changes * address comment: delete only 1 matching rule, in case ACL is shared between tiers * increase log verbosity * address comments - oob exception check * preventive addition of rules to default acl lists * prevent re-adding rules on ensureLoadbalancer runs * fix log
1 parent 968f98d commit a315d9e

File tree

1 file changed

+170
-7
lines changed

1 file changed

+170
-7
lines changed

cloudstack_loadbalancer.go

+170-7
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,17 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
181181
return nil, err
182182
}
183183

184-
if lbRule != nil && isFirewallSupported(network.Service) {
185-
klog.V(4).Infof("Creating firewall rules for load balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip, port.Port)
186-
if _, err := lb.updateFirewallRule(lbRule.Publicipid, int(port.Port), protocol, service.Spec.LoadBalancerSourceRanges); err != nil {
187-
return nil, err
184+
if lbRule != nil {
185+
if isFirewallSupported(network.Service) {
186+
klog.V(4).Infof("Creating firewall rules for load balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip, port.Port)
187+
if _, err := lb.updateFirewallRule(lbRule.Publicipid, int(port.Port), protocol, service.Spec.LoadBalancerSourceRanges); err != nil {
188+
return nil, err
189+
}
190+
} else if isNetworkACLSupported(network.Service) {
191+
klog.V(4).Infof("Creating ACL rules for load balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip, port.Port)
192+
if _, err := lb.updateNetworkACL(int(port.Port), protocol, network.Id); err != nil {
193+
return nil, err
194+
}
188195
}
189196
}
190197
}
@@ -205,6 +212,11 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
205212
return nil, err
206213
}
207214

215+
klog.V(4).Infof("Deleting Network ACL rules associated with load balancer rule: %v (%v:%v)", lbRule.Name, protocol, port)
216+
if _, err := lb.deleteNetworkACLRule(int(port), protocol, lb.networkID); err != nil {
217+
return nil, err
218+
}
219+
208220
klog.V(4).Infof("Deleting obsolete load balancer rule: %v", lbRule.Name)
209221
if err := lb.deleteLoadBalancerRule(lbRule); err != nil {
210222
return nil, err
@@ -278,6 +290,15 @@ func isFirewallSupported(services []cloudstack.NetworkServiceInternal) bool {
278290
return false
279291
}
280292

293+
func isNetworkACLSupported(services []cloudstack.NetworkServiceInternal) bool {
294+
for _, svc := range services {
295+
if svc.Name == "NetworkACL" {
296+
return true
297+
}
298+
}
299+
return false
300+
}
301+
281302
// EnsureLoadBalancerDeleted deletes the specified load balancer if it exists, returning
282303
// nil if the load balancer specified either didn't exist or was successfully deleted.
283304
func (cs *CSCloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *corev1.Service) error {
@@ -290,7 +311,7 @@ func (cs *CSCloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName st
290311
}
291312

292313
for _, lbRule := range lb.rules {
293-
klog.V(4).Infof("Deleting firewall rules for load balancer: %v", lbRule.Name)
314+
klog.V(4).Infof("Deleting firewall rules / Network ACLs for load balancer: %v", lbRule.Name)
294315
protocol := ProtocolFromLoadBalancer(lbRule.Protocol)
295316
if protocol == LoadBalancerProtocolInvalid {
296317
klog.Errorf("Error parsing protocol: %v", lbRule.Protocol)
@@ -299,9 +320,29 @@ func (cs *CSCloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName st
299320
if err != nil {
300321
klog.Errorf("Error parsing port: %v", err)
301322
} else {
302-
_, err = lb.deleteFirewallRule(lbRule.Publicipid, int(port), protocol)
323+
networkId, err := cs.getNetworkIDFromIPAddress(lb.ipAddrID)
303324
if err != nil {
304-
klog.Errorf("Error deleting firewall rule: %v", err)
325+
return err
326+
}
327+
network, count, err := lb.Network.GetNetworkByID(networkId, cloudstack.WithProject(lb.projectID))
328+
if err != nil {
329+
if count == 0 {
330+
klog.Errorf("No network found with ID: %v", networkId)
331+
return err
332+
}
333+
return err
334+
}
335+
if network.Vpcid == "" {
336+
_, err = lb.deleteFirewallRule(lbRule.Publicipid, int(port), protocol)
337+
if err != nil {
338+
klog.Errorf("Error deleting firewall rule: %v", err)
339+
}
340+
} else {
341+
klog.V(4).Infof("Deleting network ACLs for %v - %v", int(port), protocol)
342+
_, err = lb.deleteNetworkACLRule(int(port), protocol, networkId)
343+
if err != nil {
344+
klog.Errorf("Error deleting Network ACL rule: %v", err)
345+
}
305346
}
306347
}
307348

@@ -365,6 +406,27 @@ func (cs *CSCloud) getLoadBalancer(service *corev1.Service) (*loadBalancer, erro
365406
return lb, nil
366407
}
367408

409+
// Get network ID from Public IP Address
410+
func (cs *CSCloud) getNetworkIDFromIPAddress(publicIpId string) (string, error) {
411+
ip, count, err := cs.client.Address.GetPublicIpAddressByID(publicIpId)
412+
if err != nil {
413+
klog.Errorf("Failed to fetch the public IP for id: %v", publicIpId)
414+
return "", err
415+
}
416+
if count == 0 {
417+
return "", err
418+
}
419+
if ip.Networkid != "" {
420+
network, _, netErr := cs.client.Network.GetNetworkByID(ip.Associatednetworkid)
421+
if netErr != nil {
422+
klog.Errorf("Failed to fetch the network for id: %v", ip.Associatednetworkid)
423+
return "", err
424+
}
425+
return network.Id, nil
426+
}
427+
return "", nil
428+
}
429+
368430
// verifyHosts verifies if all hosts belong to the same network, and returns the host ID's and network ID.
369431
func (cs *CSCloud) verifyHosts(nodes []*corev1.Node) ([]string, string, error) {
370432
hostNames := map[string]bool{}
@@ -790,6 +852,67 @@ func (lb *loadBalancer) updateFirewallRule(publicIpId string, publicPort int, pr
790852
return true, err
791853
}
792854

855+
func (lb *loadBalancer) updateNetworkACL(publicPort int, protocol LoadBalancerProtocol, networkId string) (bool, error) {
856+
network, _, err := lb.Network.GetNetworkByID(networkId)
857+
if err != nil {
858+
return false, fmt.Errorf("error fetching Network with ID: %v, due to: %s", networkId, err)
859+
}
860+
861+
networkAclList, count, err := lb.NetworkACL.GetNetworkACLListByID(network.Aclid)
862+
if err != nil {
863+
return false, fmt.Errorf("error fetching Network ACL List with ID: %v, due to: %s", network.Aclid, err)
864+
}
865+
866+
if count == 0 {
867+
return false, fmt.Errorf("failed to find network ACL List with id: %v", network.Aclid)
868+
}
869+
870+
if networkAclList.Name == "default_allow" || networkAclList.Name == "default_deny" {
871+
klog.Infof("Network is using a default network ACL. Cannot add ACL rules to default ACLs")
872+
return true, err
873+
}
874+
875+
networkAclParams := lb.NetworkACL.NewListNetworkACLsParams()
876+
networkAclParams.SetAclid(network.Aclid)
877+
networkAclParams.SetNetworkid(networkId)
878+
879+
networkAclResponse, err := lb.NetworkACL.ListNetworkACLs(networkAclParams)
880+
881+
if err != nil {
882+
return false, fmt.Errorf("error fetching Network ACL with ID: %v for network with id: %v, due to: %s", network.Aclid, networkId, err)
883+
}
884+
885+
// find all network ACL rules that have a matching proto+port
886+
// a map may or may not be faster, but is a bit easier to understand
887+
filtered := make(map[*cloudstack.NetworkACL]bool)
888+
for _, netAclRule := range networkAclResponse.NetworkACLs {
889+
if netAclRule.Protocol == protocol.IPProtocol() && netAclRule.Startport == strconv.Itoa(publicPort) && netAclRule.Endport == strconv.Itoa(publicPort) {
890+
filtered[netAclRule] = true
891+
}
892+
}
893+
894+
if len(filtered) > 0 {
895+
klog.V(4).Infof("Network ACL rule for port %v and protocol %v already exists. No need to added a duplicate rule", publicPort, protocol)
896+
return true, err
897+
}
898+
899+
// create ACL rule
900+
acl := lb.NetworkACL.NewCreateNetworkACLParams(protocol.CSProtocol())
901+
acl.SetAclid(network.Aclid)
902+
acl.SetAction("Allow")
903+
acl.SetCidrlist([]string{"0.0.0.0/0"})
904+
acl.SetStartport(publicPort)
905+
acl.SetEndport(publicPort)
906+
acl.SetNetworkid(networkId)
907+
acl.SetTraffictype("Ingress")
908+
909+
_, err = lb.NetworkACL.CreateNetworkACL(acl)
910+
if err != nil {
911+
return false, fmt.Errorf("error creating Network ACL for port: %v, due to: %s", publicPort, err)
912+
}
913+
return true, err
914+
}
915+
793916
// deleteFirewallRule deletes the firewall rule associated with the ip:port:protocol combo
794917
//
795918
// returns true when corresponding rules were deleted
@@ -828,6 +951,46 @@ func (lb *loadBalancer) deleteFirewallRule(publicIpId string, publicPort int, pr
828951
return deleted, err
829952
}
830953

954+
// Delete Network ACLs deletes the Network ACL rule associated with the ip:port:protocol combo
955+
func (lb *loadBalancer) deleteNetworkACLRule(publicPort int, protocol LoadBalancerProtocol, networkID string) (bool, error) {
956+
p := lb.NetworkACL.NewListNetworkACLsParams()
957+
p.SetListall(true)
958+
p.SetNetworkid(networkID)
959+
if lb.projectID != "" {
960+
p.SetProjectid(lb.projectID)
961+
}
962+
963+
r, err := lb.NetworkACL.ListNetworkACLs(p)
964+
if err != nil {
965+
return false, fmt.Errorf("error fetching Network ACL rules Network ID %v: %v", networkID, err)
966+
}
967+
968+
// filter by proto:port
969+
filtered := make([]*cloudstack.NetworkACL, 0, 1)
970+
for _, rule := range r.NetworkACLs {
971+
if rule.Protocol == protocol.IPProtocol() && rule.Startport == strconv.Itoa(publicPort) && rule.Endport == strconv.Itoa(publicPort) {
972+
filtered = append(filtered, rule)
973+
}
974+
}
975+
976+
// delete first filtered rules
977+
if len(filtered) == 0 {
978+
klog.V(4).Infof("No ACL rules found matching protocol: %v and port: %v", protocol, publicPort)
979+
return true, nil
980+
}
981+
deleted := false
982+
ruleToBeDeleted := filtered[0]
983+
deleteAclParams := lb.NetworkACL.NewDeleteNetworkACLParams(ruleToBeDeleted.Id)
984+
_, err = lb.NetworkACL.DeleteNetworkACL(deleteAclParams)
985+
if err != nil {
986+
klog.Errorf("Error deleting old Network ACL rule %v: %v", ruleToBeDeleted.Id, err)
987+
} else {
988+
deleted = true
989+
}
990+
991+
return deleted, err
992+
}
993+
831994
// getStringFromServiceAnnotation searches a given v1.Service for a specific annotationKey and either returns the annotation's value or a specified defaultSetting
832995
func getStringFromServiceAnnotation(service *corev1.Service, annotationKey string, defaultSetting string) string {
833996
klog.V(4).Infof("getStringFromServiceAnnotation(%s/%s, %v, %v)", service.Namespace, service.Name, annotationKey, defaultSetting)

0 commit comments

Comments
 (0)