# PoSh, RESTful, Credentials

This topic contains 20 replies, has 6 voices, and was last updated by  Sean 10 months ago.

• Author
Posts
• #79396

Sean
Participant

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

• #79540

Don Jones
Keymaster

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.

• #79681

Sean
Participant

Don,

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

Thanks

• #79556

Ian Hockett
Participant

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" • #79565 Don Jones Keymaster 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. • #79579 Ian Hockett Participant 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. • #79687 Sean Participant 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 • #79682 Sean Participant 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! • #79705 Sean Participant 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!

• #79706

Ian Hockett
Participant

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.

• #79730

Sean
Participant

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!

• #79820

Ian Hockett
Participant

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.

• #79664

Max Kozlov
Participant

You can get plain text password from credentials this way

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

• #79684

Sean
Participant

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

• #79709

Rick
Participant

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

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.

• #79726

Sean
Participant

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!

• #79732

Dudebro
Participant

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.

• #79735

Sean
Participant

Dudebro,

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

• #79741

Rick
Participant

# 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)  • #79822 Sean Participant 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. • #79988 Sean Participant Thank you everyone for all your help! I successfully got what I needed with a little bit of help from each of you! Here is the final [sanitized] version: # Client_Zerto_Resource_Report.ps1 by {me} #region Description & Versioning #endregion Description & Versioning #region Static Variables #Date Variables$date = Get-Date -Format Y
$from = (Get-Date).AddDays(-1).ToString("yyyy-MM-dd")$to = Get-Date -Format yyyy-MM-dd
$repDate = Get-Date -Format yyyy-MM$repTitle = $repDate + "_ZertoResourceReport.xlsx" #Zerto Variables$strZVMIP = {ip}
$strZVMPort = {port}$zCreds = Import-Clixml -Path "C:\path\to\pass.xml"
$strZVMUser =$zCreds.GetNetworkCredential().UserName
$strZVMPwd =$zCreds.GetNetworkCredential().Password
#Mail Variables
$smtpServer = {smtp host}$msgFrom = {from acct}
$msgTo = {recipient}$msgSubject = "Zerto Resource Report for $date"$msgBody = "Zerto Resource Report Attached"
#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/json"
$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 #region Do Work #Invoke the Zerto API:$urlReportAPI = "https://" + $strZVMIP + ":"+$strZVMPort+"/ZvmService/ResourcesReport/getSamples?fromTimeString=$from&toTimeString=$to&startIndex=0&count=500"
#Pull all data for report:
$dataAll = (Invoke-RestMethod -Uri$urlReportAPI -Headers $zertoSessionHeader -ContentType "application/json") #Build empty billing array$dataBilling = @()
#Build objects for relavent data
foreach($row in$dataAll){
$obj = "" | Select Client, MemoryGB, vCPU, ProvStorageGB$obj.Client = $row.Zorg$obj.MemoryGB = [math]::Round(($row.MemoryInMB / 1024))$obj.vCPU = $row.NumberOfvCpu$obj.ProvStorageGB = [math]::Round($row.SourceVolumesProvisionedStorageInGB) #Load objects into array$dataBilling+=$obj } #Create Report .csv from array$dataBilling | sort Client | Export-Excel "C:\Reports\$repTitle" #Email Report to {recipient} Send-MailMessage -From$msgFrom -To $msgTo -Subject$msgSubject -Body $msgBody -Attachments "C:\Reports\$repTitle" -SmtpServer \$smtpServer
#endregion Do Work


Thank you Ian, Max, and DudeBro for your guidance.

Regards
Sean

You must be logged in to reply to this topic.