Skip to content

Commit

Permalink
[PLAT-14809][YBA CLI]Support Master Key Rotation
Browse files Browse the repository at this point in the history
Summary:
Support MKR in the universe security operations
Security sub command in universe:
```
./yba universe security
Error: required flag(s) "name" not set
Usage:
  yba universe security [flags]
  yba universe security [command]

Available Commands:
  ear         Encryption-at-rest settings for a universe

Flags:
  -f, --force              [Optional] Bypass the prompt for non-interactive usage.
  -n, --name string        [Required] The name of the universe for the operation.
  -s, --skip-validations   [Optional] Skip validations before running the CLI command.
  -h, --help               help for security

Global Flags:
  -a, --apiToken string    YugabyteDB Anywhere api token.
      --config string      Config file, defaults to $HOME/.yba-cli.yaml
      --debug              Use debug mode, same as --logLevel debug.
      --disable-color      Disable colors in output. (default false)
  -H, --host string        YugabyteDB Anywhere Host (default "http://localhost:9000")
  -l, --logLevel string    Select the desired log level format. Allowed values: debug, info, warn, error, fatal. (default "info")
  -o, --output string      Select the desired output format. Allowed values: table, json, pretty. (default "table")
      --timeout duration   Wait command timeout, example: 5m, 1h. (default 168h0m0s)
      --wait               Wait until the task is completed, otherwise it will exit immediately. (default true)
```

EAR command for universe:
```
./yba universe security ear -h
Encryption-at-rest settings for a universe

Usage:
  yba universe security ear [flags]

Aliases:
  ear, encryption-at-rest, kms

Flags:
      --operation string     [Required] Enable or disable encryption-at-rest in a universe. Allowed values: enable, disable, rotate-universe-key, rotate-kms-config.
      --config-name string   [Optional] Key management service configuration name for master key. Required for enable and rotate-kms-config operations, ignored otherwise.
  -h, --help                 help for ear

Global Flags:
  -a, --apiToken string    YugabyteDB Anywhere api token.
      --config string      Config file, defaults to $HOME/.yba-cli.yaml
      --debug              Use debug mode, same as --logLevel debug.
      --disable-color      Disable colors in output. (default false)
  -f, --force              [Optional] Bypass the prompt for non-interactive usage.
  -H, --host string        YugabyteDB Anywhere Host (default "http://localhost:9000")
  -l, --logLevel string    Select the desired log level format. Allowed values: debug, info, warn, error, fatal. (default "info")
  -n, --name string        [Required] The name of the universe for the operation.
  -o, --output string      Select the desired output format. Allowed values: table, json, pretty. (default "table")
  -s, --skip-validations   [Optional] Skip validations before running the CLI command.
      --timeout duration   Wait command timeout, example: 5m, 1h. (default 168h0m0s)
      --wait               Wait until the task is completed, otherwise it will exit immediately. (default true)
```

Changes to go-client: yugabyte/platform-go-client@c3a4e13

Test Plan:
Tested out commands:

To change Master Key config of the universe
`./yba universe security ear --name dkumar --operation rotate-kms-config --config-name dkumar-aws-cli `

Enable EAR on a universe which has it disabled:
`./yba universe security ear --name dkumar --operation enable --config-name dkumar-aws-cli `

Disable EAR
`./yba universe security ear --name dkumar --operation disable `

Rotate universe key
`/yba universe security ear --name dkumar --operation rotate-universe-key`

Reviewers: #yba-api-review!, sneelakantan, skurapati

Reviewed By: skurapati

Subscribers: yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D37835
  • Loading branch information
