Repository: kubernetes-sigs/cluster-api-provider-aws
Pull request #4010 (2023-01-27 12:43:15)

capa fix hardcoded role arn for aws iam authenticator
## **State**
### READY

## **What is the purpose of the pull request**
ARN hardcoded with ":aws:" partition do not work with "us-gov" cloud.

## **Implementation**
Issue: For few roles/policies arn partition is hardcoded to "aws". While working with "aws-us-gov" cloud partition this does not work and cluster creation failed.

Fix: Check for aws cloud "partition" or "region" to derive correct arn and use it.

<!-- Thanks for this PR! If this is your first PR please read the [contributing guide](../CONTRIBUTING.md) -->
<!-- If this PR is still work-in-progress and is being open for visibility please prefix the title with `WIP:` -->

/kind bug

**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 -->

- [x] squashed commits
- [ ] includes documentation
- [x] adds unit tests
- [ ] adds or updates e2e tests


NOTE: This is reopened PR based on suggestion [here](https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/3926#issuecomment-1383753246)

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign timothysc for approval by writing `/assign @timothysc` in a comment. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["timothysc"]} -->
k8s-ci-robot: Hi @AmitSahastra. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-aws).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Skarlso: /test ?
k8s-ci-robot: @Skarlso: The following commands are available to trigger required jobs:
* `/test pull-cluster-api-provider-aws-build`
* `/test pull-cluster-api-provider-aws-test`
* `/test pull-cluster-api-provider-aws-verify`

The following commands are available to trigger optional jobs:
* `/test pull-cluster-api-provider-aws-apidiff-main`
* `/test pull-cluster-api-provider-aws-e2e`
* `/test pull-cluster-api-provider-aws-e2e-blocking`
* `/test pull-cluster-api-provider-aws-e2e-clusterclass`
* `/test pull-cluster-api-provider-aws-e2e-conformance`
* `/test pull-cluster-api-provider-aws-e2e-conformance-with-ci-artifacts`
* `/test pull-cluster-api-provider-aws-e2e-eks`
* `/test pull-cluster-api-provider-aws-e2e-eks-gc`
* `/test pull-cluster-api-provider-aws-e2e-eks-testing`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-cluster-api-provider-aws-apidiff-main`
* `pull-cluster-api-provider-aws-build`
* `pull-cluster-api-provider-aws-test`
* `pull-cluster-api-provider-aws-verify`



<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/4010#issuecomment-1406464509):

>/test ?


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
Skarlso: /test pull-cluster-api-provider-aws-e2e
k8s-ci-robot: @AmitSahastra: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-e2e | d17fc30729b8fc8f0385a67035186b1996a2ff66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e/1618956971375857664) | false | `/test pull-cluster-api-provider-aws-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=4010). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3AAmitSahastra). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
AmitSahastra: > Yaaay it failed tada That's good news. That means it's consistently wrong. @AmitSahastra Would you mind checking out what the heck is the failure there? :D Or do you need help with that?

Would like to have some input on this, this are failing for timeout and provision state. Also wasn't able to execute tests locally.
Skarlso: >Would like to have some input on this, this are failing for timeout and provision state differences. Also wasn't able to execute tests locally.

Right, which is super weird, but it started failing and fails consistently after this change. So it must be somehow related.

