Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/stackit_ske_credentials_complete-rotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion docs/stackit_ske_credentials_start-rotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]
Expand All @@ -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
Expand Down
10 changes: 7 additions & 3 deletions docs/stackit_ske_kubeconfig_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <value><unit>, e.g. 30d for 30 days and you can't combine units.

```
stackit ske kubeconfig create CLUSTER_NAME [flags]
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 7 additions & 3 deletions internal/cmd/ske/credentials/start-rotation/start_rotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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(
Expand All @@ -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 {
Expand Down
51 changes: 31 additions & 20 deletions internal/cmd/ske/kubeconfig/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,26 @@ const (
clusterNameArg = "CLUSTER_NAME"

expirationFlag = "expiration"
locationFlag = "location"
filepathFlag = "filepath"
)

type inputModel struct {
*globalflags.GlobalFlagModel
ClusterName string
Location *string
Filepath *string
ExpirationTime *string
}

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 <value><unit>, e.g. 30d for 30 days and you can't combine units."),
Args: args.SingleArg(clusterNameArg, nil),
Example: examples.Build(
examples.NewExample(
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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) {
Expand All @@ -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
}

Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions internal/cmd/ske/kubeconfig/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}),
},
{
Expand Down Expand Up @@ -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")}),
Expand Down
10 changes: 5 additions & 5 deletions internal/pkg/services/ske/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,15 @@ func getDefaultPayloadNodepool(resp *ske.ProviderOptions) (*ske.Nodepool, error)
// The time string must be in the format of <value><unit>, 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:]

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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down