Skip to content

Commit ad53d5b

Browse files
feat(volume): support volume encryption (#1705)
STACKITCLI-544
1 parent 3c3f120 commit ad53d5b

8 files changed

Lines changed: 243 additions & 37 deletions

File tree

‎docs/stackit_volume_create.md‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,31 @@ stackit volume create [flags]
2424
2525
Create a volume with availability zone "eu01-1", performance class "storage_premium_perf1" and size 64 GB
2626
$ stackit volume create --availability-zone eu01-1 --performance-class storage_premium_perf1 --size 64
27+
28+
Create an encrypted volume using a STACKIT KMS key
29+
$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com
30+
31+
Create an encrypted volume with a predefined base64-encoded secret read from a file
32+
$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com --encryption-key-payload-base64 @./secret.txt
2733
```
2834

2935
### Options
3036

3137
```
32-
--availability-zone string Availability zone
33-
--description string Volume description
34-
-h, --help Help for "stackit volume create"
35-
--labels stringToString Labels are key-value string pairs which can be attached to a volume. E.g. '--labels key1=value1,key2=value2,...' (default [])
36-
-n, --name string Volume name
37-
--performance-class string Performance class
38-
--size int Volume size (GB). Either 'size' or the 'source-id' and 'source-type' flags must be given
39-
--source-id string ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given
40-
--source-type string Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given
38+
--availability-zone string Availability zone
39+
--description string Volume description
40+
--encryption-kek-key-id string UUID of the STACKIT KMS key used for encryption
41+
--encryption-kek-key-version int Version of the STACKIT KMS key used for encryption
42+
--encryption-kek-keyring-id string UUID of the STACKIT KMS keyring containing the key
43+
--encryption-key-payload-base64 secret Encryption-Key-Payload-Base64. Use @<file> to read from a file (example: @./secret.txt), or - to read from stdin (hidden input on a terminal). Literal values are not supported. Must be base64 encoded. If omitted, the API generates a random secret.
44+
--encryption-service-account string Service account linked to the STACKIT KMS key
45+
-h, --help Help for "stackit volume create"
46+
--labels stringToString Labels are key-value string pairs which can be attached to a volume. E.g. '--labels key1=value1,key2=value2,...' (default [])
47+
-n, --name string Volume name
48+
--performance-class string Performance class
49+
--size int Volume size (GB). Either 'size' or the 'source-id' and 'source-type' flags must be given
50+
--source-id string ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given
51+
--source-type string Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given
4152
```
4253

4354
### Options inherited from parent commands

‎docs/stackit_volume_describe.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Shows details of a volume
44

55
### Synopsis
66

7-
Shows details of a volume.
7+
Shows details of a volume, including its encryption status.
88

99
```
1010
stackit volume describe VOLUME_ID [flags]

‎internal/cmd/volume/create/create.go‎

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const (
3232
sizeFlag = "size"
3333
sourceIdFlag = "source-id"
3434
sourceTypeFlag = "source-type"
35+
36+
encryptionKekKeyIdFlag = "encryption-kek-key-id"
37+
encryptionKekKeyringIdFlag = "encryption-kek-keyring-id"
38+
encryptionKekKeyVersionFlag = "encryption-kek-key-version"
39+
encryptionServiceAccountFlag = "encryption-service-account"
40+
encryptionKeyPayloadFlag = "encryption-key-payload-base64"
3541
)
3642

3743
type inputModel struct {
@@ -44,6 +50,12 @@ type inputModel struct {
4450
Size *int64
4551
SourceId *string
4652
SourceType *string
53+
54+
EncryptionKekKeyId *string
55+
EncryptionKekKeyringId *string
56+
EncryptionKekKeyVersion *int64
57+
EncryptionServiceAccount *string
58+
EncryptionKeyPayload *string `json:"-"`
4759
}
4860

4961
func NewCmd(params *types.CmdParams) *cobra.Command {
@@ -69,6 +81,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
6981
`Create a volume with availability zone "eu01-1", performance class "storage_premium_perf1" and size 64 GB`,
7082
`$ stackit volume create --availability-zone eu01-1 --performance-class storage_premium_perf1 --size 64`,
7183
),
84+
examples.NewExample(
85+
`Create an encrypted volume using a STACKIT KMS key`,
86+
`$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com`,
87+
),
88+
examples.NewExample(
89+
`Create an encrypted volume with a predefined base64-encoded secret read from a file`,
90+
`$ stackit volume create --availability-zone eu01-1 --size 64 --encryption-kek-key-id xxx --encryption-kek-keyring-id yyy --encryption-kek-key-version 1 --encryption-service-account service-account@example.com --encryption-key-payload-base64 @./secret.txt`,
91+
),
7292
),
7393
RunE: func(cmd *cobra.Command, args []string) error {
7494
ctx := context.Background()
@@ -96,7 +116,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
96116
}
97117

98118
// Call API
99-
req := buildRequest(ctx, model, apiClient)
119+
req := buildRequest(ctx, model, apiClient.DefaultAPI)
100120
resp, err := req.Execute()
101121
if err != nil {
102122
return fmt.Errorf("create volume : %w", err)
@@ -117,11 +137,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
117137
return outputResult(params.Printer, model, projectLabel, resp)
118138
},
119139
}
120-
configureFlags(cmd)
140+
configureFlags(cmd, params)
121141
return cmd
122142
}
123143

