Repository: vmware/govmomi
Pull request #3553 (2024-09-19 19:55:01)

govc: add kms commands


Patch:
diff --git a/crypto/manager_kmip.go b/crypto/manager_kmip.go
index e0e51afb..6fac3592 100644
--- a/crypto/manager_kmip.go
+++ b/crypto/manager_kmip.go
@@ -229,7 +229,7 @@ func (m ManagerKmip) SetDefaultKmsClusterId(
 	return nil
 }
 
-func (m ManagerKmip) RegisterKmipCluster(
+func (m ManagerKmip) RegisterKmsCluster(
 	ctx context.Context,
 	providerID string,
 	managementType types.KmipClusterInfoKmsManagementType) error {
diff --git a/govc/USAGE.md b/govc/USAGE.md
index 3c5a6c5f..f4f59780 100644
--- a/govc/USAGE.md
+++ b/govc/USAGE.md
@@ -218,6 +218,11 @@ but appear via `govc $cmd -h`:
  - [import.ovf](#importovf)
  - [import.spec](#importspec)
  - [import.vmdk](#importvmdk)
+ - [kms.add](#kmsadd)
+ - [kms.default](#kmsdefault)
+ - [kms.ls](#kmsls)
+ - [kms.rm](#kmsrm)
+ - [kms.trust](#kmstrust)
  - [library.checkin](#librarycheckin)
  - [library.checkout](#librarycheckout)
  - [library.clone](#libraryclone)
@@ -3582,6 +3587,93 @@ Options:
   -pool=                 Resource pool [GOVC_RESOURCE_POOL]
 ```
 
+## kms.add
+
+```
+Usage: govc kms.add [OPTIONS] NAME
+
+Add KMS cluster.
+
+Server name and address are required, port defaults to 5696.
+
+Examples:
+  govc kms.add -n my-server -a kms.example.com my-kp
+
+Options:
+  -a=                    Server address
+  -n=                    Server name
+  -p=5696                Server port
+```
+
+## kms.default
+
+```
+Usage: govc kms.default [OPTIONS] NAME
+
+Set default KMS cluster.
+
+Examples:
+  govc kms.default my-kp
+  govc kms.default - # clear default
+  govc kms.default -e /dc/host/cluster my-kp
+  govc kms.default -e /dc/host/cluster my-kp - # clear default
+
+Options:
+  -e=                    Set entity default KMS cluster (cluster or host folder)
+```
+
+## kms.ls
+
+```
+Usage: govc kms.ls [OPTIONS] NAME
+
+Display KMS info.
+
+Examples:
+  govc kms.ls
+  govc kms.ls -json
+  govc kms.ls - # default provider
+  govc kms.ls ProviderName
+  govc kms.ls -json ProviderName
+
+Options:
+```
+
+## kms.rm
+
+```
+Usage: govc kms.rm [OPTIONS] NAME
+
+Remove KMS server or cluster.
+
+Examples:
+  govc kms.rm my-kp
+  govc kms.rm -s my-server my-kp
+
+Options:
+  -s=                    Server name
+```
+
+## kms.trust
+
+```
+Usage: govc kms.trust [OPTIONS] NAME
+
+Establish trust between KMS and vCenter.
+
+Examples:
+  # "Make vCenter Trust KMS"
+  govc kms.trust -server-cert "$(govc about.cert -show)" my-kp
+
+  # "Make KMS Trust vCenter" -> "KMS certificate and private key"
+  govc kms.trust -client-cert "$(cat crt.pem) -client-key "$(cat key.pem) my-kp
+
+  # "Download the vCenter certificate and upload it to the KMS"
+  govc about.cert -show > vcenter-cert.pem
+
+Options:
+```
+
 ## library.checkin
 
 ```
diff --git a/govc/kms/add.go b/govc/kms/add.go
new file mode 100644
index 00000000..12963832
--- /dev/null
+++ b/govc/kms/add.go
@@ -0,0 +1,84 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kms
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type add struct {
+	*flags.ClientFlag
+
+	types.KmipServerSpec
+}
+
+func init() {
+	cli.Register("kms.add", &add{})
+}
+
+func (cmd *add) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+
+	f.StringVar(&cmd.Info.Name, "n", "", "Server name")
+	f.StringVar(&cmd.Info.Address, "a", "", "Server address")
+	cmd.Info.Port = 5696 // default
+	f.Var(flags.NewInt32(&cmd.Info.Port), "p", "Server port")
+}
+
+func (cmd *add) Usage() string {
+	return "NAME"
+}
+
+func (cmd *add) Description() string {
+	return `Add KMS cluster.
+
+Server name and address are required, port defaults to 5696.
+
+Examples:
+  govc kms.add -n my-server -a kms.example.com my-kp`
+}
+
+func (cmd *add) Run(ctx context.Context, f *flag.FlagSet) error {
+	id := f.Arg(0)
+	if id == "" {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.Client()
+	if err != nil {
+		return err
+	}
+
+	m, err := crypto.GetManagerKmip(c)
+	if err != nil {
+		return err
+	}
+
+	spec := types.KmipServerSpec{
+		ClusterId: types.KeyProviderId{Id: id},
+		Info:      cmd.Info,
+	}
+
+	return m.RegisterKmipServer(ctx, spec)
+}
diff --git a/govc/kms/ls.go b/govc/kms/ls.go
new file mode 100644
index 00000000..466316f4
--- /dev/null
+++ b/govc/kms/ls.go
@@ -0,0 +1,196 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kms
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"text/tabwriter"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type ls struct {
+	*flags.ClientFlag
+	*flags.OutputFlag
+}
+
+func init() {
+	cli.Register("kms.ls", &ls{})
+}
+
+func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+
+	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
+	cmd.OutputFlag.Register(ctx, f)
+}
+
+func (cmd *ls) Process(ctx context.Context) error {
+	if err := cmd.ClientFlag.Process(ctx); err != nil {
+		return err
+	}
+	return cmd.OutputFlag.Process(ctx)
+}
+
+func (cmd *ls) Usage() string {
+	return "NAME"
+}
+
+func (cmd *ls) Description() string {
+	return `Display KMS info.
+
+Examples:
+  govc kms.ls
+  govc kms.ls -json
+  govc kms.ls - # default provider
+  govc kms.ls ProviderName
+  govc kms.ls -json ProviderName`
+}
+
+func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
+	c, err := cmd.Client()
+	if err != nil {
+		return err
+	}
+
+	m, err := crypto.GetManagerKmip(c)
+	if err != nil {
+		return err
+	}
+
+	info, err := m.ListKmipServers(ctx, nil)
+	if err != nil {
+		return err
+	}
+
+	id := f.Arg(0)
+
+	if id == "" {
+		status, err := m.GetStatus(ctx, info...)
+		if err != nil {
+			return err
+		}
+		return cmd.WriteResult(&clusterResult{Info: info, Status: status})
+	}
+
+	if id == "-" {
+		for _, s := range info {
+			if s.UseAsDefault {
+				id = s.ClusterId.Id
+				break
+			}
+		}
+	}
+
+	status, err := m.GetClusterStatus(ctx, id)
+	if err != nil {
+		return err
+	}
+
+	format := &serverResult{Status: status}
+
+	for _, s := range info {
+		if s.ClusterId.Id == id {
+			format.Info = s
+		}
+	}
+
+	return cmd.WriteResult(format)
+}
+
+type serverResult struct {
+	Info   types.KmipClusterInfo                 `json:"info"`
+	Status *types.CryptoManagerKmipClusterStatus `json:"status"`
+}
+
+func (r *serverResult) status(name string) types.ManagedEntityStatus {
+	for _, server := range r.Status.Servers {
+		if server.Name == name {
+			return server.Status
+		}
+	}
+	return types.ManagedEntityStatusGray
+}
+
+func (r *serverResult) Write(w io.Writer) error {
+	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
+
+	if r.Info.ManagementType == string(types.KmipClusterInfoKmsManagementTypeNativeProvider) {
+		boolVal := func(v *bool) bool {
+			if v == nil {
+				return false
+			}
+			return *v
+		}
+
+		fmt.Fprintf(tw, "Key ID: %s\tHas Backup: %t\tTPM Required: %t\n",
+			r.Info.KeyId, boolVal(r.Info.HasBackup), boolVal(r.Info.TpmRequired))
+	} else {
+		for _, s := range r.Info.Servers {
+			status := r.status(s.Name)
+			fmt.Fprintf(tw, "%s\t%s:%d\t%s\n", s.Name, s.Address, s.Port, status)
+		}
+	}
+
+	return tw.Flush()
+}
+
+type clusterResult struct {
+	Info   []types.KmipClusterInfo                `json:"info"`
+	Status []types.CryptoManagerKmipClusterStatus `json:"status"`
+}
+
+func (r *clusterResult) status(id types.KeyProviderId) types.ManagedEntityStatus {
+	for _, status := range r.Status {
+		if status.ClusterId == id {
+			return status.OverallStatus
+		}
+	}
+	return types.ManagedEntityStatusGray
+}
+
+func kmsType(kind string) string {
+	switch types.KmipClusterInfoKmsManagementType(kind) {
+	case types.KmipClusterInfoKmsManagementTypeVCenter:
+		return "Standard"
+	case types.KmipClusterInfoKmsManagementTypeNativeProvider:
+		return "Native"
+	default:
+		return kind
+	}
+}
+
+func (r *clusterResult) Write(w io.Writer) error {
+	tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
+
+	for _, info := range r.Info {
+		name := info.ClusterId.Id
+		kind := kmsType(info.ManagementType)
+		status := r.status(info.ClusterId)
+		fmt.Fprintf(tw, "%s\t%s\t%s\n", name, kind, status)
+	}
+
+	return tw.Flush()
+}
diff --git a/govc/kms/rm.go b/govc/kms/rm.go
new file mode 100644
index 00000000..ef7ad9e8
--- /dev/null
+++ b/govc/kms/rm.go
@@ -0,0 +1,78 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kms
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+)
+
+type rm struct {
+	*flags.ClientFlag
+
+	server string
+}
+
+func init() {
+	cli.Register("kms.rm", &rm{})
+}
+
+func (cmd *rm) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+
+	f.StringVar(&cmd.server, "s", "", "Server name")
+}
+
+func (cmd *rm) Usage() string {
+	return "NAME"
+}
+
+func (cmd *rm) Description() string {
+	return `Remove KMS server or cluster.
+
+Examples:
+  govc kms.rm my-kp
+  govc kms.rm -s my-server my-kp`
+}
+
+func (cmd *rm) Run(ctx context.Context, f *flag.FlagSet) error {
+	id := f.Arg(0)
+	if id == "" {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.Client()
+	if err != nil {
+		return err
+	}
+
+	m, err := crypto.GetManagerKmip(c)
+	if err != nil {
+		return err
+	}
+
+	if cmd.server != "" {
+		return m.RemoveKmipServer(ctx, id, cmd.server)
+	}
+
+	return m.UnregisterKmsCluster(ctx, id)
+}
diff --git a/govc/kms/setdefault.go b/govc/kms/setdefault.go
new file mode 100644
index 00000000..989fadc2
--- /dev/null
+++ b/govc/kms/setdefault.go
@@ -0,0 +1,92 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kms
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type setdefault struct {
+	*flags.DatacenterFlag
+
+	entity string
+}
+
+func init() {
+	cli.Register("kms.default", &setdefault{})
+}
+
+func (cmd *setdefault) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
+	cmd.DatacenterFlag.Register(ctx, f)
+
+	f.StringVar(&cmd.entity, "e", "", "Set entity default KMS cluster (cluster or host folder)")
+}
+
+func (cmd *setdefault) Usage() string {
+	return "NAME"
+}
+
+func (cmd *setdefault) Description() string {
+	return `Set default KMS cluster.
+
+Examples:
+  govc kms.default my-kp
+  govc kms.default - # clear default
+  govc kms.default -e /dc/host/cluster my-kp
+  govc kms.default -e /dc/host/cluster my-kp - # clear default`
+}
+
+func (cmd *setdefault) Run(ctx context.Context, f *flag.FlagSet) error {
+	id := f.Arg(0)
+	if id == "" {
+		return flag.ErrHelp
+	}
+
+	if id == "-" {
+		id = "" // clear default
+	}
+
+	c, err := cmd.Client()
+	if err != nil {
+		return err
+	}
+
+	m, err := crypto.GetManagerKmip(c)
+	if err != nil {
+		return err
+	}
+
+	var entity *types.ManagedObjectReference
+
+	if cmd.entity != "" {
+		obj, err := cmd.ManagedObject(ctx, cmd.entity)
+		if err != nil {
+			return err
+		}
+
+		entity = types.NewReference(obj.Reference())
+	}
+
+	return m.SetDefaultKmsClusterId(ctx, id, entity)
+}
diff --git a/govc/kms/trust.go b/govc/kms/trust.go
new file mode 100644
index 00000000..e51d0c4e
--- /dev/null
+++ b/govc/kms/trust.go
@@ -0,0 +1,103 @@
+/*
+Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kms
+
+import (
+	"context"
+	"flag"
+
+	"github.com/vmware/govmomi/crypto"
+	"github.com/vmware/govmomi/govc/cli"
+	"github.com/vmware/govmomi/govc/flags"
+	"github.com/vmware/govmomi/vim25/methods"
+	"github.com/vmware/govmomi/vim25/types"
+)
+
+type trust struct {
+	*flags.ClientFlag
+
+	client types.UploadClientCert
+	server types.UploadKmipServerCert
+}
+
+func init() {
+	cli.Register("kms.trust", &trust{})
+}
+
+func (cmd *trust) Register(ctx context.Context, f *flag.FlagSet) {
+	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
+	cmd.ClientFlag.Register(ctx, f)
+
+	f.StringVar(&cmd.client.Certificate, "client-cert", "", "Client public certificate")
+	f.StringVar(&cmd.client.PrivateKey, "client-key", "", "Client private key")
+	f.StringVar(&cmd.server.Certificate, "server-cert", "", "Server public certificate")
+}
+
+func (cmd *trust) Usage() string {
+	return "NAME"
+}
+
+func (cmd *trust) Description() string {
+	return `Establish trust between KMS and vCenter.
+
+Examples:
+  # "Make vCenter Trust KMS"
+  govc kms.trust -server-cert "$(govc about.cert -show)" my-kp
+
+  # "Make KMS Trust vCenter" -> "KMS certificate and private key"
+  govc kms.trust -client-cert "$(cat crt.pem) -client-key "$(cat key.pem) my-kp
+
+  # "Download the vCenter certificate and upload it to the KMS"
+  govc about.cert -show > vcenter-cert.pem`
+}
+
+func (cmd *trust) Run(ctx context.Context, f *flag.FlagSet) error {
+	id := f.Arg(0)
+	if id == "" {
+		return flag.ErrHelp
+	}
+
+	c, err := cmd.Client()
+	if err != nil {
+		return err
+	}
+
+	m, err := crypto.GetManagerKmip(c)
+	if err != nil {
+		return err
+	}
+
+	if cmd.client.Certificate != "" {
+		cmd.client.This = m.Reference()
+		cmd.client.Cluster.Id = id
+		_, err = methods.UploadClientCert(ctx, c, &cmd.client)
+		if err != nil {
+			return err
+		}
+	}
+
+	if cmd.server.Certificate != "" {
+		cmd.server.This = m.Reference()
+		cmd.server.Cluster.Id = id
+		_, err = methods.UploadKmipServerCert(ctx, c, &cmd.server)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/govc/main.go b/govc/main.go
index 5fb8de56..e3c4f923 100644
--- a/govc/main.go
+++ b/govc/main.go
@@ -72,6 +72,7 @@ import (
 	_ "github.com/vmware/govmomi/govc/host/vnic"
 	_ "github.com/vmware/govmomi/govc/host/vswitch"
 	_ "github.com/vmware/govmomi/govc/importx"
+	_ "github.com/vmware/govmomi/govc/kms"
 	_ "github.com/vmware/govmomi/govc/library"
 	_ "github.com/vmware/govmomi/govc/library/policy"
 	_ "github.com/vmware/govmomi/govc/library/session"
diff --git a/simulator/crypto_manager_kmip.go b/simulator/crypto_manager_kmip.go
index 3bbe22de..ed0c2db5 100644
--- a/simulator/crypto_manager_kmip.go
+++ b/simulator/crypto_manager_kmip.go
@@ -257,15 +257,28 @@ func (m *CryptoManagerKmip) SetDefaultKmsCluster(
 	return &body
 }
 
+// real vCenter only allows TrustAuthority, but we allow more to simplify test setup
+var validClusterTypes = []string{
+	string(types.KmipClusterInfoKmsManagementTypeTrustAuthority),
+	string(types.KmipClusterInfoKmsManagementTypeUnknown),
+	string(types.KmipClusterInfoKmsManagementTypeNativeProvider),
+}
+
 func (m *CryptoManagerKmip) RegisterKmsCluster(
 	ctx *Context, req *types.RegisterKmsCluster) soap.HasFault {
 
 	var body methods.RegisterKmsClusterBody
 
-	for i := range m.KmipServers {
-		if req.ClusterId.Id == m.KmipServers[i].ClusterId.Id {
-			body.Fault_ = Fault("Already registered", &types.RuntimeFault{})
+	if slices.Contains(validClusterTypes, req.ManagementType) {
+		for i := range m.KmipServers {
+			if req.ClusterId.Id == m.KmipServers[i].ClusterId.Id {
+				body.Fault_ = Fault("Already registered", &types.RuntimeFault{})
+			}
 		}
+	} else {
+		body.Fault_ = Fault("", &types.InvalidArgument{
+			InvalidProperty: "managementType",
+		})
 	}
 	if body.Fault_ == nil {
 		body.Res = &types.RegisterKmsClusterResponse{}
@@ -312,6 +325,11 @@ func (m *CryptoManagerKmip) RegisterKmipServer(
 		body              methods.RegisterKmipServerBody
 	)
 
+	if req.Server.Info.Name == "" {
+		body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "server.info.name"})
+		return &body
+	}
+
 	for i := range m.KmipServers {
 		c := &m.KmipServers[i]
 
@@ -333,11 +351,20 @@ func (m *CryptoManagerKmip) RegisterKmipServer(
 		}
 	}
 
-	if !validClusterID {
-		body.Fault_ = Fault("Invalid cluster ID", &types.RuntimeFault{})
-	} else if alreadyRegistered {
+	if alreadyRegistered {
 		body.Fault_ = Fault("Already registered", &types.RuntimeFault{})
 	} else {
+		if !validClusterID {
+			m.KmipServers = append(m.KmipServers,
+				types.KmipClusterInfo{
+					ClusterId: types.KeyProviderId{
+						Id: req.Server.ClusterId.Id,
+					},
+					ManagementType: string(types.KmipClusterInfoKmsManagementTypeVCenter),
+					Servers:        []types.KmipServerInfo{req.Server.Info},
+				})
+		}
+
 		body.Res = &types.RegisterKmipServerResponse{}
 	}
 


Test patch:
diff --git a/crypto/manager_kmip_test.go b/crypto/manager_kmip_test.go
index ca044f6b..9bd67016 100644
--- a/crypto/manager_kmip_test.go
+++ b/crypto/manager_kmip_test.go
@@ -44,7 +44,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 
 			providerID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -53,7 +53,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.NoError(t, err)
 			assert.True(t, isValid)
 
-			err = m.RegisterKmipCluster(
+			err = m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown)
@@ -73,7 +73,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.EqualError(t, err, "invalid cluster ID")
 			assert.Nil(t, status)
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -96,7 +96,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
 			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -124,7 +124,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.NoError(t, err)
 			assert.False(t, ok)
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -144,15 +144,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2ID := uuid.NewString()
 			provider3ID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -230,14 +230,11 @@ func TestCryptoManagerKmip(t *testing.T) {
 				},
 			}
 
-			err = m.RegisterKmipServer(ctx, serverSpec)
-			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
-			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
-
-			assert.NoError(t, m.RegisterKmipCluster(
+			err = m.RegisterKmsCluster(
 				ctx,
 				providerID,
-				types.KmipClusterInfoKmsManagementTypeUnknown))
+				types.KmipClusterInfoKmsManagementTypeVCenter)
+			assert.True(t, fault.Is(err, &types.InvalidArgument{}))
 
 			assert.NoError(t, m.RegisterKmipServer(ctx, serverSpec))
 
@@ -263,7 +260,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.EqualError(t, err, "invalid cluster ID")
 			assert.Nil(t, status)
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -308,15 +305,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2serverName3 := uuid.NewString()
 			provider3serverName1 := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -454,7 +451,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			providerID := uuid.NewString()
 			serverName := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -524,7 +521,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.EqualError(t, err, "ServerFaultCode: Invalid cluster ID")
 			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -562,7 +559,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			providerID := uuid.NewString()
 			serverName := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -608,15 +605,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2serverName3 := uuid.NewString()
 			provider3serverName1 := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -723,15 +720,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2ID := uuid.NewString()
 			provider3ID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -758,15 +755,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2ID := uuid.NewString()
 			provider3ID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -803,15 +800,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			provider2ID := uuid.NewString()
 			provider3ID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider1ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider2ID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				provider3ID,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -851,7 +848,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			providerID1 := uuid.NewString()
 			providerID2 := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID1,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -869,7 +866,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.True(t, fault.Is(err, &types.RuntimeFault{}))
 			assert.Empty(t, keyID)
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID2,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -899,15 +896,15 @@ func TestCryptoManagerKmip(t *testing.T) {
 			assert.NoError(t, err)
 			assert.Len(t, keys, 0)
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID1,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID2,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID3,
 				types.KmipClusterInfoKmsManagementTypeNativeProvider))
@@ -979,7 +976,7 @@ func TestCryptoManagerKmip(t *testing.T) {
 
 			providerID := uuid.NewString()
 
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
diff --git a/govc/test/kms.bats b/govc/test/kms.bats
new file mode 100755
index 00000000..071ed9b9
--- /dev/null
+++ b/govc/test/kms.bats
@@ -0,0 +1,55 @@
+#!/usr/bin/env bats
+
+load test_helper
+
+@test "kms" {
+  vcsim_env
+
+  run govc kms.ls
+  assert_success
+
+  run govc kms.ls -json
+  assert_success
+
+  run govc kms.ls enoent
+  assert_failure
+
+  host=$(govc env -x GOVC_URL_HOST)
+
+  run govc kms.add vcsim-kp
+  assert_failure # InvalidProperty server.info.name
+
+  run govc kms.add -n my-server -a "$host" vcsim-kp
+  assert_success
+
+  run govc kms.add -n my-server vcsim-kp
+  assert_failure # already registered
+
+  run govc kms.ls
+  assert_success
+  assert_matches vcsim-kp
+
+  run govc kms.ls vcsim-kp
+  assert_success
+
+  run govc kms.default
+  assert_failure
+
+  run govc kms.default vcsim-kp
+  assert_success
+
+  run govc kms.default -
+  assert_success
+
+  run govc kms.rm -s my-server vcsim-kp
+  assert_success
+
+  run govc kms.add -n my-server -a "$host" vcsim-kp
+  assert_success
+
+  run govc kms.rm vcsim-kp
+  assert_success
+
+  run govc kms.rm vcsim-kp
+  assert_failure # does not exist
+}
diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go
index 3bbe4313..30079b90 100644
--- a/simulator/virtual_machine_test.go
+++ b/simulator/virtual_machine_test.go
@@ -3423,7 +3423,7 @@ func TestCreateVmWithDefaultKeyProvider(t *testing.T) {
 		Test(func(ctx context.Context, c *vim25.Client) {
 			providerID := uuid.NewString()
 			m := crypto.NewManagerKmip(c)
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -3495,7 +3495,7 @@ func TestCreateVmWithDefaultKeyProvider(t *testing.T) {
 		Test(func(ctx context.Context, c *vim25.Client) {
 			providerID := uuid.NewString()
 			m := crypto.NewManagerKmip(c)
-			assert.NoError(t, m.RegisterKmipCluster(
+			assert.NoError(t, m.RegisterKmsCluster(
 				ctx,
 				providerID,
 				types.KmipClusterInfoKmsManagementTypeUnknown))
@@ -3557,7 +3557,7 @@ func TestCreateVmWithGeneratedKey(t *testing.T) {
 	Test(func(ctx context.Context, c *vim25.Client) {
 		providerID := uuid.NewString()
 		m := crypto.NewManagerKmip(c)
-		assert.NoError(t, m.RegisterKmipCluster(
+		assert.NoError(t, m.RegisterKmsCluster(
 			ctx,
 			providerID,
 			types.KmipClusterInfoKmsManagementTypeUnknown))