This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/cloudstack-kubernetes-provider.git
The following commit(s) were added to refs/heads/main by this push:
new a315d9e1 Add support for NetworkACLs for LB on VPC networks (#69)
a315d9e1 is described below
commit a315d9e1c2aa3d571abf2ef992c230d5cbcf58c6
Author: Pearl Dsilva <[email protected]>
AuthorDate: Wed Jul 31 08:56:03 2024 -0400
Add support for NetworkACLs for LB on VPC networks (#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
---
cloudstack_loadbalancer.go | 177 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 170 insertions(+), 7 deletions(-)
diff --git a/cloudstack_loadbalancer.go b/cloudstack_loadbalancer.go
index 09011127..7a3fd6b0 100644
--- a/cloudstack_loadbalancer.go
+++ b/cloudstack_loadbalancer.go
@@ -181,10 +181,17 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx
context.Context, clusterName string, s
return nil, err
}
- if lbRule != nil && isFirewallSupported(network.Service) {
- klog.V(4).Infof("Creating firewall rules for load
balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip, port.Port)
- if _, err := lb.updateFirewallRule(lbRule.Publicipid,
int(port.Port), protocol, service.Spec.LoadBalancerSourceRanges); err != nil {
- return nil, err
+ if lbRule != nil {
+ if isFirewallSupported(network.Service) {
+ klog.V(4).Infof("Creating firewall rules for
load balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip,
port.Port)
+ if _, err :=
lb.updateFirewallRule(lbRule.Publicipid, int(port.Port), protocol,
service.Spec.LoadBalancerSourceRanges); err != nil {
+ return nil, err
+ }
+ } else if isNetworkACLSupported(network.Service) {
+ klog.V(4).Infof("Creating ACL rules for load
balancer rule: %v (%v:%v:%v)", lbRuleName, protocol, lbRule.Publicip, port.Port)
+ if _, err :=
lb.updateNetworkACL(int(port.Port), protocol, network.Id); err != nil {
+ return nil, err
+ }
}
}
}
@@ -205,6 +212,11 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context,
clusterName string, s
return nil, err
}
+ klog.V(4).Infof("Deleting Network ACL rules associated with
load balancer rule: %v (%v:%v)", lbRule.Name, protocol, port)
+ if _, err := lb.deleteNetworkACLRule(int(port), protocol,
lb.networkID); err != nil {
+ return nil, err
+ }
+
klog.V(4).Infof("Deleting obsolete load balancer rule: %v",
lbRule.Name)
if err := lb.deleteLoadBalancerRule(lbRule); err != nil {
return nil, err
@@ -278,6 +290,15 @@ func isFirewallSupported(services
[]cloudstack.NetworkServiceInternal) bool {
return false
}
+func isNetworkACLSupported(services []cloudstack.NetworkServiceInternal) bool {
+ for _, svc := range services {
+ if svc.Name == "NetworkACL" {
+ return true
+ }
+ }
+ return false
+}
+
// EnsureLoadBalancerDeleted deletes the specified load balancer if it exists,
returning
// nil if the load balancer specified either didn't exist or was successfully
deleted.
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
}
for _, lbRule := range lb.rules {
- klog.V(4).Infof("Deleting firewall rules for load balancer:
%v", lbRule.Name)
+ klog.V(4).Infof("Deleting firewall rules / Network ACLs for
load balancer: %v", lbRule.Name)
protocol := ProtocolFromLoadBalancer(lbRule.Protocol)
if protocol == LoadBalancerProtocolInvalid {
klog.Errorf("Error parsing protocol: %v",
lbRule.Protocol)
@@ -299,9 +320,29 @@ func (cs *CSCloud) EnsureLoadBalancerDeleted(ctx
context.Context, clusterName st
if err != nil {
klog.Errorf("Error parsing port: %v", err)
} else {
- _, err =
lb.deleteFirewallRule(lbRule.Publicipid, int(port), protocol)
+ networkId, err :=
cs.getNetworkIDFromIPAddress(lb.ipAddrID)
if err != nil {
- klog.Errorf("Error deleting firewall
rule: %v", err)
+ return err
+ }
+ network, count, err :=
lb.Network.GetNetworkByID(networkId, cloudstack.WithProject(lb.projectID))
+ if err != nil {
+ if count == 0 {
+ klog.Errorf("No network found
with ID: %v", networkId)
+ return err
+ }
+ return err
+ }
+ if network.Vpcid == "" {
+ _, err =
lb.deleteFirewallRule(lbRule.Publicipid, int(port), protocol)
+ if err != nil {
+ klog.Errorf("Error deleting
firewall rule: %v", err)
+ }
+ } else {
+ klog.V(4).Infof("Deleting network ACLs
for %v - %v", int(port), protocol)
+ _, err =
lb.deleteNetworkACLRule(int(port), protocol, networkId)
+ if err != nil {
+ klog.Errorf("Error deleting
Network ACL rule: %v", err)
+ }
}
}
@@ -365,6 +406,27 @@ func (cs *CSCloud) getLoadBalancer(service
*corev1.Service) (*loadBalancer, erro
return lb, nil
}
+// Get network ID from Public IP Address
+func (cs *CSCloud) getNetworkIDFromIPAddress(publicIpId string) (string,
error) {
+ ip, count, err := cs.client.Address.GetPublicIpAddressByID(publicIpId)
+ if err != nil {
+ klog.Errorf("Failed to fetch the public IP for id: %v",
publicIpId)
+ return "", err
+ }
+ if count == 0 {
+ return "", err
+ }
+ if ip.Networkid != "" {
+ network, _, netErr :=
cs.client.Network.GetNetworkByID(ip.Associatednetworkid)
+ if netErr != nil {
+ klog.Errorf("Failed to fetch the network for id: %v",
ip.Associatednetworkid)
+ return "", err
+ }
+ return network.Id, nil
+ }
+ return "", nil
+}
+
// verifyHosts verifies if all hosts belong to the same network, and returns
the host ID's and network ID.
func (cs *CSCloud) verifyHosts(nodes []*corev1.Node) ([]string, string, error)
{
hostNames := map[string]bool{}
@@ -790,6 +852,67 @@ func (lb *loadBalancer) updateFirewallRule(publicIpId
string, publicPort int, pr
return true, err
}
+func (lb *loadBalancer) updateNetworkACL(publicPort int, protocol
LoadBalancerProtocol, networkId string) (bool, error) {
+ network, _, err := lb.Network.GetNetworkByID(networkId)
+ if err != nil {
+ return false, fmt.Errorf("error fetching Network with ID: %v,
due to: %s", networkId, err)
+ }
+
+ networkAclList, count, err :=
lb.NetworkACL.GetNetworkACLListByID(network.Aclid)
+ if err != nil {
+ return false, fmt.Errorf("error fetching Network ACL List with
ID: %v, due to: %s", network.Aclid, err)
+ }
+
+ if count == 0 {
+ return false, fmt.Errorf("failed to find network ACL List with
id: %v", network.Aclid)
+ }
+
+ if networkAclList.Name == "default_allow" || networkAclList.Name ==
"default_deny" {
+ klog.Infof("Network is using a default network ACL. Cannot add
ACL rules to default ACLs")
+ return true, err
+ }
+
+ networkAclParams := lb.NetworkACL.NewListNetworkACLsParams()
+ networkAclParams.SetAclid(network.Aclid)
+ networkAclParams.SetNetworkid(networkId)
+
+ networkAclResponse, err :=
lb.NetworkACL.ListNetworkACLs(networkAclParams)
+
+ if err != nil {
+ return false, fmt.Errorf("error fetching Network ACL with ID:
%v for network with id: %v, due to: %s", network.Aclid, networkId, err)
+ }
+
+ // find all network ACL rules that have a matching proto+port
+ // a map may or may not be faster, but is a bit easier to understand
+ filtered := make(map[*cloudstack.NetworkACL]bool)
+ for _, netAclRule := range networkAclResponse.NetworkACLs {
+ if netAclRule.Protocol == protocol.IPProtocol() &&
netAclRule.Startport == strconv.Itoa(publicPort) && netAclRule.Endport ==
strconv.Itoa(publicPort) {
+ filtered[netAclRule] = true
+ }
+ }
+
+ if len(filtered) > 0 {
+ klog.V(4).Infof("Network ACL rule for port %v and protocol %v
already exists. No need to added a duplicate rule", publicPort, protocol)
+ return true, err
+ }
+
+ // create ACL rule
+ acl := lb.NetworkACL.NewCreateNetworkACLParams(protocol.CSProtocol())
+ acl.SetAclid(network.Aclid)
+ acl.SetAction("Allow")
+ acl.SetCidrlist([]string{"0.0.0.0/0"})
+ acl.SetStartport(publicPort)
+ acl.SetEndport(publicPort)
+ acl.SetNetworkid(networkId)
+ acl.SetTraffictype("Ingress")
+
+ _, err = lb.NetworkACL.CreateNetworkACL(acl)
+ if err != nil {
+ return false, fmt.Errorf("error creating Network ACL for port:
%v, due to: %s", publicPort, err)
+ }
+ return true, err
+}
+
// deleteFirewallRule deletes the firewall rule associated with the
ip:port:protocol combo
//
// returns true when corresponding rules were deleted
@@ -828,6 +951,46 @@ func (lb *loadBalancer) deleteFirewallRule(publicIpId
string, publicPort int, pr
return deleted, err
}
+// Delete Network ACLs deletes the Network ACL rule associated with the
ip:port:protocol combo
+func (lb *loadBalancer) deleteNetworkACLRule(publicPort int, protocol
LoadBalancerProtocol, networkID string) (bool, error) {
+ p := lb.NetworkACL.NewListNetworkACLsParams()
+ p.SetListall(true)
+ p.SetNetworkid(networkID)
+ if lb.projectID != "" {
+ p.SetProjectid(lb.projectID)
+ }
+
+ r, err := lb.NetworkACL.ListNetworkACLs(p)
+ if err != nil {
+ return false, fmt.Errorf("error fetching Network ACL rules
Network ID %v: %v", networkID, err)
+ }
+
+ // filter by proto:port
+ filtered := make([]*cloudstack.NetworkACL, 0, 1)
+ for _, rule := range r.NetworkACLs {
+ if rule.Protocol == protocol.IPProtocol() && rule.Startport ==
strconv.Itoa(publicPort) && rule.Endport == strconv.Itoa(publicPort) {
+ filtered = append(filtered, rule)
+ }
+ }
+
+ // delete first filtered rules
+ if len(filtered) == 0 {
+ klog.V(4).Infof("No ACL rules found matching protocol: %v and
port: %v", protocol, publicPort)
+ return true, nil
+ }
+ deleted := false
+ ruleToBeDeleted := filtered[0]
+ deleteAclParams :=
lb.NetworkACL.NewDeleteNetworkACLParams(ruleToBeDeleted.Id)
+ _, err = lb.NetworkACL.DeleteNetworkACL(deleteAclParams)
+ if err != nil {
+ klog.Errorf("Error deleting old Network ACL rule %v: %v",
ruleToBeDeleted.Id, err)
+ } else {
+ deleted = true
+ }
+
+ return deleted, err
+}
+
// getStringFromServiceAnnotation searches a given v1.Service for a specific
annotationKey and either returns the annotation's value or a specified
defaultSetting
func getStringFromServiceAnnotation(service *corev1.Service, annotationKey
string, defaultSetting string) string {
klog.V(4).Infof("getStringFromServiceAnnotation(%s/%s, %v, %v)",
service.Namespace, service.Name, annotationKey, defaultSetting)