PoSh, RESTful, Credentials

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?

Thanks

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.

Perhaps an answer using the REST option would be to encode the credentials in a clixml file. Example:

Get-Credential | Export-Clixml -Path "C:\path\to\credentials.xml"

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.

Then store it in a variable:

$Credential = Import-Clixml -Path "C:\path\to\credentials.xml"

I think the difficulty here is that the commands don’t have any way for that credential to be used. They’re using their own interactive prompt.

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.

You can get plain text password from credentials this way

PS C:\> $c = Get-Credential
PS C:\> $c.GetNetworkCredential().password

Don,

Reaching out to the vendor was my first reaction and got me no where. The API is my decided method.

Thanks

Ian,

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.

Thank you!

Max,

Thanks for your reply. I am aware of this method but it is entirely human interactive which is exactly what I’m trying to avoid.

Thanks

Don,

Yes, that is the case with the Zerto cmdlets, but as Max pointed out, I’m willing and have ultimately decided to use REST.

Thanks

Ian, (edited to address right person)

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.

Thanks!

Sean,

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.

I would suggest encrypting the password in the script or possibly obfuscating the script altogether.

Encrypt Password: https://www.pdq.com/blog/secure-password-with-powershell-encrypting-credentials-part-1/

Obfuscating Powershell: http://www.powertheshell.com/powershell-obfuscator/

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.

Rick,

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.

Thank you!

Ian,

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.

Thanks!

What I believe Max was suggesting is to replace this:

$strZVMPwd = $zCreds.Password

With this:

$strZVMPwd = $zCreds.GetNetworkCredential().Password

This will extract the password in plain text from the PSCredential object.

Dudebro,

Ohhhh, that’s awesome. I’ll give that a shot. Thanks for the clarification

How about something like this?

# 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.

Rick,

Thank you for this. This sounds like a viable option and I will add it to my TRY bucket when I get time again to revisit this script.