diff --git a/examples/46-deletion-protection-example.yaml b/examples/46-deletion-protection-example.yaml new file mode 100644 index 0000000000..d7754f1cd9 --- /dev/null +++ b/examples/46-deletion-protection-example.yaml @@ -0,0 +1,19 @@ +# A sample ClusterConfig file that creates a cluster with deletion protection enabled. + +# DeletionProtection prevents accidental cluster deletion +# Valid values are true or false (default) +# - https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateCluster.html +# - https://docs.aws.amazon.com/eks/latest/APIReference/API_CreateCluster.html#AmazonEKS-CreateCluster-request-deletionProtection + +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig + +metadata: + name: deletion-protection-cluster + region: us-west-2 + +deletionProtection: true + +managedNodeGroups: + - name: mng-1 + desiredCapacity: 1 diff --git a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json index 1ff48713ed..0d655c3383 100755 --- a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json +++ b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json @@ -467,6 +467,11 @@ "description": "See [CloudWatch support](/usage/cloudwatch-cluster-logging/)", "x-intellij-html-description": "See CloudWatch support" }, + "deletionProtection": { + "type": "boolean", + "description": "specifies whether deletion protection is enabled for the cluster", + "x-intellij-html-description": "specifies whether deletion protection is enabled for the cluster" + }, "fargateProfiles": { "items": { "$ref": "#/definitions/FargateProfile" @@ -569,6 +574,7 @@ "apiVersion", "metadata", "upgradePolicy", + "deletionProtection", "kubernetesNetworkConfig", "autoModeConfig", "remoteNetworkConfig", diff --git a/pkg/apis/eksctl.io/v1alpha5/types.go b/pkg/apis/eksctl.io/v1alpha5/types.go index 3e92239cc7..4a3a0f64ef 100644 --- a/pkg/apis/eksctl.io/v1alpha5/types.go +++ b/pkg/apis/eksctl.io/v1alpha5/types.go @@ -966,6 +966,10 @@ type ClusterConfig struct { // +optional UpgradePolicy *UpgradePolicy `json:"upgradePolicy,omitempty"` + // DeletionProtection specifies whether deletion protection is enabled for the cluster + // +optional + DeletionProtection *bool `json:"deletionProtection,omitempty"` + // +optional KubernetesNetworkConfig *KubernetesNetworkConfig `json:"kubernetesNetworkConfig,omitempty"` diff --git a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go index 8fa51a7250..317a40018b 100644 --- a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go +++ b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go @@ -559,6 +559,11 @@ func (in *ClusterConfig) DeepCopyInto(out *ClusterConfig) { *out = new(UpgradePolicy) (*in).DeepCopyInto(*out) } + if in.DeletionProtection != nil { + in, out := &in.DeletionProtection, &out.DeletionProtection + *out = new(bool) + **out = **in + } return } diff --git a/pkg/cfn/builder/cluster.go b/pkg/cfn/builder/cluster.go index 71324fac8d..946d41516c 100644 --- a/pkg/cfn/builder/cluster.go +++ b/pkg/cfn/builder/cluster.go @@ -364,6 +364,11 @@ func (c *ClusterResourceSet) addResourcesForControlPlane(subnetDetails *SubnetDe } } + var deletionProtection *gfnt.Value + if c.spec.DeletionProtection != nil { + deletionProtection = gfnt.NewBoolean(*c.spec.DeletionProtection) + } + cluster := gfneks.Cluster{ EncryptionConfig: encryptionConfigs, Logging: makeClusterLogging(c.spec), @@ -372,6 +377,7 @@ func (c *ClusterResourceSet) addResourcesForControlPlane(subnetDetails *SubnetDe RoleArn: serviceRoleARN, BootstrapSelfManagedAddons: gfnt.NewBoolean(false), UpgradePolicy: upgradePolicy, + DeletionProtection: deletionProtection, AccessConfig: &gfneks.Cluster_AccessConfig{ AuthenticationMode: gfnt.NewString(string(c.spec.AccessConfig.AuthenticationMode)), BootstrapClusterCreatorAdminPermissions: gfnt.NewBoolean(!api.IsDisabled(c.spec.AccessConfig.BootstrapClusterCreatorAdminPermissions)), diff --git a/pkg/ctl/utils/update_deletion_protection.go b/pkg/ctl/utils/update_deletion_protection.go new file mode 100644 index 0000000000..9cbbd5ad04 --- /dev/null +++ b/pkg/ctl/utils/update_deletion_protection.go @@ -0,0 +1,80 @@ +package utils + +import ( + "context" + "fmt" + "strconv" + + "github.com/kris-nova/logger" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/ctl/cmdutils" +) + +func updateClusterDeletionProtectionCmd(cmd *cmdutils.Cmd) { + cfg := api.NewClusterConfig() + cmd.ClusterConfig = cfg + + var enabled string + + cmd.SetDescription("deletion-protection", "Update cluster deletion protection", "") + + cmdutils.AddCommonFlagsForAWS(cmd, &cmd.ProviderConfig, false) + + cmd.FlagSetGroup.InFlagSet("General", func(fs *pflag.FlagSet) { + fs.StringVarP(&cfg.Metadata.Name, "name", "n", "", "EKS cluster name") + cmdutils.AddRegionFlag(fs, &cmd.ProviderConfig) + cmdutils.AddConfigFileFlag(fs, &cmd.ClusterConfigFile) + cmdutils.AddApproveFlag(fs, cmd) + fs.StringVar(&enabled, "enabled", "", "Enable or disable deletion protection (true|false)") + }) + + cmd.CobraCommand.RunE = func(_ *cobra.Command, args []string) error { + cmd.NameArg = cmdutils.GetNameArg(args) + + if enabled == "" { + return fmt.Errorf("--enabled flag is required (true|false)") + } + + val, err := strconv.ParseBool(enabled) + if err != nil { + return fmt.Errorf("--enabled must be 'true' or 'false', got: %s", enabled) + } + + cfg.DeletionProtection = &val + + return doUpdateClusterDeletionProtection(cmd) + } +} + +func doUpdateClusterDeletionProtection(cmd *cmdutils.Cmd) error { + ctx := context.Background() + if err := cmdutils.NewMetadataLoader(cmd).Load(); err != nil { + return err + } + + cfg := cmd.ClusterConfig + if cfg.Metadata.Name == "" { + return fmt.Errorf("cluster name is required") + } + + ctl, err := cmd.NewProviderForExistingCluster(ctx) + if err != nil { + return err + } + + if cmd.Plan { + logger.Critical("--dry-run is not supported for this command") + return nil + } + + action := "disabling" + if cfg.DeletionProtection != nil && *cfg.DeletionProtection { + action = "enabling" + } + + logger.Info("%s deletion protection for cluster %q", action, cfg.Metadata.Name) + return ctl.UpdateClusterConfigForDeletionProtection(ctx, cfg) +} diff --git a/pkg/ctl/utils/utils.go b/pkg/ctl/utils/utils.go index 9f7c08fcb9..b2ff57d7c0 100644 --- a/pkg/ctl/utils/utils.go +++ b/pkg/ctl/utils/utils.go @@ -22,6 +22,7 @@ func Command(flagGrouping *cmdutils.FlagGrouping) *cobra.Command { cmdutils.AddResourceCmd(flagGrouping, verbCmd, updateClusterEndpointsCmd) cmdutils.AddResourceCmd(flagGrouping, verbCmd, publicAccessCIDRsCmd) cmdutils.AddResourceCmd(flagGrouping, verbCmd, updateClusterVPCConfigCmd) + cmdutils.AddResourceCmd(flagGrouping, verbCmd, updateClusterDeletionProtectionCmd) cmdutils.AddResourceCmd(flagGrouping, verbCmd, enableSecretsEncryptionCmd) cmdutils.AddResourceCmd(flagGrouping, verbCmd, schemaCmd) cmdutils.AddResourceCmd(flagGrouping, verbCmd, nodeGroupHealthCmd) diff --git a/pkg/eks/update.go b/pkg/eks/update.go index 9d9edec1c9..8b4fbc70a7 100644 --- a/pkg/eks/update.go +++ b/pkg/eks/update.go @@ -156,6 +156,15 @@ func (c *ClusterProvider) UpdatePublicAccessCIDRs(ctx context.Context, clusterCo return c.UpdateClusterConfig(ctx, input) } +// UpdateClusterConfigForDeletionProtection calls eks.UpdateClusterConfig and updates deletion protection +func (c *ClusterProvider) UpdateClusterConfigForDeletionProtection(ctx context.Context, cfg *api.ClusterConfig) error { + input := &eks.UpdateClusterConfigInput{ + Name: &cfg.Metadata.Name, + DeletionProtection: cfg.DeletionProtection, + } + return c.UpdateClusterConfig(ctx, input) +} + // UpdateClusterConfig calls EKS.UpdateClusterConfig and waits for the update to complete. func (c *ClusterProvider) UpdateClusterConfig(ctx context.Context, input *eks.UpdateClusterConfigInput) error { output, err := c.AWSProvider.EKS().UpdateClusterConfig(ctx, input) diff --git a/pkg/goformation/cloudformation/eks/aws-eks-cluster.go b/pkg/goformation/cloudformation/eks/aws-eks-cluster.go index 17c0c9ec63..dcc0785ba6 100644 --- a/pkg/goformation/cloudformation/eks/aws-eks-cluster.go +++ b/pkg/goformation/cloudformation/eks/aws-eks-cluster.go @@ -30,6 +30,11 @@ type Cluster struct { // See: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-computeconfig ComputeConfig *Cluster_ComputeConfig `json:"ComputeConfig,omitempty"` + // DeletionProtection AWS CloudFormation Property + // Required: false + // See: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-deletionprotection + DeletionProtection *types.Value `json:"DeletionProtection,omitempty"` + // EncryptionConfig AWS CloudFormation Property // Required: false // See: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-encryptionconfig diff --git a/userdocs/mkdocs.yml b/userdocs/mkdocs.yml index c2f1823fe7..baa5c21351 100644 --- a/userdocs/mkdocs.yml +++ b/userdocs/mkdocs.yml @@ -158,6 +158,7 @@ nav: - usage/cluster-upgrade.md - usage/addon-upgrade.md - usage/upgrade-policy.md + - usage/deletion-protection.md - usage/zonal-shift.md - Nodegroups: - usage/nodegroups.md diff --git a/userdocs/src/usage/deletion-protection.md b/userdocs/src/usage/deletion-protection.md new file mode 100644 index 0000000000..0e7c93a31a --- /dev/null +++ b/userdocs/src/usage/deletion-protection.md @@ -0,0 +1,46 @@ +# Cluster Deletion Protection + +This document describes how to configure deletion protection for your EKS cluster using eksctl. + +## Overview + +The `deletionProtection` field allows you to enable deletion protection for your EKS cluster. This prevents accidental cluster deletion. + +## Configuration + +You can specify deletion protection in your cluster configuration file: + +```yaml +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig + +metadata: + name: my-cluster + region: us-west-2 + +deletionProtection: true +``` + +## Command Line Usage + +When creating a cluster with deletion protection: + +```bash +eksctl create cluster --config-file=cluster-config.yaml +``` + +To update deletion protection on an existing cluster: + +```bash +# Enable deletion protection +eksctl utils deletion-protection --name=my-cluster --enabled=true --approve + +# Disable deletion protection +eksctl utils deletion-protection --name=my-cluster --enabled=false --approve +``` + +## Notes + +- If no `deletionProtection` is specified, AWS will use its default behavior (false) +- Deletion protection can be set during cluster creation and updated later +- When enabled, you must disable deletion protection before you can delete the cluster