Skip to content

Commit a43fbfc

Browse files
committed
Only manage security groups for ENIs tagged by CAPA
1 parent 0cdd2b1 commit a43fbfc

File tree

2 files changed

+287
-0
lines changed

2 files changed

+287
-0
lines changed

controllers/awsmachine_controller_unit_test.go

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,6 +2996,285 @@ func TestAWSMachineReconcilerReconcileDefaultsToLoadBalancerTypeClassic(t *testi
29962996
g.Expect(err).To(BeNil())
29972997
}
29982998

2999+
func TestAWSMachineReconcilerReconcileDoesntAddSecurityGroupsToNonManagedNetworkInterfaces(t *testing.T) {
3000+
g := NewWithT(t)
3001+
3002+
ns := "testns"
3003+
3004+
cp := &kubeadmv1beta1.KubeadmControlPlane{}
3005+
cp.SetName("capi-cp-test-1")
3006+
cp.SetNamespace(ns)
3007+
3008+
ownerCluster := &clusterv1.Cluster{
3009+
ObjectMeta: metav1.ObjectMeta{Name: "capi-test-1", Namespace: ns},
3010+
Spec: clusterv1.ClusterSpec{
3011+
InfrastructureRef: clusterv1.ContractVersionedObjectReference{
3012+
Kind: "AWSCluster",
3013+
Name: "capi-test-1", // assuming same name
3014+
APIGroup: infrav1.GroupVersion.Group,
3015+
},
3016+
ControlPlaneRef: clusterv1.ContractVersionedObjectReference{
3017+
Kind: "KubeadmControlPlane",
3018+
Name: cp.Name,
3019+
APIGroup: kubeadmv1beta1.GroupVersion.Group,
3020+
},
3021+
},
3022+
Status: clusterv1.ClusterStatus{
3023+
Initialization: clusterv1.ClusterInitializationStatus{
3024+
InfrastructureProvisioned: ptr.To(true),
3025+
},
3026+
},
3027+
}
3028+
3029+
awsCluster := &infrav1.AWSCluster{
3030+
ObjectMeta: metav1.ObjectMeta{
3031+
Name: "capi-test-1",
3032+
Namespace: ns,
3033+
OwnerReferences: []metav1.OwnerReference{
3034+
{
3035+
APIVersion: clusterv1.GroupVersion.String(),
3036+
Kind: "Cluster",
3037+
Name: ownerCluster.Name,
3038+
UID: "1",
3039+
},
3040+
},
3041+
},
3042+
Spec: infrav1.AWSClusterSpec{
3043+
ControlPlaneLoadBalancer: &infrav1.AWSLoadBalancerSpec{
3044+
Scheme: &infrav1.ELBSchemeInternetFacing,
3045+
// `LoadBalancerType` not set (i.e. empty string; must default to attaching instance to classic LB)
3046+
},
3047+
NetworkSpec: infrav1.NetworkSpec{
3048+
Subnets: infrav1.Subnets{
3049+
infrav1.SubnetSpec{
3050+
ID: "subnet-1",
3051+
IsPublic: false,
3052+
},
3053+
infrav1.SubnetSpec{
3054+
IsPublic: false,
3055+
},
3056+
},
3057+
},
3058+
},
3059+
Status: infrav1.AWSClusterStatus{
3060+
Ready: true,
3061+
Network: infrav1.NetworkStatus{
3062+
SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{
3063+
infrav1.SecurityGroupControlPlane: {
3064+
ID: "1",
3065+
},
3066+
infrav1.SecurityGroupNode: {
3067+
ID: "2",
3068+
},
3069+
infrav1.SecurityGroupLB: {
3070+
ID: "3",
3071+
},
3072+
},
3073+
},
3074+
},
3075+
}
3076+
3077+
ownerMachine := &clusterv1.Machine{
3078+
ObjectMeta: metav1.ObjectMeta{
3079+
Labels: map[string]string{
3080+
clusterv1.ClusterNameLabel: "capi-test-1",
3081+
clusterv1.MachineControlPlaneLabel: "", // control plane node so that controller tries to register it with LB
3082+
},
3083+
Name: "capi-test-machine",
3084+
Namespace: ns,
3085+
},
3086+
Spec: clusterv1.MachineSpec{
3087+
ClusterName: "capi-test",
3088+
Bootstrap: clusterv1.Bootstrap{
3089+
DataSecretName: aws.String("bootstrap-data"),
3090+
},
3091+
},
3092+
}
3093+
3094+
awsMachine := &infrav1.AWSMachine{
3095+
ObjectMeta: metav1.ObjectMeta{
3096+
Name: "aws-test-7",
3097+
Namespace: ns,
3098+
OwnerReferences: []metav1.OwnerReference{
3099+
{
3100+
APIVersion: clusterv1.GroupVersion.String(),
3101+
Kind: "Machine",
3102+
Name: "capi-test-machine",
3103+
UID: "1",
3104+
},
3105+
},
3106+
},
3107+
Spec: infrav1.AWSMachineSpec{
3108+
InstanceType: "test",
3109+
ProviderID: aws.String("aws://the-zone/two"),
3110+
CloudInit: infrav1.CloudInit{
3111+
SecureSecretsBackend: infrav1.SecretBackendSecretsManager,
3112+
SecretPrefix: "prefix",
3113+
SecretCount: 1000,
3114+
},
3115+
},
3116+
Status: infrav1.AWSMachineStatus{
3117+
Conditions: clusterv1beta1.Conditions{
3118+
{
3119+
Type: "Paused",
3120+
Status: corev1.ConditionFalse,
3121+
Reason: "NotPaused",
3122+
},
3123+
},
3124+
},
3125+
}
3126+
3127+
controllerIdentity := &infrav1.AWSClusterControllerIdentity{
3128+
TypeMeta: metav1.TypeMeta{
3129+
Kind: string(infrav1.ControllerIdentityKind),
3130+
},
3131+
ObjectMeta: metav1.ObjectMeta{
3132+
Name: "default",
3133+
},
3134+
Spec: infrav1.AWSClusterControllerIdentitySpec{
3135+
AWSClusterIdentitySpec: infrav1.AWSClusterIdentitySpec{
3136+
AllowedNamespaces: &infrav1.AllowedNamespaces{},
3137+
},
3138+
},
3139+
}
3140+
3141+
secret := &corev1.Secret{
3142+
ObjectMeta: metav1.ObjectMeta{
3143+
Name: "bootstrap-data",
3144+
Namespace: ns,
3145+
},
3146+
Data: map[string][]byte{
3147+
"value": []byte("shell-script"),
3148+
},
3149+
}
3150+
3151+
fakeClient := fake.NewClientBuilder().WithObjects(ownerCluster, awsCluster, ownerMachine, awsMachine, controllerIdentity, secret, cp).WithStatusSubresource(awsCluster, awsMachine).Build()
3152+
3153+
recorder := record.NewFakeRecorder(10)
3154+
reconciler := &AWSMachineReconciler{
3155+
Client: fakeClient,
3156+
Recorder: recorder,
3157+
}
3158+
3159+
mockCtrl := gomock.NewController(t)
3160+
ec2Mock := mocks.NewMockEC2API(mockCtrl)
3161+
elbMock := mocks.NewMockELBAPI(mockCtrl)
3162+
secretMock := mock_services.NewMockSecretInterface(mockCtrl)
3163+
3164+
cs, err := getClusterScope(*awsCluster)
3165+
g.Expect(err).To(BeNil())
3166+
3167+
ec2Svc := ec2Service.NewService(cs)
3168+
ec2Svc.EC2Client = ec2Mock
3169+
reconciler.ec2ServiceFactory = func(scope scope.EC2Scope) services.EC2Interface {
3170+
return ec2Svc
3171+
}
3172+
3173+
elbSvc := elbService.NewService(cs)
3174+
elbSvc.EC2Client = ec2Mock
3175+
elbSvc.ELBClient = elbMock
3176+
reconciler.elbServiceFactory = func(scope scope.ELBScope) services.ELBInterface {
3177+
return elbSvc
3178+
}
3179+
3180+
reconciler.secretsManagerServiceFactory = func(clusterScope cloud.ClusterScoper) services.SecretInterface {
3181+
return secretMock
3182+
}
3183+
3184+
ec2Mock.EXPECT().DescribeInstances(context.TODO(), gomock.Eq(&ec2.DescribeInstancesInput{
3185+
InstanceIds: []string{"two"},
3186+
})).Return(&ec2.DescribeInstancesOutput{
3187+
Reservations: []ec2types.Reservation{
3188+
{
3189+
Instances: []ec2types.Instance{
3190+
{
3191+
InstanceId: aws.String("two"),
3192+
InstanceType: ec2types.InstanceTypeM5Large,
3193+
SubnetId: aws.String("subnet-1"),
3194+
ImageId: aws.String("ami-1"),
3195+
State: &ec2types.InstanceState{
3196+
Name: ec2types.InstanceStateNameRunning,
3197+
},
3198+
Placement: &ec2types.Placement{
3199+
AvailabilityZone: aws.String("thezone"),
3200+
},
3201+
MetadataOptions: &ec2types.InstanceMetadataOptionsResponse{
3202+
HttpEndpoint: ec2types.InstanceMetadataEndpointState(string(infrav1.InstanceMetadataEndpointStateEnabled)),
3203+
HttpPutResponseHopLimit: aws.Int32(1),
3204+
HttpTokens: ec2types.HttpTokensState(string(infrav1.HTTPTokensStateOptional)),
3205+
InstanceMetadataTags: ec2types.InstanceMetadataTagsState(string(infrav1.InstanceMetadataEndpointStateDisabled)),
3206+
},
3207+
},
3208+
},
3209+
},
3210+
},
3211+
}, nil)
3212+
3213+
// Must attach to a classic LB, not another type. Only these mock calls are therefore expected.
3214+
mockedCreateLBCalls(t, elbMock.EXPECT(), false)
3215+
3216+
ec2Mock.EXPECT().DescribeNetworkInterfaces(context.TODO(), gomock.Eq(&ec2.DescribeNetworkInterfacesInput{Filters: []ec2types.Filter{
3217+
{
3218+
Name: aws.String("attachment.instance-id"),
3219+
Values: []string{"two"},
3220+
},
3221+
}})).Return(&ec2.DescribeNetworkInterfacesOutput{
3222+
NetworkInterfaces: []ec2types.NetworkInterface{
3223+
{
3224+
NetworkInterfaceId: aws.String("eni-1"),
3225+
Groups: []ec2types.GroupIdentifier{
3226+
{
3227+
GroupId: aws.String("2"),
3228+
},
3229+
{
3230+
GroupId: aws.String("3"),
3231+
},
3232+
{
3233+
GroupId: aws.String("1"),
3234+
},
3235+
},
3236+
TagSet: []ec2types.Tag{
3237+
{
3238+
Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
3239+
Value: aws.String("owned"),
3240+
},
3241+
},
3242+
},
3243+
{
3244+
NetworkInterfaceId: aws.String("eni-cilium"),
3245+
Groups: []ec2types.GroupIdentifier{
3246+
{
3247+
GroupId: aws.String("3"),
3248+
},
3249+
},
3250+
TagSet: []ec2types.Tag{
3251+
{
3252+
Key: aws.String("cilium-managed"),
3253+
Value: aws.String("true"),
3254+
},
3255+
},
3256+
},
3257+
}}, nil).MaxTimes(3)
3258+
ec2Mock.EXPECT().DescribeNetworkInterfaceAttribute(context.TODO(), gomock.Eq(&ec2.DescribeNetworkInterfaceAttributeInput{
3259+
NetworkInterfaceId: aws.String("eni-1"),
3260+
Attribute: ec2types.NetworkInterfaceAttributeGroupSet,
3261+
})).Return(&ec2.DescribeNetworkInterfaceAttributeOutput{Groups: []ec2types.GroupIdentifier{{GroupId: aws.String("3")}}}, nil).MaxTimes(1)
3262+
ec2Mock.EXPECT().DescribeNetworkInterfaceAttribute(context.TODO(), gomock.Eq(&ec2.DescribeNetworkInterfaceAttributeInput{
3263+
NetworkInterfaceId: aws.String("eni-cilium"),
3264+
Attribute: ec2types.NetworkInterfaceAttributeGroupSet,
3265+
})).Return(&ec2.DescribeNetworkInterfaceAttributeOutput{Groups: []ec2types.GroupIdentifier{{GroupId: aws.String("3")}}}, nil).MaxTimes(1)
3266+
ec2Mock.EXPECT().ModifyNetworkInterfaceAttribute(context.TODO(), gomock.Any()).Times(1)
3267+
3268+
_, err = reconciler.Reconcile(ctx, ctrl.Request{
3269+
NamespacedName: client.ObjectKey{
3270+
Namespace: awsMachine.Namespace,
3271+
Name: awsMachine.Name,
3272+
},
3273+
})
3274+
3275+
g.Expect(err).To(BeNil())
3276+
}
3277+
29993278
func createObject(g *WithT, obj client.Object, namespace string) {
30003279
if obj.DeepCopyObject() != nil {
30013280
obj.SetNamespace(namespace)

pkg/cloud/services/ec2/instances.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/base64"
2222
"fmt"
23+
"slices"
2324
"sort"
2425
"strings"
2526
"time"
@@ -808,6 +809,13 @@ func (s *Service) UpdateInstanceSecurityGroups(instanceID string, ids []string)
808809
s.scope.Debug("Found ENIs on instance", "number-of-enis", len(enis), "instance-id", instanceID)
809810

810811
for _, eni := range enis {
812+
// Other components like cilium may add ENIs, and we don't want CAPA changing the security groups on those.
813+
if !slices.ContainsFunc(eni.TagSet, func(t types.Tag) bool {
814+
return aws.ToString(t.Key) == infrav1.ClusterTagKey(s.scope.Name())
815+
}) {
816+
s.scope.Debug("Skipping ENI without cluster tag", "eni-id", *eni.NetworkInterfaceId)
817+
continue
818+
}
811819
if err := s.attachSecurityGroupsToNetworkInterface(ids, aws.ToString(eni.NetworkInterfaceId)); err != nil {
812820
return errors.Wrapf(err, "failed to modify network interfaces on instance %q", instanceID)
813821
}

0 commit comments

Comments
 (0)