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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user