fix: use SecureString for guest password, validate Pool/CephPool conflict

Set-PveVmGuestPassword now accepts SecureString instead of plain string for
the -Password parameter, matching the established pattern in Set-PvePassword.
The password is converted via Marshal and zeroed from memory after use.

New-PveStorage now validates that -Pool and -CephPool cannot both be specified,
since both mapped to the same API key and CephPool silently overwrote Pool.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-22 08:14:39 -05:00
parent 001c93c45c
commit 379e6d2ada
2 changed files with 25 additions and 3 deletions
@@ -15,7 +15,7 @@ namespace PSProxmoxVE.Cmdlets.Storage
/// </summary> /// </summary>
[Cmdlet(VerbsCommon.New, "PveStorage", SupportsShouldProcess = true)] [Cmdlet(VerbsCommon.New, "PveStorage", SupportsShouldProcess = true)]
[OutputType(typeof(PveStorage))] [OutputType(typeof(PveStorage))]
public class NewPveStorageCmdlet : PveCmdletBase public sealed class NewPveStorageCmdlet : PveCmdletBase
{ {
/// <summary>The unique storage identifier/name.</summary> /// <summary>The unique storage identifier/name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The storage pool name.")] [Parameter(Mandatory = true, Position = 0, HelpMessage = "The storage pool name.")]
@@ -80,6 +80,14 @@ namespace PSProxmoxVE.Cmdlets.Storage
if (!ShouldProcess(Storage, "Create PVE Storage")) if (!ShouldProcess(Storage, "Create PVE Storage"))
return; return;
if (!string.IsNullOrEmpty(Pool) && !string.IsNullOrEmpty(CephPool))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Pool and CephPool cannot both be specified. Use Pool for ZFS pools or CephPool for Ceph/RBD pools."),
"PoolConflict", ErrorCategory.InvalidArgument, null));
return;
}
var session = GetSession(); var session = GetSession();
using var client = new PveHttpClient(session); using var client = new PveHttpClient(session);
@@ -1,4 +1,6 @@
using System.Management.Automation; using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security;
using PSProxmoxVE.Core.Services; using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms namespace PSProxmoxVE.Cmdlets.Vms
@@ -12,6 +14,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
/// </para> /// </para>
/// </summary> /// </summary>
[Cmdlet(VerbsCommon.Set, "PveVmGuestPassword", SupportsShouldProcess = true)] [Cmdlet(VerbsCommon.Set, "PveVmGuestPassword", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public sealed class SetPveVmGuestPasswordCmdlet : PveCmdletBase public sealed class SetPveVmGuestPasswordCmdlet : PveCmdletBase
{ {
/// <summary>The Proxmox VE node name.</summary> /// <summary>The Proxmox VE node name.</summary>
@@ -33,7 +36,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
/// <para type="description">The new password for the user.</para> /// <para type="description">The new password for the user.</para>
/// </summary> /// </summary>
[Parameter(Mandatory = true, HelpMessage = "The new password.")] [Parameter(Mandatory = true, HelpMessage = "The new password.")]
public string Password { get; set; } = string.Empty; public SecureString Password { get; set; } = new SecureString();
/// <summary> /// <summary>
/// <para type="description">When specified, indicates the password is already in crypted/hashed format.</para> /// <para type="description">When specified, indicates the password is already in crypted/hashed format.</para>
@@ -48,9 +51,20 @@ namespace PSProxmoxVE.Cmdlets.Vms
var session = GetSession(); var session = GetSession();
var ptr = Marshal.SecureStringToGlobalAllocUnicode(Password);
string plainPassword;
try
{
plainPassword = Marshal.PtrToStringUni(ptr) ?? string.Empty;
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
WriteVerbose($"Setting password for user '{Username}' on VM {VmId} via guest agent..."); WriteVerbose($"Setting password for user '{Username}' on VM {VmId} via guest agent...");
var service = new VmService(); var service = new VmService();
service.SetGuestPassword(session, Node, VmId, Username, Password, Crypted.IsPresent); service.SetGuestPassword(session, Node, VmId, Username, plainPassword, Crypted.IsPresent);
} }
} }
} }