Skip to content

Commit

Permalink
api: add vapi/crypto package with support for native KMS
Browse files Browse the repository at this point in the history
  • Loading branch information
dougm committed Sep 25, 2024
1 parent d2ccadf commit d95d350
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 0 deletions.
21 changes: 21 additions & 0 deletions crypto/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
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 crypto provides access to CryptoManagerKmip methods used to manage cryptographic key providers.
For creating and delete native providers, see package vapi/crypto.
*/
package crypto
104 changes: 104 additions & 0 deletions vapi/crypto/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
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 crypto

import (
"context"
"fmt"
"net/http"

"github.com/vmware/govmomi/vapi/crypto/internal"
"github.com/vmware/govmomi/vapi/rest"
)

// Manager extends rest.Client, adding crypto related methods.
// Currently providing create, delete and export only.
// See crypto.ManagerKmip for getting provider details.
type Manager struct {
*rest.Client
}

// NewManager creates a new Manager instance with the given client.
func NewManager(client *rest.Client) *Manager {
return &Manager{
Client: client,
}
}

type KmsProviderConstraints struct {
TpmRequired bool `json:"tpm_required"`
}

type KmsProviderCreateSpec struct {
Provider string `json:"provider"`
Constraints KmsProviderConstraints `json:"constraints"`
}

type KmsProviderExportSpec struct {
Provider string `json:"provider"`
Password string `json:"password,omitempty"`
}

type KmsProviderDownloadToken struct {
Token string `json:"token"`
Expiry string `json:"expiry"`
}

type KmsProviderExportLocation struct {
URL string `json:"url"`
DownloadToken KmsProviderDownloadToken `json:"download_token"`
}

type KmsProviderExport struct {
Type string `json:"type"`
Location *KmsProviderExportLocation `json:"location,omitempty"`
}

func (c *Manager) KmsProviderCreate(ctx context.Context, spec KmsProviderCreateSpec) error {
resource := c.Resource(internal.KmsProvidersPath)
request := resource.Request(http.MethodPost, spec)
return c.Do(ctx, request, nil)
}

func (c *Manager) KmsProviderDelete(ctx context.Context, provider string) error {
resource := c.Resource(internal.KmsProvidersPath).WithSubpath(provider)
request := resource.Request(http.MethodDelete)
return c.Do(ctx, request, nil)
}

func (c *Manager) KmsProviderExport(ctx context.Context, spec KmsProviderExportSpec) (*KmsProviderExport, error) {
resource := c.Resource(internal.KmsProvidersPath).WithParam("action", "export")
request := resource.Request(http.MethodPost, spec)

var res KmsProviderExport
if err := c.Do(ctx, request, &res); err != nil {
return nil, err
}

return &res, nil
}

func (c *Manager) KmsProviderExportRequest(ctx context.Context, export *KmsProviderExportLocation) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, export.URL, nil)
if err != nil {
return nil, err
}

req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", export.DownloadToken.Token))

return req, nil
}
23 changes: 23 additions & 0 deletions vapi/crypto/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
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 vapi/crypto provides access to the Crypto Manager REST APIs that are not available in the SOAP API.
Currently for creating and deleting native providers only.
See the top-level package crypto for getting provider details via crypto.ManagerKmip.
See also: https://blogs.vmware.com/code/2023/07/30/automate-vsphere-native-key-providers/
*/
package crypto
21 changes: 21 additions & 0 deletions vapi/crypto/internal/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
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 internal

const (
KmsProvidersPath = "/api/vcenter/crypto-manager/kms/providers"
)
183 changes: 183 additions & 0 deletions vapi/crypto/simulator/simulator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
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 simulator

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"time"

"github.com/google/uuid"

"github.com/vmware/govmomi/simulator"
"github.com/vmware/govmomi/vapi/crypto"
"github.com/vmware/govmomi/vapi/crypto/internal"
vapi "github.com/vmware/govmomi/vapi/simulator"
"github.com/vmware/govmomi/vim25/types"
)

const (
typeNativeProvider = string(types.KmipClusterInfoKmsManagementTypeNativeProvider)
backupPath = "/cryptomanager/kms/"
)

func init() {
simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) {
New(r, s.Listen).Register(s, r)
})
}

