Fix Set-CredentialStoreItem (#76)
All checks were successful
continuous-integration/drone/push Build is passing

#### 📖 Summary

- Fix Set-CredentialStoreItem. Currently it doesn't warn if the user is trying to update a CredentialStoreItem, that does not exist.
- Also add a bit of documentation and UnitTests.
- Fix the quotation of Test-CredentialStoreItem.

#### 📑 Test Plan

> 💡 Select your test plan for the code changes.

- [x] Tested via Drone.io pipeline
- [ ] Custom test
- [ ] No test plan

##### Details / Justification

<!-- Add your test details or justification for missing tests here. -->

#### 📚 Additional Notes

<!-- A place for additional detail notes. -->

Co-authored-by: OCram85 <ocram85@noreply.local>
Reviewed-on: #76
Reviewed-by: OCram85 <ocram85@noreply.local>
Co-authored-by: pinguinfuss <christian@heimdaheim.de>
Co-committed-by: pinguinfuss <christian@heimdaheim.de>
This commit is contained in:
pinguinfuss 2022-10-10 10:05:08 +02:00 committed by OCram85
parent 2bd250971b
commit 42fdb0a373
3 changed files with 179 additions and 20 deletions

View File

@ -0,0 +1,130 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSAvoidUsingConvertToSecureStringWithPlainText',
'',
Justification = 'just used in pester tests.'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSProvideCommentHelp',
'',
Justification = 'no need in internal pester helpers.'
)]
param ()
BeforeAll {
$ManifestFile = (Get-Item -Path './src/*.psd1').FullName
Import-Module $ManifestFile -Force
$PrivateFunctions = (Get-ChildItem -Path './src/Private/*.ps1' | Where-Object {
$_.BaseName -notmatch '.Tests'
}
).FullName
foreach ( $func in $PrivateFunctions) {
. $func
}
# Backup existing credential stores
$VerbosePreference = 'Continue'
Write-Verbose -Message 'Backup private Credential Store...'
$CSPath = Get-DefaultCredentialStorePath
$BackupFile = '{0}.back' -f $CSPath
if (Test-Path -Path $CSPath) {
Move-Item -Path $CSPath -Destination $BackupFile
}
Write-Verbose -Message 'Backup shared CredentialStore...'
$CSShared = Get-DefaultCredentialStorePath -Shared
$BackupSharedFile = '{0}.back' -f $CSShared
if (Test-Path -Path $CSShared) {
Move-Item -Path $CSShared -Destination $BackupSharedFile
}
Write-Verbose -Message 'Remove old CredentialStore in Temp dir'
$CSTemp = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json'
if (Test-Path -Path $CSTemp) {
Remove-Item -Path $CSTemp
}
$VerbosePreference = 'SilentlyContinue'
}
Describe 'New-CredentialStoreItem' {
Context 'Private Credential Store tests' {
It 'Add entry to a private store.' {
# Create a fresh CredentialStore first
New-CredentialStore -Force
# Define the content of the CredentialStoreItem.
$RemoteHost = 'barfoo'
$UserName = 'MyUser'
$Password = 'fooobarysdfsfs' | ConvertTo-SecureString -AsPlainText -Force
# Form the CredentialObject.
$creds = [PSCredential]::new($UserName, $Password)
# Create the CredentialStoreItem.
New-CredentialStoreItem -RemoteHost $RemoteHost -Credential $creds
# Formulate an update to the CredentialStoreItem.
$ClearPassword = 'fooobaryadfafa'
$Password = $ClearPassword | ConvertTo-SecureString -AsPlainText -Force
$creds = [PSCredential]::new($UserName, $Password)
{
Set-CredentialStoreItem -RemoteHost $RemoteHost -Credential $creds
} | Should -Not -Throw
# Control the content of the CredentialStore.
$content = Get-CredentialStoreItem -RemoteHost $RemoteHost
$content.GetNetworkCredential().Password | Should -Be $ClearPassword
}
}
Context 'Shared Credential Store tests' {
It 'Add entry to a shared store.' {
# Create a fresh CredentialStore first
$tmpCS = Join-Path -Path (Get-TempDir) -ChildPath '/CredentialStore.json'
New-CredentialStore -Path $tmpCS -Force -Shared
# Define the content of the CredentialStoreItem.
$RemoteHost = 'barfoo'
$UserName = 'MyUser'
$Password = 'fooobarysdfsfs' | ConvertTo-SecureString -AsPlainText -Force
# Form the CredentialObject.
$creds = [PSCredential]::new($UserName, $Password)
# Create the CredentialStoreItem.
New-CredentialStoreItem -RemoteHost $RemoteHost -Credential $creds -Path $tmpCS -Shared
# Formulate an update to the CredentialStoreItem.
$ClearPassword = 'fooobaryadfafa'
$Password = $ClearPassword | ConvertTo-SecureString -AsPlainText -Force
$creds = [PSCredential]::new($UserName, $Password)
{
Set-CredentialStoreItem -RemoteHost $RemoteHost -Credential $creds -Path $tmpCS -Shared
} | Should -Not -Throw
# Control the content of the CredentialStore.
$content = Get-CredentialStoreItem -RemoteHost $RemoteHost -Path $tmpCS -Shared
$content.GetNetworkCredential().Password | Should -Be $ClearPassword
}
}
}
AfterAll {
# Cleanup test stores and restore existing ones.
$VerbosePreference = 'Continue'
Write-Verbose -Message 'Restoring private CredentialStore'
If (Test-Path -Path $BackupFile) {
If (Test-Path -Path $CSPath) {
Remove-Item -Path $CSPath
Move-Item -Path $BackupFile -Destination $CSPath
}
}
Write-Verbose -Message 'Restoring shared CredentialStore'
If (Test-Path -Path $BackupSharedFile) {
If (Test-Path -Path $CSShared) {
Remove-Item -Path $CSShared
Move-Item -Path $BackupSharedFile -Destination $CSShared
}
}
$VerbosePreference = 'SilentlyContinue'
}

