Skip to content

Commit

Permalink
feat: eks pod identity support for controllers
Browse files Browse the repository at this point in the history
This adds support for using EKS pod identity for the CAPA controller
when the management cluster is an EKS cluster

Signed-off-by: Richard Case <[email protected]>
  • Loading branch information
richardcase committed Nov 4, 2024
1 parent 85759ce commit 02e6fa4
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ func (t Template) controllersPolicyRoleAttachments() []string {
return attachments
}

func (t Template) controllersTrustPolicy() *iamv1.PolicyDocument {
policyDocument := ec2AssumeRolePolicy()
func (t Template) controllersTrustPolicy(eksEnabled bool) *iamv1.PolicyDocument {
policyDocument := ec2AssumeRolePolicy(eksEnabled)
policyDocument.Statement = append(policyDocument.Statement, t.Spec.ClusterAPIControllers.TrustStatements...)
return policyDocument
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (t Template) controlPlanePolicies() []cfn_iam.Role_Policy {
}

func (t Template) controlPlaneTrustPolicy() *iamv1.PolicyDocument {
policyDocument := ec2AssumeRolePolicy()
policyDocument := ec2AssumeRolePolicy(false)
policyDocument.Statement = append(policyDocument.Statement, t.Spec.ControlPlane.TrustStatements...)
return policyDocument
}
2 changes: 1 addition & 1 deletion cmd/clusterawsadm/cloudformation/bootstrap/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (t Template) nodePolicies() []cfn_iam.Role_Policy {
}

func (t Template) nodeTrustPolicy() *iamv1.PolicyDocument {
policyDocument := ec2AssumeRolePolicy()
policyDocument := ec2AssumeRolePolicy(false)
policyDocument.Statement = append(policyDocument.Statement, t.Spec.Nodes.TrustStatements...)
return policyDocument
}
10 changes: 7 additions & 3 deletions cmd/clusterawsadm/cloudformation/bootstrap/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (t Template) RenderCloudFormation() *cloudformation.Template {

template.Resources[AWSIAMRoleControllers] = &cfn_iam.Role{
RoleName: t.NewManagedName("controllers"),
AssumeRolePolicyDocument: t.controllersTrustPolicy(),
AssumeRolePolicyDocument: t.controllersTrustPolicy(!t.Spec.EKS.Disable),
Policies: t.controllersRolePolicy(),
Tags: converters.MapToCloudFormationTags(t.Spec.ClusterAPIControllers.Tags),
}
Expand Down Expand Up @@ -218,8 +218,12 @@ func (t Template) RenderCloudFormation() *cloudformation.Template {
return template
}

func ec2AssumeRolePolicy() *iamv1.PolicyDocument {
return AssumeRolePolicy(iamv1.PrincipalService, []string{"ec2.amazonaws.com"})
func ec2AssumeRolePolicy(withEKS bool) *iamv1.PolicyDocument {
principalIDs := []string{"ec2.amazonaws.com"}
if withEKS {
principalIDs = append(principalIDs, "pods.eks.amazonaws.com")
}
return AssumeRolePolicy(iamv1.PrincipalService, principalIDs)
}

// AWSArnAssumeRolePolicy will assume Policies using PolicyArns.
Expand Down
1 change: 1 addition & 0 deletions cmd/clusterawsadm/cmd/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func RootCmd() *cobra.Command {
newCmd.AddCommand(credentials.UpdateCredentialsCmd())
newCmd.AddCommand(credentials.PrintCredentialsCmd())
newCmd.AddCommand(rollout.RolloutControllersCmd())
newCmd.AddCommand(credentials.UseEKSPodIdentityCmd())

return newCmd
}
173 changes: 173 additions & 0 deletions cmd/clusterawsadm/cmd/controller/credentials/use_pod_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package credentials

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/eks"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/spf13/cobra"

"sigs.k8s.io/cluster-api/cmd/clusterctl/cmd"
)

// UseEKSPodIdentityCmd is a CLI command that will enable using EKS pod identity for CAPA.
func UseEKSPodIdentityCmd() *cobra.Command {
clusterName := ""
region := ""
namespace := ""
serviceAccount := ""
roleName := ""

newCmd := &cobra.Command{
Use: "use-pod-identity",
Short: "Enable EKS pod identiy with CAPA",
Long: cmd.LongDesc(`
Updates CAPA running in an EKS cluster to use EKS pod identity
`),
Example: cmd.Examples(`
clusterawsadm controller use-pod-identity --cluster-name cluster1
`),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return usePodIdentity(region, clusterName, namespace, serviceAccount, roleName)
},
}

newCmd.Flags().StringVarP(&region, "region", "r", "", "The AWS region containing the EKS cluster")
newCmd.Flags().StringVarP(&clusterName, "cluster-name", "n", "", "The name of the EKS management cluster")
newCmd.Flags().StringVar(&namespace, "namespace", "capa-system", "The namespace of CAPA controller")
newCmd.Flags().StringVar(&serviceAccount, "service-account", "capa-controller-manager", "The service account for the CAPA controller")
newCmd.Flags().StringVar(&roleName, "role-name", "controllers.cluster-api-provider-aws.sigs.k8s.io", "The name of the CAPA controller role. If you have used a prefix or suffix this will need to be changed.")

newCmd.MarkFlagRequired("cluster-name")

Check failure on line 60 in cmd/clusterawsadm/cmd/controller/credentials/use_pod_identity.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `newCmd.MarkFlagRequired` is not checked (errcheck)

Check failure on line 60 in cmd/clusterawsadm/cmd/controller/credentials/use_pod_identity.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `newCmd.MarkFlagRequired` is not checked (errcheck)

return newCmd
}

func usePodIdentity(region, clusterName, namespace, serviceAccount, roleName string) error {
cfg := aws.Config{}
if region != "" {
cfg.Region = aws.String(region)
}

sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: cfg,
})
if err != nil {
return fmt.Errorf("failed creating aws session: %w", err)
}

roleArn, err := getRoleArn(sess, roleName)
if err != nil {
return err
}

eksClient := eks.New(sess)

listInput := &eks.ListPodIdentityAssociationsInput{
ClusterName: aws.String(clusterName),
Namespace: aws.String(namespace),
}

listOutput, err := eksClient.ListPodIdentityAssociations(listInput)
if err != nil {
return fmt.Errorf("listing existing pod identity associations for cluster %s in namespace %s: %w", clusterName, namespace, err)
}

for _, association := range listOutput.Associations {
if *association.ServiceAccount == serviceAccount {
needsUpdate, err := podIdentityNeedsUpdate(eksClient, association, roleName)
if err != nil {
return err
}
if !needsUpdate {
fmt.Printf("EKS pod association for service account %s already exists, no action taken\n", serviceAccount)
}

return updatePodIdentity(eksClient, association, roleName)
}
}

fmt.Printf("Creating pod association for service account %s.....\n", serviceAccount)

createInpuut := &eks.CreatePodIdentityAssociationInput{
ClusterName: &clusterName,
Namespace: &namespace,
RoleArn: &roleArn,
ServiceAccount: &serviceAccount,
}

output, err := eksClient.CreatePodIdentityAssociation(createInpuut)
if err != nil {
return fmt.Errorf("failed to create pod identity association: %w", err)
}

fmt.Printf("Created pod identity association (%s)\n", *output.Association.AssociationId)

return nil
}

