sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/securitygroups/securitygroups_test.go (about)

     1  /*
     2  Copyright 2019 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  	"testing"
    22  
    23  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4"
    24  	. "github.com/onsi/gomega"
    25  	"github.com/pkg/errors"
    26  	"go.uber.org/mock/gomock"
    27  	"k8s.io/utils/ptr"
    28  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    30  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async"
    31  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/securitygroups/mock_securitygroups"
    32  	gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
    33  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    34  )
    35  
    36  var (
    37  	annotation = azure.SecurityRuleLastAppliedAnnotation
    38  	fakeNSG    = NSGSpec{
    39  		Name:        "test-nsg",
    40  		Location:    "test-location",
    41  		ClusterName: "my-cluster",
    42  		SecurityRules: infrav1.SecurityRules{
    43  			securityRule1,
    44  		},
    45  		ResourceGroup: "test-group",
    46  	}
    47  	noRulesNSG = NSGSpec{
    48  		Name:          "test-nsg-2",
    49  		Location:      "test-location",
    50  		ClusterName:   "my-cluster",
    51  		SecurityRules: infrav1.SecurityRules{},
    52  		ResourceGroup: "test-group",
    53  	}
    54  	multipleRulesNSG = NSGSpec{
    55  		Name:        "multiple-rules-nsg",
    56  		Location:    "test-location",
    57  		ClusterName: "my-cluster",
    58  		SecurityRules: infrav1.SecurityRules{
    59  			securityRule1,
    60  			securityRule2,
    61  		},
    62  		ResourceGroup: "test-group",
    63  	}
    64  	securityRule1 = infrav1.SecurityRule{
    65  		Name:             "allow_ssh",
    66  		Description:      "Allow SSH",
    67  		Priority:         2200,
    68  		Protocol:         infrav1.SecurityGroupProtocolTCP,
    69  		Direction:        infrav1.SecurityRuleDirectionInbound,
    70  		Source:           ptr.To("*"),
    71  		SourcePorts:      ptr.To("*"),
    72  		Destination:      ptr.To("*"),
    73  		DestinationPorts: ptr.To("22"),
    74  		Action:           infrav1.SecurityRuleActionAllow,
    75  	}
    76  	securityRule2 = infrav1.SecurityRule{
    77  		Name:             "other_rule",
    78  		Description:      "Test Rule",
    79  		Priority:         500,
    80  		Protocol:         infrav1.SecurityGroupProtocolTCP,
    81  		Direction:        infrav1.SecurityRuleDirectionInbound,
    82  		Source:           ptr.To("*"),
    83  		SourcePorts:      ptr.To("*"),
    84  		Destination:      ptr.To("*"),
    85  		DestinationPorts: ptr.To("80"),
    86  		Action:           infrav1.SecurityRuleActionAllow,
    87  	}
    88  	errFake      = errors.New("this is an error")
    89  	notDoneError = azure.NewOperationNotDoneError(&infrav1.Future{})
    90  )
    91  
    92  func TestReconcileSecurityGroups(t *testing.T) {
    93  	testcases := []struct {
    94  		name          string
    95  		expectedError string
    96  		expect        func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder)
    97  	}{
    98  		{
    99  			name:          "create single security group with single rule succeeds, should return no error",
   100  			expectedError: "",
   101  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   102  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   103  				s.IsVnetManaged().Return(true)
   104  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG})
   105  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1)
   106  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, nil)
   107  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil)
   108  			},
   109  		},
   110  		{
   111  			name:          "create single security group with multiple rules succeeds, should return no error",
   112  			expectedError: "",
   113  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   114  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   115  				s.IsVnetManaged().Return(true)
   116  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&multipleRulesNSG})
   117  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{multipleRulesNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description, securityRule2.Name: securityRule2.Description}}).Times(1)
   118  				r.CreateOrUpdateResource(gomockinternal.AContext(), &multipleRulesNSG, serviceName).Return(nil, nil)
   119  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil)
   120  			},
   121  		},
   122  		{
   123  			name:          "create multiple security groups, should return no error",
   124  			expectedError: "",
   125  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   126  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   127  				s.IsVnetManaged().Return(true)
   128  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   129  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1)
   130  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, nil)
   131  				r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, nil)
   132  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil)
   133  			},
   134  		},
   135  		{
   136  			name:          "first security groups create fails, should return error",
   137  			expectedError: errFake.Error(),
   138  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   139  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   140  				s.IsVnetManaged().Return(true)
   141  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   142  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1)
   143  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, errFake)
   144  				r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, nil)
   145  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake)
   146  			},
   147  		},
   148  		{
   149  			name:          "first sg create fails, second sg create not done, should return create error",
   150  			expectedError: errFake.Error(),
   151  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   152  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   153  				s.IsVnetManaged().Return(true)
   154  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   155  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1)
   156  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, errFake)
   157  				r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, notDoneError)
   158  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake)
   159  			},
   160  		},
   161  		{
   162  			name:          "security groups create not done, should return not done error",
   163  			expectedError: notDoneError.Error(),
   164  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   165  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   166  				s.IsVnetManaged().Return(true)
   167  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG})
   168  				s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}})
   169  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, notDoneError)
   170  				s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, notDoneError)
   171  			},
   172  		},
   173  		{
   174  			name:          "vnet is not managed, should skip reconcile",
   175  			expectedError: "",
   176  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   177  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   178  				s.IsVnetManaged().Return(false)
   179  			},
   180  		},
   181  	}
   182  	for _, tc := range testcases {
   183  		tc := tc
   184  		t.Run(tc.name, func(t *testing.T) {
   185  			g := NewWithT(t)
   186  			t.Parallel()
   187  			mockCtrl := gomock.NewController(t)
   188  			defer mockCtrl.Finish()
   189  
   190  			scopeMock := mock_securitygroups.NewMockNSGScope(mockCtrl)
   191  			reconcilerMock := mock_async.NewMockReconciler(mockCtrl)
   192  
   193  			tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT())
   194  
   195  			s := &Service{
   196  				Scope:      scopeMock,
   197  				Reconciler: reconcilerMock,
   198  			}
   199  
   200  			err := s.Reconcile(context.TODO())
   201  			if tc.expectedError != "" {
   202  				g.Expect(err).To(HaveOccurred())
   203  				g.Expect(err).To(MatchError(tc.expectedError))
   204  			} else {
   205  				g.Expect(err).NotTo(HaveOccurred())
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestDeleteSecurityGroups(t *testing.T) {
   212  	testcases := []struct {
   213  		name          string
   214  		expectedError string
   215  		expect        func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder)
   216  	}{
   217  		{
   218  			name:          "delete multiple security groups succeeds, should return no error",
   219  			expectedError: "",
   220  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   221  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   222  				s.IsVnetManaged().Return(true)
   223  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   224  				r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil)
   225  				r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil)
   226  				s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil)
   227  			},
   228  		},
   229  		{
   230  			name:          "first security groups delete fails, should return an error",
   231  			expectedError: errFake.Error(),
   232  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   233  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   234  				s.IsVnetManaged().Return(true)
   235  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   236  				r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(errFake)
   237  				r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil)
   238  				s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake)
   239  			},
   240  		},
   241  		{
   242  			name:          "first security groups delete fails and second security groups create not done, should return an error",
   243  			expectedError: errFake.Error(),
   244  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   245  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   246  				s.IsVnetManaged().Return(true)
   247  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG})
   248  				r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(errFake)
   249  				r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(notDoneError)
   250  				s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake)
   251  			},
   252  		},
   253  		{
   254  			name:          "security groups delete not done, should return not done error",
   255  			expectedError: notDoneError.Error(),
   256  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   257  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   258  				s.IsVnetManaged().Return(true)
   259  				s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG})
   260  				r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(notDoneError)
   261  				s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, notDoneError)
   262  			},
   263  		},
   264  		{
   265  			name:          "vnet is not managed, should skip delete",
   266  			expectedError: "",
   267  			expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   268  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   269  				s.IsVnetManaged().Return(false)
   270  			},
   271  		},
   272  	}
   273  	for _, tc := range testcases {
   274  		tc := tc
   275  		t.Run(tc.name, func(t *testing.T) {
   276  			g := NewWithT(t)
   277  			t.Parallel()
   278  			mockCtrl := gomock.NewController(t)
   279  			defer mockCtrl.Finish()
   280  
   281  			scopeMock := mock_securitygroups.NewMockNSGScope(mockCtrl)
   282  			reconcilerMock := mock_async.NewMockReconciler(mockCtrl)
   283  
   284  			tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT())
   285  
   286  			s := &Service{
   287  				Scope:      scopeMock,
   288  				Reconciler: reconcilerMock,
   289  			}
   290  
   291  			err := s.Delete(context.TODO())
   292  			if tc.expectedError != "" {
   293  				g.Expect(err).To(HaveOccurred())
   294  				g.Expect(err).To(MatchError(tc.expectedError))
   295  			} else {
   296  				g.Expect(err).NotTo(HaveOccurred())
   297  			}
   298  		})
   299  	}
   300  }
   301  
   302  var (
   303  	ruleA = &armnetwork.SecurityRule{
   304  		Name: ptr.To("A"),
   305  		Properties: &armnetwork.SecurityRulePropertiesFormat{
   306  			Description:              ptr.To("this is rule A"),
   307  			Protocol:                 ptr.To(armnetwork.SecurityRuleProtocolTCP),
   308  			DestinationPortRange:     ptr.To("*"),
   309  			SourcePortRange:          ptr.To("*"),
   310  			DestinationAddressPrefix: ptr.To("*"),
   311  			SourceAddressPrefix:      ptr.To("*"),
   312  			Priority:                 ptr.To[int32](100),
   313  			Direction:                ptr.To(armnetwork.SecurityRuleDirectionInbound),
   314  			Access:                   ptr.To(armnetwork.SecurityRuleAccessAllow),
   315  		},
   316  	}
   317  	ruleB = &armnetwork.SecurityRule{
   318  		Name: ptr.To("B"),
   319  		Properties: &armnetwork.SecurityRulePropertiesFormat{
   320  			Description:              ptr.To("this is rule B"),
   321  			Protocol:                 ptr.To(armnetwork.SecurityRuleProtocolTCP),
   322  			DestinationPortRange:     ptr.To("*"),
   323  			SourcePortRange:          ptr.To("*"),
   324  			DestinationAddressPrefix: ptr.To("*"),
   325  			SourceAddressPrefix:      ptr.To("*"),
   326  			Priority:                 ptr.To[int32](100),
   327  			Direction:                ptr.To(armnetwork.SecurityRuleDirectionOutbound),
   328  			Access:                   ptr.To(armnetwork.SecurityRuleAccessAllow),
   329  		},
   330  	}
   331  	ruleBModified = &armnetwork.SecurityRule{
   332  		Name: ptr.To("B"),
   333  		Properties: &armnetwork.SecurityRulePropertiesFormat{
   334  			Description:              ptr.To("this is rule B"),
   335  			Protocol:                 ptr.To(armnetwork.SecurityRuleProtocolTCP),
   336  			DestinationPortRange:     ptr.To("80"),
   337  			SourcePortRange:          ptr.To("*"),
   338  			DestinationAddressPrefix: ptr.To("*"),
   339  			SourceAddressPrefix:      ptr.To("*"),
   340  			Priority:                 ptr.To[int32](100),
   341  			Direction:                ptr.To(armnetwork.SecurityRuleDirectionOutbound),
   342  			Access:                   ptr.To(armnetwork.SecurityRuleAccessAllow),
   343  		},
   344  	}
   345  )