I’m in the process of writing a script that will pull a resources report from our Zerto server. I have the Zerto cmdlets installed and I am able to get what data I want just fine, the issue lies in the authentication. Ideally, this script will be running autonomously on a schedule with no human interaction, but therein lies the problem. With the Zerto cmdlets, there is no -credential switch, just -user. The only authentication available with the cmdlets is interactive where you have to type in your password each time the script is ran. With REST or Zerto’s API I can do the authentication programmatically but not without the password being in plain text somewhere in the script.
Is there some method I am missing or a more obtuse way of doing this that as a slightly-more-advanced-than-noob PoSh scripter, I just can’t think of accomplishing what I need?
Your choices would appear to be to (A) ask the vendor to fix their commands to provided a -Credential parameter or (B) code directly against their REST API using Invoke-WebRequest.
This you will run once, which will prompt for credentials and save them (password as a securestring) in an .xml file on the local machine. It is my understanding that this credential file will only work for the user that created it and on the machine on which it was created.
If he’s using Invoke-RestMethod to access the API (per second-to-last sentence) and the only hurdle is a plain-text password in the script, then this will help. But I may have misinterpreted that statement, and if he’s using the vendor cmdlets either-way, then this will not help, unless there’s a way to convert the securestring password to a plain-text string and inject it somehow.
This sounds like a truly viable option. I will give it a try and let you know how it worked out.
I use a very similar method with my other scripts that use cmdlets that have -credential available and the un/pw into a PS Creds object, but I just couldn’t figure out how to translate the securestring text file back into a readable pw. I was using plain text files though, not xml so that might be the key in this case.
I tried the method you suggested and I’m getting an error that states:
Invoke-WebRequest : The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
Here is the code as I have it:
#region Static Variables
#Zerto Variables
$strZVMIP = "{IP of ZVM}"
$strZVMPort = "{Port}"
$zCreds = Import-Clixml -Path "C:\pathto\pass.xml"
$strZVMUser = $zCreds.UserName
$strZVMPwd = $zCreds.Password
#endregion Static Variables
#region Authenticate
#Perform authentication so that Zerto APIs can run. Return a session identifier that needs to be inserted in the header for subsequent requests.
function getxZertoSession ($userName,$password){
$baseURL = "https://" + $strZVMIP + ":" + $strZVMPort
$xZertoSessionURI = $baseURL +"/v1/session/add"
$authInfo = ("{0}:{1}" -f $userName,$password)
$authInfo = [System.Text.Encoding]::UTF8.GetBytes($authInfo)
$authInfo = [System.Convert]::ToBase64String($authInfo)
$headers = @{Authorization=("Basic {0}" -f $authInfo)}
$body = '{"AuthenticationMethod": "1"}'
$contentType = "application/XML"
$xZertoSessionResponse = Invoke-WebRequest -Uri $xZertoSessionURI -Headers $headers -Method POST -Body $body -ContentType $contentType
return $xZertoSessionResponse.headers.get_item("x-zerto-session")
}
#Extract x-zerto-session from the response, and add it to the actual API:
$xZertoSession = getxZertoSession $strZVMUser $strZVMPwd
$zertoSessionHeader = @{"x-zerto-session"=$xZertoSession}
#endregion Authenticate
This is the exact code I had with the un/pw in plaintext defined in $strZVMUser and $strZVMPass, I just changed it to use the data loaded from the xml file in $zCreds.
Not sure if that is all that is required to make this work or if I’m missing something else.
I’ve used similar code (encoding as base64string etc) for a different system, and it worked using http. However, I remember the documentation for that specific application stated that in order to use https, I was required to download a certificate and include that. It was not a requirement so I never tried it, but maybe it is something to look at.
A 3rd option would be to put the password in a file in a secured UNC path. Then only grant permissions to that unc path to the account running the scheduled task. This way the password is stored remotely and is secured.
Thank you for this info. The encrypted password method was my first thought as well as I use that in many of my other scripts. However I have not been able to figure out how to get the password imported from the secure pw file in a format that works with the api. Not sure if this a limitation of the Zerto API or a limitation of my knowledge. It isn’t an option for the Zerto cmdlets because there is no -credential switch that I can load with a PS.Automation.Cred object.
The obfuscating method might be worth looking into but isn’t ideal as there may come a time when someone is trying to troubleshoot the script and I can see this being troublesome.
The secured UNC path seems reasonable enough if I can’t get any other method to work so I will keep that in mind.
Thanks for the direction. I’ve never done anything like this but it sounds pretty straight forward. I will give it a shot and see what I can come up with.
# Function to encrypt a string.
Function encryptPass ($pass) {
$encPass = $pass | ConvertTo-SecureString -AsPlainText -Force
Write-Output ($encpass | ConvertFrom-SecureString)
#uncomment the line below to store the password in a text file for copying.
#($encPass | ConvertFrom-SecureString) | Set-Content encpass.txt
}
# Function to decrypt string.
Function decryptPass ($encPass) {
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encPass)
Write-Output ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR))
}
encryptPass "myPassW0rd" # Just an example to get the encrypted password string.
# The variable below is the encrypted string derived from the encryptpass function.
$encryptedPassword = '01000000d08c9ddf0115d1118c7a00c04fc297eb010000006bf1d4f68a456f4ab7b6d83787e0c7650000000002000000000003660000c000000010000000bcb563a7df82c4daa0747b025024e7760000000004800000a000000010000000deed235cf5c0f94bc24fefa6f3d4817a180000009a23d77945bfe6ddcd936b08de5dda63089ac3983b70cdd514000000a53b085ddb90fdc8ea3691e49a42a29a05f518dc' | ConvertTo-SecureString
decryptPass $encryptedPassword
# To use this with your restAPI call you would use the code below:
$encryptedPassword = '01000000d08c9ddf0115d1118c7a00c04fc297eb010000006bf1d4f68a456f4ab7b6d83787e0c7650000000002000000000003660000c000000010000000bcb563a7df82c4daa0747b025024e7760000000004800000a000000010000000deed235cf5c0f94bc24fefa6f3d4817a180000009a23d77945bfe6ddcd936b08de5dda63089ac3983b70cdd514000000a53b085ddb90fdc8ea3691e49a42a29a05f518dc' | ConvertTo-SecureString
Invoke-RestMethod ....... -password (decryptpass $encryptedPassword)
Also, just as an fyi I believe I used the Invoke-RestMethod cmdlet, which may or may not behave differently than Invoke-WebRequest. My only real experience is using the former, but the rest of code is similar to what I did. I missed that detail before.