Skip to content

Commit 1aa9ee0

Browse files
authored
SKE Credentials Rotation: kubeconfig create command (#184)
* initial kubeconfig create command * finish create command implementation, add testing * fix linting, generate docs * address PR comments * extract funcs to utils, add testing * improve function documentation * fix linting * address PR comments, minor improvements * make utils testing work on all OSes
1 parent d155760 commit 1aa9ee0

12 files changed

+810
-2
lines changed

docs/stackit_ske.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ stackit ske [flags]
3333
* [stackit ske describe](./stackit_ske_describe.md) - Shows overall details regarding SKE
3434
* [stackit ske disable](./stackit_ske_disable.md) - Disables SKE for a project
3535
* [stackit ske enable](./stackit_ske_enable.md) - Enables SKE for a project
36+
* [stackit ske kubeconfig](./stackit_ske_kubeconfig.md) - Provides functionality for SKE kubeconfig
3637
* [stackit ske options](./stackit_ske_options.md) - Lists SKE provider options
3738

docs/stackit_ske_credentials.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ stackit ske credentials [flags]
2828
### SEE ALSO
2929

3030
* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
31+
* [stackit ske credentials complete-rotation](./stackit_ske_credentials_complete-rotation.md) - Completes the rotation of the credentials associated to a SKE cluster
3132
* [stackit ske credentials describe](./stackit_ske_credentials_describe.md) - Shows details of the credentials associated to a SKE cluster
3233
* [stackit ske credentials rotate](./stackit_ske_credentials_rotate.md) - Rotates credentials associated to a SKE cluster
3334
* [stackit ske credentials start-rotation](./stackit_ske_credentials_start-rotation.md) - Starts the rotation of the credentials associated to a SKE cluster
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## stackit ske credentials complete-rotation
2+
3+
Completes the rotation of the credentials associated to a SKE cluster
4+
5+
### Synopsis
6+
7+
Completes the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster.
8+
To ensure continued access to the Kubernetes cluster, please update your kubeconfig service account to the newly created account.
9+
This is step 2 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include:
10+
- The old certification authority will be dropped from the package.
11+
- The old signing key for the service account will be dropped from the bundle.
12+
13+
If you haven't, please start the process by running:
14+
$ stackit ske credentials start-rotation my-cluster
15+
After completing the rotation of credentials, you can generate a new kubeconfig file by running:
16+
$ stackit ske kubeconfig create my-cluster
17+
18+
```
19+
stackit ske credentials complete-rotation CLUSTER_NAME [flags]
20+
```
21+
22+
### Examples
23+
24+
```
25+
Complete the rotation of the credentials associated to the SKE cluster with name "my-cluster"
26+
$ stackit ske credentials complete-rotation my-cluster
27+
28+
Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file
29+
$ stackit ske credentials start-rotation my-cluster
30+
$ stackit ske credentials complete-rotation my-cluster
31+
$ stackit ske kubeconfig create my-cluster
32+
```
33+
34+
### Options
35+
36+
```
37+
-h, --help Help for "stackit ske credentials complete-rotation"
38+
```
39+
40+
### Options inherited from parent commands
41+
42+
```
43+
-y, --assume-yes If set, skips all confirmation prompts
44+
--async If set, runs the command asynchronously
45+
-o, --output-format string Output format, one of ["json" "pretty"]
46+
-p, --project-id string Project ID
47+
```
48+
49+
### SEE ALSO
50+
51+
* [stackit ske credentials](./stackit_ske_credentials.md) - Provides functionality for SKE credentials
52+

docs/stackit_ske_credentials_start-rotation.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ Starts the rotation of the credentials associated to a SKE cluster
44

55
### Synopsis
66

7-
Starts the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster. This is step 1 of a two-step process.
8-
Complete the rotation using the 'stackit ske credentials complete-rotation' command.
7+
Starts the rotation of the credentials associated to a STACKIT Kubernetes Engine (SKE) cluster.
8+
This is step 1 of a 2-step process to rotate all SKE cluster credentials. Tasks accomplished in this phase include:
9+
- Rolling recreation of all worker nodes
10+
- A new Certificate Authority (CA) will be established and incorporated into the existing CA bundle.
11+
- A new etcd encryption key is generated and added to the Certificate Authority (CA) bundle.
12+
- A new signing key will be generated for the service account and added to the Certificate Authority (CA) bundle.
13+
- The kube-apiserver will rewrite all secrets in the cluster, encrypting them with the new encryption key.
14+
The old CA, encryption key and signing key will be retained until the rotation is completed.
15+
16+
Complete the rotation by running:
17+
$ stackit ske credentials complete-rotation my-cluster
918

1019
```
1120
stackit ske credentials start-rotation CLUSTER_NAME [flags]
@@ -16,6 +25,11 @@ stackit ske credentials start-rotation CLUSTER_NAME [flags]
1625
```
1726
Start the rotation of the credentials associated to the SKE cluster with name "my-cluster"
1827
$ stackit ske credentials start-rotation my-cluster
28+
29+
Flow of the 2-step process to rotate all SKE cluster credentials, including generating a new kubeconfig file
30+
$ stackit ske credentials start-rotation my-cluster
31+
$ stackit ske credentials complete-rotation my-cluster
32+
$ stackit ske kubeconfig create my-cluster
1933
```
2034

2135
### Options

docs/stackit_ske_kubeconfig.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## stackit ske kubeconfig
2+
3+
Provides functionality for SKE kubeconfig
4+
5+
### Synopsis
6+
7+
Provides functionality for STACKIT Kubernetes Engine (SKE) kubeconfig.
8+
9+
```
10+
stackit ske kubeconfig [flags]
11+
```
12+
13+
### Options
14+
15+
```
16+
-h, --help Help for "stackit ske kubeconfig"
17+
```
18+
19+
### Options inherited from parent commands
20+
21+
```
22+
-y, --assume-yes If set, skips all confirmation prompts
23+
--async If set, runs the command asynchronously
24+
-o, --output-format string Output format, one of ["json" "pretty"]
25+
-p, --project-id string Project ID
26+
```
27+
28+
### SEE ALSO
29+
30+
* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
31+
* [stackit ske kubeconfig create](./stackit_ske_kubeconfig_create.md) - Creates a kubeconfig for an SKE cluster
32+

docs/stackit_ske_kubeconfig_create.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
## stackit ske kubeconfig create
2+
3+
Creates a kubeconfig for an SKE cluster
4+
5+
### Synopsis
6+
7+
Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster.
8+
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.
9+
10+
```
11+
stackit ske kubeconfig create CLUSTER_NAME [flags]
12+
```
13+
14+
### Examples
15+
16+
```
17+
Create a kubeconfig for the SKE cluster with name "my-cluster"
18+
$ stackit ske kubeconfig create my-cluster
19+
20+
Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days
21+
$ stackit ske kubeconfig create my-cluster --expiration 30d
22+
23+
Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months
24+
$ stackit ske kubeconfig create my-cluster --expiration 2M
25+
26+
Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom location
27+
$ stackit ske kubeconfig create my-cluster --location /path/to/config
28+
```
29+
30+
### Options
31+
32+
```
33+
-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
34+
-h, --help Help for "stackit ske kubeconfig create"
35+
--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.
36+
```
37+
38+
### Options inherited from parent commands
39+
40+
```
41+
-y, --assume-yes If set, skips all confirmation prompts
42+
--async If set, runs the command asynchronously
43+
-o, --output-format string Output format, one of ["json" "pretty"]
44+
-p, --project-id string Project ID
45+
```
46+
47+
### SEE ALSO
48+
49+
* [stackit ske kubeconfig](./stackit_ske_kubeconfig.md) - Provides functionality for SKE kubeconfig
50+
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/confirm"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
14+
skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils"
15+
16+
"github.com/spf13/cobra"
17+
"github.com/stackitcloud/stackit-sdk-go/services/ske"
18+
)
19+
20+
const (
21+
clusterNameArg = "CLUSTER_NAME"
22+
23+
expirationFlag = "expiration"
24+
locationFlag = "location"
25+
)
26+
27+
type inputModel struct {
28+
*globalflags.GlobalFlagModel
29+
ClusterName string
30+
Location *string
31+
ExpirationTime *string
32+
}
33+
34+
func NewCmd() *cobra.Command {
35+
cmd := &cobra.Command{
36+
Use: fmt.Sprintf("create %s", clusterNameArg),
37+
Short: "Creates a kubeconfig for an SKE cluster",
38+
Long: fmt.Sprintf("%s\n%s",
39+
"Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster.",
40+
"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."),
41+
Args: args.SingleArg(clusterNameArg, nil),
42+
Example: examples.Build(
43+
examples.NewExample(
44+
`Create a kubeconfig for the SKE cluster with name "my-cluster"`,
45+
"$ stackit ske kubeconfig create my-cluster"),
46+
examples.NewExample(
47+
`Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days`,
48+
"$ stackit ske kubeconfig create my-cluster --expiration 30d"),
49+
examples.NewExample(
50+
`Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months`,
51+
"$ stackit ske kubeconfig create my-cluster --expiration 2M"),
52+
examples.NewExample(
53+
`Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom location`,
54+
"$ stackit ske kubeconfig create my-cluster --location /path/to/config"),
55+
),
56+
RunE: func(cmd *cobra.Command, args []string) error {
57+
ctx := context.Background()
58+
model, err := parseInput(cmd, args)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Configure API client
64+
apiClient, err := client.ConfigureClient(cmd)
65+
if err != nil {
66+
return err
67+
}
68+
69+
if !model.AssumeYes {
70+
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)
71+
err = confirm.PromptForConfirmation(cmd, prompt)
72+
if err != nil {
73+
return err
74+
}
75+
}
76+
77+
// Call API
78+
req, err := buildRequest(ctx, model, apiClient)
79+
if err != nil {
80+
return fmt.Errorf("build kubeconfig create request: %w", err)
81+
}
82+
resp, err := req.Execute()
83+
if err != nil {
84+
return fmt.Errorf("create kubeconfig for SKE cluster: %w", err)
85+
}
86+
87+
// Create the config file
88+
if resp.Kubeconfig == nil {
89+
return fmt.Errorf("no kubeconfig returned from the API")
90+
}
91+
92+
var kubeconfigPath string
93+
if model.Location == nil {
94+
kubeconfigPath, err = skeUtils.GetDefaultKubeconfigLocation()
95+
if err != nil {
96+
return fmt.Errorf("get default kubeconfig location: %w", err)
97+
}
98+
} else {
99+
kubeconfigPath = *model.Location
100+
}
101+
102+
err = skeUtils.WriteConfigFile(kubeconfigPath, *resp.Kubeconfig)
103+
if err != nil {
104+
return fmt.Errorf("write kubeconfig file: %w", err)
105+
}
106+
107+
fmt.Printf("Created kubeconfig file for cluster %s in %q, with expiration date %v (UTC)\n", model.ClusterName, kubeconfigPath, *resp.ExpirationTimestamp)
108+
109+
return nil
110+
},
111+
}
112+
configureFlags(cmd)
113+
return cmd
114+
}
115+
116+
func configureFlags(cmd *cobra.Command) {
117+
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")
118+
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.")
119+
}
120+
121+
func parseInput(cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
122+
clusterName := inputArgs[0]
123+
124+
globalFlags := globalflags.Parse(cmd)
125+
if globalFlags.ProjectId == "" {
126+
return nil, &errors.ProjectIdError{}
127+
}
128+
129+
return &inputModel{
130+
GlobalFlagModel: globalFlags,
131+
ClusterName: clusterName,
132+
Location: flags.FlagToStringPointer(cmd, locationFlag),
133+
ExpirationTime: flags.FlagToStringPointer(cmd, expirationFlag),
134+
}, nil
135+
}
136+
137+
func buildRequest(ctx context.Context, model *inputModel, apiClient *ske.APIClient) (ske.ApiCreateKubeconfigRequest, error) {
138+
req := apiClient.CreateKubeconfig(ctx, model.ProjectId, model.ClusterName)
139+
140+
payload := ske.CreateKubeconfigPayload{}
141+
142+
if model.ExpirationTime != nil {
143+
expirationTime, err := skeUtils.ConvertToSeconds(*model.ExpirationTime)
144+
if err != nil {
145+
return req, fmt.Errorf("parse expiration time: %w", err)
146+
}
147+
148+
payload.ExpirationSeconds = expirationTime
149+
}
150+
151+
return req.CreateKubeconfigPayload(payload), nil
152+
}

0 commit comments

Comments
 (0)