#requires -Version 5.1 [CmdletBinding()] param( [switch]$UpdateConfig, [string]$ConfigPath ) $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($ConfigPath)) { $scriptDirectory = $PSScriptRoot if ([string]::IsNullOrWhiteSpace($scriptDirectory) -and -not [string]::IsNullOrWhiteSpace($MyInvocation.MyCommand.Path)) { $scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path } if ([string]::IsNullOrWhiteSpace($scriptDirectory)) { throw 'Cannot determine the script directory. Specify -ConfigPath with the full config path.' } $ConfigPath = Join-Path $scriptDirectory 'appsettings.Development.json' } function ConvertTo-EncryptedValue { param( [Security.SecureString]$Value, [byte[]]$AesKey, [byte[]]$AesIv ) $pointer = [IntPtr]::Zero $bytes = $null $aes = $null $encryptor = $null try { $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Value) $bytes = [Text.Encoding]::UTF8.GetBytes([Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer)) $aes = [Security.Cryptography.Aes]::Create() $aes.Key = $AesKey $aes.IV = $AesIv $encryptor = $aes.CreateEncryptor() $encrypted = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length) return 'ENC(' + [Convert]::ToBase64String($encrypted) + ')' } finally { if ($null -ne $encryptor) { $encryptor.Dispose() } if ($null -ne $aes) { $aes.Dispose() } if ($null -ne $bytes) { [Array]::Clear($bytes, 0, $bytes.Length) } if ($pointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } } } # Read the same machine variables as CryptoHelper.cs on the deployment server. $key = [Text.Encoding]::UTF8.GetBytes([string][Environment]::GetEnvironmentVariable('ENC_AES_KEY', 'Machine')) $iv = [Text.Encoding]::UTF8.GetBytes([string][Environment]::GetEnvironmentVariable('ENC_AES_IV', 'Machine')) if ($key.Length -notin @(16, 24, 32) -or $iv.Length -ne 16) { throw 'ENC_AES_KEY must be 16/24/32 UTF-8 bytes and ENC_AES_IV must be 16 UTF-8 bytes. Run on the deployment server.' } $config = $null if ($UpdateConfig) { $ConfigPath = [IO.Path]::GetFullPath($ConfigPath) $config = [IO.File]::ReadAllText($ConfigPath) foreach ($field in @('AccessKeyId', 'AccessKeySecret')) { if ([regex]::Matches($config, '("' + $field + '"\s*:\s*)"[^"\r\n]*"').Count -ne 1) { throw "Expected exactly one $field field in the config. No changes were made." } } } $id = $null $secret = $null try { $id = Read-Host 'Enter the complete RAM user AccessKey ID (hidden input)' -AsSecureString $secret = Read-Host 'Enter the complete RAM user AccessKey Secret (hidden input)' -AsSecureString if ($id.Length -eq 0 -or $secret.Length -eq 0) { throw 'AccessKey ID and AccessKey Secret cannot be empty. No changes were made.' } $encryptedId = ConvertTo-EncryptedValue -Value $id -AesKey $key -AesIv $iv $encryptedSecret = ConvertTo-EncryptedValue -Value $secret -AesKey $key -AesIv $iv if ($UpdateConfig) { $config = [regex]::Replace($config, '("AccessKeyId"\s*:\s*)"[^"\r\n]*"', '${1}"' + $encryptedId + '"') $config = [regex]::Replace($config, '("AccessKeySecret"\s*:\s*)"[^"\r\n]*"', '${1}"' + $encryptedSecret + '"') [IO.File]::WriteAllText($ConfigPath, $config, [Text.UTF8Encoding]::new($false)) Write-Host 'Encrypted credentials saved. Deploy the updated config and restart the service.' } else { Write-Output ('AccessKeyId: ' + $encryptedId) Write-Output ('AccessKeySecret: ' + $encryptedSecret) } } finally { if ($null -ne $id) { $id.Dispose() } if ($null -ne $secret) { $secret.Dispose() } [Array]::Clear($key, 0, $key.Length) [Array]::Clear($iv, 0, $iv.Length) }