From ccf449e4aea848cd6ff290e6d32425960b1541f5 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 17 Sep 2024 20:15:34 -0700 Subject: [PATCH] govc: add kms commands --- govc/USAGE.md | 92 +++++++++++++++++++ govc/kms/add.go | 84 ++++++++++++++++++ govc/kms/ls.go | 196 +++++++++++++++++++++++++++++++++++++++++ govc/kms/rm.go | 78 ++++++++++++++++ govc/kms/setdefault.go | 92 +++++++++++++++++++ govc/kms/trust.go | 103 ++++++++++++++++++++++ govc/main.go | 1 + govc/test/kms.bats | 55 ++++++++++++ 8 files changed, 701 insertions(+) create mode 100644 govc/kms/add.go create mode 100644 govc/kms/ls.go create mode 100644 govc/kms/rm.go create mode 100644 govc/kms/setdefault.go create mode 100644 govc/kms/trust.go create mode 100755 govc/test/kms.bats diff --git a/govc/USAGE.md b/govc/USAGE.md index 3c5a6c5f7..f4f597808 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 000000000..129638325 --- /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 000000000..466316f47 --- /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 000000000..ef7ad9e80 --- /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 000000000..989fadc29 --- /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 000000000..e51d0c4e7 --- /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 5fb8de568..e3c4f923f 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/govc/test/kms.bats b/govc/test/kms.bats new file mode 100755 index 000000000..071ed9b99 --- /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 +}