SecretManagement.LastPass.Extension/SecretManagement.LastPass.Extension.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. using namespace Microsoft.PowerShell.SecretManagement # The capture groups are: # 1. The ls short output (just the name & id) # 2. The name of the secret # 3. The username of the secret $lsLongOutput = "\d\d\d\d-\d\d-\d\d \d\d:\d\d ((.*) \[id: \d*\]) \[username: (.*)\]" function Invoke-lpass { [CmdletBinding()] param ( [Parameter()] [string[]] $Arguments, [Parameter(ValueFromPipeline)] [object] $InputObject ) if (Get-Command lpass -ErrorAction SilentlyContinue) { if ($InputObject) { return $InputObject | & lpass @Arguments } return lpass @Arguments } throw "lpass executable not found or installed." } function Get-Secret { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string] $VaultName, [Parameter(ValueFromPipelineByPropertyName)] [hashtable] $AdditionalParameters ) # TODO error handling if ($Name -match ".* \[id: (\d*)\]") { $Name = $Matches[1] } $res = Invoke-lpass 'show','--name', $Name, '--password' if ([string]::IsNullOrWhiteSpace($res)) { $res = Invoke-lpass 'show', '--name', $Name, '--notes' } else { # We have a password, check for a username $username = Invoke-lpass 'show', '--name', $Name, '--username' if ($username) { $res = [System.Management.Automation.PSCredential]::new( $username, (ConvertTo-SecureString $res -AsPlainText -Force)) } } return $res } function Set-Secret { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [object] $Secret, [Parameter(ValueFromPipelineByPropertyName)] [string] $VaultName, [Parameter(ValueFromPipelineByPropertyName)] [hashtable] $AdditionalParameters ) if($Secret -is [string]) { $Secret = @{ URL = "http://sn" Notes = $Secret } } $sb = [System.Text.StringBuilder]::new() if($Secret.UserName) { $sb.Append("Username: ").AppendLine($Secret.UserName) $sb.Append("login: ").AppendLine($Secret.UserName) } if($Secret.Password) { $pass = $Secret.Password if ($Secret -is [pscredential]) { $pass = $Secret.GetNetworkCredential().password } elseif ($pass -is [securestring]) { $pass = $pass | ConvertFrom-SecureString } $sb.Append("Password: ").AppendLine($pass) $sb.Append("password: ").AppendLine($pass) $sb.Append("sudo_password: ").AppendLine($pass) } if($Secret.URL) { $sb.Append("URL: ").AppendLine($Secret.URL) } if($Secret.Notes) { $sb.AppendLine("Notes:").AppendLine($Secret.Notes) } try { $sb.ToString() | Invoke-lpass 'add', $Name, '--non-interactive' } catch { return $false } return $true } function Remove-Secret { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [string] $Name, [Parameter(ValueFromPipelineByPropertyName)] [string] $VaultName, [Parameter(ValueFromPipelineByPropertyName)] [hashtable] $AdditionalParameters ) if ($Name -match ".* \[id: (\d*)\]") { $Name = $Matches[1] } lpass rm $Name return $? } function Get-SecretInfo { param ( [string] $Filter, [string] $VaultName, [hashtable] $AdditionalParameters ) $Filter = "*$Filter" $pattern = [WildcardPattern]::new($Filter) Invoke-lpass 'ls','-l' | Where-Object { $_ -match $lsLongOutput | Out-Null $pattern.IsMatch($Matches[2]) } | ForEach-Object { $type = if ($Matches[3]) { [SecretType]::PSCredential } else { [SecretType]::SecureString } [SecretInformation]::new( $Matches[1], $type, $VaultName) } } function Test-SecretVault { [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [string] $VaultName, [Parameter(ValueFromPipelineByPropertyName)] [hashtable] $AdditionalParameters ) return (Get-Command lpass -ErrorAction SilentlyContinue) -and (lpass status -match "Logged in as .*") } |