// Handler implements the Cluster Modules API simulator
type Handler struct {
URL *url.URL
Map *simulator.Registry
}

// New creates a Handler instance
func New(r *simulator.Registry, u *url.URL) *Handler {
return &Handler{
Map: r,
URL: u,
}
}

// Register Namespace Management API paths with the vapi simulator's http.ServeMux
func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) {
if r.IsVPX() {
s.HandleFunc(internal.KmsProvidersPath, h.providers)
s.HandleFunc(internal.KmsProvidersPath+"/", h.providersID)
s.HandleFunc(backupPath, h.backup)
}
}

// We need to use the simulator objects directly when updating fields (e.g. HasBackup)
// Skipping the trouble of locking for now, as existing use-cases would not race.
func (h *Handler) find(id string) *types.KmipClusterInfo {
m := h.Map.CryptoManager()
for i := range m.KmipServers {
p := &m.KmipServers[i]
if p.ClusterId.Id == id {
return p
}
}
return nil
}

func (h *Handler) backup(w http.ResponseWriter, r *http.Request) {
id := path.Base(r.RequestURI)
p := h.find(id)
if p == nil {
vapi.ApiErrorNotFound(w)
return
}

// Content of the simulated backup does not matter for the use-case we're covering:
// Export sets HasBackup=true, which sets CryptoManagerKmipClusterStatus.OverallStatus=green
p.HasBackup = types.NewBool(true)

name := fmt.Sprintf("%s%s.p12", id, time.Now().Format(time.RFC3339))

w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name))
_ = json.NewEncoder(w).Encode(p)
}

func (h *Handler) providers(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
switch r.URL.Query().Get("action") {
case "":
var spec crypto.KmsProviderCreateSpec

if vapi.Decode(r, w, &spec) {
if h.find(spec.Provider) != nil {
vapi.ApiErrorAlreadyExists(w)
return
}
}

m := h.Map.CryptoManager()
m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{
ClusterId: types.KeyProviderId{Id: spec.Provider},
ManagementType: typeNativeProvider,
TpmRequired: &spec.Constraints.TpmRequired,
HasBackup: types.NewBool(false),
})
case "export":
var spec crypto.KmsProviderExportSpec
var p *types.KmipClusterInfo

if vapi.Decode(r, w, &spec) {
if p = h.find(spec.Provider); p == nil {
vapi.ApiErrorNotFound(w)
return
}
if p.ManagementType != typeNativeProvider {
vapi.ApiErrorUnsupported(w)
return
}

u := url.URL{
Scheme: h.URL.Scheme,
Host: h.URL.Host,
Path: backupPath + spec.Provider,
}

res := crypto.KmsProviderExport{
Type: "LOCATION",
Location: &crypto.KmsProviderExportLocation{
URL: u.String(),
DownloadToken: crypto.KmsProviderDownloadToken{
Token: uuid.NewString(),
Expiry: time.Now().Add(time.Minute).Format(time.RFC3339),
},
},
}

vapi.StatusOK(w, res)
}
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}

func (h *Handler) providersID(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodDelete:
id := path.Base(r.RequestURI)
p := h.find(id)
if p == nil {
vapi.ApiErrorNotFound(w)
return
}
if p.ManagementType != typeNativeProvider {
vapi.ApiErrorUnsupported(w)
return
}
m := h.Map.CryptoManager()
_ = m.UnregisterKmsCluster(simulator.SpoofContext(), &types.UnregisterKmsCluster{
This: m.Self,
ClusterId: types.KeyProviderId{Id: id},
})
vapi.StatusOK(w)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
1 change: 1 addition & 0 deletions vcsim/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
_ "github.com/vmware/govmomi/vapi/appliance/simulator"
_ "github.com/vmware/govmomi/vapi/cis/tasks/simulator"
_ "github.com/vmware/govmomi/vapi/cluster/simulator"
_ "github.com/vmware/govmomi/vapi/crypto/simulator"
_ "github.com/vmware/govmomi/vapi/esx/settings/simulator"
_ "github.com/vmware/govmomi/vapi/namespace/simulator"
_ "github.com/vmware/govmomi/vapi/simulator"
Expand Down

0 comments on commit d95d350

Please sign in to comment.