diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go index abd5b940f3..129c8b0c82 100644 --- a/api/v1beta1/awscluster_conversion.go +++ b/api/v1beta1/awscluster_conversion.go @@ -17,40 +17,41 @@ limitations under the License. package v1beta1 import ( - infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + infrav2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/conversion" ) -// ConvertTo converts the v1beta1 AWSCluster receiver to a v1beta1 AWSCluster. +// ConvertTo converts the v1beta1 AWSCluster receiver to a v1beta2 AWSCluster. func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*infrav1.AWSCluster) + dst := dstRaw.(*infrav2.AWSCluster) if err := Convert_v1beta1_AWSCluster_To_v1beta2_AWSCluster(src, dst, nil); err != nil { return err } // Manually restore data. - restored := &infrav1.AWSCluster{} + restored := &infrav2.AWSCluster{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { return err } if restored.Spec.ControlPlaneLoadBalancer != nil { if dst.Spec.ControlPlaneLoadBalancer == nil { - dst.Spec.ControlPlaneLoadBalancer = &infrav1.AWSLoadBalancerSpec{} + dst.Spec.ControlPlaneLoadBalancer = &infrav2.AWSLoadBalancerSpec{} } restoreControlPlaneLoadBalancer(restored.Spec.ControlPlaneLoadBalancer, dst.Spec.ControlPlaneLoadBalancer) } restoreControlPlaneLoadBalancerStatus(&restored.Status.Network.APIServerELB, &dst.Status.Network.APIServerELB) dst.Spec.S3Bucket = restored.Spec.S3Bucket + dst.Spec.Partition = restored.Spec.Partition return nil } // restoreControlPlaneLoadBalancerStatus manually restores the control plane loadbalancer status data. // Assumes restored and dst are non-nil. -func restoreControlPlaneLoadBalancerStatus(restored, dst *infrav1.LoadBalancer) { +func restoreControlPlaneLoadBalancerStatus(restored, dst *infrav2.LoadBalancer) { dst.ARN = restored.ARN dst.LoadBalancerType = restored.LoadBalancerType dst.ELBAttributes = restored.ELBAttributes @@ -59,7 +60,7 @@ func restoreControlPlaneLoadBalancerStatus(restored, dst *infrav1.LoadBalancer) // restoreControlPlaneLoadBalancer manually restores the control plane loadbalancer data. // Assumes restored and dst are non-nil. -func restoreControlPlaneLoadBalancer(restored, dst *infrav1.AWSLoadBalancerSpec) { +func restoreControlPlaneLoadBalancer(restored, dst *infrav2.AWSLoadBalancerSpec) { dst.Name = restored.Name dst.HealthCheckProtocol = restored.HealthCheckProtocol dst.LoadBalancerType = restored.LoadBalancerType @@ -69,7 +70,7 @@ func restoreControlPlaneLoadBalancer(restored, dst *infrav1.AWSLoadBalancerSpec) // ConvertFrom converts the v1beta1 AWSCluster receiver to a v1beta1 AWSCluster. func (r *AWSCluster) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*infrav1.AWSCluster) + src := srcRaw.(*infrav2.AWSCluster) if err := Convert_v1beta2_AWSCluster_To_v1beta1_AWSCluster(src, r, nil); err != nil { return err @@ -85,14 +86,14 @@ func (r *AWSCluster) ConvertFrom(srcRaw conversion.Hub) error { // ConvertTo converts the v1beta1 AWSClusterList receiver to a v1beta2 AWSClusterList. func (src *AWSClusterList) ConvertTo(dstRaw conversion.Hub) error { - dst := dstRaw.(*infrav1.AWSClusterList) + dst := dstRaw.(*infrav2.AWSClusterList) return Convert_v1beta1_AWSClusterList_To_v1beta2_AWSClusterList(src, dst, nil) } // ConvertFrom converts the v1beta2 AWSClusterList receiver to a v1beta1 AWSClusterList. func (r *AWSClusterList) ConvertFrom(srcRaw conversion.Hub) error { - src := srcRaw.(*infrav1.AWSClusterList) + src := srcRaw.(*infrav2.AWSClusterList) return Convert_v1beta2_AWSClusterList_To_v1beta1_AWSClusterList(src, r, nil) } diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 2fb0fe3066..6e197c66b8 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -911,6 +911,7 @@ func autoConvert_v1beta2_AWSClusterSpec_To_v1beta1_AWSClusterSpec(in *v1beta2.AW return err } out.Region = in.Region + // WARNING: in.Partition requires manual conversion: does not exist in peer-type out.SSHKeyName = (*string)(unsafe.Pointer(in.SSHKeyName)) out.ControlPlaneEndpoint = in.ControlPlaneEndpoint out.AdditionalTags = *(*Tags)(unsafe.Pointer(&in.AdditionalTags)) diff --git a/api/v1beta2/awscluster_types.go b/api/v1beta2/awscluster_types.go index 732a5f143a..bf4c81070c 100644 --- a/api/v1beta2/awscluster_types.go +++ b/api/v1beta2/awscluster_types.go @@ -39,6 +39,10 @@ type AWSClusterSpec struct { // The AWS Region the cluster lives in. Region string `json:"region,omitempty"` + // Partition is the AWS security partition being used. Defaults to "aws" + // +optional + Partition string `json:"partition,omitempty"` + // SSHKeyName is the name of the ssh key to attach to the bastion host. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name) // +optional SSHKeyName *string `json:"sshKeyName,omitempty"` diff --git a/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go b/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go index 7888d05d11..88e2440760 100644 --- a/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go +++ b/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go @@ -31,6 +31,8 @@ const ( DefaultStackName = "cluster-api-provider-aws-sigs-k8s-io" // DefaultPartitionName is the default security partition for AWS ARNs. DefaultPartitionName = "aws" + // PartitionNameUSGov is the default security partition for AWS ARNs. + PartitionNameUSGov = "aws-us-gov" // DefaultKMSAliasPattern is the default KMS alias. DefaultKMSAliasPattern = "cluster-api-provider-aws-*" // DefaultS3BucketPrefix is the default S3 bucket prefix. diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fargate.go b/cmd/clusterawsadm/cloudformation/bootstrap/fargate.go index b2856ab3ec..9a57cc1446 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fargate.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fargate.go @@ -17,12 +17,18 @@ limitations under the License. package bootstrap import ( + "strings" + bootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/api/bootstrap/v1beta1" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks" ) -func fargateProfilePolicies(roleSpec *bootstrapv1.AWSIAMRoleSpec) []string { - policies := eks.FargateRolePolicies() +func (t Template) fargateProfilePolicies(roleSpec *bootstrapv1.AWSIAMRoleSpec) []string { + var policies []string + policies = eks.FargateRolePolicies() + if strings.Contains(t.Spec.Partition, bootstrapv1.PartitionNameUSGov) { + policies = eks.FargateRolePoliciesUSGov() + } if roleSpec.ExtraPolicyAttachments != nil { policies = append(policies, roleSpec.ExtraPolicyAttachments...) } diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/managed_nodegroup.go b/cmd/clusterawsadm/cloudformation/bootstrap/managed_nodegroup.go index 8d5dc5b6dd..791f25602c 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/managed_nodegroup.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/managed_nodegroup.go @@ -16,10 +16,20 @@ limitations under the License. package bootstrap -import "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks" +import ( + "strings" + + bootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/api/bootstrap/v1beta1" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks" +) func (t Template) eksMachinePoolPolicies() []string { - policies := eks.NodegroupRolePolicies() + var policies []string + + policies = eks.NodegroupRolePolicies() + if strings.Contains(t.Spec.Partition, bootstrapv1.PartitionNameUSGov) { + policies = eks.NodegroupRolePoliciesUSGov() + } if t.Spec.EKS.ManagedMachinePool.ExtraPolicyAttachments != nil { policies = append(policies, t.Spec.EKS.ManagedMachinePool.ExtraPolicyAttachments...) } diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/template.go b/cmd/clusterawsadm/cloudformation/bootstrap/template.go index f1f9a74d58..030bc248ee 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/template.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/template.go @@ -200,7 +200,7 @@ func (t Template) RenderCloudFormation() *cloudformation.Template { template.Resources[AWSIAMRoleEKSFargate] = &cfn_iam.Role{ RoleName: expinfrav1.DefaultEKSFargateRole, AssumeRolePolicyDocument: AssumeRolePolicy(iamv1.PrincipalService, []string{eksiam.EKSFargateService}), - ManagedPolicyArns: fargateProfilePolicies(t.Spec.EKS.Fargate), + ManagedPolicyArns: t.fargateProfilePolicies(t.Spec.EKS.Fargate), Tags: converters.MapToCloudFormationTags(t.Spec.EKS.Fargate.Tags), } } diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 0e30f03ac1..3cfaf7df54 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -1936,6 +1936,10 @@ spec: prefixing. type: string type: object + partition: + description: Partition is the AWS security partition being used. Defaults + to "aws" + type: string region: description: The AWS Region the cluster lives in. type: string diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml index 2384ee65fa..3b9395cf6d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml @@ -1241,6 +1241,10 @@ spec: type: object type: object type: object + partition: + description: Partition is the AWS security partition being used. Defaults + to "aws" + type: string region: description: The AWS Region the cluster lives in. type: string diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml index a1c476f5b9..1dd80ac6fa 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml @@ -848,6 +848,10 @@ spec: type: object type: object type: object + partition: + description: Partition is the AWS security partition being + used. Defaults to "aws" + type: string region: description: The AWS Region the cluster lives in. type: string diff --git a/controlplane/eks/api/v1beta1/conversion.go b/controlplane/eks/api/v1beta1/conversion.go index ed6650420b..57284afd25 100644 --- a/controlplane/eks/api/v1beta1/conversion.go +++ b/controlplane/eks/api/v1beta1/conversion.go @@ -32,13 +32,14 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { if err := Convert_v1beta1_AWSManagedControlPlane_To_v1beta2_AWSManagedControlPlane(r, dst, nil); err != nil { return err } - + // Manually restore data. restored := &ekscontrolplanev1.AWSManagedControlPlane{} if ok, err := utilconversion.UnmarshalData(r, restored); err != nil || !ok { return err } dst.Spec.VpcCni.Disable = r.Spec.DisableVPCCNI + dst.Spec.Partition = restored.Spec.Partition return nil } @@ -50,7 +51,7 @@ func (r *AWSManagedControlPlane) ConvertFrom(srcRaw conversion.Hub) error { if err := Convert_v1beta2_AWSManagedControlPlane_To_v1beta1_AWSManagedControlPlane(src, r, nil); err != nil { return err } - + r.Spec.DisableVPCCNI = src.Spec.VpcCni.Disable if err := utilconversion.MarshalData(src, r); err != nil { return err @@ -110,3 +111,8 @@ func Convert_v1beta1_AWSManagedControlPlaneSpec_To_v1beta2_AWSManagedControlPlan func Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(in *ekscontrolplanev1.VpcCni, out *VpcCni, s apiconversion.Scope) error { return autoConvert_v1beta2_VpcCni_To_v1beta1_VpcCni(in, out, s) } + +// Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec is a generated conversion function +func Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in *ekscontrolplanev1.AWSManagedControlPlaneSpec, out *AWSManagedControlPlaneSpec, scope apiconversion.Scope) error { + return autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in, out, scope) +} diff --git a/controlplane/eks/api/v1beta1/conversion_test.go b/controlplane/eks/api/v1beta1/conversion_test.go index fd809cb1df..207a6b6695 100644 --- a/controlplane/eks/api/v1beta1/conversion_test.go +++ b/controlplane/eks/api/v1beta1/conversion_test.go @@ -18,9 +18,9 @@ package v1beta1 import ( "testing" - + . "github.com/onsi/gomega" - + fuzz "github.com/google/gofuzz" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" "k8s.io/apimachinery/pkg/runtime" @@ -47,9 +47,9 @@ func TestFuzzyConversion(t *testing.T) { g.Expect(v1beta2.AddToScheme(scheme)).To(Succeed()) t.Run("for AWSManagedControlPlane", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Scheme: scheme, - Hub: &v1beta2.AWSManagedControlPlane{}, - Spoke: &AWSManagedControlPlane{}, + Scheme: scheme, + Hub: &v1beta2.AWSManagedControlPlane{}, + Spoke: &AWSManagedControlPlane{}, FuzzerFuncs: []fuzzer.FuzzerFuncs{fuzzFuncs}, })) } diff --git a/controlplane/eks/api/v1beta1/zz_generated.conversion.go b/controlplane/eks/api/v1beta1/zz_generated.conversion.go index f38dfa2132..44429be0ff 100644 --- a/controlplane/eks/api/v1beta1/zz_generated.conversion.go +++ b/controlplane/eks/api/v1beta1/zz_generated.conversion.go @@ -60,11 +60,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.AWSManagedControlPlaneSpec)(nil), (*AWSManagedControlPlaneSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(a.(*v1beta2.AWSManagedControlPlaneSpec), b.(*AWSManagedControlPlaneSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*AWSManagedControlPlaneStatus)(nil), (*v1beta2.AWSManagedControlPlaneStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_AWSManagedControlPlaneStatus_To_v1beta2_AWSManagedControlPlaneStatus(a.(*AWSManagedControlPlaneStatus), b.(*v1beta2.AWSManagedControlPlaneStatus), scope) }); err != nil { @@ -240,6 +235,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.AWSManagedControlPlaneSpec)(nil), (*AWSManagedControlPlaneSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(a.(*v1beta2.AWSManagedControlPlaneSpec), b.(*AWSManagedControlPlaneSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*apiv1beta2.Bastion)(nil), (*apiv1beta1.Bastion)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_Bastion_To_v1beta1_Bastion(a.(*apiv1beta2.Bastion), b.(*apiv1beta1.Bastion), scope) }); err != nil { @@ -374,6 +374,7 @@ func autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControl out.NetworkSpec = in.NetworkSpec out.SecondaryCidrBlock = (*string)(unsafe.Pointer(in.SecondaryCidrBlock)) out.Region = in.Region + // WARNING: in.Partition requires manual conversion: does not exist in peer-type out.SSHKeyName = (*string)(unsafe.Pointer(in.SSHKeyName)) out.Version = (*string)(unsafe.Pointer(in.Version)) out.RoleName = (*string)(unsafe.Pointer(in.RoleName)) @@ -403,11 +404,6 @@ func autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControl return nil } -// Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec is an autogenerated conversion function. -func Convert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in *v1beta2.AWSManagedControlPlaneSpec, out *AWSManagedControlPlaneSpec, s conversion.Scope) error { - return autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControlPlaneSpec(in, out, s) -} - func autoConvert_v1beta1_AWSManagedControlPlaneStatus_To_v1beta2_AWSManagedControlPlaneStatus(in *AWSManagedControlPlaneStatus, out *v1beta2.AWSManagedControlPlaneStatus, s conversion.Scope) error { out.Network = in.Network out.FailureDomains = *(*clusterapiapiv1beta1.FailureDomains)(unsafe.Pointer(&in.FailureDomains)) diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index f11d17bf7c..3ca8ded16f 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -55,6 +55,10 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned // The AWS Region the cluster lives in. Region string `json:"region,omitempty"` + // Partition is the AWS security partition being used. Defaults to "aws" + // +optional + Partition string `json:"partition,omitempty"` + // SSHKeyName is the name of the ssh key to attach to the bastion host. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name) // +optional SSHKeyName *string `json:"sshKeyName,omitempty"` diff --git a/controlplane/eks/api/v1beta2/conversion.go b/controlplane/eks/api/v1beta2/conversion.go index 905fb76b78..2d22661673 100644 --- a/controlplane/eks/api/v1beta2/conversion.go +++ b/controlplane/eks/api/v1beta2/conversion.go @@ -21,3 +21,6 @@ func (*AWSManagedControlPlane) Hub() {} // Hub marks AWSManagedControlPlaneList as a conversion hub. func (*AWSManagedControlPlaneList) Hub() {} + +// Hub marks AWSManagedControlPlaneSpec as a conversion hub. +func (*AWSManagedControlPlaneSpec) Hub() {} diff --git a/pkg/cloud/scope/cluster.go b/pkg/cloud/scope/cluster.go index 94b009b56a..9fdb56992f 100644 --- a/pkg/cloud/scope/cluster.go +++ b/pkg/cloud/scope/cluster.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" @@ -351,3 +352,11 @@ func (s *ClusterScope) ImageLookupOrg() string { func (s *ClusterScope) ImageLookupBaseOS() string { return s.AWSCluster.Spec.ImageLookupBaseOS } + +// Partition returns the cluster partition. +func (s *ClusterScope) Partition() string { + if s.AWSCluster.Spec.Partition == "" { + s.AWSCluster.Spec.Partition = system.GetPartitionFromRegion(s.Region()) + } + return s.AWSCluster.Spec.Partition +} diff --git a/pkg/cloud/scope/fargate.go b/pkg/cloud/scope/fargate.go index e35dc7885f..7a58137f6d 100644 --- a/pkg/cloud/scope/fargate.go +++ b/pkg/cloud/scope/fargate.go @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" "sigs.k8s.io/cluster-api/util/patch" @@ -157,6 +158,14 @@ func (s *FargateProfileScope) SubnetIDs() []string { return s.FargateProfile.Spec.SubnetIDs } +// Partition returns the machine pool subnet IDs. +func (s *FargateProfileScope) Partition() string { + if s.ControlPlane.Spec.Partition == "" { + s.ControlPlane.Spec.Partition = system.GetPartitionFromRegion(s.ControlPlane.Spec.Region) + } + return s.ControlPlane.Spec.Partition +} + // IAMReadyFalse marks the ready condition false using warning if error isn't // empty. func (s *FargateProfileScope) IAMReadyFalse(reason string, err string) error { diff --git a/pkg/cloud/scope/managedcontrolplane.go b/pkg/cloud/scope/managedcontrolplane.go index 1ce4d74e9c..d4ca84f907 100644 --- a/pkg/cloud/scope/managedcontrolplane.go +++ b/pkg/cloud/scope/managedcontrolplane.go @@ -36,6 +36,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/controllers/remote" "sigs.k8s.io/cluster-api/util/patch" @@ -401,3 +402,11 @@ func (s *ManagedControlPlaneScope) ServiceCidrs() *clusterv1.NetworkRanges { func (s *ManagedControlPlaneScope) ControlPlaneLoadBalancer() *infrav1.AWSLoadBalancerSpec { return nil } + +// Partition returns the cluster partition. +func (s *ManagedControlPlaneScope) Partition() string { + if s.ControlPlane.Spec.Partition == "" { + s.ControlPlane.Spec.Partition = system.GetPartitionFromRegion(s.Region()) + } + return s.ControlPlane.Spec.Partition +} diff --git a/pkg/cloud/scope/managednodegroup.go b/pkg/cloud/scope/managednodegroup.go index 1f32bb0c4e..fb730313bb 100644 --- a/pkg/cloud/scope/managednodegroup.go +++ b/pkg/cloud/scope/managednodegroup.go @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/throttle" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" @@ -158,6 +159,11 @@ func (s *ManagedMachinePoolScope) AllowAdditionalRoles() bool { return s.allowAdditionalRoles } +// Partition returns the machine pool subnet IDs. +func (s *ManagedMachinePoolScope) Partition() string { + return system.GetPartitionFromRegion(s.ControlPlane.Spec.Region) +} + // IdentityRef returns the cluster identityRef. func (s *ManagedMachinePoolScope) IdentityRef() *infrav1.AWSIdentityReference { return s.ControlPlane.Spec.IdentityRef diff --git a/pkg/cloud/services/ec2/ami.go b/pkg/cloud/services/ec2/ami.go index 5c70e7f936..25b2120c38 100644 --- a/pkg/cloud/services/ec2/ami.go +++ b/pkg/cloud/services/ec2/ami.go @@ -32,7 +32,9 @@ import ( "github.com/pkg/errors" infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/api/bootstrap/v1beta1" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/record" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" ) const ( @@ -44,6 +46,8 @@ const ( // https://ubuntu.com/server/docs/cloud-images/amazon-ec2 ubuntuOwnerID = "099720109477" + ubuntuOwnerIDUsGov = "513442679011" + // Description regex for fetching Ubuntu AMIs for bastion host. ubuntuImageDescription = "Canonical??Ubuntu??20.04?LTS??amd64?focal?image*" @@ -198,10 +202,6 @@ func GetLatestImage(imgs []*ec2.Image) (*ec2.Image, error) { func (s *Service) defaultBastionAMILookup() (string, error) { describeImageInput := &ec2.DescribeImagesInput{ Filters: []*ec2.Filter{ - { - Name: aws.String("owner-id"), - Values: []*string{aws.String(ubuntuOwnerID)}, - }, { Name: aws.String("architecture"), Values: []*string{aws.String("x86_64")}, @@ -220,6 +220,19 @@ func (s *Service) defaultBastionAMILookup() (string, error) { }, }, } + + ownerID := ubuntuOwnerID + partition := system.GetPartitionFromRegion(s.scope.Region()) + if strings.Contains(partition, v1beta1.PartitionNameUSGov) { + ownerID = ubuntuOwnerIDUsGov + } + + filter := &ec2.Filter{ + Name: aws.String("owner-id"), + Values: []*string{aws.String(ownerID)}, + } + describeImageInput.Filters = append(describeImageInput.Filters, filter) + out, err := s.EC2Client.DescribeImages(describeImageInput) if err != nil { return "", errors.Wrapf(err, "failed to describe images within region: %q", s.scope.Region()) diff --git a/pkg/cloud/services/ec2/bastion_test.go b/pkg/cloud/services/ec2/bastion_test.go index b24f9c1c8b..5e04ac507f 100644 --- a/pkg/cloud/services/ec2/bastion_test.go +++ b/pkg/cloud/services/ec2/bastion_test.go @@ -307,10 +307,242 @@ func TestServiceReconcileBastion(t *testing.T) { m.DescribeInstances(gomock.Eq(describeInput)). Return(&ec2.DescribeInstancesOutput{}, nil).MinTimes(1) m.DescribeImages(gomock.Eq(&ec2.DescribeImagesInput{Filters: []*ec2.Filter{ + { + Name: aws.String("architecture"), + Values: aws.StringSlice([]string{"x86_64"}), + }, + { + Name: aws.String("state"), + Values: aws.StringSlice([]string{"available"}), + }, + { + Name: aws.String("virtualization-type"), + Values: aws.StringSlice([]string{"hvm"}), + }, + { + Name: aws.String("description"), + Values: aws.StringSlice([]string{ubuntuImageDescription}), + }, { Name: aws.String("owner-id"), Values: aws.StringSlice([]string{ubuntuOwnerID}), }, + }})).Return(&ec2.DescribeImagesOutput{Images: images{ + { + ImageId: aws.String("ubuntu-ami-id-latest"), + CreationDate: aws.String("2019-02-08T17:02:31.000Z"), + }, + { + ImageId: aws.String("ubuntu-ami-id-old"), + CreationDate: aws.String("2014-02-08T17:02:31.000Z"), + }, + }}, nil) + m.RunInstances(gomock.Any()). + Return(&ec2.Reservation{ + Instances: []*ec2.Instance{ + { + State: &ec2.InstanceState{ + Name: aws.String(ec2.InstanceStateNameRunning), + }, + IamInstanceProfile: &ec2.IamInstanceProfile{ + Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + }, + InstanceId: aws.String("id123"), + InstanceType: aws.String("t3.micro"), + SubnetId: aws.String("subnet-1"), + ImageId: aws.String("ubuntu-ami-id-latest"), + RootDeviceName: aws.String("device-1"), + BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ + { + DeviceName: aws.String("device-1"), + Ebs: &ec2.EbsInstanceBlockDevice{ + VolumeId: aws.String("volume-1"), + }, + }, + }, + Placement: &ec2.Placement{ + AvailabilityZone: aws.String("us-east-1"), + }, + }, + }, + }, nil) + m.WaitUntilInstanceRunningWithContext(gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + }, + bastionEnabled: true, + expectError: false, + bastionStatus: &infrav1.Instance{ + ID: "id123", + State: "running", + Type: "t3.micro", + SubnetID: "subnet-1", + ImageID: "ubuntu-ami-id-latest", + IAMProfile: "foo", + Addresses: []clusterv1.MachineAddress{}, + AvailabilityZone: "us-east-1", + VolumeIDs: []string{"volume-1"}, + }, + }, + } + + for _, tc := range tests { + managedValues := []bool{false, true} + for i := range managedValues { + managed := managedValues[i] + + t.Run(fmt.Sprintf("managed=%t %s", managed, tc.name), func(t *testing.T) { + g := NewWithT(t) + + mockControl := gomock.NewController(t) + defer mockControl.Finish() + + ec2Mock := mocks.NewMockEC2API(mockControl) + + scheme, err := setupScheme() + g.Expect(err).To(BeNil()) + + awsCluster := &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + ID: "vpcID", + }, + Subnets: infrav1.Subnets{ + { + ID: "subnet-1", + }, + { + ID: "subnet-2", + IsPublic: true, + }, + }, + }, + Bastion: infrav1.Bastion{Enabled: tc.bastionEnabled}, + }, + } + + client := fake.NewClientBuilder().WithScheme(scheme).Build() + ctx := context.TODO() + client.Create(ctx, awsCluster) + + scope, err := scope.NewClusterScope(scope.ClusterScopeParams{ + Cluster: &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + Name: clusterName, + }, + }, + AWSCluster: awsCluster, + Client: client, + }) + g.Expect(err).To(BeNil()) + + if managed { + scope.AWSCluster.Spec.NetworkSpec.VPC.Tags = infrav1.Tags{ + infrav1.ClusterTagKey(clusterName): string(infrav1.ResourceLifecycleOwned), + } + } + + tc.expect(ec2Mock.EXPECT()) + s := NewService(scope) + s.EC2Client = ec2Mock + + err = s.ReconcileBastion() + if tc.expectError { + g.Expect(err).NotTo(BeNil()) + return + } + + g.Expect(err).To(BeNil()) + + g.Expect(scope.AWSCluster.Status.Bastion).To(BeEquivalentTo(tc.bastionStatus)) + }) + } + } +} + +func TestServiceReconcileBastionUSGOV(t *testing.T) { + clusterName := "cluster-us-gov" + + describeInput := &ec2.DescribeInstancesInput{ + Filters: []*ec2.Filter{ + filter.EC2.ProviderRole(infrav1.BastionRoleTagValue), + filter.EC2.Cluster(clusterName), + filter.EC2.InstanceStates( + ec2.InstanceStateNamePending, + ec2.InstanceStateNameRunning, + ec2.InstanceStateNameStopping, + ec2.InstanceStateNameStopped, + ), + }, + } + + foundOutput := &ec2.DescribeInstancesOutput{ + Reservations: []*ec2.Reservation{ + { + Instances: []*ec2.Instance{ + { + InstanceId: aws.String("id123"), + State: &ec2.InstanceState{ + Name: aws.String(ec2.InstanceStateNameRunning), + }, + Placement: &ec2.Placement{ + AvailabilityZone: aws.String("us-gov-east-1"), + }, + }, + }, + }, + }, + } + + tests := []struct { + name string + bastionEnabled bool + expect func(m *mocks.MockEC2APIMockRecorder) + expectError bool + bastionStatus *infrav1.Instance + }{ + { + name: "Should ignore reconciliation if instance not found", + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstances(gomock.Eq(describeInput)). + Return(&ec2.DescribeInstancesOutput{}, nil) + }, + expectError: false, + }, + { + name: "Should fail reconcile if describe instance fails", + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstances(gomock.Eq(describeInput)). + Return(nil, errors.New("some error")) + }, + expectError: true, + }, + { + name: "Should fail reconcile if terminate instance fails", + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstances(gomock.Eq(describeInput)). + Return(foundOutput, nil).MinTimes(1) + m. + TerminateInstances( + gomock.Eq(&ec2.TerminateInstancesInput{ + InstanceIds: aws.StringSlice([]string{"id123"}), + }), + ). + Return(nil, errors.New("some error")) + }, + expectError: true, + }, + { + name: "Should create bastion successfully", + expect: func(m *mocks.MockEC2APIMockRecorder) { + m.DescribeInstances(gomock.Eq(describeInput)). + Return(&ec2.DescribeInstancesOutput{}, nil).MinTimes(1) + m.DescribeImages(gomock.Eq(&ec2.DescribeImagesInput{Filters: []*ec2.Filter{ { Name: aws.String("architecture"), Values: aws.StringSlice([]string{"x86_64"}), @@ -327,6 +559,10 @@ func TestServiceReconcileBastion(t *testing.T) { Name: aws.String("description"), Values: aws.StringSlice([]string{ubuntuImageDescription}), }, + { + Name: aws.String("owner-id"), + Values: aws.StringSlice([]string{ubuntuOwnerIDUsGov}), + }, }})).Return(&ec2.DescribeImagesOutput{Images: images{ { ImageId: aws.String("ubuntu-ami-id-latest"), @@ -345,7 +581,7 @@ func TestServiceReconcileBastion(t *testing.T) { Name: aws.String(ec2.InstanceStateNameRunning), }, IamInstanceProfile: &ec2.IamInstanceProfile{ - Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + Arn: aws.String("arn:aws-us-gov:iam::123456789012:instance-profile/foo"), }, InstanceId: aws.String("id123"), InstanceType: aws.String("t3.micro"), @@ -361,7 +597,7 @@ func TestServiceReconcileBastion(t *testing.T) { }, }, Placement: &ec2.Placement{ - AvailabilityZone: aws.String("us-east-1"), + AvailabilityZone: aws.String("us-gov-east-1"), }, }, }, @@ -379,7 +615,7 @@ func TestServiceReconcileBastion(t *testing.T) { ImageID: "ubuntu-ami-id-latest", IAMProfile: "foo", Addresses: []clusterv1.MachineAddress{}, - AvailabilityZone: "us-east-1", + AvailabilityZone: "us-gov-east-1", VolumeIDs: []string{"volume-1"}, }, }, @@ -419,6 +655,7 @@ func TestServiceReconcileBastion(t *testing.T) { }, }, Bastion: infrav1.Bastion{Enabled: tc.bastionEnabled}, + Region: "us-gov-east-1", }, } diff --git a/pkg/cloud/services/eks/roles.go b/pkg/cloud/services/eks/roles.go index 3a3eb14d59..3bc0dfbc12 100644 --- a/pkg/cloud/services/eks/roles.go +++ b/pkg/cloud/services/eks/roles.go @@ -18,12 +18,14 @@ package eks import ( "fmt" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" "github.com/pkg/errors" + "sigs.k8s.io/cluster-api-provider-aws/v2/cmd/clusterawsadm/api/bootstrap/v1beta1" ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2" eksiam "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/eks/iam" @@ -52,6 +54,22 @@ func FargateRolePolicies() []string { } } +// NodegroupRolePoliciesUSGov gives the policies required for a nodegroup role. +func NodegroupRolePoliciesUSGov() []string { + return []string{ + "arn:aws-us-gov:iam::aws:policy/AmazonEKSWorkerNodePolicy", + "arn:aws-us-gov:iam::aws:policy/AmazonEKS_CNI_Policy", //TODO: Can remove when CAPA supports provisioning of OIDC web identity federation with service account token volume projection + "arn:aws-us-gov:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", + } +} + +// FargateRolePoliciesUSGov gives the policies required for a fargate role. +func FargateRolePoliciesUSGov() []string { + return []string{ + "arn:aws-us-gov:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy", + } +} + func (s *Service) reconcileControlPlaneIAMRole() error { s.scope.Debug("Reconciling EKS Control Plane IAM Role") @@ -94,8 +112,9 @@ func (s *Service) reconcileControlPlaneIAMRole() error { //TODO: check tags and trust relationship to see if they need updating policies := []*string{ - aws.String("arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"), + aws.String(fmt.Sprintf("arn:%s:iam::aws:policy/AmazonEKSClusterPolicy", s.scope.Partition())), } + if s.scope.ControlPlane.Spec.RoleAdditionalPolicies != nil { if !s.scope.AllowAdditionalRoles() && len(*s.scope.ControlPlane.Spec.RoleAdditionalPolicies) > 0 { return ErrCannotUseAdditionalRoles @@ -204,6 +223,10 @@ func (s *NodegroupService) reconcileNodegroupIAMRole() error { } policies := NodegroupRolePolicies() + if strings.Contains(s.scope.Partition(), v1beta1.PartitionNameUSGov) { + policies = NodegroupRolePoliciesUSGov() + } + if len(s.scope.ManagedMachinePool.Spec.RoleAdditionalPolicies) > 0 { if !s.scope.AllowAdditionalRoles() { return ErrCannotUseAdditionalRoles @@ -320,6 +343,10 @@ func (s *FargateService) reconcileFargateIAMRole() (requeue bool, err error) { } policies := FargateRolePolicies() + if strings.Contains(s.scope.Partition(), v1beta1.PartitionNameUSGov) { + policies = FargateRolePoliciesUSGov() + } + updatedPolicies, err := s.EnsurePoliciesAttached(role, aws.StringSlice(policies)) if err != nil { return updatedRole, errors.Wrapf(err, "error ensuring policies are attached: %v", policies) diff --git a/pkg/cloud/services/iamauth/reconcile.go b/pkg/cloud/services/iamauth/reconcile.go index 64b9f07ef7..0261efbecc 100644 --- a/pkg/cloud/services/iamauth/reconcile.go +++ b/pkg/cloud/services/iamauth/reconcile.go @@ -27,6 +27,7 @@ import ( ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2" iamv1 "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" ) // ReconcileIAMAuthenticator is used to create the aws-iam-authenticator in a cluster. @@ -49,7 +50,8 @@ func (s *Service) ReconcileIAMAuthenticator(ctx context.Context) error { return fmt.Errorf("getting aws-iam-authenticator backend: %w", err) } - roleARN := fmt.Sprintf("arn:aws:iam::%s:role/nodes%s", accountID, iamv1.DefaultNameSuffix) + partition := system.GetPartitionFromRegion(s.scope.Region()) + roleARN := fmt.Sprintf("arn:%s:iam::%s:role/nodes%s", partition, accountID, iamv1.DefaultNameSuffix) nodesRoleMapping := ekscontrolplanev1.RoleMapping{ RoleARN: roleARN, KubernetesMapping: ekscontrolplanev1.KubernetesMapping{ diff --git a/pkg/cloud/services/s3/s3.go b/pkg/cloud/services/s3/s3.go index 9386fb55a4..2687ebcba5 100644 --- a/pkg/cloud/services/s3/s3.go +++ b/pkg/cloud/services/s3/s3.go @@ -33,6 +33,7 @@ import ( iam "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" + "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" ) // Service holds a collection of interfaces. @@ -236,16 +237,17 @@ func (s *Service) bucketPolicy(bucketName string) (string, error) { } bucket := s.scope.Bucket() + partition := system.GetPartitionFromRegion(s.scope.Region()) statements := []iam.StatementEntry{ { Sid: "control-plane", Effect: iam.EffectAllow, Principal: map[iam.PrincipalType]iam.PrincipalID{ - iam.PrincipalAWS: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", *accountID.Account, bucket.ControlPlaneIAMInstanceProfile)}, + iam.PrincipalAWS: []string{fmt.Sprintf("arn:%s:iam::%s:role/%s", s.scope, *accountID.Account, bucket.ControlPlaneIAMInstanceProfile)}, }, Action: []string{"s3:GetObject"}, - Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/control-plane/*", bucketName)}, + Resource: []string{fmt.Sprintf("arn:%s:s3:::%s/control-plane/*", partition, bucketName)}, }, } @@ -254,10 +256,10 @@ func (s *Service) bucketPolicy(bucketName string) (string, error) { Sid: iamInstanceProfile, Effect: iam.EffectAllow, Principal: map[iam.PrincipalType]iam.PrincipalID{ - iam.PrincipalAWS: []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", *accountID.Account, iamInstanceProfile)}, + iam.PrincipalAWS: []string{fmt.Sprintf("arn:%s:iam::%s:role/%s", partition, *accountID.Account, iamInstanceProfile)}, }, Action: []string{"s3:GetObject"}, - Resource: []string{fmt.Sprintf("arn:aws:s3:::%s/node/*", bucketName)}, + Resource: []string{fmt.Sprintf("arn:%s:s3:::%s/node/*", partition, bucketName)}, }) } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index b343afc012..00916a1511 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -15,7 +15,6 @@ limitations under the License. */ // Package logger -//nolint: logrlint package logger import ( diff --git a/util/system/util.go b/util/system/util.go index 390573b420..786150950d 100644 --- a/util/system/util.go +++ b/util/system/util.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" + "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/pkg/errors" ) @@ -68,3 +69,19 @@ func GetNamespaceFromFile(nsFilePath string) (string, error) { } return string(namespace), nil } + +// GetPartitionFromRegion returns the cluster partition. +func GetPartitionFromRegion(region string) string { + switch region { + case endpoints.UsGovEast1RegionID, endpoints.UsGovWest1RegionID: + return endpoints.AwsUsGovPartitionID + case endpoints.CnNorth1RegionID, endpoints.CnNorthwest1RegionID: + return endpoints.AwsCnPartitionID + case endpoints.UsIsoEast1RegionID, endpoints.UsIsoWest1RegionID: + return endpoints.AwsIsoPartitionID + case endpoints.UsIsobEast1RegionID: + return endpoints.AwsIsoBPartitionID + default: + return endpoints.AwsPartitionID + } +}