/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package alitasks import ( "encoding/json" "fmt" "sort" "k8s.io/klog/v2" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" slbnew "github.com/aliyun/alibaba-cloud-sdk-go/services/slb" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/aliup" "k8s.io/kops/upup/pkg/fi/cloudup/terraform" ) // +kops:fitask type LoadBalancerACL struct { ID *string Name *string LoadBalancer *LoadBalancer LoadBalancerListener *LoadBalancerListener SourceItems []*string Lifecycle *fi.Lifecycle } type AclEntry struct { Entry string `json:"entry"` Comment string `json:"comment"` } var _ fi.CompareWithID = &LoadBalancerACL{} func (l *LoadBalancerACL) CompareWithID() *string { return l.Name } func (l *LoadBalancerACL) Find(c *fi.Context) (*LoadBalancerACL, error) { if l.LoadBalancer == nil || l.LoadBalancer.LoadbalancerId == nil { klog.V(4).Infof("LoadBalancer / LoadbalancerId not found for %s, skipping Find", fi.StringValue(l.Name)) return nil, nil } if l.LoadBalancerListener == nil || l.LoadBalancerListener.ListenerPort == nil { klog.V(4).Infof("LoadBalancerListener / LoadbalancerListenerPort not found for %s, skipping Find", fi.StringValue(l.Name)) return nil, nil } cloud := c.Cloud.(aliup.ALICloud) describeAclReq := slbnew.CreateDescribeAccessControlListsRequest() describeAclReq.AclName = fi.StringValue(l.Name) describeAclResp, err := cloud.SLB().DescribeAccessControlLists(describeAclReq) if err != nil { return nil, fmt.Errorf("error listing LoadBalancerAccessControlList: %v", err) } acls := describeAclResp.Acls.Acl if len(acls) == 0 { return nil, nil } if len(acls) > 1 { return nil, fmt.Errorf("found multiple LoadBalancerAccessControlList with name %s", fi.StringValue(l.Name)) } acl := acls[0] klog.V(2).Infof("found matching LoadBalancerAccessControlList: %s", acl.AclId) describeAclAttrReq := slbnew.CreateDescribeAccessControlListAttributeRequest() describeAclAttrReq.AclId = acl.AclId describeAclAttrResp, err := cloud.SLB().DescribeAccessControlListAttribute(describeAclAttrReq) if err != nil { return nil, fmt.Errorf("error describing LoadBalancerAccessControlListAttribute: %v", err) } var sourceItems []*string for _, entry := range describeAclAttrResp.AclEntrys.AclEntry { ip := entry.AclEntryIP sourceItems = append(sourceItems, &ip) } actual := &LoadBalancerACL{ ID: fi.String(acl.AclId), Name: fi.String(describeAclAttrResp.AclName), SourceItems: sourceItems, } listeners := describeAclAttrResp.RelatedListeners.RelatedListener if len(listeners) != 1 { actual.LoadBalancerListener = nil actual.LoadBalancer = nil } else { listener := listeners[0] lb := &LoadBalancer{LoadbalancerId: fi.String(listener.LoadBalancerId)} actual.LoadBalancer = lb actual.LoadBalancerListener = &LoadBalancerListener{ LoadBalancer: lb, ListenerPort: fi.Int(listener.ListenerPort), } } // Ignore "system" fields l.ID = actual.ID actual.Lifecycle = l.Lifecycle return actual, nil } func (l *LoadBalancerACL) Run(c *fi.Context) error { l.Normalize() return fi.DefaultDeltaRunMethod(l, c) } func (l *LoadBalancerACL) Normalize() { // We need to sort our arrays consistently, so we don't get spurious changes sort.Stable(StringPointers(l.SourceItems)) } // StringPointers implements sort.Interface for []*string type StringPointers []*string func (s StringPointers) Len() int { return len(s) } func (s StringPointers) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s StringPointers) Less(i, j int) bool { return fi.StringValue(s[i]) < fi.StringValue(s[j]) } func (_ *LoadBalancerACL) CheckChanges(a, e, changes *LoadBalancerACL) error { if a == nil { if e.Name == nil { return fi.RequiredField("Name") } } return nil } func (_ *LoadBalancerACL) RenderALI(t *aliup.ALIAPITarget, a, e, changes *LoadBalancerACL) error { if a == nil { klog.V(2).Infof("Creating LoadBalancerAccessControlList with name: %q for SLB %q", *e.Name, *e.LoadBalancer.LoadbalancerId) if err := createAcl(t.Cloud, e); err != nil { return err } return e.on(t.Cloud) } else { if changes.SourceItems != nil { klog.V(2).Info("Turning off LoadBalancerACL for SLB") if err := e.off(t.Cloud); err != nil { return err } klog.V(2).Infof("Deleting LoadBalancerAccessControlList %q", *a.Name) err := deleteAcl(t.Cloud, a) if err != nil { return fmt.Errorf("error deleting LoadBalancerAccessControlList: %v", err) } klog.V(2).Infof("Creating LoadBalancerAccessControlList with name: %q for SLB %q", *e.Name, *e.LoadBalancer.LoadbalancerId) if err := createAcl(t.Cloud, e); err != nil { return err } return e.on(t.Cloud) } if changes.LoadBalancer != nil || changes.LoadBalancerListener != nil { klog.V(2).Info("Turning on LoadBalancerACL for SLB") return e.on(t.Cloud) } } return nil } func (l *LoadBalancerACL) on(alicloud aliup.ALICloud) error { setLBTCPlistenerAttrReq := slbnew.CreateSetLoadBalancerTCPListenerAttributeRequest() setLBTCPlistenerAttrReq.AclId = fi.StringValue(l.ID) setLBTCPlistenerAttrReq.AclType = "white" setLBTCPlistenerAttrReq.AclStatus = "on" setLBTCPlistenerAttrReq.LoadBalancerId = fi.StringValue(l.LoadBalancer.LoadbalancerId) setLBTCPlistenerAttrReq.ListenerPort = requests.NewInteger(fi.IntValue(l.LoadBalancerListener.ListenerPort)) _, err := alicloud.SLB().SetLoadBalancerTCPListenerAttribute(setLBTCPlistenerAttrReq) if err != nil { return fmt.Errorf("error turning on LoadBalancerACL %v", err) } return nil } func (l *LoadBalancerACL) off(alicloud aliup.ALICloud) error { setLBTCPlistenerAttrReq := slbnew.CreateSetLoadBalancerTCPListenerAttributeRequest() setLBTCPlistenerAttrReq.AclStatus = "off" setLBTCPlistenerAttrReq.LoadBalancerId = fi.StringValue(l.LoadBalancer.LoadbalancerId) setLBTCPlistenerAttrReq.ListenerPort = requests.NewInteger(fi.IntValue(l.LoadBalancerListener.ListenerPort)) _, err := alicloud.SLB().SetLoadBalancerTCPListenerAttribute(setLBTCPlistenerAttrReq) if err != nil { return fmt.Errorf("error turning off LoadBalancerACL %v", err) } return nil } func createAcl(alicloud aliup.ALICloud, acl *LoadBalancerACL) error { createAclReq := slbnew.CreateCreateAccessControlListRequest() createAclReq.AclName = fi.StringValue(acl.Name) resp, err := alicloud.SLB().CreateAccessControlList(createAclReq) if err != nil { return fmt.Errorf("error creating LoadBalancerAccessControlList: %v", err) } aclID := resp.AclId acl.ID = fi.String(aclID) var aclEntries []AclEntry for _, each := range acl.SourceItems { aclEntries = append(aclEntries, AclEntry{Entry: *each}) } aclEntriesBytes, err := json.Marshal(aclEntries) if err != nil { return fmt.Errorf("error marshalling %v : %v", aclEntries, err) } addAclEntryReq := slbnew.CreateAddAccessControlListEntryRequest() addAclEntryReq.AclId = aclID addAclEntryReq.AclEntrys = string(aclEntriesBytes) _, err = alicloud.SLB().AddAccessControlListEntry(addAclEntryReq) if err != nil { return fmt.Errorf("error adding AclEntries %v: %v", addAclEntryReq, err) } return nil } func deleteAcl(alicloud aliup.ALICloud, acl *LoadBalancerACL) error { deleteAclReq := slbnew.CreateDeleteAccessControlListRequest() deleteAclReq.AclId = fi.StringValue(acl.ID) _, err := alicloud.SLB().DeleteAccessControlList(deleteAclReq) if err != nil { return fmt.Errorf("error deleting LoadBalancerAccessControlList %q, %v", fi.StringValue(acl.ID), err) } return nil } func (_ *LoadBalancerACL) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *LoadBalancerACL) error { klog.Warningf("terraform does not support LoadBalancerAccessControlList on ALI cloud") return nil }