Repository: kubernetes-sigs/cluster-api-provider-aws
Pull request #3430 (2022-04-18 18:51:22)

Scope cleanup
/kind cleanup

**What this PR does / why we need it**:

Half of the scope interfaces are in `scope` package to avoid cyclic imports.
This PR moves all scope interfaces to that package for having all interfaces in the same package.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:

**Checklist**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR in which case these can be deleted -->

- [ ] squashed commits
- [ ] includes documentation
- [ ] adds unit tests
- [ ] adds or updates e2e tests



Patch:
diff --git a/api/v1alpha4/conversion.go b/api/v1alpha4/conversion.go
index d7875aed2..6b1644fbe 100644
--- a/api/v1alpha4/conversion.go
+++ b/api/v1alpha4/conversion.go
@@ -17,10 +17,23 @@ limitations under the License.
 package v1alpha4
 
 import (
-	"k8s.io/apimachinery/pkg/conversion"
-	"sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
+	apiconversion "k8s.io/apimachinery/pkg/conversion"
+	conversion "k8s.io/apimachinery/pkg/conversion"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
+	clusterv1alpha4 "sigs.k8s.io/cluster-api/api/v1alpha4"
+	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 )
 
+// Convert_v1alpha4_APIEndpoint_To_v1beta1_APIEndpoint .
+func Convert_v1alpha4_ObjectMeta_To_v1beta1_ObjectMeta(in *clusterv1alpha4.ObjectMeta, out *clusterv1.ObjectMeta, s apiconversion.Scope) error {
+	return clusterv1alpha4.Convert_v1alpha4_ObjectMeta_To_v1beta1_ObjectMeta(in, out, s)
+}
+
+// Convert_v1beta1_APIEndpoint_To_v1alpha4_APIEndpoint .
+func Convert_v1beta1_ObjectMeta_To_v1alpha4_ObjectMeta(in *clusterv1.ObjectMeta, out *clusterv1alpha4.ObjectMeta, s apiconversion.Scope) error {
+	return clusterv1alpha4.Convert_v1beta1_ObjectMeta_To_v1alpha4_ObjectMeta(in, out, s)
+}
+
 func Convert_v1beta1_AWSClusterSpec_To_v1alpha4_AWSClusterSpec(in *v1beta1.AWSClusterSpec, out *AWSClusterSpec, s conversion.Scope) error {
 	return autoConvert_v1beta1_AWSClusterSpec_To_v1alpha4_AWSClusterSpec(in, out, s)
 }
diff --git a/api/v1beta1/awsmachine_webhook.go b/api/v1beta1/awsmachine_webhook.go
index 68dd749c5..057009877 100644
--- a/api/v1beta1/awsmachine_webhook.go
+++ b/api/v1beta1/awsmachine_webhook.go
@@ -30,7 +30,7 @@ import (
 )
 
 // log is for logging in this package.
-var log = logf.Log.WithName("awsmachine-resource")
+var _ = logf.Log.WithName("awsmachine-resource")
 
 func (r *AWSMachine) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr).
@@ -253,9 +253,6 @@ func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
 		if len(additionalSecurityGroup.Filters) > 0 && additionalSecurityGroup.ID != nil {
 			allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.additionalSecurityGroups"), "only one of ID or Filters may be specified, specifying both is forbidden"))
 		}
-		if additionalSecurityGroup.ARN != nil {
-			log.Info("ARN field is deprecated and is no operation function.")
-		}
 	}
 	return allErrs
 }
diff --git a/api/v1beta1/tags.go b/api/v1beta1/tags.go
index e5b27212d..4b759f9d5 100644
--- a/api/v1beta1/tags.go
+++ b/api/v1beta1/tags.go
@@ -18,7 +18,6 @@ package v1beta1
 
 import (
 	"fmt"
-	"regexp"
 
 	"github.com/google/go-cmp/cmp"
 	"k8s.io/apimachinery/pkg/types"
@@ -75,18 +74,10 @@ func (t Tags) Merge(other Tags) {
 	}
 }
 
-// Validate checks if tags are valid for the AWS API/Resources.
-// Keys must have at least 1 and max 128 characters.
-// Values must be max 256 characters long.
-// Keys and Values can only have alphabets, numbers, spaces and _ . : / = + - @ as characters.
-// Tag's key cannot have prefix "aws:".
-// Max count of User tags for a specific resource can be 50.
+// Validate checks if tags are valid for the AWS API. Keys must have at
+// least 1 character and max 128. Values must be max 256 characters long.
 func (t Tags) Validate() []*field.Error {
-	// Defines the maximum number of user tags which can be created for a specific resource
-	const maxUserTagsAllowed = 50
 	var errs field.ErrorList
-	var userTagCount = len(t)
-	re := regexp.MustCompile(`^[a-zA-Z0-9\\s\_\.\:\=\+\-\@\/]*$`)
 
 	for k, v := range t {
 		if len(k) < 1 {
@@ -104,39 +95,11 @@ func (t Tags) Validate() []*field.Error {
 				field.Invalid(field.NewPath("spec", "additionalTags"), v, "value cannot be longer than 256 characters"),
 			)
 		}
-		if wrongUserTagNomenclature(k) {
-			errs = append(errs,
-				field.Invalid(field.NewPath("spec", "additionalTags"), k, "user created tag's key cannot have prefix aws:"),
-			)
-		}
-		val := re.MatchString(k)
-		if !val {
-			errs = append(errs,
-				field.Invalid(field.NewPath("spec", "additionalTags"), k, "key cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ ."),
-			)
-		}
-		val = re.MatchString(v)
-		if !val {
-			errs = append(errs,
-				field.Invalid(field.NewPath("spec", "additionalTags"), v, "value cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ ."),
-			)
-		}
-	}
-
-	if userTagCount > maxUserTagsAllowed {
-		errs = append(errs,
-			field.Invalid(field.NewPath("spec", "additionalTags"), t, "user created tags cannot be more than 50"),
-		)
 	}
 
 	return errs
 }
 
-// Checks whether the tag created is user tag or not.
-func wrongUserTagNomenclature(k string) bool {
-	return len(k) > 3 && k[0:4] == "aws:"
-}
-
 // ResourceLifecycle configures the lifecycle of a resource.
 type ResourceLifecycle string
 
diff --git a/api/v1beta1/types.go b/api/v1beta1/types.go
index a741eb1cc..0a6833454 100644
--- a/api/v1beta1/types.go
+++ b/api/v1beta1/types.go
@@ -22,17 +22,16 @@ import (
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 )
 