func podIdentityNeedsUpdate(client *eks.EKS, association *eks.PodIdentityAssociationSummary, roleArn string) (bool, error) {
input := &eks.DescribePodIdentityAssociationInput{
AssociationId: association.AssociationId,
ClusterName: association.ClusterName,
}

output, err := client.DescribePodIdentityAssociation(input)
if err != nil {
return false, fmt.Errorf("failed describing pod identity association: %w", err)
}

return *output.Association.RoleArn != roleArn, nil
}

func updatePodIdentity(client *eks.EKS, association *eks.PodIdentityAssociationSummary, roleArn string) error {
input := &eks.UpdatePodIdentityAssociationInput{
AssociationId: association.AssociationId,
ClusterName: association.ClusterName,
RoleArn: &roleArn,
}

_, err := client.UpdatePodIdentityAssociation(input)
if err != nil {
return fmt.Errorf("failed updating pod identity association: %w", err)
}

fmt.Printf("Updated pod identity to use role %s\n", roleArn)

return nil
}

func getRoleArn(sess *session.Session, roleName string) (string, error) {
client := iam.New(sess)

input := &iam.GetRoleInput{
RoleName: &roleName,
}

output, err := client.GetRole(input)
if err != nil {
return "", fmt.Errorf("failed looking up role %s: %w", roleName, err)
}

return *output.Role.Arn, nil
}
32 changes: 32 additions & 0 deletions docs/book/src/topics/eks/eks-pod-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Using EKS Pod Identity for CAPA Controller

You can use [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) to supply the credentials for the CAPA controller when the management is in EKS. This is an alternative to using the static boostrap credentials or IRSA.

## Pre-requisites

- Management cluster must be an EKS cluster
- AWS environment variables set for your account

## Steps

1. Install the **Amazon EKS Pod Identity Agent** EKS addon into the cluster. This can be done using the AWS console or using the AWS cli.

> NOTE: If your management cluster is a "self-managed" CAPI cluster then its possible to install the addon via the **EKSManagedControlPlane**.
2. Create an EKS pod identity association for CAPA by running the following (replacing **<clustername>** with the name of your EKS cluster):

```bash
clusterawsadm controller use-pod-identity --cluster-name <clustername>
```

3. Ensure any credentials set for the controller are removed (a.k.a zeroed out):

```bash
clusterawsadm controller zero-credentials --namespace=capa-system
```

4. Force CAPA to restart so that the AWS credentials are injected:

```bash
clusterawsadm controller rollout-controller --kubeconfig=kubeconfig --namespace=capa-system
```
3 changes: 2 additions & 1 deletion docs/book/src/topics/eks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ And a number of new templates are available in the templates folder for creating
* [Using EKS Console](eks-console.md)
* [Using EKS Addons](addons.md)
* [Enabling Encryption](encryption.md)
* [Cluster Upgrades](cluster-upgrades.md)
* [Cluster Upgrades](cluster-upgrades.md)
* [Using EKS Pod Identity for controller credentials](eks-pod-identity.md)

0 comments on commit 02e6fa4

Please sign in to comment.