diff --git a/cmd/kms/crypto/crypto.go b/cmd/kms/crypto/crypto.go new file mode 100644 index 000000000..55275920a --- /dev/null +++ b/cmd/kms/crypto/crypto.go @@ -0,0 +1,15 @@ +package crypto + +import ( + "github.com/exoscale/cli/cmd/kms" + "github.com/spf13/cobra" +) + +var cryptoCmd = &cobra.Command{ + Use: "crypto", + Short: "KMS key cryptographic operations", +} + +func init() { + kms.KMSCmd.AddCommand(cryptoCmd) +} diff --git a/cmd/kms/crypto/crypto_decrypt.go b/cmd/kms/crypto/crypto_decrypt.go new file mode 100644 index 000000000..90f7f44f2 --- /dev/null +++ b/cmd/kms/crypto/crypto_decrypt.go @@ -0,0 +1,97 @@ +package crypto + +import ( + "encoding/base64" + "os" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoDecryptOutput struct { + Plaintext string `json:"plaintext"` +} + +func (o *cryptoDecryptOutput) ToJSON() { output.JSON(o) } +func (o *cryptoDecryptOutput) ToText() { output.Text(o) } +func (o *cryptoDecryptOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "PLAINTEXT", + }) + + t.Append([]string{ + o.Plaintext, + }) +} + +type cryptoDecryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"decrypt"` + + Key string `cli-arg:"#" cli-usage:"ID"` + Ciphertext string `cli-arg:"#" cli-usage:"CIPHERTEXT"` + + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for decryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"crypto zone"` +} + +func (c *cryptoDecryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoDecryptCmd) CmdShort() string { + return "Decrypt a ciphertext." +} + +func (c *cryptoDecryptCmd) CmdLong() string { + return "Decrypt a ciphertext." +} + +func (c *cryptoDecryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoDecryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + decoded, err := base64.StdEncoding.DecodeString(c.Ciphertext) + if err != nil { + return err + } + req := v3.DecryptRequest{ + Ciphertext: decoded, + EncryptionContext: &ec, + } + + resp, err := client.Decrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoDecryptOutput{ + Plaintext: base64.StdEncoding.EncodeToString(resp.Plaintext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoDecryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_encrypt.go b/cmd/kms/crypto/crypto_encrypt.go new file mode 100644 index 000000000..6e2493fd9 --- /dev/null +++ b/cmd/kms/crypto/crypto_encrypt.go @@ -0,0 +1,93 @@ +package crypto + +import ( + "encoding/base64" + "os" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoEncryptOutput struct { + Ciphertext string `json:"ciphertext"` +} + +func (o *cryptoEncryptOutput) ToJSON() { output.JSON(o) } +func (o *cryptoEncryptOutput) ToText() { output.Text(o) } +func (o *cryptoEncryptOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "CIPHERTEXT", + }) + + t.Append([]string{ + o.Ciphertext, + }) +} + +type cryptoEncryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"encrypt"` + + Key string `cli-arg:"#" cli-usage:"ID"` + Plaintext string `cli-arg:"#" cli-usage:"PLAINTEXT"` + + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for encryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoEncryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoEncryptCmd) CmdShort() string { + return "Encrypt a plaintext." +} + +func (c *cryptoEncryptCmd) CmdLong() string { + return "Encrypt a plaintext." +} + +func (c *cryptoEncryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoEncryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + req := v3.EncryptRequest{ + Plaintext: []byte(c.Plaintext), + EncryptionContext: &ec, + } + + resp, err := client.Encrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoEncryptOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoEncryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_generate_data_key.go b/cmd/kms/crypto/crypto_generate_data_key.go new file mode 100644 index 000000000..5f5150867 --- /dev/null +++ b/cmd/kms/crypto/crypto_generate_data_key.go @@ -0,0 +1,109 @@ +package crypto + +import ( + "encoding/base64" + "os" + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoGenerateDataKeyOutput struct { + Plaintext string `json:"plaintext"` + Ciphertext string `json:"ciphertext"` +} + +func (o *cryptoGenerateDataKeyOutput) ToJSON() { output.JSON(o) } +func (o *cryptoGenerateDataKeyOutput) ToText() { output.Text(o) } +func (o *cryptoGenerateDataKeyOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "PLAINTEXT", + "CIPHERTEXT", + }) + + t.Append([]string{ + o.Plaintext, + o.Ciphertext, + }) +} + +type cryptoGenerateDataKeyCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"generate-data-key"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + KeySpec v3.GenerateDataKeyRequestKeySpec `cli-short:"s" cli-flag:"key-spec" cli-usage:"key spec for DEK [AES_256]"` + BytesCount string `cli-short:"b" cli-flag:"bytes-count" cli-usage:"number of bytes for DEK (1 - 1024)"` + EncryptionContext string `cli-short:"e" cli-flag:"encryption-context" cli-usage:"encryption context to use for DEK generation"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoGenerateDataKeyCmd) CmdAliases() []string { return nil } + +func (c *cryptoGenerateDataKeyCmd) CmdShort() string { + return "Generate a Data Encryption Key from a given KMS Key." +} + +func (c *cryptoGenerateDataKeyCmd) CmdLong() string { + return "Generate a Data Encryption Key from a given KMS Key." +} + +func (c *cryptoGenerateDataKeyCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoGenerateDataKeyCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + ec := []byte(c.EncryptionContext) + + var bytecount int + if c.BytesCount != "" { + n, err := strconv.Atoi(c.BytesCount) + if err != nil { + return err + } + bytecount = n + } + + req := v3.GenerateDataKeyRequest{ + KeySpec: c.KeySpec, + BytesCount: bytecount, + EncryptionContext: &ec, + } + + resp, err := client.GenerateDataKey(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoGenerateDataKeyOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + Plaintext: base64.StdEncoding.EncodeToString(resp.Plaintext), + } + return c.OutputFunc(&out, nil) + } + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoGenerateDataKeyCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/crypto/crypto_reencrypt.go b/cmd/kms/crypto/crypto_reencrypt.go new file mode 100644 index 000000000..7df7e2ad2 --- /dev/null +++ b/cmd/kms/crypto/crypto_reencrypt.go @@ -0,0 +1,96 @@ +package crypto + +import ( + "encoding/base64" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type cryptoReencryptCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"reencrypt"` + + Key string `cli-arg:"#" cli-usage:"SRC_ID"` + DestinationKey string `cli-arg:"#" cli-usage:"DEST_ID"` + Ciphertext string `cli-arg:"#" cli-usage:"CIPHERTEXT"` + + SourceEncryptionContext string `cli-short:"s" cli-flag:"source-encryption-context" cli-usage:"encryption context to use for source ciphertext decryption"` + DestEncryptionContext string `cli-short:"d" cli-flag:"dest-encryption-context" cli-usage:"encryption context to use for destination ciphertext encryption"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *cryptoReencryptCmd) CmdAliases() []string { return nil } + +func (c *cryptoReencryptCmd) CmdShort() string { + return "Decrypts and encrypts an exisiting ciphertext with newest key material or a different KMS key." +} + +func (c *cryptoReencryptCmd) CmdLong() string { + return "Decrypts an existing ciphertext using its original key material and re-encrypts the underlying plaintext using a specified KMS key or the latest key material of the same KMS Key." +} + +func (c *cryptoReencryptCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *cryptoReencryptCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var sourceEC []byte + if c.SourceEncryptionContext != "" { + sourceEC = []byte(c.SourceEncryptionContext) + } + + decodedCipher, err := base64.StdEncoding.DecodeString(c.Ciphertext) + if err != nil { + return err + } + source := &v3.ReEncryptRequestSource{ + Ciphertext: decodedCipher, + EncryptionContext: &sourceEC, + Key: v3.UUID(c.Key), + } + + var destEC []byte + if c.DestEncryptionContext != "" { + destEC = []byte(c.DestEncryptionContext) + } + dest := &v3.ReEncryptRequestDestination{ + Key: v3.UUID(c.DestinationKey), + EncryptionContext: &destEC, + } + + req := v3.ReEncryptRequest{ + Source: source, + Destination: dest, + } + + resp, err := client.ReEncrypt(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := cryptoEncryptOutput{ + Ciphertext: base64.StdEncoding.EncodeToString(resp.Ciphertext), + } + return c.OutputFunc(&out, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(cryptoCmd, &cryptoReencryptCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/formatting.go b/cmd/kms/key/formatting.go new file mode 100644 index 000000000..348745867 --- /dev/null +++ b/cmd/kms/key/formatting.go @@ -0,0 +1,61 @@ +package key + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" +) + +type successResponseOutput v3.SuccessResponse + +func (o *successResponseOutput) ToJSON() { output.JSON(o) } +func (o *successResponseOutput) ToText() { output.Text(o) } +func (o *successResponseOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "STATUS", + }) + + t.Append([]string{ + string(o.Status), + }) +} + +func formatKeyRotationConfig(s *v3.KeyRotationConfig) string { + if s == nil { + return "" + } + return fmt.Sprintf("auto: %s\ncount: %d\nnextAt: %s\nrotationPeriod: %d", + strconv.FormatBool(*s.Automatic), + s.ManualCount, + s.NextAT, + s.RotationPeriod) +} + +func formatKeyMaterial(s *v3.KeyMaterial) string { + if s == nil { + return "-" + } + return fmt.Sprintf("auto: %s\ncreatedAt: %s\nversion: %d", + strconv.FormatBool(*s.Automatic), + s.CreatedAT, + s.Version) +} + +func formatReplicaStatus(s []v3.ReplicaState) string { + if len(s) == 0 { + return "-" + } + var res []string + for _, r := range s { + res = append(res, r.Zone) + } + return strings.Join(res, ", ") +} diff --git a/cmd/kms/key/key.go b/cmd/kms/key/key.go new file mode 100644 index 000000000..d1f7969a6 --- /dev/null +++ b/cmd/kms/key/key.go @@ -0,0 +1,15 @@ +package key + +import ( + "github.com/exoscale/cli/cmd/kms" + "github.com/spf13/cobra" +) + +var keyCmd = &cobra.Command{ + Use: "key", + Short: "KMS key", +} + +func init() { + kms.KMSCmd.AddCommand(keyCmd) +} diff --git a/cmd/kms/key/key_cancel_deletion.go b/cmd/kms/key/key_cancel_deletion.go new file mode 100644 index 000000000..3e512f1a7 --- /dev/null +++ b/cmd/kms/key/key_cancel_deletion.go @@ -0,0 +1,61 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyCancelDeletionCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"cancel-deletion"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyCancelDeletionCmd) CmdAliases() []string { return nil } + +func (c *keyCancelDeletionCmd) CmdShort() string { + return "Cancel the scheduled deletion of a KMS Key." +} + +func (c *keyCancelDeletionCmd) CmdLong() string { + return "Cancel the scheduled deletion of a KMS Key." +} + +func (c *keyCancelDeletionCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyCancelDeletionCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + _, err = client.CancelKmsKeyDeletion(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyCancelDeletionCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_create.go b/cmd/kms/key/key_create.go new file mode 100644 index 000000000..83f91f984 --- /dev/null +++ b/cmd/kms/key/key_create.go @@ -0,0 +1,71 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyCreateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"create"` + + Name string `cli-arg:"#" cli-usage:"NAME"` + + Description string `cli-short:"d" cli-flag:"description" cli-usage:"key description"` + Usage string `cli-short:"u" cli-flag:"usage" cli-usage:"key usage [encrypt-decrypt]"` + Multizone bool `cli-short:"m" cli-flag:"multizone" cli-usage:"allow replication accross zones (default: false)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyCreateCmd) CmdAliases() []string { return nil } + +func (c *keyCreateCmd) CmdShort() string { + return "Create a KMS Key in a given zone with a given name." +} + +func (c *keyCreateCmd) CmdLong() string { + return "Create a KMS Key in a given zone with a given name." +} + +func (c *keyCreateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyCreateCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + req := v3.CreateKmsKeyRequest{ + Name: c.Name, + Description: c.Description, + Usage: v3.CreateKmsKeyRequestUsage(c.Usage), + MultiZone: &c.Multizone, + } + + resp, err := client.CreateKmsKey(ctx, req) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: resp.ID.String(), + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyCreateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_disable.go b/cmd/kms/key/key_disable.go new file mode 100644 index 000000000..edce33b55 --- /dev/null +++ b/cmd/kms/key/key_disable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyDisableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"disable"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyDisableCmd) CmdAliases() []string { return nil } + +func (c *keyDisableCmd) CmdShort() string { + return "Disables a KMS Key." +} + +func (c *keyDisableCmd) CmdLong() string { + return "Disables a KMS Key." +} + +func (c *keyDisableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyDisableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.DisableKmsKey(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyDisableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_enable.go b/cmd/kms/key/key_enable.go new file mode 100644 index 000000000..9d4fed4e8 --- /dev/null +++ b/cmd/kms/key/key_enable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyEnableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"enable"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyEnableCmd) CmdAliases() []string { return nil } + +func (c *keyEnableCmd) CmdShort() string { + return "Enables a KMS Key." +} + +func (c *keyEnableCmd) CmdLong() string { + return "Enables a KMS Key." +} + +func (c *keyEnableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyEnableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.EnableKmsKey(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyEnableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_list.go b/cmd/kms/key/key_list.go new file mode 100644 index 000000000..2b92c35de --- /dev/null +++ b/cmd/kms/key/key_list.go @@ -0,0 +1,104 @@ +package key + +import ( + "os" + "strconv" + "strings" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyListOutput struct { + v3.ListKmsKeysResponse +} + +func (o *keyListOutput) ToJSON() { output.JSON(o) } +func (o *keyListOutput) ToText() { output.Text(o) } +func (o *keyListOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "ID", + "NAME", + "ORIGINZONE", + "STATUS", + "MULTIZONE", + "REPLICAS", + }) + + for _, key := range o.KmsKeys { + t.Append([]string{ + string(key.ID), + key.Name, + string(key.OriginZone), + string(key.Status), + strconv.FormatBool(*key.MultiZone), + strings.Join(key.Replicas, ", "), + }) + } +} + +type keyListCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"list"` + + IgnoreReplica bool `cli-short:"i" cli-flag:"ignore-replica" cli-usage:"filter out replicas"` + Status string `cli-short:"s" cli-flag:"status" cli-usage:"filter by key status [enabled|disabled|pending-deletion]"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyListCmd) CmdAliases() []string { return exocmd.GListAlias } + +func (c *keyListCmd) CmdShort() string { + return "List KMS Keys details for an organization in a given zone." +} + +func (c *keyListCmd) CmdLong() string { + return "List KMS Keys details for an organization in a given zone." +} + +func (c *keyListCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyListCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + keys, err := client.ListKmsKeys(ctx) + if err != nil { + return err + } + + filtered := make([]v3.ListKmsKeysResponseEntry, 0, len(keys.KmsKeys)) + for _, key := range keys.KmsKeys { + if c.IgnoreReplica && key.OriginZone != string(c.Zone) { + continue + } + if c.Status != "" && string(key.Status) != c.Status { + continue + } + filtered = append(filtered, key) + } + + out := keyListOutput{v3.ListKmsKeysResponse{KmsKeys: filtered}} + + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyListCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_replicate.go b/cmd/kms/key/key_replicate.go new file mode 100644 index 000000000..79e8e4e19 --- /dev/null +++ b/cmd/kms/key/key_replicate.go @@ -0,0 +1,65 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyReplicateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"replicate"` + + Key string `cli-arg:"#" cli-usage:"ID"` + TargetZone v3.ZoneName `cli-arg:"#" cli-usage:"TARGET_ZONE"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyReplicateCmd) CmdAliases() []string { return nil } + +func (c *keyReplicateCmd) CmdShort() string { + return "Replicate a KMS key to a target zone." +} + +func (c *keyReplicateCmd) CmdLong() string { + return "Replicate a KMS key to a target zone." +} + +func (c *keyReplicateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyReplicateCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + req := v3.ReplicateKmsKeyRequest{ + Zone: string(c.TargetZone), + } + + resp, err := client.ReplicateKmsKey(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + out := successResponseOutput{ + Status: resp.Status, + } + return c.OutputFunc(&out, nil) + } + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyReplicateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotate.go b/cmd/kms/key/key_rotate.go new file mode 100644 index 000000000..509bcb0ea --- /dev/null +++ b/cmd/kms/key/key_rotate.go @@ -0,0 +1,61 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotateCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"rotate"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotateCmd) CmdAliases() []string { return nil } + +func (c *keyRotateCmd) CmdShort() string { + return "Perform a manual rotation of the key material for a symmetric key." +} + +func (c *keyRotateCmd) CmdLong() string { + return "Perform a manual rotation of the key material for a symmetric key." +} + +func (c *keyRotateCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotateCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + _, err = client.RotateKmsKey(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotateCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_disable.go b/cmd/kms/key/key_rotation_disable.go new file mode 100644 index 000000000..8166bb4c0 --- /dev/null +++ b/cmd/kms/key/key_rotation_disable.go @@ -0,0 +1,60 @@ +package key + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationDisableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"disable-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationDisableCmd) CmdAliases() []string { return nil } + +func (c *keyRotationDisableCmd) CmdShort() string { + return "Disable the periodic rotation of a KMS Key." +} + +func (c *keyRotationDisableCmd) CmdLong() string { + return "Disable the periodic rotation of a KMS Key." +} + +func (c *keyRotationDisableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationDisableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + if _, err := client.DisableKmsKeyRotation(ctx, v3.UUID(c.Key)); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationDisableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_enable.go b/cmd/kms/key/key_rotation_enable.go new file mode 100644 index 000000000..ca67840d2 --- /dev/null +++ b/cmd/kms/key/key_rotation_enable.go @@ -0,0 +1,74 @@ +package key + +import ( + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationEnableCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"enable-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + RotationPeriod string `cli-flag:"rotation-period" cli-short:"r" cli-usage:"number of days for auto rotation period (90 - 2560, default: 365)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationEnableCmd) CmdAliases() []string { return nil } + +func (c *keyRotationEnableCmd) CmdShort() string { + return "Enable the periodic rotation of a KMS Key." +} + +func (c *keyRotationEnableCmd) CmdLong() string { + return "Enable the periodic rotation of a KMS Key." +} + +func (c *keyRotationEnableCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationEnableCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var req v3.EnableKmsKeyRotationRequest + if c.RotationPeriod != "" { + n, err := strconv.Atoi(c.RotationPeriod) + if err != nil { + return err + } + req = v3.EnableKmsKeyRotationRequest{ + RotationPeriod: n, + } + } + + if _, err := client.EnableKmsKeyRotation(ctx, v3.UUID(c.Key), req); err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationEnableCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_rotation_list.go b/cmd/kms/key/key_rotation_list.go new file mode 100644 index 000000000..94621b5ad --- /dev/null +++ b/cmd/kms/key/key_rotation_list.go @@ -0,0 +1,86 @@ +package key + +import ( + "os" + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + "github.com/exoscale/cli/table" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyRotationListOutput struct { + v3.ListKmsKeyRotationsResponse +} + +func (o *keyRotationListOutput) ToJSON() { output.JSON(o) } +func (o *keyRotationListOutput) ToText() { output.Text(o) } +func (o *keyRotationListOutput) ToTable() { + t := table.NewTable(os.Stdout) + defer t.Render() + + t.SetHeader([]string{ + "VERSION", + "ROTATED_AT", + "AUTOMATIC", + }) + + for _, rotation := range o.Rotations { + t.Append([]string{ + strconv.Itoa(rotation.Version), + rotation.RotatedAT.String(), + strconv.FormatBool(*rotation.Automatic), + }) + } +} + +type keyRotationListCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"list-rotation"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyRotationListCmd) CmdAliases() []string { return exocmd.GListAlias } + +func (c *keyRotationListCmd) CmdShort() string { + return "List all the key material versions of a KMS Key." +} + +func (c *keyRotationListCmd) CmdLong() string { + return "List all the key material versions of a KMS Key." +} + +func (c *keyRotationListCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyRotationListCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + resp, err := client.ListKmsKeyRotations(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + out := keyRotationListOutput{*resp} + + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyRotationListCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_schedule_deletion.go b/cmd/kms/key/key_schedule_deletion.go new file mode 100644 index 000000000..4256fdedf --- /dev/null +++ b/cmd/kms/key/key_schedule_deletion.go @@ -0,0 +1,78 @@ +package key + +import ( + "fmt" + "strconv" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type keyScheduleDeletionCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"schedule-deletion"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + DelayDays string `cli-short:"d" cli-flag:"delay-days" cli-usage:"number of days before deletion (7 - 30, default: 30)"` + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *keyScheduleDeletionCmd) CmdAliases() []string { return nil } + +func (c *keyScheduleDeletionCmd) CmdShort() string { + return "Schedule a KMS key for deletion after a delay." +} + +func (c *keyScheduleDeletionCmd) CmdLong() string { + return "Schedule a KMS key for deletion after a delay." +} + +func (c *keyScheduleDeletionCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *keyScheduleDeletionCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + var delayDays int + if c.DelayDays != "" { + n, err := strconv.Atoi(c.DelayDays) + if err != nil { + return fmt.Errorf("invalid delay days: %v", err) + } + delayDays = n + } + + req := v3.ScheduleKmsKeyDeletionRequest{ + DelayDays: delayDays, + } + + _, err = client.ScheduleKmsKeyDeletion(ctx, v3.UUID(c.Key), req) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&KeyShowCmd{ + CliCommandSettings: c.CliCommandSettings, + Key: c.Key, + }).CmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &keyScheduleDeletionCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/key/key_show.go b/cmd/kms/key/key_show.go new file mode 100644 index 000000000..4755321e8 --- /dev/null +++ b/cmd/kms/key/key_show.go @@ -0,0 +1,91 @@ +package key + +import ( + "time" + + exocmd "github.com/exoscale/cli/cmd" + "github.com/exoscale/cli/pkg/globalstate" + "github.com/exoscale/cli/pkg/output" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type KeyShowOutput struct { + ID v3.UUID `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + CreatedAt time.Time `json:"created-at" validate:"required"` + Multizone bool `json:"multi-zone" validate:"required"` + OriginZone string `json:"origin-zone" validate:"required"` + Status v3.GetKmsKeyResponseStatus `json:"status" validate:"required"` + ReplicasStatus string `json:"replicas-status,omitempty"` + Material string `json:"material" validate:"required"` + Rotation string `json:"rotation" validate:"required"` + Usage string `json:"usage" validate:"required"` + Source v3.GetKmsKeyResponseSource `json:"source" validate:"required"` + Description string `json:"description" validate:"required"` +} + +func (o *KeyShowOutput) Type() string { return "KMS key" } +func (o *KeyShowOutput) ToJSON() { output.JSON(o) } +func (o *KeyShowOutput) ToText() { output.Text(o) } +func (o *KeyShowOutput) ToTable() { output.Table(o) } + +type KeyShowCmd struct { + exocmd.CliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"show"` + + Key string `cli-arg:"#" cli-usage:"ID"` + + Zone v3.ZoneName `cli-short:"z" cli-flag:"zone" cli-usage:"key zone"` +} + +func (c *KeyShowCmd) CmdAliases() []string { return exocmd.GShowAlias } + +func (c *KeyShowCmd) CmdShort() string { + return "Retrieve KMS Key details." +} + +func (c *KeyShowCmd) CmdLong() string { + return "Retrieve KMS Key details." +} + +func (c *KeyShowCmd) CmdPreRun(cmd *cobra.Command, args []string) error { + exocmd.CmdSetZoneFlagFromDefault(cmd) + return exocmd.CliCommandDefaultPreRun(c, cmd, args) +} + +func (c *KeyShowCmd) CmdRun(_ *cobra.Command, _ []string) error { + ctx := exocmd.GContext + client, err := exocmd.SwitchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } + + resp, err := client.GetKmsKey(ctx, v3.UUID(c.Key)) + if err != nil { + return err + } + + out := KeyShowOutput{ + ID: resp.ID, + Name: resp.Name, + CreatedAt: resp.CreatedAT, + Multizone: *resp.MultiZone, + OriginZone: resp.OriginZone, + Status: resp.Status, + ReplicasStatus: formatReplicaStatus(resp.ReplicasStatus), + Material: formatKeyMaterial(resp.Material), + Rotation: formatKeyRotationConfig(resp.Rotation), + Usage: string(resp.Usage), + Source: resp.Source, + Description: resp.Description, + } + return c.OutputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(exocmd.RegisterCLICommand(keyCmd, &KeyShowCmd{ + CliCommandSettings: exocmd.DefaultCLICmdSettings(), + })) +} diff --git a/cmd/kms/kms.go b/cmd/kms/kms.go new file mode 100644 index 000000000..67791de36 --- /dev/null +++ b/cmd/kms/kms.go @@ -0,0 +1,15 @@ +package kms + +import ( + exocmd "github.com/exoscale/cli/cmd" + "github.com/spf13/cobra" +) + +var KMSCmd = &cobra.Command{ + Use: "kms", + Short: "Key management", +} + +func init() { + exocmd.RootCmd.AddCommand(KMSCmd) +} diff --git a/cmd/root.go b/cmd/root.go index d1623e0ed..cc996ab7e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -116,7 +116,7 @@ func formatError(err error) string { return apiErr.Unwrap().Error() } - msg := apiErr.Unwrap().Error() + ": " + lead + msg := apiErr.Unwrap().Error() + ": " + apiErr.Title + ": " + lead for _, e := range apiErr.Errors { field := formatFieldName(e.Location) detail := formatDetail(e.Detail) diff --git a/cmd/subcommands/init.go b/cmd/subcommands/init.go index bdb53bdcd..d54545275 100644 --- a/cmd/subcommands/init.go +++ b/cmd/subcommands/init.go @@ -26,5 +26,8 @@ import ( _ "github.com/exoscale/cli/cmd/dbaas" _ "github.com/exoscale/cli/cmd/dns" _ "github.com/exoscale/cli/cmd/iam" + _ "github.com/exoscale/cli/cmd/kms" + _ "github.com/exoscale/cli/cmd/kms/crypto" + _ "github.com/exoscale/cli/cmd/kms/key" _ "github.com/exoscale/cli/cmd/storage" )