Skip to content

Commit

Permalink
Implement updating and deleting pod identity associations (#7315)
Browse files Browse the repository at this point in the history
* Implement `update podidentityassociation`

* Implement `delete podidentityassociation`

* change create task order

* add integration test

* delete IAM role stacks on cluster deletion

* move validations from cluster config level to command config level

* avoid duplicating stack deletion tasks due to passing name by reference

* fix integration tests

* add eksctl prefix to integrationt test IAM roles

* make IAM roles trust the production EKS Service Principal

---------

Co-authored-by: Tibi <[email protected]>
  • Loading branch information
cPu1 and TiberiuGC authored Nov 27, 2023
1 parent 401127c commit 73aa3d1
Show file tree
Hide file tree
Showing 29 changed files with 2,130 additions and 133 deletions.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/actions/addon/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func CreateAddonTasks(ctx context.Context, cfg *api.ClusterConfig, clusterProvid
var preAddons []*api.Addon
var postAddons []*api.Addon
for _, addon := range cfg.Addons {
if strings.ToLower(addon.Name) == "vpc-cni" {
if strings.EqualFold(addon.Name, api.VPCCNIAddon) {
preAddons = append(preAddons, addon)
} else {
postAddons = append(postAddons, addon)
Expand Down
9 changes: 8 additions & 1 deletion pkg/actions/cluster/owned.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (

"github.com/weaveworks/eksctl/pkg/actions/addon"
"github.com/weaveworks/eksctl/pkg/actions/nodegroup"
"github.com/weaveworks/eksctl/pkg/actions/podidentityassociation"
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
"github.com/weaveworks/eksctl/pkg/cfn/manager"
"github.com/weaveworks/eksctl/pkg/ctl/cmdutils"
"github.com/weaveworks/eksctl/pkg/eks"
iamoidc "github.com/weaveworks/eksctl/pkg/iam/oidc"
"github.com/weaveworks/eksctl/pkg/kubernetes"
"github.com/weaveworks/eksctl/pkg/utils/tasks"
"github.com/weaveworks/eksctl/pkg/vpc"
)

Expand Down Expand Up @@ -121,7 +123,12 @@ func (c *OwnedCluster) Delete(ctx context.Context, _, podEvictionWaitPeriod time
return c.ctl.NewOpenIDConnectManager(ctx, c.cfg)
}
newTasksToDeleteAddonIAM := addon.NewRemover(c.stackManager).DeleteAddonIAMTasks
tasks, err := c.stackManager.NewTasksToDeleteClusterWithNodeGroups(ctx, c.clusterStack, allStacks, clusterOperable, newOIDCManager, newTasksToDeleteAddonIAM, c.ctl.Status.ClusterInfo.Cluster, kubernetes.NewCachedClientSet(clientSet), wait, force, func(errs chan error, _ string) error {
newTasksToDeletePodIdentityRoles := func() (*tasks.TaskTree, error) {
return podidentityassociation.NewDeleter(c.cfg.Metadata.Name, c.stackManager, c.ctl.AWSProvider.EKS()).
DeleteTasks(ctx, []podidentityassociation.Identifier{})
}

tasks, err := c.stackManager.NewTasksToDeleteClusterWithNodeGroups(ctx, c.clusterStack, allStacks, clusterOperable, newOIDCManager, newTasksToDeleteAddonIAM, newTasksToDeletePodIdentityRoles, c.ctl.Status.ClusterInfo.Cluster, kubernetes.NewCachedClientSet(clientSet), wait, force, func(errs chan error, _ string) error {
logger.Info("trying to cleanup dangling network interfaces")
stack, err := c.stackManager.DescribeClusterStack(ctx)
if err != nil {
Expand Down
17 changes: 1 addition & 16 deletions pkg/actions/podidentityassociation/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package podidentityassociation
import (
"context"
"fmt"
"strings"

"github.com/kris-nova/logger"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
"github.com/weaveworks/eksctl/pkg/awsapi"
Expand All @@ -28,19 +25,7 @@ func NewCreator(clusterName string, stackManager StackManager, eksAPI awsapi.EKS
}

func (c *Creator) CreatePodIdentityAssociations(ctx context.Context, podIdentityAssociations []api.PodIdentityAssociation) error {
taskTree := c.CreateTasks(ctx, podIdentityAssociations)
logger.Info(taskTree.Describe())

if errs := taskTree.DoAllSync(); len(errs) > 0 {
var allErrs []string
for _, err := range errs {
allErrs = append(allErrs, err.Error())
}
return fmt.Errorf(strings.Join(allErrs, "\n"))
}

logger.Info("successfully created all pod identity associations")
return nil
return runAllTasks(c.CreateTasks(ctx, podIdentityAssociations))
}

func (c *Creator) CreateTasks(ctx context.Context, podIdentityAssociations []api.PodIdentityAssociation) *tasks.TaskTree {
Expand Down
186 changes: 186 additions & 0 deletions pkg/actions/podidentityassociation/deleter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package podidentityassociation

import (
"context"
"fmt"
"strings"

cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"

"golang.org/x/exp/slices"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/eks"

"github.com/kris-nova/logger"

api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"
"github.com/weaveworks/eksctl/pkg/cfn/manager"
"github.com/weaveworks/eksctl/pkg/utils/tasks"
)

// A StackLister lists and describes CloudFormation stacks.
type StackLister interface {
ListStackNames(ctx context.Context, regExp string) ([]string, error)
DescribeStack(ctx context.Context, stack *manager.Stack) (*manager.Stack, error)
}

// A StackDeleter lists and deletes CloudFormation stacks.
type StackDeleter interface {
StackLister
DeleteStackBySpecSync(ctx context.Context, stack *cfntypes.Stack, errCh chan error) error
}

// APILister lists pod identity associations using the EKS API.
type APILister interface {
ListPodIdentityAssociations(ctx context.Context, params *eks.ListPodIdentityAssociationsInput, optFns ...func(*eks.Options)) (*eks.ListPodIdentityAssociationsOutput, error)
}

// APIDeleter lists and deletes pod identity associations using the EKS API.
type APIDeleter interface {
APILister
DeletePodIdentityAssociation(ctx context.Context, params *eks.DeletePodIdentityAssociationInput, optFns ...func(*eks.Options)) (*eks.DeletePodIdentityAssociationOutput, error)
}

// A Deleter deletes pod identity associations.
type Deleter struct {
// ClusterName is the cluster name.
ClusterName string
// StackDeleter is used to delete stacks.
StackDeleter StackDeleter
// APIDeleter deletes pod identity associations using the EKS API.
APIDeleter APIDeleter
}

// Identifier represents a pod identity association.
type Identifier struct {
// Namespace is the namespace the service account belongs to.
Namespace string
// ServiceAccountName is the name of the Kubernetes ServiceAccount.
ServiceAccountName string
}

func NewDeleter(clusterName string, stackDeleter StackDeleter, apiDeleter APIDeleter) *Deleter {
return &Deleter{
ClusterName: clusterName,
StackDeleter: stackDeleter,
APIDeleter: apiDeleter,
}
}

// Delete deletes the specified podIdentityAssociations.
func (d *Deleter) Delete(ctx context.Context, podIDs []Identifier) error {
tasks, err := d.DeleteTasks(ctx, podIDs)
if err != nil {
return err
}
return runAllTasks(tasks)
}

func (d *Deleter) DeleteTasks(ctx context.Context, podIDs []Identifier) (*tasks.TaskTree, error) {
roleStackNames, err := d.StackDeleter.ListStackNames(ctx, fmt.Sprintf("^%s*", makeStackNamePrefix(d.ClusterName)))
if err != nil {
return nil, fmt.Errorf("error listing stack names for pod identity associations: %w", err)
}
taskTree := &tasks.TaskTree{Parallel: true}

// this is true during cluster deletion, when no association identifier is given as user input,
// instead we will delete all pod-identity-role stacks for the cluster
if len(podIDs) == 0 {
for _, stackName := range roleStackNames {
name := strings.Clone(stackName)
taskTree.Append(&tasks.GenericTask{
Description: fmt.Sprintf("deleting IAM resources stack %q", stackName),
Doer: func() error {
return d.deleteRoleStack(ctx, name)
},
})
}
return taskTree, nil
}

for _, p := range podIDs {
taskTree.Append(d.makeDeleteTask(ctx, p, roleStackNames))
}
return taskTree, nil
}

func (d *Deleter) makeDeleteTask(ctx context.Context, p Identifier, roleStackNames []string) tasks.Task {
podIdentityAssociationID := makeID(p.Namespace, p.ServiceAccountName)
return &tasks.GenericTask{
Description: fmt.Sprintf("delete pod identity association %q", podIdentityAssociationID),
Doer: func() error {
if err := d.deletePodIdentityAssociation(ctx, p, roleStackNames, podIdentityAssociationID); err != nil {
return fmt.Errorf("error deleting pod identity association %q: %w", podIdentityAssociationID, err)
}
return nil
},
}
}

func (d *Deleter) deletePodIdentityAssociation(ctx context.Context, p Identifier, roleStackNames []string, podIdentityAssociationID string) error {
output, err := d.APIDeleter.ListPodIdentityAssociations(ctx, &eks.ListPodIdentityAssociationsInput{
ClusterName: aws.String(d.ClusterName),
Namespace: aws.String(p.Namespace),
ServiceAccount: aws.String(p.ServiceAccountName),
})
if err != nil {
return fmt.Errorf("listing pod identity associations: %w", err)
}
switch len(output.Associations) {
case 0:
logger.Warning("pod identity association %q not found", podIdentityAssociationID)
default:
return fmt.Errorf("expected to find only 1 pod identity association for %q; got %d", podIdentityAssociationID, len(output.Associations))
case 1:
if _, err := d.APIDeleter.DeletePodIdentityAssociation(ctx, &eks.DeletePodIdentityAssociationInput{
ClusterName: aws.String(d.ClusterName),
AssociationId: output.Associations[0].AssociationId,
}); err != nil {
return fmt.Errorf("deleting pod identity association: %w", err)
}
}

stackName := MakeStackName(d.ClusterName, p.Namespace, p.ServiceAccountName)
if !slices.Contains(roleStackNames, stackName) {
return nil
}
logger.Info("deleting IAM resources stack %q for pod identity association %q", stackName, podIdentityAssociationID)
return d.deleteRoleStack(ctx, stackName)
}

func (d *Deleter) deleteRoleStack(ctx context.Context, stackName string) error {
stack, err := d.StackDeleter.DescribeStack(ctx, &manager.Stack{
StackName: aws.String(stackName),
})
if err != nil {
return fmt.Errorf("describing stack %q: %w", stackName, err)
}

deleteStackCh := make(chan error)
if err := d.StackDeleter.DeleteStackBySpecSync(ctx, stack, deleteStackCh); err != nil {
return fmt.Errorf("deleting stack %q for IAM role: %w", stackName, err)
}
select {
case err := <-deleteStackCh:
return err
case <-ctx.Done():
return fmt.Errorf("timed out waiting for deletion of pod identity association: %w", ctx.Err())
}
}

// ToIdentifiers maps a list of PodIdentityAssociations to a list of Identifiers.
func ToIdentifiers(podIdentityAssociations []api.PodIdentityAssociation) []Identifier {
identifiers := make([]Identifier, len(podIdentityAssociations))
for i, p := range podIdentityAssociations {
identifiers[i] = Identifier{
Namespace: p.Namespace,
ServiceAccountName: p.ServiceAccountName,
}
}
return identifiers
}

func makeID(namespace, serviceAccountName string) string {
return fmt.Sprintf("%s/%s", namespace, serviceAccountName)
}
Loading

0 comments on commit 73aa3d1

Please sign in to comment.