Add self-hosted RustDesk bootstrap management
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^[A-Za-z0-9][A-Za-z0-9-]{0,62}$')]
|
||||
[string]$ComputerName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^\d+$')]
|
||||
[string]$RustDeskId,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateLength(12, 256)]
|
||||
[string]$AccessPassword,
|
||||
|
||||
[string]$InventoryRoot = "$env:ProgramData\SGU\RustDesk\Devices"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Assert-Administrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Only a local administrator can register a RustDesk device credential.'
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-DataProtection {
|
||||
if (-not ('SguRustDeskDataProtection' -as [type])) {
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
public static class SguRustDeskDataProtection {
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct DataBlob { public int cbData; public IntPtr pbData; }
|
||||
[DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool CryptProtectData(ref DataBlob input, string description, IntPtr entropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob output);
|
||||
[DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool CryptUnprotectData(ref DataBlob input, IntPtr description, IntPtr entropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob output);
|
||||
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LocalFree(IntPtr memory);
|
||||
private const int CryptProtectLocalMachine = 0x4;
|
||||
private static DataBlob ToBlob(byte[] value) { var blob = new DataBlob { cbData = value.Length, pbData = IntPtr.Zero }; if (value.Length > 0) { blob.pbData = Marshal.AllocHGlobal(value.Length); Marshal.Copy(value, 0, blob.pbData, value.Length); } return blob; }
|
||||
private static byte[] FromBlob(DataBlob blob) { var value = new byte[blob.cbData]; if (blob.cbData > 0) Marshal.Copy(blob.pbData, value, 0, blob.cbData); return value; }
|
||||
public static byte[] Protect(byte[] value) { var input = ToBlob(value); var output = new DataBlob(); try { if (!CryptProtectData(ref input, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, CryptProtectLocalMachine, out output)) throw new Win32Exception(Marshal.GetLastWin32Error()); return FromBlob(output); } finally { if (input.pbData != IntPtr.Zero) Marshal.FreeHGlobal(input.pbData); if (output.pbData != IntPtr.Zero) LocalFree(output.pbData); } }
|
||||
public static byte[] Unprotect(byte[] value) { var input = ToBlob(value); var output = new DataBlob(); try { if (!CryptUnprotectData(ref input, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, out output)) throw new Win32Exception(Marshal.GetLastWin32Error()); return FromBlob(output); } finally { if (input.pbData != IntPtr.Zero) Marshal.FreeHGlobal(input.pbData); if (output.pbData != IntPtr.Zero) LocalFree(output.pbData); } }
|
||||
}
|
||||
'@ -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
function Set-PrivateDirectoryAcl {
|
||||
param([Parameter(Mandatory)][string]$Path)
|
||||
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
$acl = New-Object Security.AccessControl.DirectorySecurity
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$inheritance = [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'
|
||||
$allow = [Security.AccessControl.AccessControlType]::Allow
|
||||
foreach ($sid in @('S-1-5-18', 'S-1-5-32-544')) {
|
||||
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
||||
[Security.Principal.SecurityIdentifier]::new($sid),
|
||||
[Security.AccessControl.FileSystemRights]::FullControl,
|
||||
$inheritance,
|
||||
[Security.AccessControl.PropagationFlags]::None,
|
||||
$allow))
|
||||
}
|
||||
Set-Acl -LiteralPath $Path -AclObject $acl
|
||||
}
|
||||
|
||||
Assert-Administrator
|
||||
Initialize-DataProtection
|
||||
if (-not $PSCmdlet.ShouldProcess($ComputerName, 'Register the protected RustDesk management credential')) {
|
||||
return
|
||||
}
|
||||
|
||||
Set-PrivateDirectoryAcl -Path $InventoryRoot
|
||||
$normalizedName = $ComputerName.ToUpperInvariant()
|
||||
$secretPath = Join-Path $InventoryRoot "$normalizedName.secret"
|
||||
$metadataPath = Join-Path $InventoryRoot "$normalizedName.json"
|
||||
$protectedPassword = [SguRustDeskDataProtection]::Protect(
|
||||
[Text.Encoding]::UTF8.GetBytes($AccessPassword))
|
||||
[IO.File]::WriteAllBytes($secretPath, $protectedPassword)
|
||||
$metadata = [ordered]@{
|
||||
ComputerName = $normalizedName
|
||||
RustDeskId = $RustDeskId
|
||||
RegisteredAt = (Get-Date).ToString('o')
|
||||
SecretPath = $secretPath
|
||||
}
|
||||
[IO.File]::WriteAllText($metadataPath, ($metadata | ConvertTo-Json), [Text.UTF8Encoding]::new($false))
|
||||
|
||||
[pscustomobject]@{
|
||||
ComputerName = $normalizedName
|
||||
RustDeskId = $RustDeskId
|
||||
Registered = $true
|
||||
MetadataPath = $metadataPath
|
||||
}
|
||||
Reference in New Issue
Block a user