-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add capability for TPM support * Use dedicated HGS key protector for eryph Closes #268
- Loading branch information
1 parent
54ead70
commit bb9d6a6
Showing
16 changed files
with
550 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/core/src/Eryph.VmConfig.Primitives/Resources/Machines/VirtualMachineSecurityData.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Eryph.Resources.Machines; | ||
|
||
public class VirtualMachineSecurityData | ||
{ | ||
public bool TpmEnabled { get; init; } | ||
|
||
public bool KsdEnabled { get; init; } | ||
|
||
public bool Shielded { get; init; } | ||
|
||
public bool EncryptStateAndVmMigrationTraffic { get; init; } | ||
|
||
public bool VirtualizationBasedSecurityOptOut { get; init; } | ||
|
||
public bool BindToHostTpm { get; init; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
src/core/src/Eryph.VmManagement/Converging/ConvergeTpm.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Eryph.ConfigModel.Catlets; | ||
using Eryph.Core; | ||
using Eryph.VmManagement.Data.Core; | ||
using Eryph.VmManagement.Data.Full; | ||
using LanguageExt; | ||
using LanguageExt.Common; | ||
|
||
using static LanguageExt.Prelude; | ||
|
||
namespace Eryph.VmManagement.Converging; | ||
|
||
/// <summary> | ||
/// This task converges the settings for the TPM. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// The TPM can only be enabled after creating a key protector | ||
/// with <c>Set-VMKeyProtector</c>. | ||
/// </para> | ||
/// <para> | ||
/// The key protector itself is protected by an HGS guardian. | ||
/// We create a dedicated one for eryph with <c>New-HgsGuardian</c>. | ||
/// The guardian stores a signing and an encryption certificate | ||
/// in the computer certificate store. These certificates (and | ||
/// their private keys) are required to access the TPMs inside | ||
/// the catlets. | ||
/// </para> | ||
/// </remarks> | ||
public class ConvergeTpm(ConvergeContext context) : ConvergeTaskBase(context) | ||
{ | ||
public override Task<Either<Error, TypedPsObject<VirtualMachineInfo>>> Converge( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
ConvergeTpmState(vmInfo).ToEither(); | ||
|
||
private EitherAsync<Error, TypedPsObject<VirtualMachineInfo>> ConvergeTpmState( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let tpmCapability = Context.Config.Capabilities.ToSeq() | ||
.Find(c => c.Name == EryphConstants.Capabilities.Tpm) | ||
let expectedTpmState = tpmCapability.Map(IsEnabled).IfNone(false) | ||
from vmSecurityInfo in GetVmSecurityInfo(vmInfo) | ||
let currentTpmState = vmSecurityInfo.TpmEnabled | ||
from __ in expectedTpmState == currentTpmState | ||
? RightAsync<Error, Unit>(unit) | ||
: ConfigureTpm(vmInfo, expectedTpmState) | ||
from updatedVmInfo in vmInfo.RecreateOrReload(Context.Engine) | ||
select updatedVmInfo; | ||
|
||
private EitherAsync<Error, VMSecurityInfo> GetVmSecurityInfo( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("Get-VMSecurity") | ||
.AddParameter("VM", vmInfo.PsObject) | ||
from vmSecurityInfos in Context.Engine.GetObjectValuesAsync<VMSecurityInfo>(command) | ||
.ToError() | ||
from vMSecurityInfo in vmSecurityInfos.HeadOrNone() | ||
.ToEitherAsync(Error.New($"Failed to fetch security information for the VM {vmInfo.Value.Id}.")) | ||
select vMSecurityInfo; | ||
|
||
private EitherAsync<Error, Unit> ConfigureTpm( | ||
TypedPsObject<VirtualMachineInfo> vmInfo, | ||
bool enableTpm) => | ||
enableTpm ? EnableTpm(vmInfo) : DisableTpm(vmInfo); | ||
|
||
private EitherAsync<Error, Unit> EnableTpm( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from _ in EnsureKeyProtector(vmInfo) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("Enable-VMTPM") | ||
.AddParameter("VM", vmInfo.PsObject) | ||
from __ in Context.Engine.RunAsync(command).ToError().ToAsync() | ||
select unit; | ||
|
||
private EitherAsync<Error, Unit> EnsureKeyProtector( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let getCommand = PsCommandBuilder.Create() | ||
.AddCommand("Get-VMKeyProtector") | ||
.AddParameter("VM", vmInfo.PsObject) | ||
from vmKeyProtectors in Context.Engine.GetObjectValuesAsync<byte[]>(getCommand) | ||
.ToError() | ||
let hasKeyProtector = vmKeyProtectors.HeadOrNone() | ||
// Get-VMKeyProtector returns the protector as a byte array. When a proper | ||
// protector exists, the byte array contains XML describing the protector. | ||
// Even when no protector exists, Hyper-V returns a short byte array (e.g. | ||
// [0, 0, 0, 4]). Hence, we just check for a minimal length. | ||
.Filter(p => p.Length >= 16) | ||
.IsSome | ||
// We cannot change the key protector when one is present as this would brick the | ||
// TPM and prevent the VM from starting. When the user manually enabled the TPM | ||
// with a different protector, we just need to keep that protector. | ||
from __ in hasKeyProtector | ||
? RightAsync<Error, Unit>(unit) | ||
: CreateKeyProtector(vmInfo) | ||
select unit; | ||
|
||
private EitherAsync<Error, Unit> CreateKeyProtector( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from guardian in EnsureHgsGuardian() | ||
let createCommand = PsCommandBuilder.Create() | ||
.AddCommand("New-HgsKeyProtector") | ||
.AddParameter("Owner", guardian.PsObject) | ||
// AllowUntrustedRoot is required as we use an HSG guardian with locally | ||
// generated certificates which are self-signed. | ||
.AddParameter("AllowUntrustedRoot") | ||
from protectors in Context.Engine.GetObjectsAsync<CimHgsKeyProtector>(createCommand) | ||
.ToError().ToAsync() | ||
from protector in protectors.HeadOrNone() | ||
.ToEitherAsync(Error.New("Failed to create HGS key protector.")) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("Set-VMKeyProtector") | ||
.AddParameter("VM", vmInfo.PsObject) | ||
.AddParameter("KeyProtector", protector.Value.RawData) | ||
from _ in Context.Engine.RunAsync(command).ToError().ToAsync() | ||
select unit; | ||
|
||
private EitherAsync<Error, TypedPsObject<CimHgsGuardian>> EnsureHgsGuardian() => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("Get-HgsGuardian") | ||
from existingGuardians in Context.Engine.GetObjectsAsync<CimHgsGuardian>(command) | ||
.ToError() | ||
.ToAsync() | ||
from guardian in existingGuardians | ||
.Find(g => g.Value.Name == EryphConstants.HgsGuardianName) | ||
.Match(Some: g => g, None: CreateHgsGuardian) | ||
select guardian; | ||
|
||
private EitherAsync<Error, TypedPsObject<CimHgsGuardian>> CreateHgsGuardian() => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("New-HgsGuardian") | ||
.AddParameter("Name", EryphConstants.HgsGuardianName) | ||
.AddParameter("GenerateCertificates") | ||
from results in Context.Engine.GetObjectsAsync<CimHgsGuardian>(command) | ||
.ToError().ToAsync() | ||
from guardian in results.HeadOrNone() | ||
.ToEitherAsync(Error.New("Failed to create HGS guardian.")) | ||
select guardian; | ||
|
||
private EitherAsync<Error, Unit> DisableTpm( | ||
TypedPsObject<VirtualMachineInfo> vmInfo) => | ||
from _ in RightAsync<Error, Unit>(unit) | ||
let command = PsCommandBuilder.Create() | ||
.AddCommand("Disable-VMTPM") | ||
.AddParameter("VM", vmInfo.PsObject) | ||
from __ in Context.Engine.RunAsync(command).ToError().ToAsync() | ||
select unit; | ||
|
||
private static bool IsEnabled(CatletCapabilityConfig capabilityConfig) => | ||
capabilityConfig.Details.ToSeq() | ||
.All(d => !string.Equals(d, EryphConstants.CapabilityDetails.Disabled, StringComparison.OrdinalIgnoreCase)); | ||
} |
24 changes: 24 additions & 0 deletions
24
src/core/src/Eryph.VmManagement/Data/Core/CimHgsGuardian.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using JetBrains.Annotations; | ||
|
||
namespace Eryph.VmManagement.Data.Core; | ||
|
||
/// <summary> | ||
/// Represents an HGS guardian as returned by the Cmdlet | ||
/// <c>Get-HgsGuardian</c>. | ||
/// </summary> | ||
public class CimHgsGuardian | ||
{ | ||
[CanBeNull] public string Name { get; init; } | ||
|
||
public bool HasPrivateSigningKey { get; init; } | ||
|
||
public X509Certificate2 EncryptionCertificate { get; init; } | ||
|
||
public X509Certificate2 SigningCertificate { get; init; } | ||
} |
18 changes: 18 additions & 0 deletions
18
src/core/src/Eryph.VmManagement/Data/Core/CimHgsKeyProtector.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Eryph.VmManagement.Data.Core; | ||
|
||
/// <summary> | ||
/// Represent a HGS key protector as returned by the Cmdlets | ||
/// <c>ConvertTo-HgsKeyProtector</c> or <c>New-HgsKeyProtector</c>. | ||
/// </summary> | ||
public class CimHgsKeyProtector | ||
{ | ||
public CimHgsGuardian Owner { get; init; } | ||
|
||
public byte[] RawData { get; init; } | ||
} |
20 changes: 20 additions & 0 deletions
20
src/core/src/Eryph.VmManagement/Data/Core/VMSecurityInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
namespace Eryph.VmManagement.Data.Core; | ||
|
||
/// <summary> | ||
/// Contains information about the security settings of a Hyper-V VM | ||
/// as returned by <c>Get-VMSecurity</c>. | ||
/// </summary> | ||
public class VMSecurityInfo | ||
{ | ||
public bool TpmEnabled { get; init; } | ||
|
||
public bool KsdEnabled { get; init; } | ||
|
||
public bool Shielded { get; init; } | ||
|
||
public bool EncryptStateAndVmMigrationTraffic { get; init; } | ||
|
||
public bool VirtualizationBasedSecurityOptOut { get; init; } | ||
|
||
public bool BindToHostTpm { get; init; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.