I'll investigate. :)
Skarlso: /test pull-cluster-api-provider-aws-e2e
k8s-ci-robot: @AmitSahastra: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-e2e | d17fc30729b8fc8f0385a67035186b1996a2ff66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e/1619718245294542848) | false | `/test pull-cluster-api-provider-aws-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=4010). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3AAmitSahastra). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
dlipovetsky: @AmitSahastra for contributing the PR. Can you please create an issue? :pray: 

(The problem appears to be complex enough to deserve its own issue. The issue is the place where we can come to a shared understanding of the problem, and the PRs are where we can review solutions. I'll copy over my comments below to the issue, once it's created).

--

I think the changes to the `clusterawsadm` CLI make sense, because the CLI already uses a default "partition" value to derive ARNs. The CLI does not "discover" facts (such as region) about the user's AWS environment.

I am not sure we need to change controllers to be aware of the "partition" in an ARN. Controllers should, and in most cases already do, use names to identify resources. Whenever an AWS API expects an ARN from the client, the controllers should ask AWS to translate the name into the ARN. The controllers themselves should probably not translate the name to ARN.

The work in #4011 has [introduces a function](https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/4011/files#diff-c53e563f45999d5f20ce7a4932249df6ab25e01c0b8b7a153d302458151b6c6dR84-R90) that asks AWS to translate the Role to its ARN. We should probably have similar functions for other kinds of AWS resources, or perhaps a single generic function, if appropriate.
AmitSahastra: > @AmitSahastra thank you for contributing the PR! Can you please create an issue? 🙏
> 
> (The problem appears to be complex enough to deserve its own issue. The issue is the place where we can come to a shared understanding of the problem, and the PRs are where we can review solutions. I'll copy over my comments below to the issue, once it's created).
> 
> --
> 
> I think the changes to the `clusterawsadm` CLI make sense, because the CLI already uses a default "partition" value to derive ARNs. The CLI does not "discover" facts (such as region) about the user's AWS environment.
> 
> I am not sure we need to change controllers to be aware of the "partition" in an ARN. Controllers should, and in most cases already do, use names to identify resources. Whenever an AWS API expects an ARN from the client, the controllers should ask AWS to translate the name into the ARN. The controllers themselves should probably not translate the name to ARN.
> 
> The work in #4011 has [introduces a function](https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/4011/files#diff-c53e563f45999d5f20ce7a4932249df6ab25e01c0b8b7a153d302458151b6c6dR84-R90) that asks AWS to translate the Role to its ARN. We should probably have similar functions for other kinds of AWS resources, or perhaps a single generic function, if appropriate.

Have raised issue [here](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/4027)
Skarlso: Sorry about this, I still haven't had the time to look into the test failure. I ran it a couple times I know this somehow messes things up, but didn't get the chance yet to investigate fully.
ryan-dyer-sp: Just curious if support for the China partition should be included as well.  I just noticed there were specific references for aws-us-gov. ([ref](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html))  I personally just need the aws-us-gov support though :)
k8s-ci-robot: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
Ankitasw: Closing for now, we could open this again once the issue is concluded.
richardcase: @Ankitasw @AmitSahastra - any context on why this PR was closed?
k8s-ci-robot: @richardcase: Reopened this PR.

<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api-provider-aws/pull/4010#issuecomment-1507157009):

>We should resurrect this in light of discussions with @wilderk on slack:
>
>/reopen


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign timothysc for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["timothysc"]} -->
wilderk: I built this PR without resolving conflicts and successfully created an iam cloudformation stack in AWS Govcloud.
```
# create an iam boostrap config file
cat << EOF > bootstrap-config.yaml
apiVersion: bootstrap.aws.infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSIAMConfiguration
spec:
  partition: aws-us-gov
EOF
```
Run clusterawsadm bootstrap iam
```
~/go/src/sigs.k8s.io/cluster-api-provider-aws/bin/clusterawsadm bootstrap iam create-cloudformation-stack --config bootstrap-config.yaml
Attempting to create AWS CloudFormation stack cluster-api-provider-aws-sigs-k8s-io

Following resources are in the stack: 

Resource                  |Type                                                                                         |Status
AWS::IAM::InstanceProfile |control-plane.cluster-api-provider-aws.sigs.k8s.io                                           |CREATE_COMPLETE
AWS::IAM::InstanceProfile |controllers.cluster-api-provider-aws.sigs.k8s.io                                             |CREATE_COMPLETE
AWS::IAM::InstanceProfile |nodes.cluster-api-provider-aws.sigs.k8s.io                                                   |CREATE_COMPLETE
AWS::IAM::ManagedPolicy   |arn:aws-us-gov:iam::XXXXXXXXXXXX:policy/control-plane.cluster-api-provider-aws.sigs.k8s.io   |CREATE_COMPLETE
AWS::IAM::ManagedPolicy   |arn:aws-us-gov:iam::XXXXXXXXXXXX:policy/nodes.cluster-api-provider-aws.sigs.k8s.io           |CREATE_COMPLETE
AWS::IAM::ManagedPolicy   |arn:aws-us-gov:iam::XXXXXXXXXXXX:policy/controllers.cluster-api-provider-aws.sigs.k8s.io     |CREATE_COMPLETE
AWS::IAM::ManagedPolicy   |arn:aws-us-gov:iam::XXXXXXXXXXXX:policy/controllers-eks.cluster-api-provider-aws.sigs.k8s.io |CREATE_COMPLETE
AWS::IAM::Role            |control-plane.cluster-api-provider-aws.sigs.k8s.io                                           |CREATE_COMPLETE
AWS::IAM::Role            |controllers.cluster-api-provider-aws.sigs.k8s.io                                             |CREATE_COMPLETE
AWS::IAM::Role            |eks-controlplane.cluster-api-provider-aws.sigs.k8s.io                                        |CREATE_COMPLETE
AWS::IAM::Role            |nodes.cluster-api-provider-aws.sigs.k8s.io                                                   |CREATE_COMPLETE
```

Skarlso: I rebased this. There are some code segments which got fixes as outlined by Daniel. I removed those. CAn you re-test @wilderk?
Skarlso: /test pull-cluster-api-provider-aws-e2e-eks
/test pull-cluster-api-provider-aws-e2e-clusterclass
k8s-ci-robot: @AmitSahastra: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-e2e | d17fc30729b8fc8f0385a67035186b1996a2ff66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e/1619718245294542848) | false | `/test pull-cluster-api-provider-aws-e2e`
pull-cluster-api-provider-aws-e2e-eks | cbd33cc7ad1fc9c5424b6c8fb6b37eb19fce5cf7 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e-eks/1646771726450692096) | false | `/test pull-cluster-api-provider-aws-e2e-eks`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=4010). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3AAmitSahastra). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Skarlso: Most likely unrelated as Remediation test has been failing for a while now.
Skarlso: /retest
k8s-ci-robot: @AmitSahastra: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-e2e | d17fc30729b8fc8f0385a67035186b1996a2ff66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e/1619718245294542848) | false | `/test pull-cluster-api-provider-aws-e2e`
pull-cluster-api-provider-aws-e2e-eks | cbd33cc7ad1fc9c5424b6c8fb6b37eb19fce5cf7 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-e2e-eks/1646771726450692096) | false | `/test pull-cluster-api-provider-aws-e2e-eks`
pull-cluster-api-provider-aws-test | cbd33cc7ad1fc9c5424b6c8fb6b37eb19fce5cf7 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/4010/pull-cluster-api-provider-aws-test/1646815764226772992) | true | `/test pull-cluster-api-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=4010). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3AAmitSahastra). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Skarlso: The test has to be updated.

