sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/securitygroups/spec.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package securitygroups 18 19 import ( 20 "context" 21 "strings" 22 23 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 24 "github.com/pkg/errors" 25 "k8s.io/utils/ptr" 26 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 27 "sigs.k8s.io/cluster-api-provider-azure/azure/converters" 28 ) 29 30 // NSGSpec defines the specification for a security group. 31 type NSGSpec struct { 32 Name string 33 SecurityRules infrav1.SecurityRules 34 Location string 35 ClusterName string 36 ResourceGroup string 37 AdditionalTags infrav1.Tags 38 LastAppliedSecurityRules map[string]interface{} 39 } 40 41 // ResourceName returns the name of the security group. 42 func (s *NSGSpec) ResourceName() string { 43 return s.Name 44 } 45 46 // ResourceGroupName returns the name of the resource group. 47 func (s *NSGSpec) ResourceGroupName() string { 48 return s.ResourceGroup 49 } 50 51 // OwnerResourceName is a no-op for security groups. 52 func (s *NSGSpec) OwnerResourceName() string { 53 return "" 54 } 55 56 // Parameters returns the parameters for the security group. 57 func (s *NSGSpec) Parameters(ctx context.Context, existing interface{}) (interface{}, error) { 58 securityRules := make([]*armnetwork.SecurityRule, 0) 59 newAnnotation := map[string]string{} 60 var etag *string 61 62 if existing != nil { 63 existingNSG, ok := existing.(armnetwork.SecurityGroup) 64 if !ok { 65 return nil, errors.Errorf("%T is not a network.SecurityGroup", existing) 66 } 67 // security group already exists 68 // We append the existing NSG etag to the header to ensure we only apply the updates if the NSG has not been modified. 69 etag = existingNSG.Etag 70 // Check if the expected rules are present 71 update := false 72 73 for _, rule := range s.SecurityRules { 74 sdkRule := converters.SecurityRuleToSDK(rule) 75 if !ruleExists(existingNSG.Properties.SecurityRules, sdkRule) { 76 update = true 77 securityRules = append(securityRules, sdkRule) 78 } 79 newAnnotation[rule.Name] = rule.Description 80 } 81 82 for _, oldRule := range existingNSG.Properties.SecurityRules { 83 _, tracked := s.LastAppliedSecurityRules[*oldRule.Name] 84 // If rule is owned by CAPZ and applied last, and not found in the new rules, then it has been deleted 85 if _, ok := newAnnotation[*oldRule.Name]; !ok && tracked { 86 // Rule has been deleted 87 update = true 88 continue 89 } 90 91 // Add previous rules that haven't been deleted 92 securityRules = append(securityRules, oldRule) 93 } 94 95 if !update { 96 // Skip update for NSG as the required default rules are present 97 return nil, nil 98 } 99 } else { 100 // new security group 101 for _, rule := range s.SecurityRules { 102 securityRules = append(securityRules, converters.SecurityRuleToSDK(rule)) 103 } 104 } 105 106 return armnetwork.SecurityGroup{ 107 Location: ptr.To(s.Location), 108 Properties: &armnetwork.SecurityGroupPropertiesFormat{ 109 SecurityRules: securityRules, 110 }, 111 Etag: etag, 112 Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ 113 ClusterName: s.ClusterName, 114 Lifecycle: infrav1.ResourceLifecycleOwned, 115 Name: ptr.To(s.Name), 116 Additional: s.AdditionalTags, 117 })), 118 }, nil 119 } 120 121 // TODO: review this logic and make sure it is what we want. It seems incorrect to skip rules that don't have a certain protocol, etc. 122 func ruleExists(rules []*armnetwork.SecurityRule, rule *armnetwork.SecurityRule) bool { 123 for _, existingRule := range rules { 124 if !strings.EqualFold(ptr.Deref(existingRule.Name, ""), ptr.Deref(rule.Name, "")) { 125 continue 126 } 127 if !strings.EqualFold(ptr.Deref(existingRule.Properties.DestinationPortRange, ""), ptr.Deref(rule.Properties.DestinationPortRange, "")) { 128 continue 129 } 130 if ptr.Deref(existingRule.Properties.Protocol, "") != armnetwork.SecurityRuleProtocolTCP && 131 ptr.Deref(existingRule.Properties.Access, "") != armnetwork.SecurityRuleAccessAllow && 132 ptr.Deref(existingRule.Properties.Direction, "") != armnetwork.SecurityRuleDirectionInbound { 133 continue 134 } 135 if !strings.EqualFold(ptr.Deref(existingRule.Properties.SourcePortRange, ""), "*") && 136 !strings.EqualFold(ptr.Deref(existingRule.Properties.SourceAddressPrefix, ""), "*") && 137 !strings.EqualFold(ptr.Deref(existingRule.Properties.DestinationAddressPrefix, ""), "*") { 138 continue 139 } 140 return true 141 } 142 return false 143 }