diff --git a/DOrcDeployModule.psm1 b/DOrcDeployModule.psm1 index 95cd69d..8a540af 100644 --- a/DOrcDeployModule.psm1 +++ b/DOrcDeployModule.psm1 @@ -1,3 +1,45 @@ +#region Helper Functions + +<# +.SYNOPSIS + Safely formats a parameter for logging, hiding sensitive values. +.PARAMETER Parameter + The parameter string in format "Name=Value" +.OUTPUTS + Formatted string safe for logging +#> +function Format-ParameterForLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string]$Parameter, + [string]$MaskString = "***HIDDEN***" + ) + + process { + if ($Parameter -notmatch '^([^=]+)=(.*)$') { + Write-Warning "Invalid format: 'paramName=paramValue' expected" + return $Parameter + } + + $paramName = $Matches[1].Trim() + $paramValue = $Matches[2].Trim() + + # Check if the parameter name itself indicates it's a secret + $secretParams = 'password', 'pwd', 'secret', 'key', 'token', 'credential', 'api(key|secret)', 'accesskey', 'auth(header|token)' + if ($paramName -match ($secretParams -join '|')) { + return "$paramName=$MaskString" + } + + # Mask secrets within connection-string style values + $maskedValue = $paramValue -replace '(?i)(password|pwd|key|secret|token|accesskey|accountkey)\s*=\s*[^;]+', "`$1=$MaskString" + + return "$paramName=$maskedValue" + } +} +#endregion + + function Invoke-VsDbCmd { Param ( @@ -304,13 +346,9 @@ function InstallMSI([string] $strComputerName, [string] $strMSIFullName, $arrPar $strUNCMSIName = Join-Path $DestFolder $strMSIName if ($arrParameters.Count -gt 0) { Write-Host "[InstallMSI] Attempting to install:" $strMSIFullName "on:" $strComputerName "with the following parameters:" + foreach ($strParameter in $arrParameters) { - if (($strParameter.ToLower().Contains("password")) -or ($strParameter.ToLower().Contains("pswd")) -or ($strParameter.ToLower().Contains("pass"))) { - Write-Host " "$strParameter.Split("=")[0] - } - else { - Write-Host " " $strParameter - } + Write-Host " " (Format-ParameterForLogging -Parameter $strParameter) $strAllParameters = $strAllParameters + " " + $strParameter.replace('%','%%') } } @@ -969,8 +1007,13 @@ function RunMTMTests($arrParameters) { $bolReturn = $false if ($arrParameters.Count -gt 0) { foreach ($strParameter in $arrParameters) { - Write-Host "Setting" $strParameter.Split("=")[0] "to:" $strParameter.Split("=")[1] - Set-Variable -Name $strParameter.Split("=")[0] -Value $strParameter.Split("=")[1] + $parameterName = $strParameter.Split("=")[0] + $parameterValue = $strParameter.Split("=")[1] + + $safeDisplay = Format-ParameterForLogging -Parameter $strParameter + Write-Host "Setting $safeDisplay" + + Set-Variable -Name $parameterName -Value $parameterValue } ## Sync $UpdateParams = " testcase /import /collection:" + $TFSInstance + " /teamproject:" + $TFSTeamProject + " /storage:" + $TestDll + " /syncsuite:" + $TestSuite diff --git a/DorcDeployModule.unit.tests.ps1 b/DorcDeployModule.unit.tests.ps1 new file mode 100644 index 0000000..9ebfc50 --- /dev/null +++ b/DorcDeployModule.unit.tests.ps1 @@ -0,0 +1,56 @@ +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module "$here\DOrcDeployModule.psm1" -Force -ErrorAction Stop + +Describe "Enhanced Parameter Security Tests" { + Context "Basic functionality" { + It "Hides sensitive parameter names" { + Format-ParameterForLogging "PASSWORD=secret123" | Should -Be "PASSWORD=***HIDDEN***" + } + + It "Preserves non-sensitive parameters" { + Format-ParameterForLogging "SERVER=localhost" | Should -Be "SERVER=localhost" + } + + It "Is case insensitive" { + Format-ParameterForLogging "my-Secret=test" | Should -Be "my-Secret=***HIDDEN***" + } + } + + Context "Real secure property examples" { + It "Masks AccessKey in Azure SignalR connection string" { + $testInput = "SIGNALR_CONN=Endpoint=https://app.service.signalr.net;AccessKey=secret123;Version=1.0" + $result = Format-ParameterForLogging $testInput + $result | Should -Match "AccessKey=\*\*\*HIDDEN\*\*\*" + $result | Should -Match "Endpoint=https://app.service.signalr.net" + } + + It "Masks Password in SQL connection string" { + $testInput = "DB_CONN=Server=srv;Database=db;Password=secret123;Timeout=30" + $result = Format-ParameterForLogging $testInput + $result | Should -Match "Password=\*\*\*HIDDEN\*\*\*" + $result | Should -Match "Server=srv" + } + + It "Handles multiple secrets in one connection string" { + $testInput = "CONN=Server=test;Password=secret;AccessKey=key123;Database=mydb" + $result = Format-ParameterForLogging $testInput + $result | Should -Match "Password=\*\*\*HIDDEN\*\*\*" + $result | Should -Match "AccessKey=\*\*\*HIDDEN\*\*\*" + $result | Should -Match "Server=test" + } + + It "Masks all examples of secret values" { + $examples = @( + "APPUSERPASSWORD", "ProdDeployPassword", "ApiAccessPassword", "SERVICE_SVCPASSWORD", + "ClientSecret", "OAUTH_CLIENT_SECRET", "OAUTH2_CLIENTAPPID_SECRET", "CLIENT_PS_SECRET", "SqsSecretKey", + "OPENAI_API_KEY", "SqsAccessKey", "ApiKey", "API_APIKEY", "PrivateKeyPassphrase", "_API_KEY_", + "API_TOKEN", "VaultToken", "AccessTokenClientSecret", "EexToken", + "BasicAuthHeader" + ) + foreach ($example in $examples) { + Format-ParameterForLogging "$example=somesecret" | Should -Match "$example=\*\*\*HIDDEN\*\*\*" + } + } + + } +}