Patch:
diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go
index a327dc70d..d1be7abe5 100644
--- a/api/v1beta1/awscluster_conversion.go
+++ b/api/v1beta1/awscluster_conversion.go
@@ -17,27 +17,27 @@ 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)
 	}
@@ -47,13 +47,14 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
 	if restored.Status.Bastion != nil {
 		dst.Status.Bastion.InstanceMetadataOptions = restored.Status.Bastion.InstanceMetadataOptions
 	}
+	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
@@ -62,7 +63,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
@@ -72,7 +73,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
@@ -88,14 +89,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 01aca175e..04d112f9b 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 811288176..dfa329257 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 fca979b6a..277afb115 100644
--- a/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go
+++ b/cmd/clusterawsadm/api/bootstrap/v1beta1/defaults.go
@@ -33,6 +33,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 b2856ab3e..9a57cc144 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 8d5dc5b6d..791f25602 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 f1f9a74d5..030bc248e 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 2dcfc6462..f92029cfe 100644
--- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml
+++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml
@@ -1989,6 +1989,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 5876e7066..1bbb0fade 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml
@@ -1249,6 +1249,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 d0fcd3b2e..cd4769ece 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml
@@ -856,6 +856,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 ed6650420..57284afd2 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/zz_generated.conversion.go b/controlplane/eks/api/v1beta1/zz_generated.conversion.go
index f38dfa213..44429be0f 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 f11d17bf7..3ca8ded16 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 905fb76b7..2d2266167 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 06bdcc96f..865d063f9 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"
@@ -352,3 +353,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 e35dc7885..7a58137f6 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 f94fb1757..026f1da2f 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 d3f096eb6..84df59582 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"
@@ -165,6 +166,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 74236a6d2..4cd2d5822 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 (
@@ -50,6 +52,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*"
 
@@ -249,10 +253,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")},
@@ -271,6 +271,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/eks/roles.go b/pkg/cloud/services/eks/roles.go
index 3a3eb14d5..3bc0dfbc1 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/s3/s3.go b/pkg/cloud/services/s3/s3.go
index 9386fb55a..2687ebcba 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/util/system/util.go b/util/system/util.go
index 390573b42..786150950 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
+	}
+}


Test patch:
diff --git a/controlplane/eks/api/v1beta1/conversion_test.go b/controlplane/eks/api/v1beta1/conversion_test.go
index fd809cb1d..207a6b669 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/pkg/cloud/services/ec2/bastion_test.go b/pkg/cloud/services/ec2/bastion_test.go
index 669242d96..822361a51 100644
--- a/pkg/cloud/services/ec2/bastion_test.go
+++ b/pkg/cloud/services/ec2/bastion_test.go
@@ -307,10 +307,240 @@ 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)
+			},
+			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 +557,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 +579,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 +595,7 @@ func TestServiceReconcileBastion(t *testing.T) {
 									},
 								},
 								Placement: &ec2.Placement{
-									AvailabilityZone: aws.String("us-east-1"),
+									AvailabilityZone: aws.String("us-gov-east-1"),
 								},
 							},
 						},
@@ -377,7 +611,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"},
 			},
 		},
@@ -417,6 +651,7 @@ func TestServiceReconcileBastion(t *testing.T) {
 							},
 						},
 						Bastion: infrav1.Bastion{Enabled: tc.bastionEnabled},
+						Region:  "us-gov-east-1",
 					},
 				}