Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions PowerShell/JumpCloud Module/Private/Console/Clear-Console.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Clear a specified number of lines from the console
function Clear-Console {
param(
[Parameter(Mandatory=$true)]
[int]$LinesToClear
)
$currentLine = $Host.UI.RawUI.CursorPosition.Y
$bufferWidth = $Host.UI.RawUI.BufferSize.Width
$startLine = $currentLine - $LinesToClear
if ($startLine -lt 0) { $startLine = 0 }

for ($i = $startLine; $i -le $currentLine; $i++) {
[Console]::SetCursorPosition(0, $i)
[Console]::Write(" " * $bufferWidth)
}
[Console]::SetCursorPosition(0, $startLine)
}
23 changes: 23 additions & 0 deletions PowerShell/JumpCloud Module/Private/Console/Confim-Console.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function Confirm-Console {
param(
[Parameter(Mandatory=$False)]
[string]$Message,
[Parameter(Mandatory=$False)]
[scriptblock]$YesAction
)
$LinesToClear = 1
if($Message) {
Write-Host $Message -ForegroundColor Yellow
# $LinesToClear += ($Message.Split("`n").Count)
}
$res = Find-Interactive -choices @("No", "Yes");
if($res -eq "Yes" -and $YesAction) {
Invoke-Command -ScriptBlock $YesAction
$temp = $true
} else {
$temp = $false
}

Clear-Console -LinesToClear $LinesToClear
return $temp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Interactive function to display choices and handle user input for selection
function Find-Interactive() {
param (
[string[]]$choices,
[scriptblock]$Callback
)
$choices = @($choices)
$index = 0
$running = $true
$trigger = $false
[Console]::CursorVisible = $false
while ($running) {
for ($i = 0; $i -lt $choices.Count; $i++) {
if ($i -eq $index) {
Write-Host " > $($choices[$i])" -ForegroundColor Cyan -BackgroundColor DarkGray
} else {
Write-Host " $($choices[$i])"
}
}
$key = [Console]::ReadKey($true)
switch ($key.Key) {
'UpArrow' { $index = if ($index -gt 0) { $index - 1 } else { $choices.Count - 1 } }
'DownArrow' { $index = if ($index -lt $choices.Count - 1) { $index + 1 } else { 0 } }
'Enter' { $running = $false ; $trigger = $false;}
'Backspace' { if($Callback) { $running = $false; $trigger = $true; } }
'Escape' { [Console]::CursorVisible = $true; return $null }
Comment thread
lucasmendes-jc marked this conversation as resolved.
}
[Console]::SetCursorPosition(0, [Console]::CursorTop - ($choices.Count))
}
[Console]::CursorVisible = $true
# Clear the choices from the console
for ($i = 0; $i -lt $choices.Count; $i++) {
Write-Host (" " * ([Console]::WindowWidth - 1))
}
[Console]::SetCursorPosition(0, [Console]::CursorTop - ($choices.Count))
if($trigger -and $Callback) {
$temp = Invoke-Command -ScriptBlock $Callback -ArgumentList $choices[$index]
return $false
}
return $choices[$index]
}
22 changes: 22 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Get-KeyFromVault.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Retrieve a key from the vault based on the provided key name
function Get-KeyFromVault() {
param(
[Parameter(Mandatory=$true)]
[string]$Key
)
Unlock-Platform
Comment thread
cursor[bot] marked this conversation as resolved.
$plat = $env:CONSOLE_PLATFORM
if ($plat -eq "MacOS") {
$serviceKey = security find-generic-password -s $Key -w
if ($LASTEXITCODE -ne 0) {
return $null
}
} elseif ($plat -eq "Windows") {
$serviceKey = [CredManager]::GetCreds($Key)
} else {
throw "Unsupported OS."
}

Write-Host "Retrieved key from Credential Manager: $key" -ForegroundColor Green
return $serviceKey
}
36 changes: 36 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Get-VaultKeys.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function Get-VaultKeys() {
param(
[Parameter(Mandatory=$true)]
[string]$sufix
)

$plat = $env:CONSOLE_PLATFORM
If($plat -eq "Windows") {
# $username = $env:USERNAME.Trim().ToLower().Replace(" ", "_")
try {
# @() ensures a single matching credential is returned as a one-element array,
# not an unwrapped string (which breaks Find-Interactive choice indexing).
$keys = @([CredManager]::GetTargetList().Where({ $_.EndsWith($sufix) }))
} catch {
Write-Host "Error retrieving keys: $_" -ForegroundColor Red
return $null
}
} ElseIf($plat -eq "MacOS") {
$keys = security dump-keychain | ForEach-Object {
if ($_ -match '0x00000007\s+<blob>="(.+?)"' -or $_ -match '"svce"<.+?>="(.+?'+$sufix+')"') {
$found = $matches[1]
if ($found.EndsWith($sufix)){return $found}
}
} | Select-Object -Unique | ForEach-Object { ,$_ }
$keys = @($keys)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null wrapping creates phantom entry in key list

Medium Severity

When no keys match on macOS, the pipeline produces no output and $keys becomes $null. Then $keys = @($keys) creates @($null) — a one-element array containing $null (count 1). The subsequent $null -eq $keys check fails because $keys is an array, not $null, so the function returns @($null). The same issue occurs in KeySelector at line 301 where @(Get-VaultKeys ...) wraps a $null return (from the Windows error path) into @($null). In both cases, the ($null -eq $keys) -or ($keys.Count -eq 0) check is defeated, and Find-Interactive receives an array with a $null element, displaying a broken selection menu.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5c8b0a7. Configure here.

} ElseIf($plat -eq "Linux") {
throw "Unsupported OS."
} Else {
throw "Unsupported OS."
}

if ($null -eq $keys) {
return $null
}
return @($keys)
}
30 changes: 30 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Remove-FromVault.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Remove a key from the vault based on the provided key name
function Remove-FromVault() {
param(
[Parameter(Mandatory=$true)]
[string]$Key
)

$plat = $env:CONSOLE_PLATFORM
if ($plat -eq "MacOS") {
try {
security delete-generic-password -s $Key 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete key from Keychain."
}
} catch {
throw "Error deleting key from Keychain: $_"
}
Comment thread
cursor[bot] marked this conversation as resolved.
} elseif ($plat -eq "Windows") {
try {
cmdkey /delete:($Key) 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete key from Credential Manager."
}
} catch {
throw "Error deleting key from Credential Manager: $_"
}
Comment thread
lucasmendes-jc marked this conversation as resolved.
} else {
throw "Unsupported OS."
}
}
12 changes: 12 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Request-NewKey.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function Request-NewKey() {
param(
[Parameter(Mandatory=$false)]
[string]$sufix_ = ".api.jc"
)
Set-ToVault -Value (
[System.Net.NetworkCredential]::new("", (Read-Host -Prompt "Type the api key" -AsSecureString)).Password
) -Key (
Read-Host -Prompt "Type the name to be saved"
) -sufix $sufix_
Clear-Console -LinesToClear 2
}
Comment thread
cursor[bot] marked this conversation as resolved.
38 changes: 38 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Set-ToVault.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Aux functions for interactive behavior and vault management
function Set-ToVault() {
param(
[Parameter(Mandatory=$true)]
[string]$Key,
[Parameter(Mandatory=$false)]
[string]$sufix,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about the choice of suffix here over prefix. For my reference, keys are gathered and parsed to see if they end with some suffix.

I think that results in better looking keys as you are selecting them like:

  • OrgName.api.jc
  • AnotherOrgName.api.jc
  • LastOrg.serviceTotken.jc (assuming we add support for service token credentialing later)

Reference Code:
https://github.com/TheJumpCloud/support/pull/744/changes#diff-2283fb1a2772f5d917313a140539f71ffb3860db63e32f4d1c0b4e1fec3549a1R11
https://github.com/TheJumpCloud/support/pull/744/changes#diff-2283fb1a2772f5d917313a140539f71ffb3860db63e32f4d1c0b4e1fec3549a1R20

[Parameter(Mandatory=$true)]
[string]$Value
)
if(!$sufix) {
$sufix = ""
}
$key_ = $Key.ToLower().Replace(" ", "_").Trim()
$plat = $env:CONSOLE_PLATFORM
$value = $Value.Trim()
if ($plat -eq "MacOS") {
try {
# Not necessary to use same approach as MacOs
# MacOs is safe to use security command as above
security add-generic-password -a $env:USER -s ($key_+$sufix) -w $value -T "" 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed to add key to Keychain."
}
} catch {
throw "Error adding key to Keychain: $_"
}
Comment thread
cursor[bot] marked this conversation as resolved.
} elseif ($plat -eq "Windows") {
try {
Unlock-Platform
[CredManager]::SetCreds(($key_ + $sufix), $env:USERNAME, $value)
} catch {
throw "Error adding key to Credential Manager: $_"
}
} else {
throw "Unsupported OS."
}
}
121 changes: 121 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Unlock-Platform.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
function Unlock-Platform() {
if([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) {
$plat = "Windows"
$Definition = @"
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Collections.Generic;

public class CredManager {
private const int CRED_TYPE_GENERIC = 1;
private const int CRED_PERSIST_LOCAL_MACHINE = 2;

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredRead(string target, int type, int reservedFlag, out IntPtr credentialPtr);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredWrite(ref PCREDENTIAL userCredential, uint flags);

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern void CredFree(IntPtr pBuffer);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PCREDENTIAL {
public int flags;
public int type;
public string targetName;
public string comment;
public long lastWritten;
public int credentialBlobSize;
public IntPtr credentialBlob;
public int persist;
public int attributeCount;
public IntPtr attributes;
public string targetAlias;
public string userName;
}

public static string[] GetTargetList() {
int count;
IntPtr pCredentials;
List<string> targets = new List<string>();

// Filtro null traz todas as credenciais genéricas (type 1)
if (CredEnumerate(null, 0, out count, out pCredentials)) {
for (int i = 0; i < count; i++) {
// Calcula o endereço de cada item no array de ponteiros
IntPtr pCurrent = Marshal.ReadIntPtr(pCredentials, i * IntPtr.Size);
PCREDENTIAL cred = (PCREDENTIAL)Marshal.PtrToStructure(pCurrent, typeof(PCREDENTIAL));
targets.Add(cred.targetName);
}
CredFree(pCredentials);
}
return targets.ToArray();
}

public static string GetCreds(string target) {
IntPtr credPtr;
if (CredRead(target, CRED_TYPE_GENERIC, 0, out credPtr)) {
try {
PCREDENTIAL cred = (PCREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(PCREDENTIAL));
int charLen = cred.credentialBlobSize / 2;
if (charLen > 0 && Marshal.ReadInt16(cred.credentialBlob, (charLen - 1) * 2) == 0) {
charLen--;
}
return Marshal.PtrToStringUni(cred.credentialBlob, charLen);
} finally {
CredFree(credPtr);
}
}
return null;
}
Comment thread
cursor[bot] marked this conversation as resolved.

public static void SetCreds(string target, string userName, string secret) {
int byteCount = secret.Length * 2;
IntPtr blobPtr = Marshal.AllocCoTaskMem(byteCount);
try {
Marshal.Copy(secret, 0, blobPtr, secret.Length);
PCREDENTIAL cred = new PCREDENTIAL();
cred.flags = 0;
cred.type = CRED_TYPE_GENERIC;
cred.targetName = target;
cred.comment = null;
cred.lastWritten = 0;
cred.credentialBlobSize = byteCount;
cred.credentialBlob = blobPtr;
cred.persist = CRED_PERSIST_LOCAL_MACHINE;
cred.attributeCount = 0;
cred.attributes = IntPtr.Zero;
cred.targetAlias = null;
cred.userName = userName;

if (!CredWrite(ref cred, 0)) {
throw new Win32Exception(Marshal.GetLastWin32Error());
}
} finally {
Marshal.FreeCoTaskMem(blobPtr);
}
}
}
"@
if (-not ("CredManager" -as [type])) { Add-Type -TypeDefinition $Definition -Language CSharp }
} elseif([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
$plat = "MacOS"
# Will be asked all times, sadly too
# try {
# security unlock-keychain
# } catch {
# throw "Error unlocking keychain: $_"
# }
} elseif([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
$plat = "Linux"
} else {
$plat = "Unknown"
}

$env:CONSOLE_PLATFORM = $plat
}
Loading