diff --git a/simulator/crypto_manager_kmip.go b/simulator/crypto_manager_kmip.go index 021ce218e..3bbe22de8 100644 --- a/simulator/crypto_manager_kmip.go +++ b/simulator/crypto_manager_kmip.go @@ -509,3 +509,79 @@ func (m *CryptoManagerKmip) ListKeys( return &body } + +func getDefaultProvider( + ctx *Context, + vm *VirtualMachine, + generateKey bool) (string, string) { + + m := ctx.Map.CryptoManager() + if m == nil { + return "", "" + } + + var ( + providerID string + keyID string + ) + + ctx.WithLock(m, func() { + // Lookup the default provider ID via the VM's parent entities: + // host, host folder, cluster. + if host := vm.Runtime.Host; host != nil { + for i := range m.KmipServers { + kmipCluster := m.KmipServers[i] + for j := range kmipCluster.UseAsEntityDefault { + parent := host + for providerID == "" && parent != nil { + if kmipCluster.UseAsEntityDefault[j] == *parent { + providerID = kmipCluster.ClusterId.Id + break + } else { + // TODO (akutz): Support looking up the + // default entity via the host + // folder and cluster. + parent = nil + } + } + if providerID != "" { + break + } + } + if providerID != "" { + break + } + } + } + + // If the default provider ID has not been discovered, see if + // any of the providers are the global default. + if providerID == "" { + for i := range m.KmipServers { + if providerID == "" && m.KmipServers[i].UseAsDefault { + providerID = m.KmipServers[i].ClusterId.Id + break + } + } + } + }) + + if providerID != "" && generateKey { + keyID = generateKeyForProvider(ctx, providerID) + } + + return providerID, keyID +} + +func generateKeyForProvider(ctx *Context, providerID string) string { + m := ctx.Map.CryptoManager() + if m == nil { + return "" + } + var keyID string + ctx.WithLock(m, func() { + keyID = uuid.NewString() + m.keyIDToProviderID[keyID] = providerID + }) + return keyID +} diff --git a/simulator/folder.go b/simulator/folder.go index 74b9ac604..b65917e40 100644 --- a/simulator/folder.go +++ b/simulator/folder.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. +Copyright (c) 2017-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 +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, @@ -402,6 +402,32 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) { vm.Runtime.Host = c.req.Host } + if cryptoSpec, ok := c.req.Config.Crypto.(*types.CryptoSpecEncrypt); ok { + if cryptoSpec.CryptoKeyId.KeyId == "" { + if cryptoSpec.CryptoKeyId.ProviderId == nil { + providerID, keyID := getDefaultProvider(c.ctx, vm, true) + if providerID == "" { + return nil, &types.InvalidVmConfig{Property: "configSpec.crypto"} + } + vm.Config.KeyId = &types.CryptoKeyId{ + KeyId: keyID, + ProviderId: &types.KeyProviderId{ + Id: providerID, + }, + } + } else { + providerID := cryptoSpec.CryptoKeyId.ProviderId.Id + keyID := generateKeyForProvider(c.ctx, providerID) + vm.Config.KeyId = &types.CryptoKeyId{ + KeyId: keyID, + ProviderId: &types.KeyProviderId{ + Id: providerID, + }, + } + } + } + } + vm.Guest = &types.GuestInfo{ ToolsStatus: types.VirtualMachineToolsStatusToolsNotInstalled, ToolsVersion: "0", diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go index fb5d3d30d..2ba287ee4 100644 --- a/simulator/virtual_machine.go +++ b/simulator/virtual_machine.go @@ -1662,23 +1662,32 @@ func (vm *VirtualMachine) updateCrypto( if err := assertEncrypted(); err != nil { return err } + var providerID *types.KeyProviderId - if pid := vm.Config.KeyId.ProviderId; pid != nil { + if pid := newKeyID.ProviderId; pid != nil { providerID = &types.KeyProviderId{ Id: pid.Id, } } - if pid := newKeyID.ProviderId; pid != nil { - providerID = &types.KeyProviderId{ - Id: pid.Id, + + keyID := newKeyID.KeyId + if providerID == nil { + if p, k := getDefaultProvider(ctx, vm, true); p != "" && k != "" { + providerID = &types.KeyProviderId{ + Id: p, + } + keyID = k } + } else if keyID == "" { + keyID = generateKeyForProvider(ctx, providerID.Id) } + ctx.Map.Update(vm, []types.PropertyChange{ { Name: configKeyId, Op: types.PropertyChangeOpAssign, Val: &types.CryptoKeyId{ - KeyId: newKeyID.KeyId, + KeyId: keyID, ProviderId: providerID, }, }, @@ -1738,12 +1747,24 @@ func (vm *VirtualMachine) updateCrypto( } } + keyID := tspec.CryptoKeyId.KeyId + if providerID == nil { + if p, k := getDefaultProvider(ctx, vm, true); p != "" && k != "" { + providerID = &types.KeyProviderId{ + Id: p, + } + keyID = k + } + } else if keyID == "" { + keyID = generateKeyForProvider(ctx, providerID.Id) + } + ctx.Map.Update(vm, []types.PropertyChange{ { Name: configKeyId, Op: types.PropertyChangeOpAssign, Val: &types.CryptoKeyId{ - KeyId: tspec.CryptoKeyId.KeyId, + KeyId: keyID, ProviderId: providerID, }, }, diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go index 3309e2842..3bbe43132 100644 --- a/simulator/virtual_machine_test.go +++ b/simulator/virtual_machine_test.go @@ -25,9 +25,12 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/vmware/govmomi" + "github.com/vmware/govmomi/crypto" + "github.com/vmware/govmomi/fault" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" @@ -2681,6 +2684,7 @@ func TestEncryptDecryptVM(t *testing.T) { name string initStateFn func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error configSpec types.VirtualMachineConfigSpec + isGeneratedKey bool expectedCryptoKeyId *types.CryptoKeyId expectedErr error }{ @@ -2703,6 +2707,49 @@ func TestEncryptDecryptVM(t *testing.T) { }, }, }, + { + name: "encrypt w default key provider", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecEncrypt{}, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + UseAsDefault: true, + }) + }) + return nil + }, + }, + { + name: "encrypt w generated key", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecEncrypt{ + CryptoKeyId: types.CryptoKeyId{ + ProviderId: &types.KeyProviderId{ + Id: "key-provider", + }, + }, + }, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + }) + }) + return nil + }, + }, { name: "encrypt w already encrypted", initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { @@ -2877,6 +2924,65 @@ func TestEncryptDecryptVM(t *testing.T) { }, }, }, + { + name: "deep recrypt w default key provider", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecDeepRecrypt{}, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, vmRef, func() { + Map.Get(vmRef).(*VirtualMachine).Config.KeyId = &types.CryptoKeyId{ + KeyId: "123", + ProviderId: &types.KeyProviderId{ + Id: "abc", + }, + } + }) + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + UseAsDefault: true, + }) + }) + return nil + }, + }, + { + name: "deep recrypt w generated key", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecDeepRecrypt{ + NewKeyId: types.CryptoKeyId{ + ProviderId: &types.KeyProviderId{ + Id: "key-provider", + }, + }, + }, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, vmRef, func() { + Map.Get(vmRef).(*VirtualMachine).Config.KeyId = &types.CryptoKeyId{ + KeyId: "key", + ProviderId: &types.KeyProviderId{ + Id: "key-provider", + }, + } + }) + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + }) + }) + return nil + }, + }, { name: "deep recrypt w same provider id", initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { @@ -2894,6 +3000,9 @@ func TestEncryptDecryptVM(t *testing.T) { Crypto: &types.CryptoSpecDeepRecrypt{ NewKeyId: types.CryptoKeyId{ KeyId: "456", + ProviderId: &types.KeyProviderId{ + Id: "abc", + }, }, }, }, @@ -3010,6 +3119,65 @@ func TestEncryptDecryptVM(t *testing.T) { }, }, }, + { + name: "shallow recrypt w default key provider", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecShallowRecrypt{}, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, vmRef, func() { + Map.Get(vmRef).(*VirtualMachine).Config.KeyId = &types.CryptoKeyId{ + KeyId: "123", + ProviderId: &types.KeyProviderId{ + Id: "abc", + }, + } + }) + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + UseAsDefault: true, + }) + }) + return nil + }, + }, + { + name: "shallow recrypt w generated key", + configSpec: types.VirtualMachineConfigSpec{ + Crypto: &types.CryptoSpecShallowRecrypt{ + NewKeyId: types.CryptoKeyId{ + ProviderId: &types.KeyProviderId{ + Id: "key-provider", + }, + }, + }, + }, + isGeneratedKey: true, + initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { + Map.WithLock(ctx, vmRef, func() { + Map.Get(vmRef).(*VirtualMachine).Config.KeyId = &types.CryptoKeyId{ + KeyId: "key", + ProviderId: &types.KeyProviderId{ + Id: "key-provider", + }, + } + }) + Map.WithLock(ctx, ctx.Map.CryptoManager(), func() { + m := ctx.Map.CryptoManager() + m.KmipServers = append(m.KmipServers, types.KmipClusterInfo{ + ClusterId: types.KeyProviderId{ + Id: "key-provider", + }, + }) + }) + return nil + }, + }, { name: "shallow recrypt w same provider id", initStateFn: func(ctx *Context, c *vim25.Client, vmRef types.ManagedObjectReference) error { @@ -3027,6 +3195,9 @@ func TestEncryptDecryptVM(t *testing.T) { Crypto: &types.CryptoSpecShallowRecrypt{ NewKeyId: types.CryptoKeyId{ KeyId: "456", + ProviderId: &types.KeyProviderId{ + Id: "abc", + }, }, }, }, @@ -3210,7 +3381,6 @@ func TestEncryptDecryptVM(t *testing.T) { model.Host = 1 Test(func(ctx context.Context, c *vim25.Client) { - ref := Map.Any("VirtualMachine").Reference() vm := object.NewVirtualMachine(c, ref) @@ -3233,7 +3403,10 @@ func TestEncryptDecryptVM(t *testing.T) { if err := vm.Properties(ctx, ref, []string{"config.keyId"}, &moVM); err != nil { t.Fatalf("fetching properties failed: %v", err) } - if tc.expectedCryptoKeyId != nil { + if tc.isGeneratedKey { + assert.NotEmpty(t, moVM.Config.KeyId.KeyId) + assert.Equal(t, "key-provider", moVM.Config.KeyId.ProviderId.Id) + } else if tc.expectedCryptoKeyId != nil { assert.Equal(t, tc.expectedCryptoKeyId, moVM.Config.KeyId) } else { assert.Nil(t, moVM.Config) @@ -3243,3 +3416,215 @@ func TestEncryptDecryptVM(t *testing.T) { }) } } + +func TestCreateVmWithDefaultKeyProvider(t *testing.T) { + + t.Run("when default key provider exists", func(t *testing.T) { + Test(func(ctx context.Context, c *vim25.Client) { + providerID := uuid.NewString() + m := crypto.NewManagerKmip(c) + assert.NoError(t, m.RegisterKmipCluster( + ctx, + providerID, + types.KmipClusterInfoKmsManagementTypeUnknown)) + assert.NoError(t, m.SetDefaultKmsClusterId(ctx, providerID, nil)) + + finder := find.NewFinder(c, false) + + dc, err := finder.DefaultDatacenter(ctx) + assert.NoError(t, err) + assert.NotNil(t, dc) + finder.SetDatacenter(dc) + + ds, err := finder.DefaultDatastore(ctx) + assert.NoError(t, err) + assert.NotNil(t, ds) + + folders, err := dc.Folders(ctx) + assert.NoError(t, err) + assert.NotNil(t, folders) + assert.NotNil(t, folders.VmFolder) + + hosts, err := finder.HostSystemList(ctx, "*/*") + assert.NoError(t, err) + assert.NotEmpty(t, hosts) + + host := hosts[rand.Intn(len(hosts))] + pool, err := host.ResourcePool(ctx) + assert.NoError(t, err) + assert.NotNil(t, pool) + + tsk, err := folders.VmFolder.CreateVM( + ctx, + types.VirtualMachineConfigSpec{ + Name: "test", + Files: &types.VirtualMachineFileInfo{ + VmPathName: fmt.Sprintf("[%s] test/test.vmx", ds.Name()), + }, + GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), + Crypto: &types.CryptoSpecEncrypt{}, + }, + pool, + host) + assert.NoError(t, err) + assert.NotNil(t, tsk) + + tskInfo, err := tsk.WaitForResult(ctx) + assert.NoError(t, err) + assert.NotNil(t, tskInfo) + assert.Nil(t, tskInfo.Error) + assert.NotNil(t, tskInfo.Result) + + vmRef, ok := tskInfo.Result.(types.ManagedObjectReference) + assert.True(t, ok) + + vm := object.NewVirtualMachine(c, vmRef) + + var moVM mo.VirtualMachine + assert.NoError(t, vm.Properties(ctx, vmRef, []string{"config.keyId"}, &moVM)) + + assert.NotNil(t, moVM.Config) + assert.NotNil(t, moVM.Config.KeyId) + assert.NotNil(t, moVM.Config.KeyId.ProviderId) + assert.Equal(t, providerID, moVM.Config.KeyId.ProviderId.Id) + assert.NotEmpty(t, moVM.Config.KeyId.KeyId) + }) + }) + + t.Run("when default key provider does not exist", func(t *testing.T) { + Test(func(ctx context.Context, c *vim25.Client) { + providerID := uuid.NewString() + m := crypto.NewManagerKmip(c) + assert.NoError(t, m.RegisterKmipCluster( + ctx, + providerID, + types.KmipClusterInfoKmsManagementTypeUnknown)) + + finder := find.NewFinder(c, false) + + dc, err := finder.DefaultDatacenter(ctx) + assert.NoError(t, err) + assert.NotNil(t, dc) + finder.SetDatacenter(dc) + + ds, err := finder.DefaultDatastore(ctx) + assert.NoError(t, err) + assert.NotNil(t, ds) + + folders, err := dc.Folders(ctx) + assert.NoError(t, err) + assert.NotNil(t, folders) + assert.NotNil(t, folders.VmFolder) + + hosts, err := finder.HostSystemList(ctx, "*/*") + assert.NoError(t, err) + assert.NotEmpty(t, hosts) + + host := hosts[rand.Intn(len(hosts))] + pool, err := host.ResourcePool(ctx) + assert.NoError(t, err) + assert.NotNil(t, pool) + + tsk, err := folders.VmFolder.CreateVM( + ctx, + types.VirtualMachineConfigSpec{ + Name: "test", + Files: &types.VirtualMachineFileInfo{ + VmPathName: fmt.Sprintf("[%s] test/test.vmx", ds.Name()), + }, + GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), + Crypto: &types.CryptoSpecEncrypt{}, + }, + pool, + host) + assert.NoError(t, err) + assert.NotNil(t, tsk) + + _, err = tsk.WaitForResult(ctx) + assert.Error(t, err) + + var flt *types.InvalidVmConfig + _, ok := fault.As(err, &flt) + assert.True(t, ok) + assert.NotNil(t, flt) + assert.Equal(t, "configSpec.crypto", flt.Property) + }) + }) +} + +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( + ctx, + providerID, + types.KmipClusterInfoKmsManagementTypeUnknown)) + finder := find.NewFinder(c, false) + + dc, err := finder.DefaultDatacenter(ctx) + assert.NoError(t, err) + assert.NotNil(t, dc) + finder.SetDatacenter(dc) + + ds, err := finder.DefaultDatastore(ctx) + assert.NoError(t, err) + assert.NotNil(t, ds) + + folders, err := dc.Folders(ctx) + assert.NoError(t, err) + assert.NotNil(t, folders) + assert.NotNil(t, folders.VmFolder) + + hosts, err := finder.HostSystemList(ctx, "*/*") + assert.NoError(t, err) + assert.NotEmpty(t, hosts) + + host := hosts[rand.Intn(len(hosts))] + pool, err := host.ResourcePool(ctx) + assert.NoError(t, err) + assert.NotNil(t, pool) + + tsk, err := folders.VmFolder.CreateVM( + ctx, + types.VirtualMachineConfigSpec{ + Name: "test", + Files: &types.VirtualMachineFileInfo{ + VmPathName: fmt.Sprintf("[%s] test/test.vmx", ds.Name()), + }, + GuestId: string(types.VirtualMachineGuestOsIdentifierOtherGuest), + Crypto: &types.CryptoSpecEncrypt{ + CryptoKeyId: types.CryptoKeyId{ + ProviderId: &types.KeyProviderId{ + Id: providerID, + }, + }, + }, + }, + pool, + host) + assert.NoError(t, err) + assert.NotNil(t, tsk) + + tskInfo, err := tsk.WaitForResult(ctx) + assert.NoError(t, err) + assert.NotNil(t, tskInfo) + assert.Nil(t, tskInfo.Error) + assert.NotNil(t, tskInfo.Result) + + vmRef, ok := tskInfo.Result.(types.ManagedObjectReference) + assert.True(t, ok) + + vm := object.NewVirtualMachine(c, vmRef) + + var moVM mo.VirtualMachine + assert.NoError(t, vm.Properties(ctx, vmRef, []string{"config.keyId"}, &moVM)) + + assert.NotNil(t, moVM.Config) + assert.NotNil(t, moVM.Config.KeyId) + assert.NotNil(t, moVM.Config.KeyId.ProviderId) + assert.Equal(t, providerID, moVM.Config.KeyId.ProviderId.Id) + assert.NotEmpty(t, moVM.Config.KeyId.KeyId) + }) +}