diff --git a/docs/stackit_ske_credentials_complete-rotation.md b/docs/stackit_ske_credentials_complete-rotation.md index 6afa588ad..404d7509f 100644 --- a/docs/stackit_ske_credentials_complete-rotation.md +++ b/docs/stackit_ske_credentials_complete-rotation.md @@ -5,15 +5,16 @@ Completes the rotation of the credentials associated to a SKE cluster ### Synopsis Completes the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster. -To ensure continued access to the Kubernetes cluster, please update your kubeconfig service account to the newly created account. + This is step 2 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include: - The old certification authority will be dropped from the package. - The old signing key for the service account will be dropped from the bundle. +To ensure continued access to the Kubernetes cluster, please update your kubeconfig with the new credentials: + $ stackit ske kubeconfig create my-cluster If you haven't, please start the process by running: $ stackit ske credentials start-rotation my-cluster -After completing the rotation of credentials, you can generate a new kubeconfig file by running: - $ stackit ske kubeconfig create my-cluster +For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html ``` stackit ske credentials complete-rotation CLUSTER_NAME [flags] @@ -27,8 +28,8 @@ stackit ske credentials complete-rotation CLUSTER_NAME [flags] Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file $ stackit ske credentials start-rotation my-cluster - $ stackit ske credentials complete-rotation my-cluster $ stackit ske kubeconfig create my-cluster + $ stackit ske credentials complete-rotation my-cluster ``` ### Options diff --git a/docs/stackit_ske_credentials_start-rotation.md b/docs/stackit_ske_credentials_start-rotation.md index 825f19306..b1c2cac66 100644 --- a/docs/stackit_ske_credentials_start-rotation.md +++ b/docs/stackit_ske_credentials_start-rotation.md @@ -5,6 +5,7 @@ Starts the rotation of the credentials associated to a SKE cluster ### Synopsis Starts the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster. + This is step 1 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include: - Rolling recreation of all worker nodes - A new Certificate Authority (CA) will be established and incorporated into the existing CA bundle. @@ -13,8 +14,11 @@ This is step 1 of a 2-step process to rotate all SKE cluster credentials. Tasks - The kube-apiserver will rewrite all secrets in the cluster, encrypting them with the new encryption key. The old CA, encryption key and signing key will be retained until the rotation is completed. +After completing the rotation of credentials, you can generate a new kubeconfig file by running: + $ stackit ske kubeconfig create my-cluster Complete the rotation by running: $ stackit ske credentials complete-rotation my-cluster +For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html ``` stackit ske credentials start-rotation CLUSTER_NAME [flags] @@ -28,8 +32,8 @@ stackit ske credentials start-rotation CLUSTER_NAME [flags] Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file $ stackit ske credentials start-rotation my-cluster - $ stackit ske credentials complete-rotation my-cluster $ stackit ske kubeconfig create my-cluster + $ stackit ske credentials complete-rotation my-cluster ``` ### Options diff --git a/docs/stackit_ske_kubeconfig_create.md b/docs/stackit_ske_kubeconfig_create.md index df8765849..fd73c5b76 100644 --- a/docs/stackit_ske_kubeconfig_create.md +++ b/docs/stackit_ske_kubeconfig_create.md @@ -5,7 +5,11 @@ Creates a kubeconfig for an SKE cluster ### Synopsis Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster. + By default the kubeconfig is created in the .kube folder, in the user's home directory. The kubeconfig file will be overwritten if it already exists. +You can override this behavior by specifying a custom filepath with the --filepath flag. +An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h. +Note that the format is , e.g. 30d for 30 days and you can't combine units. ``` stackit ske kubeconfig create CLUSTER_NAME [flags] @@ -23,16 +27,16 @@ stackit ske kubeconfig create CLUSTER_NAME [flags] Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months $ stackit ske kubeconfig create my-cluster --expiration 2M - Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom location - $ stackit ske kubeconfig create my-cluster --location /path/to/config + Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath + $ stackit ske kubeconfig create my-cluster --filepath /path/to/config ``` ### Options ``` -e, --expiration string Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h + --filepath string Path to create the kubeconfig file. By default, the kubeconfig is created in the .kube folder, in the user's home directory. -h, --help Help for "stackit ske kubeconfig create" - --location string Folder location to store the kubeconfig file. By default, the kubeconfig is created in the .kube folder, in the user's home directory. ``` ### Options inherited from parent commands diff --git a/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go b/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go index c322a9b3f..d7953efe2 100644 --- a/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go +++ b/internal/cmd/ske/credentials/complete-rotation/complete_rotation.go @@ -30,26 +30,28 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("complete-rotation %s", clusterNameArg), Short: "Completes the rotation of the credentials associated to a SKE cluster", - Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n\n%s\n%s\n%s\n%s", + Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s\n%s\n\n%s\n%s\n%s", "Completes the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster.", - "To ensure continued access to the Kubernetes cluster, please update your kubeconfig service account to the newly created account.", "This is step 2 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include:", " - The old certification authority will be dropped from the package.", " - The old signing key for the service account will be dropped from the bundle.", + "To ensure continued access to the Kubernetes cluster, please update your kubeconfig with the new credentials:", + " $ stackit ske kubeconfig create my-cluster", "If you haven't, please start the process by running:", " $ stackit ske credentials start-rotation my-cluster", - "After completing the rotation of credentials, you can generate a new kubeconfig file by running:", - " $ stackit ske kubeconfig create my-cluster"), + "For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html", + ), Args: args.SingleArg(clusterNameArg, nil), Example: examples.Build( examples.NewExample( `Complete the rotation of the credentials associated to the SKE cluster with name "my-cluster"`, - "$ stackit ske credentials complete-rotation my-cluster"), + "$ stackit ske credentials complete-rotation my-cluster", + ), examples.NewExample( `Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file`, "$ stackit ske credentials start-rotation my-cluster", - "$ stackit ske credentials complete-rotation my-cluster", "$ stackit ske kubeconfig create my-cluster", + "$ stackit ske credentials complete-rotation my-cluster", ), ), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cmd/ske/credentials/start-rotation/start_rotation.go b/internal/cmd/ske/credentials/start-rotation/start_rotation.go index d56ab6d4f..da9b0cb91 100644 --- a/internal/cmd/ske/credentials/start-rotation/start_rotation.go +++ b/internal/cmd/ske/credentials/start-rotation/start_rotation.go @@ -30,7 +30,7 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("start-rotation %s", clusterNameArg), Short: "Starts the rotation of the credentials associated to a SKE cluster", - Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s\n%s", + Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s\n%s\n%s\n%s\n%s", "Starts the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster.", "This is step 1 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include:", " - Rolling recreation of all worker nodes", @@ -39,8 +39,12 @@ func NewCmd() *cobra.Command { " - A new signing key will be generated for the service account and added to the Certificate Authority (CA) bundle.", " - The kube-apiserver will rewrite all secrets in the cluster, encrypting them with the new encryption key.", "The old CA, encryption key and signing key will be retained until the rotation is completed.", + "After completing the rotation of credentials, you can generate a new kubeconfig file by running:", + " $ stackit ske kubeconfig create my-cluster", "Complete the rotation by running:", - " $ stackit ske credentials complete-rotation my-cluster"), + " $ stackit ske credentials complete-rotation my-cluster", + "For more information, visit: https://docs.stackit.cloud/stackit/en/how-to-rotate-ske-credentials-200016334.html", + ), Args: args.SingleArg(clusterNameArg, nil), Example: examples.Build( examples.NewExample( @@ -49,8 +53,8 @@ func NewCmd() *cobra.Command { examples.NewExample( `Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file`, "$ stackit ske credentials start-rotation my-cluster", - "$ stackit ske credentials complete-rotation my-cluster", "$ stackit ske kubeconfig create my-cluster", + "$ stackit ske credentials complete-rotation my-cluster", ), ), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/internal/cmd/ske/kubeconfig/create/create.go b/internal/cmd/ske/kubeconfig/create/create.go index e3aacd633..6308a729a 100644 --- a/internal/cmd/ske/kubeconfig/create/create.go +++ b/internal/cmd/ske/kubeconfig/create/create.go @@ -21,13 +21,13 @@ const ( clusterNameArg = "CLUSTER_NAME" expirationFlag = "expiration" - locationFlag = "location" + filepathFlag = "filepath" ) type inputModel struct { *globalflags.GlobalFlagModel ClusterName string - Location *string + Filepath *string ExpirationTime *string } @@ -35,9 +35,12 @@ func NewCmd() *cobra.Command { cmd := &cobra.Command{ Use: fmt.Sprintf("create %s", clusterNameArg), Short: "Creates a kubeconfig for an SKE cluster", - Long: fmt.Sprintf("%s\n%s", + Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s", "Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster.", - "By default the kubeconfig is created in the .kube folder, in the user's home directory. The kubeconfig file will be overwritten if it already exists."), + "By default the kubeconfig is created in the .kube folder, in the user's home directory. The kubeconfig file will be overwritten if it already exists.", + "You can override this behavior by specifying a custom filepath with the --filepath flag.", + "An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h.", + "Note that the format is , e.g. 30d for 30 days and you can't combine units."), Args: args.SingleArg(clusterNameArg, nil), Example: examples.Build( examples.NewExample( @@ -50,8 +53,8 @@ func NewCmd() *cobra.Command { `Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months`, "$ stackit ske kubeconfig create my-cluster --expiration 2M"), examples.NewExample( - `Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom location`, - "$ stackit ske kubeconfig create my-cluster --location /path/to/config"), + `Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath`, + "$ stackit ske kubeconfig create my-cluster --filepath /path/to/config"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() @@ -67,7 +70,7 @@ func NewCmd() *cobra.Command { } if !model.AssumeYes { - prompt := fmt.Sprintf("Are you sure you want to create a kubeconfig for SKE cluster %q? This will OVERWRITE your current configuration, if it exists.", model.ClusterName) + prompt := fmt.Sprintf("Are you sure you want to create a kubeconfig for SKE cluster %q? This will OVERWRITE your current kubeconfig file, if it exists.", model.ClusterName) err = confirm.PromptForConfirmation(cmd, prompt) if err != nil { return err @@ -90,13 +93,13 @@ func NewCmd() *cobra.Command { } var kubeconfigPath string - if model.Location == nil { - kubeconfigPath, err = skeUtils.GetDefaultKubeconfigLocation() + if model.Filepath == nil { + kubeconfigPath, err = skeUtils.GetDefaultKubeconfigPath() if err != nil { - return fmt.Errorf("get default kubeconfig location: %w", err) + return fmt.Errorf("get default kubeconfig path: %w", err) } } else { - kubeconfigPath = *model.Location + kubeconfigPath = *model.Filepath } err = skeUtils.WriteConfigFile(kubeconfigPath, *resp.Kubeconfig) @@ -115,7 +118,7 @@ func NewCmd() *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(expirationFlag, "e", "", "Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h") - cmd.Flags().String(locationFlag, "", "Folder location to store the kubeconfig file. By default, the kubeconfig is created in the .kube folder, in the user's home directory.") + cmd.Flags().String(filepathFlag, "", "Path to create the kubeconfig file. By default, the kubeconfig is created as 'config' in the .kube folder, in the user's home directory.") } func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -126,11 +129,24 @@ func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) { return nil, &errors.ProjectIdError{} } + expTime := flags.FlagToStringPointer(cmd, expirationFlag) + + if expTime != nil { + var err error + expTime, err = skeUtils.ConvertToSeconds(*expTime) + if err != nil { + return nil, &errors.FlagValidationError{ + Flag: expirationFlag, + Details: err.Error(), + } + } + } + return &inputModel{ GlobalFlagModel: globalFlags, ClusterName: clusterName, - Location: flags.FlagToStringPointer(cmd, locationFlag), - ExpirationTime: flags.FlagToStringPointer(cmd, expirationFlag), + Filepath: flags.FlagToStringPointer(cmd, filepathFlag), + ExpirationTime: expTime, }, nil } @@ -140,12 +156,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClie payload := ske.CreateKubeconfigPayload{} if model.ExpirationTime != nil { - expirationTime, err := skeUtils.ConvertToSeconds(*model.ExpirationTime) - if err != nil { - return req, fmt.Errorf("parse expiration time: %w", err) - } - - payload.ExpirationSeconds = expirationTime + payload.ExpirationSeconds = model.ExpirationTime } return req.CreateKubeconfigPayload(payload), nil diff --git a/internal/cmd/ske/kubeconfig/create/create_test.go b/internal/cmd/ske/kubeconfig/create/create_test.go index fb507ebd6..ec907dbd3 100644 --- a/internal/cmd/ske/kubeconfig/create/create_test.go +++ b/internal/cmd/ske/kubeconfig/create/create_test.go @@ -87,19 +87,19 @@ func TestParseInput(t *testing.T) { }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.ExpirationTime = utils.Ptr("30d") + model.ExpirationTime = utils.Ptr("2592000") }), }, { - description: "custom location", + description: "custom filepath", argValues: fixtureArgValues(), flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues["location"] = "/path/to/config" + flagValues["filepath"] = "/path/to/config" }), isValid: true, expectedModel: fixtureInputModel(func(model *inputModel) { - model.Location = utils.Ptr("/path/to/config") + model.Filepath = utils.Ptr("/path/to/config") }), }, { @@ -213,7 +213,7 @@ func TestBuildRequest(t *testing.T) { { description: "expiration time", model: fixtureInputModel(func(model *inputModel) { - model.ExpirationTime = utils.Ptr("30d") + model.ExpirationTime = utils.Ptr("2592000") }), expectedRequest: fixtureRequest().CreateKubeconfigPayload(ske.CreateKubeconfigPayload{ ExpirationSeconds: utils.Ptr("2592000")}), diff --git a/internal/pkg/services/ske/utils/utils.go b/internal/pkg/services/ske/utils/utils.go index abc9e9089..901e961c3 100644 --- a/internal/pkg/services/ske/utils/utils.go +++ b/internal/pkg/services/ske/utils/utils.go @@ -199,7 +199,7 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error) // The time string must be in the format of , where unit is one of s, m, h, d, M. func ConvertToSeconds(timeStr string) (*string, error) { if len(timeStr) < 2 { - return nil, fmt.Errorf("invalid time format: %s", timeStr) + return nil, fmt.Errorf("invalid time: %s", timeStr) } unit := timeStr[len(timeStr)-1:] @@ -207,7 +207,7 @@ func ConvertToSeconds(timeStr string) (*string, error) { valueStr := timeStr[:len(timeStr)-1] value, err := strconv.ParseUint(valueStr, 10, 64) if err != nil { - return nil, fmt.Errorf("unable to parse uint: %s", timeStr) + return nil, fmt.Errorf("invalid time value: %s", valueStr) } var multiplier uint64 @@ -228,7 +228,7 @@ func ConvertToSeconds(timeStr string) (*string, error) { case "M": multiplier = 60 * 60 * 24 * 30 default: - return nil, fmt.Errorf("invalid time format: %s", timeStr) + return nil, fmt.Errorf("invalid time unit: %s", unit) } result := uint64(value) * multiplier @@ -256,8 +256,8 @@ func WriteConfigFile(configPath, data string) error { return nil } -// GetDefaultKubeconfigLocation returns the default location for the kubeconfig file. -func GetDefaultKubeconfigLocation() (string, error) { +// GetDefaultKubeconfigPath returns the default location for the kubeconfig file. +func GetDefaultKubeconfigPath() (string, error) { userHome, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("get user home directory: %w", err)