Deepti-yb committed Sep 6, 2024
1 parent 82bca83 commit 011830f
Show file tree
Hide file tree
Showing 19 changed files with 429 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import com.yugabyte.yw.rbac.annotations.Resource;
import com.yugabyte.yw.rbac.enums.SourceType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.Authorization;
import java.io.IOException;
Expand Down Expand Up @@ -178,6 +180,12 @@ public Result resume(UUID customerUUID, UUID universeUUID, Http.Request request)
value = "Set a universe's key",
nickname = "setUniverseKey",
response = UniverseResp.class)
@ApiImplicitParams(
@ApiImplicitParam(
name = "SetUniverseKeyRequest",
paramType = "body",
dataType = "com.yugabyte.yw.forms.EncryptionAtRestConfig",
required = true))
@YbaApi(visibility = YbaApi.YbaApiVisibility.PUBLIC, sinceYBAVersion = "2.2.0.0")
@AuthzPath({
@RequiredPermissionOnResource(
Expand Down
7 changes: 7 additions & 0 deletions managed/src/main/resources/swagger-strict.json
Original file line number Diff line number Diff line change
Expand Up @@ -25906,6 +25906,13 @@
"in" : "query",
"name" : "request",
"required" : false
}, {
"in" : "body",
"name" : "SetUniverseKeyRequest",
"required" : true,
"schema" : {
"$ref" : "#/definitions/EncryptionAtRestConfig"
}
} ],
"responses" : {
"200" : {
Expand Down
7 changes: 7 additions & 0 deletions managed/src/main/resources/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -27506,6 +27506,13 @@
"in" : "query",
"name" : "request",
"required" : false
}, {
"in" : "body",
"name" : "SetUniverseKeyRequest",
"required" : true,
"schema" : {
"$ref" : "#/definitions/EncryptionAtRestConfig"
}
} ],
"responses" : {
"200" : {
Expand Down
2 changes: 1 addition & 1 deletion managed/yba-cli/cmd/universe/create_universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ var createUniverseCmd = &cobra.Command{
enableVolumeEncryption := v1.GetBool("enable-volume-encryption")

if enableVolumeEncryption {
opType = "ENABLE"
opType = util.EnableKMSOpType
kmsConfigName, err := cmd.Flags().GetString("kms-config")
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
Expand Down
259 changes: 259 additions & 0 deletions managed/yba-cli/cmd/universe/security/ear_universe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/*
* Copyright (c) YugaByte, Inc.
*/

package security

import (
"fmt"
"strings"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
ybaclient "github.com/yugabyte/platform-go-client"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/universe/upgrade"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/util"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/internal/client"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/internal/formatter"
)

// encryptionAtRestCmd represents the universe security encryption-at-rest command
var encryptionAtRestCmd = &cobra.Command{
Use: "ear",
Aliases: []string{"encryption-at-rest", "kms"},
Short: "Encryption-at-rest settings for a universe",
Long: "Encryption-at-rest settings for a universe",
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("force", cmd.Flags().Lookup("force"))
universeName, err := cmd.Flags().GetString("name")
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
if len(universeName) == 0 {
cmd.Help()
logrus.Fatalln(
formatter.Colorize("No universe name found to change settings\n",
formatter.RedColor))
}

// Validations before gflags upgrade operation
skipValidations, err := cmd.Flags().GetBool("skip-validations")
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
if !skipValidations {
_, _, err := upgrade.Validations(cmd, util.SecurityOperation)
if err != nil {
logrus.Fatalf(
formatter.Colorize(err.Error()+"\n", formatter.RedColor),
)
}

}
err = util.ConfirmCommand(
fmt.Sprintf("Are you sure you want to change encryption at rest configuration for %s: %s",
util.UniverseType, universeName),
viper.GetBool("force"))
if err != nil {
logrus.Fatal(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
},
Run: func(cmd *cobra.Command, args []string) {
authAPI, universe, err := upgrade.Validations(cmd, util.UpgradeOperation)
if err != nil {
logrus.Fatalf(
formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}

universeName := universe.GetName()
universeUUID := universe.GetUniverseUUID()
universeDetails := universe.GetUniverseDetails()
earConfig := universeDetails.GetEncryptionAtRestConfig()

operation, err := cmd.Flags().GetString("operation")
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
configName, err := cmd.Flags().GetString("config-name")
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}

operation = strings.ToUpper(operation)
if strings.Compare(operation, util.EnableKMSOpType) != 0 &&
strings.Compare(operation, util.DisableKMSOpType) != 0 &&
strings.Compare(operation, util.RotateKMSConfigKMSOpType) != 0 &&
strings.Compare(operation, util.RotateUniverseKeyKMSOpType) != 0 {
logrus.Fatalf(
formatter.Colorize(
"Invalid operation. "+
"Allowed values: enable, disable,"+
" rotate-universe-key, rotate-kms-config\n", formatter.RedColor))
}
var requestBody ybaclient.EncryptionAtRestConfig
switch operation {
case util.EnableKMSOpType, util.RotateKMSConfigKMSOpType:
requestBody, err = earEnableRequest(authAPI, earConfig, configName, operation)
if err != nil {
logrus.Fatalf(
formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
case util.DisableKMSOpType:
requestBody, err = earDisableRequest(earConfig)
if err != nil {
logrus.Fatalf(
formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
case util.RotateUniverseKeyKMSOpType:
if earConfig.GetEncryptionAtRestEnabled() ||
strings.Compare(earConfig.GetOpType(), util.EnableKMSOpType) == 0 {
requestBody = ybaclient.EncryptionAtRestConfig{
KmsConfigUUID: util.GetStringPointer(earConfig.GetKmsConfigUUID()),
OpType: util.GetStringPointer(util.EnableKMSOpType),
}
} else {
logrus.Fatal(
formatter.Colorize(
"Cannot rotate universe key if encryption at rest is not enabled",
formatter.RedColor))
}

}

earAPICall(authAPI, universeName, universeUUID, requestBody)

},
}

func init() {
encryptionAtRestCmd.Flags().SortFlags = false

encryptionAtRestCmd.Flags().String("operation", "",
"[Required] Enable or disable encryption-at-rest in a universe. "+
"Allowed values: enable, disable, rotate-universe-key, rotate-kms-config.")
encryptionAtRestCmd.MarkFlagRequired("operation")
encryptionAtRestCmd.Flags().String("config-name", "",
fmt.Sprintf("[Optional] Key management service configuration name for master key. %s.",
formatter.Colorize("Required for enable and rotate-kms-config operations, "+
"ignored otherwise", formatter.GreenColor)))

}

func earEnableRequest(
authAPI *client.AuthAPIClient,
earConfig ybaclient.EncryptionAtRestConfig,
configName, operation string) (
ybaclient.EncryptionAtRestConfig,
error,
) {
if earConfig.GetEncryptionAtRestEnabled() ||
strings.Compare(earConfig.GetOpType(), util.EnableKMSOpType) == 0 {
if strings.Compare(operation, util.EnableKMSOpType) == 0 {
logrus.Fatalf(
formatter.Colorize("Encryption at rest is already enabled\n",
formatter.RedColor))
}
} else {
if strings.Compare(operation, util.RotateKMSConfigKMSOpType) == 0 {
logrus.Fatalf(
formatter.Colorize("Encryption at rest is not enabled, cannot rotate KMS configuration\n",
formatter.RedColor))
}
}

if len(strings.TrimSpace(configName)) == 0 {
logrus.Fatalf(
formatter.Colorize(
"Configuration name is required to enable encryption at rest "+
"or to rotate KMS configuration.\n",
formatter.RedColor))
}

configUUID, err := getKMSConfigUUID(authAPI, configName)
if err != nil {
logrus.Fatalf(
formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}

if strings.Compare(operation, util.RotateKMSConfigKMSOpType) == 0 &&
strings.Compare(configUUID, earConfig.GetKmsConfigUUID()) == 0 {
logrus.Fatal(
formatter.Colorize(
fmt.Sprintf("Universe encryption at rest is already using configuration: %s (%s)\n",
configName, configUUID),
formatter.RedColor,
),
)
}
{
}

return ybaclient.EncryptionAtRestConfig{
KmsConfigUUID: util.GetStringPointer(configUUID),
OpType: util.GetStringPointer(util.EnableKMSOpType),
}, nil

}

func earDisableRequest(
earConfig ybaclient.EncryptionAtRestConfig) (
ybaclient.EncryptionAtRestConfig,
error,
) {
if !earConfig.GetEncryptionAtRestEnabled() ||
strings.Compare(earConfig.GetOpType(), util.DisableKMSOpType) == 0 {
logrus.Fatalf(
formatter.Colorize("Encryption at rest is already disabled\n",
formatter.RedColor))
}

return ybaclient.EncryptionAtRestConfig{
OpType: util.GetStringPointer(util.DisableKMSOpType),
}, nil
}

func getKMSConfigUUID(authAPI *client.AuthAPIClient, configName string) (string, error) {
r, response, err := authAPI.ListKMSConfigs().Execute()
if err != nil {
errMessage := util.ErrorFromHTTPResponse(
response, err, "Universe", "Master Key Rotation - List")
logrus.Fatalf(formatter.Colorize(errMessage.Error()+"\n", formatter.RedColor))
}
for _, k := range r {
kmsConfig, err := util.ConvertToKMSConfig(k)
if err != nil {
logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor))
}
if strings.Compare(kmsConfig.Name, configName) == 0 {
return kmsConfig.ConfigUUID, nil
}

}
return "", fmt.Errorf("No configurations with name: %s found\n", configName)
}

func earAPICall(
authAPI *client.AuthAPIClient,
universeName string,
universeUUID string,
requestBody ybaclient.EncryptionAtRestConfig,
) {
r, response, err := authAPI.SetUniverseKey(
universeUUID).SetUniverseKeyRequest(requestBody).Execute()
if err != nil {
errMessage := util.ErrorFromHTTPResponse(
response, err, "Universe", "Master Key Rotation")
logrus.Fatalf(formatter.Colorize(errMessage.Error()+"\n", formatter.RedColor))
}

taskUUID := r.GetTaskUUID()
logrus.Info(
fmt.Sprintf("Setting encryption at rest configuration in universe %s (%s)\n",
formatter.Colorize(universeName, formatter.GreenColor),
universeUUID,
))

upgrade.WaitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)

}
32 changes: 32 additions & 0 deletions managed/yba-cli/cmd/universe/security/security_universe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) YugaByte, Inc.
*/

package security

import "github.com/spf13/cobra"

// SecurityUniverseCmd represents the universe security command
var SecurityUniverseCmd = &cobra.Command{
Use: "security",
Short: "Manage security settings for a universe",
Long: "Manage security settings for a universe",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

func init() {
SecurityUniverseCmd.Flags().SortFlags = false

SecurityUniverseCmd.AddCommand(encryptionAtRestCmd)

SecurityUniverseCmd.PersistentFlags().StringP("name", "n", "",
"[Required] The name of the universe for the operation.")
SecurityUniverseCmd.MarkPersistentFlagRequired("name")
SecurityUniverseCmd.PersistentFlags().BoolP("force", "f", false,
"[Optional] Bypass the prompt for non-interactive usage.")
SecurityUniverseCmd.PersistentFlags().BoolP("skip-validations", "s", false,
"[Optional] Skip validations before running the CLI command.")

}
2 changes: 2 additions & 0 deletions managed/yba-cli/cmd/universe/universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package universe
import (
"github.com/spf13/cobra"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/universe/node"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/universe/security"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/universe/upgrade"
"github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/util"
)
Expand All @@ -30,4 +31,5 @@ func init() {
UniverseCmd.AddCommand(upgrade.UpgradeUniverseCmd)
UniverseCmd.AddCommand(upgrade.RestartCmd)
UniverseCmd.AddCommand(node.NodeCmd)
UniverseCmd.AddCommand(security.SecurityUniverseCmd)
}
2 changes: 1 addition & 1 deletion managed/yba-cli/cmd/universe/upgrade/gflags_universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ var upgradeGflagsCmd = &cobra.Command{
universeUUID,
))

waitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
WaitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
},
}

Expand Down
2 changes: 1 addition & 1 deletion managed/yba-cli/cmd/universe/upgrade/restart_universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ var RestartCmd = &cobra.Command{
fmt.Sprintf("Restarting universe %s\n",
formatter.Colorize(universeName, formatter.GreenColor)))

waitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
WaitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
},
}

Expand Down
2 changes: 1 addition & 1 deletion managed/yba-cli/cmd/universe/upgrade/software_universe.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ var upgradeSoftwareCmd = &cobra.Command{
universeUUID,
oldYBDBVersion, ybdbVersion))

waitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
WaitForUpgradeUniverseTask(authAPI, universeName, universeUUID, taskUUID)
},
}

Expand Down
Loading

0 comments on commit 011830f

Please sign in to comment.