improved error handling and better session management

Added

- New ClearSession parameter to remove the stored Vaultwarden session from the Windows Credential Manager.
- New Test-Prerequisites function to check if the required tools (ssh-add, Bitwarden CLI) are installed and available.
- New Get-BWSession function to retrieve the Vaultwarden session from the Windows Credential Manager, and fallback to getting a new session if the stored one is invalid.
- New Clear-SensitiveData function to clear sensitive data (SecureString, string) from memory.
- New Clear-BWSession function to remove the stored session from the Windows Credential Manager.

Changed

- The Test-VaultwardenConfig function now prompts the user to enter the Vaultwarden server URL if it's not configured, rather than throwing an error.
- The Get-FolderId and Get-FolderItems functions now take the session as a parameter, rather than relying on a global session variable.
- The Get-PrivatePublicKey function now uses the --raw flag to retrieve the private key attachment, instead of joining the attachment content.
The main script execution has been reorganized to handle the session retrieval and management more explicitly.
- The script now includes a detailed help section at the top, providing information about the script's usage and parameters.
Removed
This commit is contained in:
Oli
2024-10-25 22:51:13 +02:00
parent d8e5346e42
commit 7744667c3d
2 changed files with 264 additions and 88 deletions

View File

@@ -1,7 +1,30 @@
# Vaultwarden SSH Agent Script
<#
.SYNOPSIS
Adds SSH keys from Vaultwarden to the SSH agent.
.DESCRIPTION
Retrieves SSH keys stored in Vaultwarden and adds them to the running SSH agent.
Public keys should be stored in the item's notes and private keys as attachments.
.PARAMETER Debug
Enables debug output.
.PARAMETER FolderName
The name of the Vaultwarden folder containing SSH keys. Defaults to "ssh-agent".
.PARAMETER ClearSession
Removes the stored Vaultwarden session from the Windows Credential Manager.
.EXAMPLE
.\Vaultwarden_ssh-agent.ps1
Imports all SSH keys from Vaultwarden to the SSH agent.
.EXAMPLE
.\Vaultwarden_ssh-agent.ps1 -FolderName "my-ssh-keys"
Imports SSH keys from a custom folder name.
.EXAMPLE
.\Vaultwarden_ssh-agent.ps1 -ClearSession
Removes the stored Vaultwarden session from the Windows Credential Manager.
#>
param(
[switch]$Debug
[switch]$Debug,
[string]$FolderName = "ssh-agent",
[switch]$ClearSession
)
# Set debug preference
@@ -11,10 +34,67 @@ if ($Debug) {
$DebugPreference = 'SilentlyContinue'
}
# Constants
$FolderName = "ssh-agent"
# Add CredManager type definition at script start
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class CredManager {
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool CredRead(string target, int type, int reservedFlag, out IntPtr credentialPtr);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern void CredFree(IntPtr buffer);
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
public struct CREDENTIAL {
public int Flags;
public int Type;
public string TargetName;
public string Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public int CredentialBlobSize;
public IntPtr CredentialBlob;
public int Persist;
public int AttributeCount;
public IntPtr Attributes;
public string TargetAlias;
public string UserName;
}
}
"@
function Clear-BWSession {
[CmdletBinding()]
param()
Write-Debug "Removing stored session from Windows Credential Manager"
& cmdkey /delete:"Vaultwarden_Session" | Out-Null
}
function Test-Prerequisites {
# Check if ssh-add exists
if (-not (Get-Command "ssh-add" -ErrorAction SilentlyContinue)) {
throw "ssh-add command not found. Please ensure OpenSSH is installed."
}
# Check if SSH agent is running
$process = Get-Process ssh-agent -ErrorAction SilentlyContinue
if (-not $process) {
throw "SSH agent is not running. Please start the SSH agent service."
}
# Check if bw CLI is available
if (-not (Get-Command "bw" -ErrorAction SilentlyContinue)) {
throw "Bitwarden CLI not found. Please install the Bitwarden CLI."
}
}
function Test-VaultwardenConfig {
[CmdletBinding()]
param()
$ConfigPath = "$env:APPDATA\Bitwarden CLI\data.json"
if (Test-Path $ConfigPath) {
$Config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
@@ -28,51 +108,99 @@ function Test-VaultwardenConfig {
Write-Host "Vaultwarden server is not configured. Please enter the server URL:"
$ServerUrl = Read-Host
& bw config server $ServerUrl
Write-Host "" # Add line break after failed attempt
Write-Host "Vaultwarden server configured to: $ServerUrl"
}
function Get-BWSession {
# Check for existing permanent session
$PersistentSession = [Environment]::GetEnvironmentVariable("BW_SESSION", "User")
if ($PersistentSession) {
Write-Debug "Existing Bitwarden session found"
$env:BW_SESSION = $PersistentSession
# Verify session with sync
$SyncResult = & bw sync --session $PersistentSession | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "Using existing session"
return $PersistentSession
[CmdletBinding()]
param()
Write-Debug "Retrieving session from Windows Credential Manager"
$CredPtr = [IntPtr]::Zero
$Result = [CredManager]::CredRead("Vaultwarden_Session", 1, 0, [ref]$CredPtr)
if ($Result) {
try {
$Cred = [System.Runtime.InteropServices.Marshal]::PtrToStructure($CredPtr, [Type][CredManager+CREDENTIAL])
if (-not $Cred.CredentialBlobSize) {
Write-Debug "No stored session found"
return $null
}
$SecretBytes = New-Object byte[] $Cred.CredentialBlobSize
[System.Runtime.InteropServices.Marshal]::Copy($Cred.CredentialBlob, $SecretBytes, 0, $Cred.CredentialBlobSize)
$Session = [System.Text.Encoding]::Unicode.GetString($SecretBytes)
# Check session status
Write-Debug "Stored session found"
# bw status is always locked when using --session
# https://github.com/bitwarden/clients/issues/9254
[Environment]::SetEnvironmentVariable("BW_SESSION", $Session, [System.EnvironmentVariableTarget]::Process)
$StatusResult = & bw status 2>&1
[Environment]::SetEnvironmentVariable("BW_SESSION", $null, [System.EnvironmentVariableTarget]::Process)
if ($LASTEXITCODE -eq 0) {
$Status = $StatusResult | ConvertFrom-Json
if ($Status.status -eq "unlocked") {
Write-Debug "Stored session is valid"
Write-Host "Using stored session"
return $Session
}
}
Write-Debug "Stored session is invalid, removing"
& cmdkey /delete:"Vaultwarden_Session" | Out-Null
}
finally {
[CredManager]::CredFree($CredPtr)
if ($Session) {
Clear-SensitiveData $Session
}
}
Write-Host "Existing session invalid: $SyncResult"
}
# Get new session
Write-Debug "No existing Bitwarden session found"
Write-Host "Getting new session"
$null = & bw login --check
if ($LASTEXITCODE -eq 0) {
Write-Debug "User logged in, unlocking vault"
$Session = & bw unlock --raw
} else {
Write-Debug "User not logged in, starting login"
$Session = & bw login --raw
# Check current login status
Write-Debug "Checking login status"
$Status = & bw status | ConvertFrom-Json
if ($Status.status -eq "unauthenticated") {
Write-Debug "Not logged in, attempting API key login"
& bw login --apikey
if ($LASTEXITCODE -ne 0) {
throw "Login failed"
}
}
if (-not $Session) {
Write-Host "" # Add line break after failed attempt
throw "Failed to get Bitwarden session"
else {
Write-Debug "Already logged in as $($Status.userEmail)"
}
Write-Host "Authentication successful"
[Environment]::SetEnvironmentVariable("BW_SESSION", $Session, "User")
$env:BW_SESSION = $Session
& bw sync --session $Session | Out-Null
return $Session
Write-Debug "Unlocking vault"
$NewSession = & bw unlock --raw
if (-not $NewSession) {
throw "Failed to unlock vault"
}
# Store session in Windows Credential Manager
Write-Debug "Storing session in Windows Credential Manager"
$Result = & cmdkey /generic:"Vaultwarden_Session" /user:$($Status.userEmail) /pass:$NewSession
if ($LASTEXITCODE -ne 0) {
throw "Failed to store session: $Result"
}
Write-Host "New session established"
return $NewSession
}
function Clear-SensitiveData {
param([System.Object]$Variable)
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[System.Object]$Variable
)
if ($Variable -is [SecureString]) {
$Variable.Dispose()
@@ -85,8 +213,12 @@ function Clear-SensitiveData {
}
function Test-SSHKey {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$KeyContent,
[Parameter(Mandatory=$true)]
[ValidateSet('Public', 'Private')]
[string]$KeyType
)
@@ -100,10 +232,14 @@ function Test-SSHKey {
}
function Get-FolderId {
param($Session)
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Session
)
Write-Debug "Getting folder: $FolderName"
$Folders = & bw list folders --search $FolderName --session $Session | ConvertFrom-Json
$Folders = & bw list folders --session $Session --search $FolderName | ConvertFrom-Json
$Folder = $Folders | Where-Object { $_.name -eq $FolderName }
if (-not $Folder) {
@@ -113,21 +249,27 @@ function Get-FolderId {
}
function Get-FolderItems {
[CmdletBinding()]
param(
$Session,
$FolderId
[Parameter(Mandatory=$true)]
[string]$Session,
[Parameter(Mandatory=$true)]
[string]$FolderId
)
Write-Debug "Getting items from folder: $FolderId"
$Items = & bw list items --folderid $FolderId --session $Session | ConvertFrom-Json
$Items = & bw list items --session $Session --folderid $FolderId | ConvertFrom-Json
Write-Debug "Found $($Items.Count) items"
return $Items
}
function Get-PrivatePublicKey {
[CmdletBinding()]
param(
$Session,
$Item
[Parameter(Mandatory=$true)]
[string]$Session,
[Parameter(Mandatory=$true)]
[PSObject]$Item
)
try {
@@ -150,7 +292,7 @@ function Get-PrivatePublicKey {
throw "No attachment found"
}
$PrivateKey = & bw get attachment $Attachment.id --itemid $Item.id --raw --session $Session
$PrivateKey = & bw get attachment $Attachment.id --session $Session --itemid $Item.id --raw
$PrivateKey = $PrivateKey -join "`n"
if (-not (Test-SSHKey -KeyContent $PrivateKey -KeyType 'Private')) {
@@ -173,7 +315,10 @@ function Get-PrivatePublicKey {
}
function Add-PrivateKeyToSSHAgent {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[PSCustomObject]$SSHKey
)
@@ -224,13 +369,27 @@ function Add-PrivateKeyToSSHAgent {
# Main script execution
try {
if ($ClearSession) {
Clear-BWSession
Write-Host "Stored session has been cleared"
return
}
Test-Prerequisites
Write-Debug "Checking Vaultwarden configuration"
Test-VaultwardenConfig
Write-Host "Connect to Vaultwarden"
$session = Get-BWSession
Write-Debug "Session = $session"
# Get session
$Session = Get-BWSession
# Sync vault
Write-Debug "Syncing vault"
& bw sync --session $Session --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to sync vault"
}
$FolderId = Get-FolderId -Session $Session
$Items = Get-FolderItems -Session $Session -FolderId $FolderId
@@ -269,3 +428,8 @@ catch {
Write-Error "Critical error: $_"
exit 1
}
finally {
if ($Session) {
Clear-SensitiveData $Session
}
}