-// AWSResourceReference is a reference to a specific AWS resource by ID or filters.
-// Only one of ID or Filters may be specified. Specifying more than one will result in
+// AWSResourceReference is a reference to a specific AWS resource by ID, ARN, or filters.
+// Only one of ID, ARN or Filters may be specified. Specifying more than one will result in
 // a validation error.
 type AWSResourceReference struct {
 	// ID of resource
 	// +optional
 	ID *string `json:"id,omitempty"`
 
-	// ARN of resource.
+	// ARN of resource
 	// +optional
-	// Deprecated: This field has no function and is going to be removed in the next release.
 	ARN *string `json:"arn,omitempty"`
 
 	// Filters is a set of key/value pairs used to identify a resource
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
index 6599cc135..089484861 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
@@ -1018,13 +1018,12 @@ spec:
                       groups defined at the cluster level or in the actuator.
                     items:
                       description: AWSResourceReference is a reference to a specific
-                        AWS resource by ID or filters. Only one of ID or Filters may
-                        be specified. Specifying more than one will result in a validation
-                        error.
+                        AWS resource by ID, ARN, or filters. Only one of ID, ARN or
+                        Filters may be specified. Specifying more than one will result
+                        in a validation error.
                       properties:
                         arn:
-                          description: 'ARN of resource. Deprecated: This field has
-                            no function and is going to be removed in the next release.'
+                          description: ARN of resource
                           type: string
                         filters:
                           description: 'Filters is a set of key/value pairs used to
@@ -1264,12 +1263,12 @@ spec:
                 description: Subnets is an array of subnet configurations
                 items:
                   description: AWSResourceReference is a reference to a specific AWS
-                    resource by ID or filters. Only one of ID or Filters may be specified.
-                    Specifying more than one will result in a validation error.
+                    resource by ID, ARN, or filters. Only one of ID, ARN or Filters
+                    may be specified. Specifying more than one will result in a validation
+                    error.
                   properties:
                     arn:
-                      description: 'ARN of resource. Deprecated: This field has no
-                        function and is going to be removed in the next release.'
+                      description: ARN of resource
                       type: string
                     filters:
                       description: 'Filters is a set of key/value pairs used to identify
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml
index dfc135412..10927471c 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml
@@ -987,12 +987,12 @@ spec:
                   change too.
                 items:
                   description: AWSResourceReference is a reference to a specific AWS
-                    resource by ID or filters. Only one of ID or Filters may be specified.
-                    Specifying more than one will result in a validation error.
+                    resource by ID, ARN, or filters. Only one of ID, ARN or Filters
+                    may be specified. Specifying more than one will result in a validation
+                    error.
                   properties:
                     arn:
-                      description: 'ARN of resource. Deprecated: This field has no
-                        function and is going to be removed in the next release.'
+                      description: ARN of resource
                       type: string
                     filters:
                       description: 'Filters is a set of key/value pairs used to identify
@@ -1245,8 +1245,7 @@ spec:
                   If not specified, the cluster subnet will be used.
                 properties:
                   arn:
-                    description: 'ARN of resource. Deprecated: This field has no function
-                      and is going to be removed in the next release.'
+                    description: ARN of resource
                     type: string
                   filters:
                     description: 'Filters is a set of key/value pairs used to identify
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml
index 86472567a..f4cfa3d15 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml
@@ -789,14 +789,12 @@ spec:
                           the attached security groups might change too.
                         items:
                           description: AWSResourceReference is a reference to a specific
-                            AWS resource by ID or filters. Only one of ID or Filters
-                            may be specified. Specifying more than one will result
-                            in a validation error.
+                            AWS resource by ID, ARN, or filters. Only one of ID, ARN
+                            or Filters may be specified. Specifying more than one
+                            will result in a validation error.
                           properties:
                             arn:
-                              description: 'ARN of resource. Deprecated: This field
-                                has no function and is going to be removed in the
-                                next release.'
+                              description: ARN of resource
                               type: string
                             filters:
                               description: 'Filters is a set of key/value pairs used
@@ -1061,9 +1059,7 @@ spec:
                           be used.
                         properties:
                           arn:
-                            description: 'ARN of resource. Deprecated: This field
-                              has no function and is going to be removed in the next
-                              release.'
+                            description: ARN of resource
                             type: string
                           filters:
                             description: 'Filters is a set of key/value pairs used
diff --git a/controllers/awsmachine_security_groups.go b/controllers/awsmachine_security_groups.go
index 8b2e933d6..51c6f0ed9 100644
--- a/controllers/awsmachine_security_groups.go
+++ b/controllers/awsmachine_security_groups.go
@@ -49,7 +49,7 @@ func (r *AWSMachineReconciler) ensureSecurityGroups(ec2svc service.EC2Interface,
 		return false, err
 	}
 
-	additionalSecurityGroupsIDs, err := ec2svc.GetAdditionalSecurityGroupsIDs(additional)
+	additionalSecurityGroupsIDs, err := r.getAdditionalSecurityGroupsIDs(ec2svc, additional)
 	if err != nil {
 		return false, nil // nolint:nilerr
 	}
@@ -121,3 +121,24 @@ func (r *AWSMachineReconciler) securityGroupsChanged(annotation map[string]inter
 
 	return false, res
 }
+
+func (r *AWSMachineReconciler) getAdditionalSecurityGroupsIDs(ec2svc service.EC2Interface, securitygroups []infrav1.AWSResourceReference) ([]string, error) {
+	additionalSecurityGroupsIDs := []string{}
+
+	for _, sg := range securitygroups {
+		if sg.ID != nil {
+			additionalSecurityGroupsIDs = append(additionalSecurityGroupsIDs, *sg.ID)
+		}
+
+		if sg.Filters != nil {
+			id, err := ec2svc.GetFilteredSecurityGroupID(sg)
+			if err != nil {
+				return nil, err
+			}
+
+			additionalSecurityGroupsIDs = append(additionalSecurityGroupsIDs, id)
+		}
+	}
+
+	return additionalSecurityGroupsIDs, nil
+}
diff --git a/docs/roadmap.md b/docs/roadmap.md
index fe5a225da..32fb3dd12 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -2,31 +2,29 @@
 
 This roadmap is a constant work in progress, subject to frequent revision. Dates are approximations.
 
-## v1.5.x (v1beta1) - April/May 2022
-- [Network load balancer support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3088)
-- [Graduating EventBridge experimental feature](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3414)
-- [EFS CSI driver support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3384)
-- [AWSManagedMachinePool - Launch Template support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2055)
+## Ongoing
 
-## v1.6.x (v1beta1) - June/July 2022
+- Documentation improvements
 
-- [Spot instance support for AWSMachinePools](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2523)
-- [Node draining support for AWSMachinePools](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2574)
-- [IPv6 Support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2420)
-- [Security group customization support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/392)
+## v0.5.x (v1alpha3+) - June/July 2020
+- [Consume Cluster API e2e testing framework](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/1435)
+- [Supporting multiple AWS accounts as Kubernetes objects](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/1552)
+- [Bootstrap failure detection](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/972)
+- [Improved status conditions](https://github.com/kubernetes-sigs/cluster-api/issues/1658)
+- [Spot instances support for Machines](https://github.com/kubernetes-sigs/cluster-api/issues/1876)
 
-## v2.0.x (v1beta2) - End of 2022
+## v0.6 (v1alpha4) ~ Q4 2020
 
-- [Support for multiple topologies](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/1484)
+- [Multiple topologies](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/1484)
+- [Machine load balancer implementation](https://github.com/kubernetes-sigs/cluster-api/issues/1250)
+- SNS/SQS-based updates from CloudWatch Events so we don’t have to poll AWS for updates
 
 ## TBD
-- [AWS Fault injector integration to improve resiliency](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2173)
-- AWSMachinePool implementation backed by Spot Fleet and EC2 Fleet
-- [Dual stack IPv4/IPv6 support](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3381)
+
+- Implement MachinePools - Autoscaling groups and instances
+- Spot instance support for MachinePool ASG/Instance implementation
+- MachinePool implementation backed by Spot Fleet
+- Dual stack IPv4/IPv6 support
 - Windows nodes
+- Support for GPU instances and Elastic GPU
 - FIPS/NIST/STIG compliance
-- Workload identity support to CAPA-managed clusters
-- [Use ACK/CrossPlane as backend for AWS SDK calls](https://github.com/kubernetes-sigs/cluster-api-provider-aws/discussions/3306)
-- Karpenter support
-- [Draining resources created by CCM/CSI like LBs, SGs](https://github.com/kubernetes-sigs/cluster-api/issues/3075)
-- [OpenTelemetry integration](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/2178)
diff --git a/exp/api/v1alpha3/conversion.go b/exp/api/v1alpha3/conversion.go
index 5cc8d5dd0..aecdaf2bc 100644
--- a/exp/api/v1alpha3/conversion.go
+++ b/exp/api/v1alpha3/conversion.go
@@ -168,11 +168,30 @@ func Convert_v1beta1_AWSManagedMachinePoolSpec_To_v1alpha3_AWSManagedMachinePool
 	return autoConvert_v1beta1_AWSManagedMachinePoolSpec_To_v1alpha3_AWSManagedMachinePoolSpec(in, out, s)
 }
 
+// Convert_v1beta1_Instance_To_v1alpha3_Instance is a conversion function.
+func Convert_v1beta1_Instance_To_v1alpha3_Instance(in *infrav1.Instance, out *infrav1alpha3.Instance, s apiconversion.Scope) error {
+	return infrav1alpha3.Convert_v1beta1_Instance_To_v1alpha3_Instance(in, out, s)
+}
+
 // Convert_v1alpha3_Instance_To_v1beta1_Instance is a conversion function.
 func Convert_v1alpha3_Instance_To_v1beta1_Instance(in *infrav1alpha3.Instance, out *infrav1.Instance, s apiconversion.Scope) error {
 	return infrav1alpha3.Convert_v1alpha3_Instance_To_v1beta1_Instance(in, out, s)
 }
 
+// ConvertTo converts
+func Convert_v1alpha3_AWSResourceReference_To_v1beta1_AMIReference(in *infrav1alpha3.AWSResourceReference, out *infrav1.AMIReference, s apiconversion.Scope) error {
+	return infrav1alpha3.Convert_v1alpha3_AWSResourceReference_To_v1beta1_AMIReference(in, out, s)
+}
+
+func Convert_v1beta1_AMIReference_To_v1alpha3_AWSResourceReference(in *infrav1.AMIReference, out *infrav1alpha3.AWSResourceReference, s apiconversion.Scope) error {
+	return infrav1alpha3.Convert_v1beta1_AMIReference_To_v1alpha3_AWSResourceReference(in, out, s)
+}
+
+// Convert_v1beta1_Volume_To_v1alpha3_Volume is a conversion function.
+func Convert_v1beta1_Volume_To_v1alpha3_Volume(in *infrav1.Volume, out *infrav1alpha3.Volume, s apiconversion.Scope) error {
+	return infrav1alpha3.Convert_v1beta1_Volume_To_v1alpha3_Volume(in, out, s)
+}
+
 // Convert_v1alpha3_Volume_To_v1beta1_Volume is a conversion function.
 func Convert_v1alpha3_Volume_To_v1beta1_Volume(in *infrav1alpha3.Volume, out *infrav1.Volume, s apiconversion.Scope) error {
 	return infrav1alpha3.Convert_v1alpha3_Volume_To_v1beta1_Volume(in, out, s)
diff --git a/exp/api/v1beta1/awsmachinepool_webhook.go b/exp/api/v1beta1/awsmachinepool_webhook.go
index 41348f6e7..a17c44f9d 100644
--- a/exp/api/v1beta1/awsmachinepool_webhook.go
+++ b/exp/api/v1beta1/awsmachinepool_webhook.go
@@ -89,9 +89,6 @@ func (r *AWSMachinePool) validateSubnets() field.ErrorList {
 	}
 
 	for _, subnet := range r.Spec.Subnets {
-		if subnet.ARN != nil {
-			log.Info("ARN field is deprecated and is no operation function.")
-		}
 		if subnet.ID != nil && subnet.Filters != nil {
 			allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.subnets.filters"), "providing either subnet ID or filter is supported, should not provide both"))
 			break
@@ -101,19 +98,6 @@ func (r *AWSMachinePool) validateSubnets() field.ErrorList {
 	return allErrs
 }
 
-func (r *AWSMachinePool) validateAdditionalSecurityGroups() field.ErrorList {
-	var allErrs field.ErrorList
-	for _, sg := range r.Spec.AWSLaunchTemplate.AdditionalSecurityGroups {
-		if sg.ID != nil && sg.Filters != nil {
-			allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.awsLaunchTemplate.AdditionalSecurityGroups"), "either ID or filters should be used"))
-		}
-		if sg.ARN != nil {
-			log.Info("ARN field is deprecated and is no operation function.")
-		}
-	}
-	return allErrs
-}
-
 // ValidateCreate will do any extra validation when creating a AWSMachinePool.
 func (r *AWSMachinePool) ValidateCreate() error {
 	log.Info("AWSMachinePool validate create", "name", r.Name)
@@ -124,7 +108,6 @@ func (r *AWSMachinePool) ValidateCreate() error {
 	allErrs = append(allErrs, r.validateRootVolume()...)
 	allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
 	allErrs = append(allErrs, r.validateSubnets()...)
-	allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
 
 	if len(allErrs) == 0 {
 		return nil
@@ -144,7 +127,6 @@ func (r *AWSMachinePool) ValidateUpdate(old runtime.Object) error {
 	allErrs = append(allErrs, r.validateDefaultCoolDown()...)
 	allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
 	allErrs = append(allErrs, r.validateSubnets()...)
-	allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
 
 	if len(allErrs) == 0 {
 		return nil
diff --git a/pkg/cloud/scope/awsnode.go b/pkg/cloud/scope/awsnode.go
new file mode 100644
index 000000000..9bdd11cbc
--- /dev/null
+++ b/pkg/cloud/scope/awsnode.go
@@ -0,0 +1,40 @@
+/*
+Copyright 2022 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 scope
+
+import (
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
+)
+
+// AWSNodeScope is the interface for the scope to be used with the awsnode reconciling service.
+type AWSNodeScope interface {
+	cloud.ClusterScoper
+
+	// RemoteClient returns the Kubernetes client for connecting to the workload cluster.
+	RemoteClient() (client.Client, error)
+	// Subnets returns the cluster subnets.
+	Subnets() infrav1.Subnets
+	// SecondaryCidrBlock returns the optional secondary CIDR block to use for pod IPs
+	SecondaryCidrBlock() *string
+	// SecurityGroups returns the control plane security groups as a map, it creates the map if empty.
+	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
+	// DisableVPCCNI returns whether the AWS VPC CNI should be disabled
+	DisableVPCCNI() bool
+}
diff --git a/pkg/cloud/scope/iamauth.go b/pkg/cloud/scope/iamauth.go
new file mode 100644
index 000000000..0378bc51a
--- /dev/null
+++ b/pkg/cloud/scope/iamauth.go
@@ -0,0 +1,34 @@
+/*
+Copyright 2022 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 scope
+
+import (
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
+)
+
+// IAMAuthScope is the interface for the scope to be used with iamauth reconciling service.
+type IAMAuthScope interface {
+	cloud.ClusterScoper
+
+	// RemoteClient returns the Kubernetes client for connecting to the workload cluster.
+	RemoteClient() (client.Client, error)
+	// IAMAuthConfig returns the IAM authenticator config
+	IAMAuthConfig() *ekscontrolplanev1.IAMAuthenticatorConfig
+}
diff --git a/pkg/cloud/scope/network.go b/pkg/cloud/scope/network.go
new file mode 100644
index 000000000..7a15252ed
--- /dev/null
+++ b/pkg/cloud/scope/network.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2022 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 scope
+
+import (
+	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
+)
+
+// NetworkScope is the interface for the scope to be used with the network services.
+type NetworkScope interface {
+	cloud.ClusterScoper
+
+	// Network returns the cluster network object.
+	Network() *infrav1.NetworkStatus
+	// VPC returns the cluster VPC.
+	VPC() *infrav1.VPCSpec
+	// Subnets returns the cluster subnets.
+	Subnets() infrav1.Subnets
+	// SetSubnets updates the clusters subnets.
+	SetSubnets(subnets infrav1.Subnets)
+	// CNIIngressRules returns the CNI spec ingress rules.
+	CNIIngressRules() infrav1.CNIIngressRules
+	// SecurityGroups returns the cluster security groups as a map, it creates the map if empty.
+	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
+	// SecondaryCidrBlock returns the optional secondary CIDR block to use for pod IPs
+	SecondaryCidrBlock() *string
+
+	// Bastion returns the bastion details for the cluster.
+	Bastion() *infrav1.Bastion
+}
diff --git a/pkg/cloud/scope/sg.go b/pkg/cloud/scope/sg.go
new file mode 100644
index 000000000..55631607a
--- /dev/null
+++ b/pkg/cloud/scope/sg.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2022 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 scope
+
+import (
+	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
+)
+
+// SGScope is the interface for the scope to be used with the sg service.
+type SGScope interface {
+	cloud.ClusterScoper
+
+	// Network returns the cluster network object.
+	Network() *infrav1.NetworkStatus
+
+	// SecurityGroups returns the cluster security groups as a map, it creates the map if empty.
+	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
+
+	// SecurityGroupOverrides returns the security groups that are overridden in the cluster spec
+	SecurityGroupOverrides() map[infrav1.SecurityGroupRole]string
+
+	// VPC returns the cluster VPC.
+	VPC() *infrav1.VPCSpec
+
+	// CNIIngressRules returns the CNI spec ingress rules.
+	CNIIngressRules() infrav1.CNIIngressRules
+
+	// Bastion returns the bastion details for the cluster.
+	Bastion() *infrav1.Bastion
+}
diff --git a/pkg/cloud/services/awsnode/service.go b/pkg/cloud/services/awsnode/service.go
index d05688440..15ba95adc 100644
--- a/pkg/cloud/services/awsnode/service.go
+++ b/pkg/cloud/services/awsnode/service.go
@@ -17,35 +17,16 @@ limitations under the License.
 package awsnode
 
 import (
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
-	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
 )
 
-// Scope is a scope for use with the awsnode reconciling service.
-type Scope interface {
-	cloud.ClusterScoper
-
-	// RemoteClient returns the Kubernetes client for connecting to the workload cluster.
-	RemoteClient() (client.Client, error)
-	// Subnets returns the cluster subnets.
-	Subnets() infrav1.Subnets
-	// SecondaryCidrBlock returns the optional secondary CIDR block to use for pod IPs
-	SecondaryCidrBlock() *string
-	// SecurityGroups returns the control plane security groups as a map, it creates the map if empty.
-	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
-	// DisableVPCCNI returns whether the AWS VPC CNI should be disabled
-	DisableVPCCNI() bool
-}
-
 // Service defines the spec for a service.
 type Service struct {
-	scope Scope
+	scope scope.AWSNodeScope
 }
 
 // NewService will create a new service.
-func NewService(awsnodeScope Scope) *Service {
+func NewService(awsnodeScope scope.AWSNodeScope) *Service {
 	return &Service{
 		scope: awsnodeScope,
 	}
diff --git a/pkg/cloud/services/ec2/ami.go b/pkg/cloud/services/ec2/ami.go
index 593c7fec8..9e89bd23e 100644
--- a/pkg/cloud/services/ec2/ami.go
+++ b/pkg/cloud/services/ec2/ami.go
@@ -139,7 +139,7 @@ func DefaultAMILookup(ec2Client ec2iface.EC2API, ownerID, baseOS, kubernetesVers
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to find ami: %q", amiName)
 	}
-	if out == nil || len(out.Images) == 0 {
+	if len(out.Images) == 0 {
 		return nil, errors.Errorf("found no AMIs with the name: %q", amiName)
 	}
 	latestImage, err := GetLatestImage(out.Images)
diff --git a/pkg/cloud/services/ec2/instances.go b/pkg/cloud/services/ec2/instances.go
index 1197583a4..f1204dd4e 100644
--- a/pkg/cloud/services/ec2/instances.go
+++ b/pkg/cloud/services/ec2/instances.go
@@ -990,3 +990,26 @@ func getInstanceMarketOptionsRequest(spotMarketOptions *infrav1.SpotMarketOption
 
 	return instanceMarketOptionsRequest
 }
+
+// GetFilteredSecurityGroupID get security group ID using filters.
+func (s *Service) GetFilteredSecurityGroupID(securityGroup infrav1.AWSResourceReference) (string, error) {
+	if securityGroup.Filters == nil {
+		return "", nil
+	}
+
+	filters := []*ec2.Filter{}
+	for _, f := range securityGroup.Filters {
+		filters = append(filters, &ec2.Filter{Name: aws.String(f.Name), Values: aws.StringSlice(f.Values)})
+	}
+
+	sgs, err := s.EC2Client.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{Filters: filters})
+	if err != nil {
+		return "", err
+	}
+
+	if len(sgs.SecurityGroups) == 0 {
+		return "", fmt.Errorf("failed to find security group matching filters: %q, reason: %w", filters, err)
+	}
+
+	return *sgs.SecurityGroups[0].GroupId, nil
+}
diff --git a/pkg/cloud/services/ec2/launchtemplate.go b/pkg/cloud/services/ec2/launchtemplate.go
index 72314c6d2..701244bc4 100644
--- a/pkg/cloud/services/ec2/launchtemplate.go
+++ b/pkg/cloud/services/ec2/launchtemplate.go
@@ -18,7 +18,6 @@ package ec2
 
 import (
 	"encoding/base64"
-	"fmt"
 	"sort"
 	"strconv"
 	"strings"
@@ -186,11 +185,9 @@ func (s *Service) createLaunchTemplateData(scope *scope.MachinePoolScope, imageI
 	}
 
 	// add additional security groups as well
-	securityGroupIDs, err := s.GetAdditionalSecurityGroupsIDs(scope.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups)
-	if err != nil {
-		return nil, err
+	for _, additionalGroup := range scope.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups {
+		data.SecurityGroupIds = append(data.SecurityGroupIds, additionalGroup.ID)
 	}
-	data.SecurityGroupIds = append(data.SecurityGroupIds, aws.StringSlice(securityGroupIDs)...)
 
 	// set the AMI ID
 	data.ImageId = imageID
@@ -371,9 +368,9 @@ func (s *Service) LaunchTemplateNeedsUpdate(scope *scope.MachinePoolScope, incom
 		return true, nil
 	}
 
-	incomingIDs, err := s.GetAdditionalSecurityGroupsIDs(incoming.AdditionalSecurityGroups)
-	if err != nil {
-		return false, err
+	incomingIDs := make([]string, len(incoming.AdditionalSecurityGroups))
+	for i, ref := range incoming.AdditionalSecurityGroups {
+		incomingIDs[i] = aws.StringValue(ref.ID)
 	}
 
 	coreIDs, err := s.GetCoreNodeSecurityGroups(scope)
@@ -382,10 +379,12 @@ func (s *Service) LaunchTemplateNeedsUpdate(scope *scope.MachinePoolScope, incom
 	}
 
 	incomingIDs = append(incomingIDs, coreIDs...)
-	existingIDs, err := s.GetAdditionalSecurityGroupsIDs(existing.AdditionalSecurityGroups)
-	if err != nil {
-		return false, err
+
+	existingIDs := make([]string, len(existing.AdditionalSecurityGroups))
+	for i, ref := range existing.AdditionalSecurityGroups {
+		existingIDs[i] = aws.StringValue(ref.ID)
 	}
+
 	sort.Strings(incomingIDs)
 	sort.Strings(existingIDs)
 
@@ -443,25 +442,6 @@ func (s *Service) DiscoverLaunchTemplateAMI(scope *scope.MachinePoolScope) (*str
 	return aws.String(lookupAMI), nil
 }
 
-func (s *Service) GetAdditionalSecurityGroupsIDs(securityGroups []infrav1.AWSResourceReference) ([]string, error) {
-	var additionalSecurityGroupsIDs []string
-
-	for _, sg := range securityGroups {
-		if sg.ID != nil {
-			additionalSecurityGroupsIDs = append(additionalSecurityGroupsIDs, *sg.ID)
-		} else if sg.Filters != nil {
-			id, err := s.getFilteredSecurityGroupID(sg)
-			if err != nil {
-				return nil, err
-			}
-
-			additionalSecurityGroupsIDs = append(additionalSecurityGroupsIDs, id)
-		}
-	}
-
-	return additionalSecurityGroupsIDs, nil
-}
-
 func (s *Service) buildLaunchTemplateTagSpecificationRequest(scope *scope.MachinePoolScope) []*ec2.LaunchTemplateTagSpecificationRequest {
 	tagSpecifications := make([]*ec2.LaunchTemplateTagSpecificationRequest, 0)
 	additionalTags := scope.AdditionalTags()
@@ -499,26 +479,3 @@ func (s *Service) buildLaunchTemplateTagSpecificationRequest(scope *scope.Machin
 	}
 	return tagSpecifications
 }
-
-// getFilteredSecurityGroupID get security group ID using filters.
-func (s *Service) getFilteredSecurityGroupID(securityGroup infrav1.AWSResourceReference) (string, error) {
-	if securityGroup.Filters == nil {
-		return "", nil
-	}
-
-	filters := []*ec2.Filter{}
-	for _, f := range securityGroup.Filters {
-		filters = append(filters, &ec2.Filter{Name: aws.String(f.Name), Values: aws.StringSlice(f.Values)})
-	}
-
-	sgs, err := s.EC2Client.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{Filters: filters})
-	if err != nil {
-		return "", err
-	}
-
-	if len(sgs.SecurityGroups) == 0 {
-		return "", fmt.Errorf("failed to find security group matching filters: %q, reason: %w", filters, err)
-	}
-
-	return *sgs.SecurityGroups[0].GroupId, nil
-}
diff --git a/pkg/cloud/services/iamauth/service.go b/pkg/cloud/services/iamauth/service.go
index b147fdef8..9e82af8bb 100644
--- a/pkg/cloud/services/iamauth/service.go
+++ b/pkg/cloud/services/iamauth/service.go
@@ -20,31 +20,19 @@ import (
 	"github.com/aws/aws-sdk-go/service/sts/stsiface"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
-	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
-	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
 	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
 )
 
-// Scope is a scope for use with the iamauth reconciling service.
-type Scope interface {
-	cloud.ClusterScoper
-
-	// RemoteClient returns the Kubernetes client for connecting to the workload cluster.
-	RemoteClient() (client.Client, error)
-	// IAMAuthConfig returns the IAM authenticator config
-	IAMAuthConfig() *ekscontrolplanev1.IAMAuthenticatorConfig
-}
-
 // Service defines the specs for a service.
 type Service struct {
-	scope     Scope
+	scope     scope.IAMAuthScope
 	backend   BackendType
 	client    client.Client
 	STSClient stsiface.STSAPI
 }
 
 // NewService will create a new Service object.
-func NewService(iamScope Scope, backend BackendType, client client.Client) *Service {
+func NewService(iamScope scope.IAMAuthScope, backend BackendType, client client.Client) *Service {
 	return &Service{
 		scope:     iamScope,
 		backend:   backend,
diff --git a/pkg/cloud/services/interfaces.go b/pkg/cloud/services/interfaces.go
index c9920cd3b..d532cd019 100644
--- a/pkg/cloud/services/interfaces.go
+++ b/pkg/cloud/services/interfaces.go
@@ -50,9 +50,9 @@ type EC2Interface interface {
 	CreateInstance(scope *scope.MachineScope, userData []byte, userDataFormat string) (*infrav1.Instance, error)
 	GetRunningInstanceByTags(scope *scope.MachineScope) (*infrav1.Instance, error)
 
-	GetAdditionalSecurityGroupsIDs(securityGroup []infrav1.AWSResourceReference) ([]string, error)
 	GetCoreSecurityGroups(machine *scope.MachineScope) ([]string, error)
 	GetInstanceSecurityGroups(instanceID string) (map[string][]string, error)
+	GetFilteredSecurityGroupID(securityGroup infrav1.AWSResourceReference) (string, error)
 	UpdateInstanceSecurityGroups(id string, securityGroups []string) error
 	UpdateResourceTags(resourceID *string, create, remove map[string]string) error
 
diff --git a/pkg/cloud/services/mock_services/ec2_interface_mock.go b/pkg/cloud/services/mock_services/ec2_interface_mock.go
index 73c42f1db..b1b5de1eb 100644
--- a/pkg/cloud/services/mock_services/ec2_interface_mock.go
+++ b/pkg/cloud/services/mock_services/ec2_interface_mock.go
@@ -153,34 +153,34 @@ func (mr *MockEC2InterfaceMockRecorder) DiscoverLaunchTemplateAMI(arg0 interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiscoverLaunchTemplateAMI", reflect.TypeOf((*MockEC2Interface)(nil).DiscoverLaunchTemplateAMI), arg0)
 }
 
-// GetAdditionalSecurityGroupsIDs mocks base method.
-func (m *MockEC2Interface) GetAdditionalSecurityGroupsIDs(arg0 []v1beta1.AWSResourceReference) ([]string, error) {
+// GetCoreSecurityGroups mocks base method.
+func (m *MockEC2Interface) GetCoreSecurityGroups(arg0 *scope.MachineScope) ([]string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetAdditionalSecurityGroupsIDs", arg0)
+	ret := m.ctrl.Call(m, "GetCoreSecurityGroups", arg0)
 	ret0, _ := ret[0].([]string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// GetAdditionalSecurityGroupsIDs indicates an expected call of GetAdditionalSecurityGroupsIDs.
-func (mr *MockEC2InterfaceMockRecorder) GetAdditionalSecurityGroupsIDs(arg0 interface{}) *gomock.Call {
+// GetCoreSecurityGroups indicates an expected call of GetCoreSecurityGroups.
+func (mr *MockEC2InterfaceMockRecorder) GetCoreSecurityGroups(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAdditionalSecurityGroupsIDs", reflect.TypeOf((*MockEC2Interface)(nil).GetAdditionalSecurityGroupsIDs), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCoreSecurityGroups", reflect.TypeOf((*MockEC2Interface)(nil).GetCoreSecurityGroups), arg0)
 }
 
-// GetCoreSecurityGroups mocks base method.
-func (m *MockEC2Interface) GetCoreSecurityGroups(arg0 *scope.MachineScope) ([]string, error) {
+// GetFilteredSecurityGroupID mocks base method.
+func (m *MockEC2Interface) GetFilteredSecurityGroupID(arg0 v1beta1.AWSResourceReference) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetCoreSecurityGroups", arg0)
-	ret0, _ := ret[0].([]string)
+	ret := m.ctrl.Call(m, "GetFilteredSecurityGroupID", arg0)
+	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// GetCoreSecurityGroups indicates an expected call of GetCoreSecurityGroups.
-func (mr *MockEC2InterfaceMockRecorder) GetCoreSecurityGroups(arg0 interface{}) *gomock.Call {
+// GetFilteredSecurityGroupID indicates an expected call of GetFilteredSecurityGroupID.
+func (mr *MockEC2InterfaceMockRecorder) GetFilteredSecurityGroupID(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCoreSecurityGroups", reflect.TypeOf((*MockEC2Interface)(nil).GetCoreSecurityGroups), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFilteredSecurityGroupID", reflect.TypeOf((*MockEC2Interface)(nil).GetFilteredSecurityGroupID), arg0)
 }
 
 // GetInstanceSecurityGroups mocks base method.
diff --git a/pkg/cloud/services/network/service.go b/pkg/cloud/services/network/service.go
index c26adf7c2..32f7a6d89 100644
--- a/pkg/cloud/services/network/service.go
+++ b/pkg/cloud/services/network/service.go
@@ -19,44 +19,19 @@ package network
 import (
 	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
 
-	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
-	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
 	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
 )
 
-// Scope is scope for use with the network service.
-type Scope interface {
-	cloud.ClusterScoper
-
-	// Network returns the cluster network object.
-	Network() *infrav1.NetworkStatus
-	// VPC returns the cluster VPC.
-	VPC() *infrav1.VPCSpec
-	// Subnets returns the cluster subnets.
-	Subnets() infrav1.Subnets
-	// SetSubnets updates the clusters subnets.
-	SetSubnets(subnets infrav1.Subnets)
-	// CNIIngressRules returns the CNI spec ingress rules.
-	CNIIngressRules() infrav1.CNIIngressRules
-	// SecurityGroups returns the cluster security groups as a map, it creates the map if empty.
-	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
-	// SecondaryCidrBlock returns the optional secondary CIDR block to use for pod IPs
-	SecondaryCidrBlock() *string
-
-	// Bastion returns the bastion details for the cluster.
-	Bastion() *infrav1.Bastion
-}
-
 // Service holds a collection of interfaces.
 // The interfaces are broken down like this to group functions together.
 // One alternative is to have a large list of functions from the ec2 client.
 type Service struct {
-	scope     Scope
+	scope     scope.NetworkScope
 	EC2Client ec2iface.EC2API
 }
 
 // NewService returns a new service given the ec2 api client.
-func NewService(networkScope Scope) *Service {
+func NewService(networkScope scope.NetworkScope) *Service {
 	return &Service{
 		scope:     networkScope,
 		EC2Client: scope.NewEC2Client(networkScope, networkScope, networkScope, networkScope.InfraCluster()),
diff --git a/pkg/cloud/services/securitygroup/service.go b/pkg/cloud/services/securitygroup/service.go
index 7f67c8fa9..fe6fb5fcf 100644
--- a/pkg/cloud/services/securitygroup/service.go
+++ b/pkg/cloud/services/securitygroup/service.go
@@ -20,45 +20,21 @@ import (
 	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
-	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud"
 	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope"
 )
 
-// Scope is a scope for use with the security group reconciling service.
-type Scope interface {
-	cloud.ClusterScoper
-
-	// Network returns the cluster network object.
-	Network() *infrav1.NetworkStatus
-
-	// SecurityGroups returns the cluster security groups as a map, it creates the map if empty.
-	SecurityGroups() map[infrav1.SecurityGroupRole]infrav1.SecurityGroup
-
-	// SecurityGroupOverrides returns the security groups that are overridden in the cluster spec
-	SecurityGroupOverrides() map[infrav1.SecurityGroupRole]string
-
-	// VPC returns the cluster VPC.
-	VPC() *infrav1.VPCSpec
-
-	// CNIIngressRules returns the CNI spec ingress rules.
-	CNIIngressRules() infrav1.CNIIngressRules
-
-	// Bastion returns the bastion details for the cluster.
-	Bastion() *infrav1.Bastion
-}
-
 // Service holds a collection of interfaces.
 // The interfaces are broken down like this to group functions together.
 // One alternative is to have a large list of functions from the ec2 client.
 type Service struct {
-	scope     Scope
+	scope     scope.SGScope
 	roles     []infrav1.SecurityGroupRole
 	EC2Client ec2iface.EC2API
 }
 
 // NewService returns a new service given the api clients with a defined
 // set of roles.
-func NewService(sgScope Scope, roles []infrav1.SecurityGroupRole) *Service {
+func NewService(sgScope scope.SGScope, roles []infrav1.SecurityGroupRole) *Service {
 	return &Service{
 		scope:     sgScope,
 		roles:     roles,


Test patch:
diff --git a/api/v1beta1/tags_test.go b/api/v1beta1/tags_test.go
index 6433ac260..c2d2b1abd 100644
--- a/api/v1beta1/tags_test.go
+++ b/api/v1beta1/tags_test.go
@@ -271,68 +271,6 @@ func TestTags_Validate(t *testing.T) {
 				},
 			},
 		},
-		{
-			name: "key has aws: prefix",
-			self: Tags{
-				"aws:key": "validValue",
-			},
-			expected: []*field.Error{
-				{
-					Type:     field.ErrorTypeInvalid,
-					Detail:   "user created tag's key cannot have prefix aws:",
-					Field:    "spec.additionalTags",
-					BadValue: "aws:key",
-				},
-			},
-		},
-		{
-			name: "key has wrong characters",
-			self: Tags{
-				"wrong*key": "validValue",
-			},
-			expected: []*field.Error{
-				{
-					Type:     field.ErrorTypeInvalid,
-					Detail:   "key cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ .",
-					Field:    "spec.additionalTags",
-					BadValue: "wrong*key",
-				},
-			},
-		},
-		{
-			name: "value has wrong characters",
-			self: Tags{
-				"validKey": "wrong*value",
-			},
-			expected: []*field.Error{
-				{
-					Type:     field.ErrorTypeInvalid,
-					Detail:   "value cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ .",
-					Field:    "spec.additionalTags",
-					BadValue: "wrong*value",
-				},
-			},
-		},
-		{
-			name: "value and key both has wrong characters",
-			self: Tags{
-				"wrong*key": "wrong*value",
-			},
-			expected: []*field.Error{
-				{
-					Type:     field.ErrorTypeInvalid,
-					Detail:   "key cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ .",
-					Field:    "spec.additionalTags",
-					BadValue: "wrong*key",
-				},
-				{
-					Type:     field.ErrorTypeInvalid,
-					Detail:   "value cannot have characters other than alphabets, numbers, spaces and _ . : / = + - @ .",
-					Field:    "spec.additionalTags",
-					BadValue: "wrong*value",
-				},
-			},
-		},
 	}
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/controllers/awsmachine_controller_unit_test.go b/controllers/awsmachine_controller_unit_test.go
index 642f8ac66..0cf672984 100644
--- a/controllers/awsmachine_controller_unit_test.go
+++ b/controllers/awsmachine_controller_unit_test.go
@@ -450,7 +450,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 						},
 					}
 					ec2Svc.EXPECT().UpdateInstanceSecurityGroups(instance.ID, []string{"sg-2345"})
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-2345"}, nil)
 
 					_, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 					expectConditions(g, ms.AWSMachine, []conditionAssertion{{conditionType: infrav1.SecurityGroupsReadyCondition, status: corev1.ConditionTrue}})
@@ -464,7 +463,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 					instanceCreate(t, g)
 					getCoreSecurityGroups(t, g)
 
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 					ec2Svc.EXPECT().UpdateInstanceSecurityGroups(gomock.Any(), gomock.Any()).Times(0)
 					if _, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs); err != nil {
 						_ = fmt.Errorf("reconcileNormal reutrned an error during test")
@@ -482,7 +480,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 					ms.AWSMachine.Spec.AdditionalTags = infrav1.Tags{"kind": "alicorn"}
 					cs.AWSCluster.Spec.AdditionalTags = infrav1.Tags{"colour": "lavender"}
 
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 					ec2Svc.EXPECT().UpdateResourceTags(
 						gomock.Any(),
 						map[string]string{
@@ -513,7 +510,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 
 					ms.AWSMachine.Spec.AdditionalTags = infrav1.Tags{"rootDeviceID": "id1", "rootDeviceSize": "30"}
 
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 					ec2Svc.EXPECT().UpdateResourceTags(
 						gomock.Any(),
 						map[string]string{
@@ -540,7 +536,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 					secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
 					ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
 					secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1)
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 				}
 
 				t.Run("should set instance to stopping and unready", func(t *testing.T) {
@@ -651,7 +646,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1)
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 				g.Expect(err).To(BeNil())
@@ -677,7 +671,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				secretSvc.EXPECT().Create(gomock.Any(), gomock.Any()).Return("test", int32(1), nil).Times(1)
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 				g.Expect(err).To(BeNil())
@@ -695,7 +688,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				ms.AWSMachine.Spec.CloudInit.InsecureSkipSecretsManager = true
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 				reconciler.elbServiceFactory = func(elbScope scope.ELBScope) services.ELBInterface {
 					return elbSvc
 				}
@@ -926,6 +918,8 @@ func TestAWSMachineReconciler(t *testing.T) {
 					ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"}
 					ms.AWSMachine.Spec.AdditionalSecurityGroups = []infrav1.AWSResourceReference{
 						{
+							ID:  aws.String("sg-1"),
+							ARN: aws.String("arn-1"),
 							Filters: []infrav1.Filter{
 								{
 									Name:   "example-name",
@@ -936,7 +930,7 @@ func TestAWSMachineReconciler(t *testing.T) {
 					}
 
 					ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil)
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-1"}, errors.New("failed to get filtered SGs"))
+					ec2Svc.EXPECT().GetFilteredSecurityGroupID(gomock.Any()).Return("sg-1", errors.New("failed to get filtered SGs"))
 
 					_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 					g.Expect(err).To(BeNil())
@@ -956,18 +950,20 @@ func TestAWSMachineReconciler(t *testing.T) {
 					ms.AWSMachine.Annotations = map[string]string{SecurityGroupsLastAppliedAnnotation: "{\"tag\":\"tag1\"}"}
 					ms.AWSMachine.Spec.AdditionalSecurityGroups = []infrav1.AWSResourceReference{
 						{
+							ID:  aws.String("sg-1"),
+							ARN: aws.String("arn-1"),
 							Filters: []infrav1.Filter{
 								{
-									Name:   "id",
-									Values: []string{"sg-1"},
+									Name:   "example-name",
+									Values: []string{"example-value"},
 								},
 							},
 						},
 					}
 
+					ec2Svc.EXPECT().GetFilteredSecurityGroupID(gomock.Any()).Return("sg-1", nil)
 					ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil)
 					ec2Svc.EXPECT().UpdateInstanceSecurityGroups(gomock.Any(), gomock.Any()).Return(errors.New("failed to update security groups"))
-					ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return([]string{"sg-1"}, nil)
 
 					_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 					g.Expect(err).ToNot(BeNil())
@@ -1000,7 +996,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				ms.AWSMachine.ObjectMeta.Labels = map[string]string{
 					clusterv1.MachineControlPlaneLabelName: "",
@@ -1043,7 +1038,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).
 					Return(map[string][]string{"eid": {}}, nil).Times(1)
 				secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
 				_, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 			})
@@ -1129,7 +1123,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).
 					Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 				secretSvc.EXPECT().Delete(gomock.Any()).Return(nil).MaxTimes(0)
 				_, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 			})
@@ -1212,7 +1205,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				secretSvc.EXPECT().UserData(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 
@@ -1259,7 +1251,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				ec2Svc.EXPECT().CreateInstance(gomock.Any(), gomock.Any(), gomock.Any()).Return(instance, nil).AnyTimes()
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				ms.AWSMachine.ObjectMeta.Labels = map[string]string{
 					clusterv1.MachineControlPlaneLabelName: "",
@@ -1307,7 +1298,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 
 				_, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 			})
@@ -1389,7 +1379,6 @@ func TestAWSMachineReconciler(t *testing.T) {
 				instance.State = infrav1.InstanceStateRunning
 				ec2Svc.EXPECT().GetInstanceSecurityGroups(gomock.Any()).Return(map[string][]string{"eid": {}}, nil).Times(1)
 				ec2Svc.EXPECT().GetCoreSecurityGroups(gomock.Any()).Return([]string{}, nil).Times(1)
-				ec2Svc.EXPECT().GetAdditionalSecurityGroupsIDs(gomock.Any()).Return(nil, nil)
 				objectStoreSvc.EXPECT().Delete(gomock.Any()).Return(nil).MaxTimes(0)
 				_, _ = reconciler.reconcileNormal(context.Background(), ms, cs, cs, cs, cs)
 			})
diff --git a/exp/api/v1alpha3/conversion_test.go b/exp/api/v1alpha3/conversion_test.go
index ca56ceb37..3f2bda8c1 100644
--- a/exp/api/v1alpha3/conversion_test.go
+++ b/exp/api/v1alpha3/conversion_test.go
@@ -18,14 +18,14 @@ package v1alpha3
 
 import (
 	"testing"
-	
-	fuzz "github.com/google/gofuzz"
+
 	. "github.com/onsi/gomega"
+
+	fuzz "github.com/google/gofuzz"
 	"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
-	"k8s.io/apimachinery/pkg/runtime"
+	runtime "k8s.io/apimachinery/pkg/runtime"
 	runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
-	
-	"sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
 	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
 )
 
diff --git a/exp/api/v1alpha4/conversion_test.go b/exp/api/v1alpha4/conversion_test.go
index 9035398e3..61c66f6a4 100644
--- a/exp/api/v1alpha4/conversion_test.go
+++ b/exp/api/v1alpha4/conversion_test.go
@@ -20,8 +20,8 @@ import (
 	"testing"
 
 	. "github.com/onsi/gomega"
-	"k8s.io/apimachinery/pkg/runtime"
 
+	runtime "k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta1"
 	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
 )
diff --git a/exp/api/v1beta1/awsmachinepool_webhook_test.go b/exp/api/v1beta1/awsmachinepool_webhook_test.go
index 5ea3ec51c..dec7b7711 100644
--- a/exp/api/v1beta1/awsmachinepool_webhook_test.go
+++ b/exp/api/v1beta1/awsmachinepool_webhook_test.go
@@ -20,7 +20,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/aws/aws-sdk-go/aws"
 	. "github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
@@ -72,23 +71,6 @@ func TestAWSMachinePool_ValidateCreate(t *testing.T) {
 			},
 			wantErr: true,
 		},
-		{
-			name: "Should fail if additional security groups are provided with both ID and Filters",
-			pool: &AWSMachinePool{
-				Spec: AWSMachinePoolSpec{
-					AWSLaunchTemplate: AWSLaunchTemplate{AdditionalSecurityGroups: []infrav1.AWSResourceReference{{
-						ID: aws.String("sg-1"),
-						Filters: []infrav1.Filter{
-							{
-								Name:   "sg-1",
-								Values: []string{"test"},
-							},
-						},
-					}}},
-				},
-			},
-			wantErr: true,
-		},
 		{
 			name: "Should fail if both subnet ID and filters passed in AWSMachinePool spec",
 			pool: &AWSMachinePool{
diff --git a/pkg/cloud/services/autoscaling/autoscalinggroup_test.go b/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
index d3a104cb7..c792eaa76 100644
--- a/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
+++ b/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
@@ -1095,7 +1095,8 @@ func getMachinePoolScope(client client.Client, clusterScope *scope.ClusterScope)
 		Spec: expinfrav1.AWSMachinePoolSpec{
 			Subnets: []infrav1.AWSResourceReference{
 				{
-					ID: aws.String("subnet1"),
+					ID:  aws.String("subnet1"),
+					ARN: aws.String("subnetARN"),
 				},
 			},
 			RefreshPreferences: &expinfrav1.RefreshPreferences{
diff --git a/pkg/cloud/services/ec2/helper_test.go b/pkg/cloud/services/ec2/helper_test.go
index c3e958b70..11e78ba4e 100644
--- a/pkg/cloud/services/ec2/helper_test.go
+++ b/pkg/cloud/services/ec2/helper_test.go
@@ -96,11 +96,12 @@ func newAWSMachinePool() *expinfrav1.AWSMachinePool {
 			AvailabilityZones: []string{"us-east-1"},
 			AdditionalTags:    infrav1.Tags{},
 			AWSLaunchTemplate: expinfrav1.AWSLaunchTemplate{
-				Name:               "aws-launch-template",
-				IamInstanceProfile: "instance-profile",
-				AMI:                infrav1.AMIReference{},
-				InstanceType:       "t3.large",
-				SSHKeyName:         aws.String("default"),
+				Name:                     "aws-launch-template",
+				IamInstanceProfile:       "instance-profile",
+				AMI:                      infrav1.AMIReference{},
+				InstanceType:             "t3.large",
+				SSHKeyName:               aws.String("default"),
+				AdditionalSecurityGroups: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
 			},
 		},
 		Status: expinfrav1.AWSMachinePoolStatus{
diff --git a/pkg/cloud/services/ec2/instances_test.go b/pkg/cloud/services/ec2/instances_test.go
index 7721072d6..b69432643 100644
--- a/pkg/cloud/services/ec2/instances_test.go
+++ b/pkg/cloud/services/ec2/instances_test.go
@@ -3195,7 +3195,7 @@ func TestGetFilteredSecurityGroupID(t *testing.T) {
 				EC2Client: ec2Mock,
 			}
 
-			id, err := s.getFilteredSecurityGroupID(tc.securityGroup)
+			id, err := s.GetFilteredSecurityGroupID(tc.securityGroup)
 			tc.check(id, err)
 		})
 	}
diff --git a/pkg/cloud/services/ec2/launchtemplate_test.go b/pkg/cloud/services/ec2/launchtemplate_test.go
index 33951e8f1..ab34077b8 100644
--- a/pkg/cloud/services/ec2/launchtemplate_test.go
+++ b/pkg/cloud/services/ec2/launchtemplate_test.go
@@ -359,14 +359,10 @@ func TestService_SDKToLaunchTemplate(t *testing.T) {
 }
 
 func TestService_LaunchTemplateNeedsUpdate(t *testing.T) {
-	mockCtrl := gomock.NewController(t)
-	defer mockCtrl.Finish()
-
 	tests := []struct {
 		name     string
 		incoming *expinfrav1.AWSLaunchTemplate
 		existing *expinfrav1.AWSLaunchTemplate
-		expect   func(m *mock_ec2iface.MockEC2APIMockRecorder)
 		want     bool
 		wantErr  bool
 	}{
@@ -441,27 +437,6 @@ func TestService_LaunchTemplateNeedsUpdate(t *testing.T) {
 			},
 			want: true,
 		},
-		{
-			name: "new additional security group with filters",
-			incoming: &expinfrav1.AWSLaunchTemplate{
-				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
-					{Filters: []infrav1.Filter{{Name: "sg-1", Values: []string{"test-1"}}}},
-				},
-			},
-			existing: &expinfrav1.AWSLaunchTemplate{
-				AdditionalSecurityGroups: []infrav1.AWSResourceReference{
-					{Filters: []infrav1.Filter{{Name: "sg-2", Values: []string{"test-2"}}}},
-				},
-			},
-			expect: func(m *mock_ec2iface.MockEC2APIMockRecorder) {
-				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-1"), Values: aws.StringSlice([]string{"test-1"})}}})).
-					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-1")}}}, nil)
-				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-2"), Values: aws.StringSlice([]string{"test-2"})}}})).
-					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-2")}}}, nil)
-			},
-			want:    true,
-			wantErr: false,
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -491,13 +466,6 @@ func TestService_LaunchTemplateNeedsUpdate(t *testing.T) {
 					AWSCluster: ac,
 				},
 			}
-			mockEC2Client := mock_ec2iface.NewMockEC2API(mockCtrl)
-			s.EC2Client = mockEC2Client
-
-			if tt.expect != nil {
-				tt.expect(mockEC2Client.EXPECT())
-			}
-
 			got, err := s.LaunchTemplateNeedsUpdate(machinePoolScope, tt.incoming, tt.existing)
 			if tt.wantErr {
 				g.Expect(err).To(HaveOccurred())
@@ -715,14 +683,12 @@ func TestCreateLaunchTemplate(t *testing.T) {
 
 	var userData = []byte{1, 0, 0}
 	testCases := []struct {
-		name                 string
-		awsResourceReference []infrav1.AWSResourceReference
-		expect               func(g *WithT, m *mock_ec2iface.MockEC2APIMockRecorder)
-		check                func(g *WithT, s string, e error)
+		name   string
+		expect func(g *WithT, m *mock_ec2iface.MockEC2APIMockRecorder)
+		check  func(g *WithT, s string, e error)
 	}{
 		{
-			name:                 "Should not return error if successfully created launch template id",
-			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
+			name: "Should not return error if successfully created launch template id",
 			expect: func(g *WithT, m *mock_ec2iface.MockEC2APIMockRecorder) {
 				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
 				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
@@ -775,64 +741,7 @@ func TestCreateLaunchTemplate(t *testing.T) {
 			},
 		},
 		{
-			name:                 "Should successfully create launch template id with AdditionalSecurityGroups Filter",
-			awsResourceReference: []infrav1.AWSResourceReference{{Filters: []infrav1.Filter{{Name: "sg-1", Values: []string{"test"}}}}},
-			expect: func(g *WithT, m *mock_ec2iface.MockEC2APIMockRecorder) {
-				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
-				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
-				sgMap[infrav1.SecurityGroupLB] = infrav1.SecurityGroup{ID: "2"}
-
-				var expectedInput = &ec2.CreateLaunchTemplateInput{
-					LaunchTemplateData: &ec2.RequestLaunchTemplateData{
-						InstanceType: aws.String("t3.large"),
-						IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{
-							Name: aws.String("instance-profile"),
-						},
-						KeyName:          aws.String("default"),
-						UserData:         pointer.StringPtr(base64.StdEncoding.EncodeToString(userData)),
-						SecurityGroupIds: aws.StringSlice([]string{"nodeSG", "lbSG", "sg-1"}),
-						ImageId:          aws.String("imageID"),
-						TagSpecifications: []*ec2.LaunchTemplateTagSpecificationRequest{
-							{
-								ResourceType: aws.String(ec2.ResourceTypeInstance),
-								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
-							},
-							{
-								ResourceType: aws.String(ec2.ResourceTypeVolume),
-								Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
-							},
-						},
-					},
-					LaunchTemplateName: aws.String("aws-mp-name"),
-					TagSpecifications: []*ec2.TagSpecification{
-						{
-							ResourceType: aws.String(ec2.ResourceTypeLaunchTemplate),
-							Tags:         defaultEC2Tags("aws-mp-name", "cluster-name"),
-						},
-					},
-				}
-				m.CreateLaunchTemplate(gomock.AssignableToTypeOf(expectedInput)).Return(&ec2.CreateLaunchTemplateOutput{
-					LaunchTemplate: &ec2.LaunchTemplate{
-						LaunchTemplateId: aws.String("launch-template-id"),
-					},
-				}, nil).Do(func(arg *ec2.CreateLaunchTemplateInput) {
-					// formatting added to match arrays during reflect.DeepEqual
-					formatTagsInput(arg)
-					if !cmp.Equal(expectedInput, arg) {
-						t.Fatalf("mismatch in input expected: %+v, got: %+v", expectedInput, arg)
-					}
-				})
-				m.DescribeSecurityGroups(gomock.Eq(&ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{{Name: aws.String("sg-1"), Values: aws.StringSlice([]string{"test"})}}})).
-					Return(&ec2.DescribeSecurityGroupsOutput{SecurityGroups: []*ec2.SecurityGroup{{GroupId: aws.String("sg-1")}}}, nil)
-			},
-			check: func(g *WithT, id string, err error) {
-				g.Expect(id).Should(Equal("launch-template-id"))
-				g.Expect(err).NotTo(HaveOccurred())
-			},
-		},
-		{
-			name:                 "Should return with error if failed to create launch template id",
-			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
+			name: "Should return with error if failed to create launch template id",
 			expect: func(g *WithT, m *mock_ec2iface.MockEC2APIMockRecorder) {
 				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
 				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
@@ -897,8 +806,6 @@ func TestCreateLaunchTemplate(t *testing.T) {
 			ms, err := setupMachinePoolScope(client, cs)
 			g.Expect(err).NotTo(HaveOccurred())
 
-			ms.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups = tc.awsResourceReference
-
 			s := NewService(cs)
 			s.EC2Client = mockEC2Client
 
@@ -947,15 +854,13 @@ func TestCreateLaunchTemplateVersion(t *testing.T) {
 	}
 	var userData = []byte{1, 0, 0}
 	testCases := []struct {
-		name                 string
-		imageID              *string
-		awsResourceReference []infrav1.AWSResourceReference
-		expect               func(m *mock_ec2iface.MockEC2APIMockRecorder)
-		wantErr              bool
+		name    string
+		imageID *string
+		expect  func(m *mock_ec2iface.MockEC2APIMockRecorder)
+		wantErr bool
 	}{
 		{
-			name:                 "Should successfully creates launch template version",
-			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
+			name: "Should successfully creates launch template version",
 			expect: func(m *mock_ec2iface.MockEC2APIMockRecorder) {
 				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
 				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
@@ -999,8 +904,7 @@ func TestCreateLaunchTemplateVersion(t *testing.T) {
 			},
 		},
 		{
-			name:                 "Should return error if AWS failed during launch template version creation",
-			awsResourceReference: []infrav1.AWSResourceReference{{ID: aws.String("1")}},
+			name: "Should return error if AWS failed during launch template version creation",
 			expect: func(m *mock_ec2iface.MockEC2APIMockRecorder) {
 				sgMap := make(map[infrav1.SecurityGroupRole]infrav1.SecurityGroup)
 				sgMap[infrav1.SecurityGroupNode] = infrav1.SecurityGroup{ID: "1"}
@@ -1056,8 +960,6 @@ func TestCreateLaunchTemplateVersion(t *testing.T) {
 			mpScope, err := setupMachinePoolScope(client, cs)
 			g.Expect(err).NotTo(HaveOccurred())
 
-			mpScope.AWSMachinePool.Spec.AWSLaunchTemplate.AdditionalSecurityGroups = tc.awsResourceReference
-
 			mockEC2Client := mock_ec2iface.NewMockEC2API(mockCtrl)
 			s := NewService(cs)
 			s.EC2Client = mockEC2Client
diff --git a/pkg/cloud/services/network/subnets_test.go b/pkg/cloud/services/network/subnets_test.go
index e1f1e73af..0cac90c53 100644
--- a/pkg/cloud/services/network/subnets_test.go
+++ b/pkg/cloud/services/network/subnets_test.go
@@ -2115,7 +2115,7 @@ func TestDeleteSubnets(t *testing.T) {
 // Test helpers
 
 type ScopeBuilder interface {
-	Build() (Scope, error)
+	Build() (scope.NetworkScope, error)
 }
 
 func NewClusterScope() *ClusterScopeBuilder {
@@ -2136,7 +2136,7 @@ func (b *ClusterScopeBuilder) WithNetwork(n *infrav1.NetworkSpec) *ClusterScopeB
 	return b
 }
 
-func (b *ClusterScopeBuilder) Build() (Scope, error) {
+func (b *ClusterScopeBuilder) Build() (scope.NetworkScope, error) {
 	scheme := runtime.NewScheme()
 	_ = infrav1.AddToScheme(scheme)
 	client := fake.NewClientBuilder().WithScheme(scheme).Build()
@@ -2185,7 +2185,7 @@ func (b *ManagedControlPlaneScopeBuilder) WithEKSClusterName(name string) *Manag
 	return b
 }
 
-func (b *ManagedControlPlaneScopeBuilder) Build() (Scope, error) {
+func (b *ManagedControlPlaneScopeBuilder) Build() (scope.NetworkScope, error) {
 	scheme := runtime.NewScheme()
 	_ = infrav1.AddToScheme(scheme)
 	_ = ekscontrolplanev1.AddToScheme(scheme)
diff --git a/test/e2e/data/e2e_conf.yaml b/test/e2e/data/e2e_conf.yaml
index ced8439f2..2d5aff199 100644
--- a/test/e2e/data/e2e_conf.yaml
+++ b/test/e2e/data/e2e_conf.yaml
@@ -229,8 +229,6 @@ providers:
           - sourcePath: "./infrastructure-aws/generated/cluster-template-peered-remote.yaml"
           - sourcePath: "./infrastructure-aws/generated/cluster-template-internal-elb.yaml"
           - sourcePath: "./infrastructure-aws/kustomize_sources/topology/clusterclass-quick-start.yaml"
-          - sourcePath: "./infrastructure-aws/generated/cluster-template-nested-multitenancy-clusterclass.yaml"
-          - sourcePath: "./infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/clusterclass-multi-tenancy.yaml"
           - sourcePath: "./shared/v1beta1_provider/metadata.yaml"
           - sourcePath: "./infrastructure-aws/generated/cluster-template-ignition.yaml"
         replacements:
diff --git a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/cluster-template.yaml b/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/cluster-template.yaml
deleted file mode 100644
index 82a663aa1..000000000
--- a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/cluster-template.yaml
+++ /dev/null
@@ -1,57 +0,0 @@
----
-apiVersion: cluster.x-k8s.io/v1beta1
-kind: Cluster
-metadata:
-  labels:
-    cni: ${CLUSTER_NAME}-crs-0
-  name: "${CLUSTER_NAME}"
-spec:
-  clusterNetwork:
-    pods:
-      cidrBlocks: ["192.168.0.0/16"]
-  topology:
-    class: "multi-tenancy"
-    version: "${KUBERNETES_VERSION}"
-    controlPlane:
-      replicas: ${CONTROL_PLANE_MACHINE_COUNT}
-    workers:
-      machineDeployments:
-      - class: "default-worker"
-        name: "md-0"
-        replicas: ${WORKER_MACHINE_COUNT}
-    variables:
-    - name: region
-      value: ${AWS_REGION}
-    - name: sshKeyName
-      value: ${AWS_SSH_KEY_NAME}
-    - name: controlPlaneMachineType
-      value: ${AWS_CONTROL_PLANE_MACHINE_TYPE}
-    - name: workerMachineType
-      value: ${AWS_NODE_MACHINE_TYPE}
-    - name: bastionEnabled
-      value: true
-    - name: vpcAZUsageLimit
-      value: 1
-    - name: identityRef
-      value:
-        kind: AWSClusterRoleIdentity
-        name: "${MULTI_TENANCY_NESTED_IDENTITY_NAME}"
----
-apiVersion: v1
-data: ${CNI_RESOURCES}
-kind: ConfigMap
-metadata:
-  name: cni-${CLUSTER_NAME}-crs-0
----
-apiVersion: addons.cluster.x-k8s.io/v1beta1
-kind: ClusterResourceSet
-metadata:
-  name: ${CLUSTER_NAME}-crs-0
-spec:
-  clusterSelector:
-    matchLabels:
-      cni: ${CLUSTER_NAME}-crs-0
-  resources:
-  - kind: ConfigMap
-    name: cni-${CLUSTER_NAME}-crs-0
-  strategy: ApplyOnce
diff --git a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/clusterclass-multi-tenancy.yaml b/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/clusterclass-multi-tenancy.yaml
deleted file mode 100644
index 236086bd4..000000000
--- a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/clusterclass-multi-tenancy.yaml
+++ /dev/null
@@ -1,214 +0,0 @@
-apiVersion: cluster.x-k8s.io/v1beta1
-kind: ClusterClass
-metadata:
-  name: multi-tenancy
-spec:
-  controlPlane:
-    ref:
-      apiVersion: controlplane.cluster.x-k8s.io/v1beta1
-      kind: KubeadmControlPlaneTemplate
-      name: multi-tenancy-control-plane
-    machineInfrastructure:
-      ref:
-        kind: AWSMachineTemplate
-        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-        name: multi-tenancy-control-plane
-  infrastructure:
-    ref:
-      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-      kind: AWSClusterTemplate
-      name: multi-tenancy
-  workers:
-    machineDeployments:
-    - class: default-worker
-      template:
-        bootstrap:
-          ref:
-            apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
-            kind: KubeadmConfigTemplate
-            name: multi-tenancy-worker-bootstraptemplate
-        infrastructure:
-          ref:
-            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-            kind: AWSMachineTemplate
-            name: multi-tenancy-worker-machinetemplate
-  variables:
-    - name: region
-      required: true
-      schema:
-        openAPIV3Schema:
-          type: string
-          default: us-east-1
-    - name: sshKeyName
-      required: true
-      schema:
-        openAPIV3Schema:
-          type: string
-          default: default
-    - name: controlPlaneMachineType
-      required: true
-      schema:
-        openAPIV3Schema:
-          type: string
-          default: t3.large
-    - name: workerMachineType
-      required: true
-      schema:
-        openAPIV3Schema:
-          type: string
-          default: t3.large
-    - name: bastionEnabled
-      required: false
-      schema:
-        openAPIV3Schema:
-          type: boolean
-    - name: vpcAZUsageLimit
-      required: false
-      schema:
-        openAPIV3Schema:
-          type: integer
-    - name: identityRef
-      required: false
-      schema:
-        openAPIV3Schema:
-          type: object
-          properties:
-            kind:
-              type: string
-            name:
-              type: string
-          required:
-          - kind
-          - name
-  patches:
-    - name: awsClusterTemplateGeneral
-      definitions:
-        - selector:
-            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-            kind: AWSClusterTemplate
-            matchResources:
-              infrastructureCluster: true
-          jsonPatches:
-            - op: add
-              path: "/spec/template/spec/region"
-              valueFrom:
-                variable: region
-            - op: add
-              path: "/spec/template/spec/sshKeyName"
-              valueFrom:
-                variable: sshKeyName
-            - op: replace
-              path: "/spec/template/spec/bastion/enabled"
-              valueFrom:
-                variable: bastionEnabled
-            - op: replace
-              path: "/spec/template/spec/network/vpc/availabilityZoneUsageLimit"
-              valueFrom:
-                variable: vpcAZUsageLimit
-            - op: replace
-              path: "/spec/template/spec/identityRef"
-              valueFrom:
-                variable: identityRef
-    - name: awsMachineTemplateControlPlane
-      definitions:
-        - selector:
-            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-            kind: AWSMachineTemplate
-            matchResources:
-              controlPlane: true
-          jsonPatches:
-            - op: replace
-              path: "/spec/template/spec/instanceType"
-              valueFrom:
-                variable: controlPlaneMachineType
-            - op: add
-              path: "/spec/template/spec/sshKeyName"
-              valueFrom:
-                variable: sshKeyName
-    - name: awsMachineTemplateWorker
-      definitions:
-        - selector:
-            apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-            kind: AWSMachineTemplate
-            matchResources:
-              machineDeploymentClass:
-                names:
-                  - default-worker
-          jsonPatches:
-            - op: replace
-              path: "/spec/template/spec/instanceType"
-              valueFrom:
-                variable: workerMachineType
-            - op: add
-              path: "/spec/template/spec/sshKeyName"
-              valueFrom:
-                variable: sshKeyName
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-kind: AWSClusterTemplate
-metadata:
-  name: multi-tenancy
-spec:
-  template:
-    spec: {}
----
-kind: KubeadmControlPlaneTemplate
-apiVersion: controlplane.cluster.x-k8s.io/v1beta1
-metadata:
-  name: multi-tenancy-control-plane
-spec:
-  template:
-    spec:
-      kubeadmConfigSpec:
-        clusterConfiguration:
-          apiServer:
-            extraArgs:
-              cloud-provider: aws
-          controllerManager:
-            extraArgs:
-              cloud-provider: aws
-        initConfiguration:
-          nodeRegistration:
-            name: '{{ ds.meta_data.local_hostname }}'
-            kubeletExtraArgs:
-              cloud-provider: aws
-        joinConfiguration:
-          nodeRegistration:
-            name: '{{ ds.meta_data.local_hostname }}'
-            kubeletExtraArgs:
-              cloud-provider: aws
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-kind: AWSMachineTemplate
-metadata:
-  name: multi-tenancy-control-plane
-spec:
-  template:
-    spec:
-      # instanceType is a required field (OpenAPI schema).
-      instanceType: REPLACEME
-      iamInstanceProfile: "control-plane.cluster-api-provider-aws.sigs.k8s.io"
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-kind: AWSMachineTemplate
-metadata:
-  name: multi-tenancy-worker-machinetemplate
-spec:
-  template:
-    spec:
-      # instanceType is a required field (OpenAPI schema).
-      instanceType: REPLACEME
-      iamInstanceProfile: "nodes.cluster-api-provider-aws.sigs.k8s.io"
----
-apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
-kind: KubeadmConfigTemplate
-metadata:
-  name: "multi-tenancy-worker-bootstraptemplate"
-spec:
-  template:
-    spec:
-      joinConfiguration:
-        nodeRegistration:
-          name: '{{ ds.meta_data.local_hostname }}'
-          kubeletExtraArgs:
-            cloud-provider: aws
diff --git a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/kustomization.yaml b/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/kustomization.yaml
deleted file mode 100644
index 9caa835a7..000000000
--- a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/kustomization.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-resources:
-  - cluster-template.yaml
-  - role.yaml
diff --git a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/role.yaml b/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/role.yaml
deleted file mode 100644
index f6724f196..000000000
--- a/test/e2e/data/infrastructure-aws/kustomize_sources/nested-multitenancy-clusterclass/role.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-kind: AWSClusterRoleIdentity
-metadata:
-  name: "${MULTI_TENANCY_JUMP_IDENTITY_NAME}"
-spec:
-  roleARN: "${MULTI_TENANCY_JUMP_ROLE_ARN}"
-  durationSeconds: 900
-  sessionName: "${MULTI_TENANCY_JUMP_IDENTITY_NAME}-session"
-  sourceIdentityRef:
-    kind: AWSClusterControllerIdentity
-    name: "default"
-  allowedNamespaces: {}
----
-apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
-kind: AWSClusterRoleIdentity
-metadata:
-  name: "${MULTI_TENANCY_NESTED_IDENTITY_NAME}"
-spec:
-  roleARN: "${MULTI_TENANCY_NESTED_ROLE_ARN}"
-  sessionName: "${MULTI_TENANCY_NESTED_IDENTITY_NAME}-session"
-  sourceIdentityRef:
-    kind: AWSClusterRoleIdentity
-    name: "${MULTI_TENANCY_JUMP_IDENTITY_NAME}"
-  allowedNamespaces: {}
diff --git a/test/e2e/shared/defaults.go b/test/e2e/shared/defaults.go
index c14ed0052..231fbc3c0 100644
--- a/test/e2e/shared/defaults.go
+++ b/test/e2e/shared/defaults.go
@@ -35,32 +35,31 @@ import (
 
 // Constants.
 const (
-	DefaultSSHKeyPairName                = "cluster-api-provider-aws-sigs-k8s-io"
-	AMIPrefix                            = "capa-ami-ubuntu-18.04-"
-	DefaultImageLookupOrg                = "258751437250"
-	KubernetesVersion                    = "KUBERNETES_VERSION"
-	KubernetesVersionManagement          = "KUBERNETES_VERSION_MANAGEMENT"
-	CNIPath                              = "CNI"
-	CNIResources                         = "CNI_RESOURCES"
-	AwsNodeMachineType                   = "AWS_NODE_MACHINE_TYPE"
-	AwsAvailabilityZone1                 = "AWS_AVAILABILITY_ZONE_1"
-	AwsAvailabilityZone2                 = "AWS_AVAILABILITY_ZONE_2"
-	MultiAzFlavor                        = "multi-az"
-	LimitAzFlavor                        = "limit-az"
-	SpotInstancesFlavor                  = "spot-instances"
-	SSMFlavor                            = "ssm"
-	UpgradeToMain                        = "upgrade-to-main"
-	ExternalCloudProvider                = "external-cloud-provider"
-	SimpleMultitenancyFlavor             = "simple-multitenancy"
-	NestedMultitenancyFlavor             = "nested-multitenancy"
-	NestedMultitenancyClusterClassFlavor = "nested-multitenancy-clusterclass"
-	KCPScaleInFlavor                     = "kcp-scale-in"
-	IgnitionFlavor                       = "ignition"
-	StorageClassOutTreeZoneLabel         = "topology.ebs.csi.aws.com/zone"
-	GPUFlavor                            = "gpu"
-	InstanceVcpu                         = "AWS_MACHINE_TYPE_VCPU_USAGE"
-	PreCSIKubernetesVer                  = "PRE_1_23_KUBERNETES_VERSION"
-	PostCSIKubernetesVer                 = "POST_1_23_KUBERNETES_VERSION"
+	DefaultSSHKeyPairName        = "cluster-api-provider-aws-sigs-k8s-io"
+	AMIPrefix                    = "capa-ami-ubuntu-18.04-"
+	DefaultImageLookupOrg        = "258751437250"
+	KubernetesVersion            = "KUBERNETES_VERSION"
+	KubernetesVersionManagement  = "KUBERNETES_VERSION_MANAGEMENT"
+	CNIPath                      = "CNI"
+	CNIResources                 = "CNI_RESOURCES"
+	AwsNodeMachineType           = "AWS_NODE_MACHINE_TYPE"
+	AwsAvailabilityZone1         = "AWS_AVAILABILITY_ZONE_1"
+	AwsAvailabilityZone2         = "AWS_AVAILABILITY_ZONE_2"
+	MultiAzFlavor                = "multi-az"
+	LimitAzFlavor                = "limit-az"
+	SpotInstancesFlavor          = "spot-instances"
+	SSMFlavor                    = "ssm"
+	UpgradeToMain                = "upgrade-to-main"
+	ExternalCloudProvider        = "external-cloud-provider"
+	SimpleMultitenancyFlavor     = "simple-multitenancy"
+	NestedMultitenancyFlavor     = "nested-multitenancy"
+	KCPScaleInFlavor             = "kcp-scale-in"
+	IgnitionFlavor               = "ignition"
+	StorageClassOutTreeZoneLabel = "topology.ebs.csi.aws.com/zone"
+	GPUFlavor                    = "gpu"
+	InstanceVcpu                 = "AWS_MACHINE_TYPE_VCPU_USAGE"
+	PreCSIKubernetesVer          = "PRE_1_23_KUBERNETES_VERSION"
+	PostCSIKubernetesVer         = "POST_1_23_KUBERNETES_VERSION"
 )
 
 var ResourceQuotaFilePath = "/tmp/capa-e2e-resource-usage.lock"
diff --git a/test/e2e/suites/unmanaged/helpers_test.go b/test/e2e/suites/unmanaged/helpers_test.go
index 7164d052b..f4b0e884c 100644
--- a/test/e2e/suites/unmanaged/helpers_test.go
+++ b/test/e2e/suites/unmanaged/helpers_test.go
@@ -76,21 +76,12 @@ type statefulSetInfo struct {
 
 // GetClusterByName returns a Cluster object given his name.
 func GetAWSClusterByName(ctx context.Context, namespace, name string) (*infrav1.AWSCluster, error) {
-	cluster := &clusterv1.Cluster{}
+	awsCluster := &infrav1.AWSCluster{}
 	key := crclient.ObjectKey{
 		Namespace: namespace,
 		Name:      name,
 	}
-	if err := e2eCtx.Environment.BootstrapClusterProxy.GetClient().Get(ctx, key, cluster); err != nil {
-		return nil, err
-	}
-
-	awsCluster := &infrav1.AWSCluster{}
-	awsClusterKey := crclient.ObjectKey{
-		Namespace: namespace,
-		Name:      cluster.Spec.InfrastructureRef.Name,
-	}
-	err := e2eCtx.Environment.BootstrapClusterProxy.GetClient().Get(ctx, awsClusterKey, awsCluster)
+	err := e2eCtx.Environment.BootstrapClusterProxy.GetClient().Get(ctx, key, awsCluster)
 	return awsCluster, err
 }
 
diff --git a/test/e2e/suites/unmanaged/unmanaged_CAPI_quick_clusterclass_test.go b/test/e2e/suites/unmanaged/unmanaged_CAPI_quick_test_cluster_class.go
similarity index 100%
rename from test/e2e/suites/unmanaged/unmanaged_CAPI_quick_clusterclass_test.go
rename to test/e2e/suites/unmanaged/unmanaged_CAPI_quick_test_cluster_class.go
diff --git a/test/e2e/suites/unmanaged/unmanaged_functional_clusterclass_test.go b/test/e2e/suites/unmanaged/unmanaged_functional_clusterclass_test.go
deleted file mode 100644
index 0f52c0cac..000000000
--- a/test/e2e/suites/unmanaged/unmanaged_functional_clusterclass_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-//go:build e2e
-// +build e2e
-
-/*
-Copyright 2022 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 unmanaged
-
-import (
-	"context"
-	"fmt"
-	"path/filepath"
-
-	"github.com/gofrs/flock"
-	"github.com/onsi/ginkgo"
-	"github.com/onsi/ginkgo/config"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/utils/pointer"
-
-	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
-	"sigs.k8s.io/cluster-api-provider-aws/test/e2e/shared"
-	"sigs.k8s.io/cluster-api/test/framework/clusterctl"
-	"sigs.k8s.io/cluster-api/util"
-)
-
-var _ = ginkgo.Context("[unmanaged] [functional] [ClusterClass]", func() {
-	var (
-		ctx               context.Context
-		result            *clusterctl.ApplyClusterTemplateAndWaitResult
-		requiredResources *shared.TestResource
-	)
-
-	ginkgo.BeforeEach(func() {
-		ctx = context.TODO()
-		result = &clusterctl.ApplyClusterTemplateAndWaitResult{}
-	})
-
-	ginkgo.Describe("Multitenancy test [ClusterClass]", func() {
-		ginkgo.It("should create cluster with nested assumed role", func() {
-			// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
-			specName := "functional-multitenancy-nested-clusterclass"
-			requiredResources = &shared.TestResource{EC2Normal: 1 * e2eCtx.Settings.InstanceVCPU, IGW: 1, NGW: 1, VPC: 1, ClassicLB: 1, EIP: 1}
-			requiredResources.WriteRequestedResources(e2eCtx, specName)
-			Expect(shared.AcquireResources(requiredResources, config.GinkgoConfig.ParallelNode, flock.New(shared.ResourceQuotaFilePath))).To(Succeed())
-			defer shared.ReleaseResources(requiredResources, config.GinkgoConfig.ParallelNode, flock.New(shared.ResourceQuotaFilePath))
-			namespace := shared.SetupSpecNamespace(ctx, specName, e2eCtx)
-			defer shared.DumpSpecResourcesAndCleanup(ctx, "", namespace, e2eCtx)
-			Expect(shared.SetMultitenancyEnvVars(e2eCtx.AWSSession)).To(Succeed())
-
-			ginkgo.By("Creating cluster")
-			clusterName := fmt.Sprintf("cluster-%s", util.RandomString(6))
-			clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
-				ClusterProxy: e2eCtx.Environment.BootstrapClusterProxy,
-				ConfigCluster: clusterctl.ConfigClusterInput{
-					LogFolder:                filepath.Join(e2eCtx.Settings.ArtifactFolder, "clusters", e2eCtx.Environment.BootstrapClusterProxy.GetName()),
-					ClusterctlConfigPath:     e2eCtx.Environment.ClusterctlConfigPath,
-					KubeconfigPath:           e2eCtx.Environment.BootstrapClusterProxy.GetKubeconfigPath(),
-					InfrastructureProvider:   clusterctl.DefaultInfrastructureProvider,
-					Flavor:                   shared.NestedMultitenancyClusterClassFlavor,
-					Namespace:                namespace.Name,
-					ClusterName:              clusterName,
-					KubernetesVersion:        e2eCtx.E2EConfig.GetVariable(shared.KubernetesVersion),
-					ControlPlaneMachineCount: pointer.Int64Ptr(1),
-					WorkerMachineCount:       pointer.Int64Ptr(0),
-				},
-				WaitForClusterIntervals:      e2eCtx.E2EConfig.GetIntervals(specName, "wait-cluster"),
-				WaitForControlPlaneIntervals: e2eCtx.E2EConfig.GetIntervals(specName, "wait-control-plane"),
-			}, result)
-
-			ginkgo.By("Checking if bastion host is running")
-			awsCluster, err := GetAWSClusterByName(ctx, namespace.Name, clusterName)
-			Expect(err).To(BeNil())
-			Expect(awsCluster.Status.Bastion.State).To(Equal(infrav1.InstanceStateRunning))
-			expectAWSClusterConditions(awsCluster, []conditionAssertion{{infrav1.BastionHostReadyCondition, corev1.ConditionTrue, "", ""}})
-
-			ginkgo.By("PASSED!")
-		})
-	})
-})