View File

@ -13,7 +13,7 @@ function Set-CredentialStoreItem {
Specify the host you for which you would like to change the credentials. Specify the host you for which you would like to change the credentials.
.PARAMETER Identifier .PARAMETER Identifier
Defaults to "". Specify a string, which separates two CredentialStoreItems for the Defaults to ''. Specify a string, which separates two CredentialStoreItems for the
same hostname. same hostname.
.PARAMETER Shared .PARAMETER Shared
@ -30,10 +30,10 @@ function Set-CredentialStoreItem {
[None] [None]
.EXAMPLE .EXAMPLE
Set-CredentialStoreItem -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.local" Set-CredentialStoreItem -Path 'C:\TMP\mystore.json' -RemoteHost 'esx01.myside.local'
.EXAMPLE .EXAMPLE
Set-CredentialStoreItem -Path "C:\TMP\mystore.json" -RemoteHost "esx01.myside.local" -Identifier svc Set-CredentialStoreItem -Path 'C:\TMP\mystore.json' -RemoteHost 'esx01.myside.local' -Identifier svc
#> #>
[CmdletBinding(DefaultParameterSetName = 'Private')] [CmdletBinding(DefaultParameterSetName = 'Private')]
@ -65,7 +65,7 @@ function Set-CredentialStoreItem {
begin { begin {
# Set the CredentialStore for private, shared or custom mode. # Set the CredentialStore for private, shared or custom mode.
Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) Write-Debug ('ParameterSetName: {0}' -f $PSCmdlet.ParameterSetName)
if ($PSCmdlet.ParameterSetName -eq 'Private') { if ($PSCmdlet.ParameterSetName -eq 'Private') {
$Path = Get-DefaultCredentialStorePath $Path = Get-DefaultCredentialStorePath
} }
@ -77,32 +77,52 @@ function Set-CredentialStoreItem {
} }
process { process {
# Lets do a quick test on the given CredentialStore. # Define the default splatting.
if (-not(Test-CredentialStore -Shared -Path $Path)) { $DefaultSplatting = @{
Path = $Path
}
# Check if the user passed -Shared. If he added -Shared, we'll pass it into the splatting
if ($PSBoundParameters.ContainsKey('Shared')) {
$DefaultSplatting.Add('Shared', $true)
}
else {
$DefaultSplatting.Add('Shared', $false)
}
# Now lets check the given CredentialStore.
if (-not(Test-CredentialStore @DefaultSplatting)) {
$MessageParams = @{ $MessageParams = @{
Message = 'Could not add anything into the given CredentailStore.' Message = ('The given CredentialStore ({0}) does no exist.' -f $Path)
ErrorAction = 'Stop' ErrorAction = 'Stop'
} }
Write-Error @MessageParams Write-Error @MessageParams
} }
# Read the file content based on the given ParameterSetName # Read the file content based on the given ParameterSetName
$CSContent = Get-CredentialStore -Shared -Path $Path $CSContent = Get-CredentialStore @DefaultSplatting
# Get a formatted current date for the last update time of the Item.
$CurrentDate = Get-Date -Format 'u' $CurrentDate = Get-Date -Format 'u'
if ($Identifier -ne "") { # Check if the user supplied an identifier. If so, we need to mangle the CredentialName, as that's where
$CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost # the identifier is actually added.
if ($Identifier -ne '') {
$CredentialName = $RemoteHost = '{0}/{1}' -f $Identifier, $RemoteHost
} }
else { else {
$CredentialName = $RemoteHost $CredentialName = $RemoteHost
} }
# If the user didn't supply a CredentialObject, we need to prompt for it.
if (-not($Credential)) { if (-not($Credential)) {
$Credential = Get-Credential -Message $CredentialName $Credential = Get-Credential -Message $CredentialName
} }
if ($Credential.UserName) { # If the username isn't empty, we ca go ahead and update the entry.
if ($null -ne $Credential.UserName -and -not [string]::IsNullOrWhiteSpace($Credential.UserName)) {
# Check if the path to the PfxCertificate is stored in the CredentialStore. If so load the certificate.
# If not, load try loading the certificate from the Filepath of the CredentialStore.
if ($null -eq $CSContent.PfxCertificate) { if ($null -eq $CSContent.PfxCertificate) {
$Cert = Get-CSCertificate -Type $CSContent.Type -Thumbprint $CSContent.Thumbprint $Cert = Get-CSCertificate -Type $CSContent.Type -Thumbprint $CSContent.Thumbprint
} }
@ -110,13 +130,17 @@ function Set-CredentialStoreItem {
$Cert = Get-PfxCertificate -FilePath $CSContent.PfxCertificate -ErrorAction Stop $Cert = Get-PfxCertificate -FilePath $CSContent.PfxCertificate -ErrorAction Stop
} }
# Now locate the Item.
if (Get-Member -InputObject $CSContent -Name $CredentialName -MemberType Properties) { if (Get-Member -InputObject $CSContent -Name $CredentialName -MemberType Properties) {
# Get a random AES key for the entry.
$RSAKey = Get-RandomAESKey $RSAKey = Get-RandomAESKey
$CSContent.$CredentialName.User = $Credential.UserName $CSContent.$CredentialName.User = $Credential.UserName
$ConvertParams = @{ $ConvertParams = @{
SecureString = $Credential.Password SecureString = $Credential.Password
Key = $RSAKey Key = $RSAKey
} }
# Now create a updated item containing the updated credentials.
$CSContent.$CredentialName.Password = ConvertFrom-SecureString @ConvertParams $CSContent.$CredentialName.Password = ConvertFrom-SecureString @ConvertParams
$CSContent.$CredentialName.LastChange = $CurrentDate $CSContent.$CredentialName.LastChange = $CurrentDate
$CSContent.$CredentialName.EncryptedKey = [Convert]::ToBase64String( $CSContent.$CredentialName.EncryptedKey = [Convert]::ToBase64String(
@ -125,10 +149,15 @@ function Set-CredentialStoreItem {
[System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1 [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1
) )
) )
# Convert the CredentialStore back into JSON and save it to the file.
ConvertTo-Json -InputObject $CSContent -Depth 5 | Out-File -FilePath $Path -Encoding utf8 ConvertTo-Json -InputObject $CSContent -Depth 5 | Out-File -FilePath $Path -Encoding utf8
} }
else {
Write-Warning -Message ('Unable to locate CredentialStoreItem for {0}' -f $CredentialName)
} }
Else { }
else {
$MessageParams = @{ $MessageParams = @{
Message = 'Please Provide at least a valid user!' Message = 'Please Provide at least a valid user!'
ErrorAction = 'Stop' ErrorAction = 'Stop'

View File

@ -33,11 +33,11 @@ function Test-CredentialStoreItem {
[None] [None]
.EXAMPLE .EXAMPLE
if (Test-CredentialStoreItem -RemoteHost "Default") { if (Test-CredentialStoreItem -RemoteHost 'Default') {
Get-CredentialStoreItem -RemoteHost "Default" Get-CredentialStoreItem -RemoteHost 'Default'
} }
else { else {
Write-Warning ("The given Remote Host {0} does not exist in the credential Store!" -f $RemoteHost) Write-Warning ('The given Remote Host {0} does not exist in the credential Store!' -f $RemoteHost)
} }
#> #>
@ -45,7 +45,7 @@ function Test-CredentialStoreItem {
[OutputType([bool])] [OutputType([bool])]
param ( param (
[Parameter(Mandatory = $false, ParameterSetName = 'Shared')] [Parameter(Mandatory = $false, ParameterSetName = 'Shared')]
[string]$Path = "{0}\PSCredentialStore\CredentialStore.json" -f $env:ProgramData, [string]$Path = '{0}\PSCredentialStore\CredentialStore.json' -f $env:ProgramData,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
@ -61,7 +61,7 @@ function Test-CredentialStoreItem {
begin { begin {
# Set the CredentialStore for private, shared or custom mode. # Set the CredentialStore for private, shared or custom mode.
Write-Debug ("ParameterSetName: {0}" -f $PSCmdlet.ParameterSetName) Write-Debug ('ParameterSetName: {0}' -f $PSCmdlet.ParameterSetName)
if ($PSCmdlet.ParameterSetName -eq 'Private') { if ($PSCmdlet.ParameterSetName -eq 'Private') {
$Path = Get-DefaultCredentialStorePath $Path = Get-DefaultCredentialStorePath
} }
@ -73,8 +73,8 @@ function Test-CredentialStoreItem {
} }
process { process {
if ($Identifier -ne "") { if ($Identifier -ne '') {
$CredentialName = $RemoteHost = "{0}/{1}" -f $Identifier, $RemoteHost $CredentialName = $RemoteHost = '{0}/{1}' -f $Identifier, $RemoteHost
} }
else { else {
$CredentialName = $RemoteHost $CredentialName = $RemoteHost
@ -92,7 +92,7 @@ function Test-CredentialStoreItem {
} }
else { else {
$MsgParams = @{ $MsgParams = @{
Message = "The given credential store ({0}) does not exist!" -f $Path Message = 'The given credential store ({0}) does not exist!' -f $Path
} }
Write-Warning @MsgParams Write-Warning @MsgParams
return $false return $false