124-
func configureFlags(cmd *cobra.Command) {
144+
func configureFlags(cmd *cobra.Command, params *types.CmdParams) {
125145
cmd.Flags().String(availabilityZoneFlag, "", "Availability zone")
126146
cmd.Flags().StringP(nameFlag, "n", "", "Volume name")
127147
cmd.Flags().String(descriptionFlag, "", "Volume description")
@@ -131,6 +151,15 @@ func configureFlags(cmd *cobra.Command) {
131151
cmd.Flags().String(sourceIdFlag, "", "ID of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given")
132152
cmd.Flags().String(sourceTypeFlag, "", "Type of the source object of volume. Either 'size' or the 'source-id' and 'source-type' flags must be given")
133153

154+
cmd.Flags().Var(flags.UUIDFlag(), encryptionKekKeyIdFlag, "UUID of the STACKIT KMS key used for encryption")
155+
cmd.Flags().Var(flags.UUIDFlag(), encryptionKekKeyringIdFlag, "UUID of the STACKIT KMS keyring containing the key")
156+
cmd.Flags().Int64(encryptionKekKeyVersionFlag, 0, "Version of the STACKIT KMS key used for encryption")
157+
cmd.Flags().String(encryptionServiceAccountFlag, "", "Service account linked to the STACKIT KMS key")
158+
159+
keyPayload := flags.SecretFlag(encryptionKeyPayloadFlag, params)
160+
cmd.Flags().Var(keyPayload, encryptionKeyPayloadFlag, keyPayload.Usage()+" Must be base64 encoded. If omitted, the API generates a random secret.")
161+
162+
cmd.MarkFlagsRequiredTogether(encryptionKekKeyIdFlag, encryptionKekKeyringIdFlag, encryptionKekKeyVersionFlag, encryptionServiceAccountFlag)
134163
err := flags.MarkFlagsRequired(cmd, availabilityZoneFlag)
135164
cobra.CheckErr(err)
136165
}
@@ -151,14 +180,33 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel,
151180
Size: flags.FlagToInt64Pointer(p, cmd, sizeFlag),
152181
SourceId: flags.FlagToStringPointer(p, cmd, sourceIdFlag),
153182
SourceType: flags.FlagToStringPointer(p, cmd, sourceTypeFlag),
183+
184+
EncryptionKekKeyId: flags.FlagToStringPointer(p, cmd, encryptionKekKeyIdFlag),
185+
EncryptionKekKeyringId: flags.FlagToStringPointer(p, cmd, encryptionKekKeyringIdFlag),
186+
EncryptionKekKeyVersion: flags.FlagToInt64Pointer(p, cmd, encryptionKekKeyVersionFlag),
187+
EncryptionServiceAccount: flags.FlagToStringPointer(p, cmd, encryptionServiceAccountFlag),
154188
}
155189

190+
// Cobra requires the encryption parameters together, so checking one is sufficient.
191+
if cmd.Flags().Changed(encryptionKeyPayloadFlag) && model.EncryptionKekKeyId == nil {
192+
return nil, &cliErr.FlagValidationError{
193+
Flag: encryptionKekKeyIdFlag,
194+
Details: fmt.Sprintf("when using flag %q, flag %q is required", encryptionKeyPayloadFlag, encryptionKekKeyIdFlag),
195+
}
196+
}
197+
198+
keyPayload, err := flags.ResolveSecretFlag(p, cmd, encryptionKeyPayloadFlag)
199+
if err != nil {
200+
return nil, err
201+
}
202+
model.EncryptionKeyPayload = keyPayload
203+
156204
p.DebugInputModel(model)
157205
return &model, nil
158206
}
159207

160-
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateVolumeRequest {
161-
req := apiClient.DefaultAPI.CreateVolume(ctx, model.ProjectId, model.Region)
208+
func buildRequest(ctx context.Context, model *inputModel, apiClient iaas.DefaultAPI) iaas.ApiCreateVolumeRequest {
209+
req := apiClient.CreateVolume(ctx, model.ProjectId, model.Region)
162210

163211
payload := iaas.CreateVolumePayload{
164212
AvailabilityZone: model.AvailabilityZone,
@@ -176,6 +224,16 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APICli
176224
}
177225
}
178226

227+
if model.EncryptionKekKeyId != nil && model.EncryptionKekKeyringId != nil && model.EncryptionKekKeyVersion != nil && model.EncryptionServiceAccount != nil {
228+
payload.EncryptionParameters = &iaas.VolumeEncryptionParameter{
229+
KekKeyId: *model.EncryptionKekKeyId,
230+
KekKeyringId: *model.EncryptionKekKeyringId,
231+
KekKeyVersion: *model.EncryptionKekKeyVersion,
232+
ServiceAccount: *model.EncryptionServiceAccount,
233+
KeyPayload: model.EncryptionKeyPayload,
234+
}
235+
}
236+
179237
return req.CreateVolumePayload(payload)
180238
}
181239

0 commit comments

Comments
 (0)