PoSh, RESTful, Credentials

This topic contains 20 replies, has 6 voices, and was last updated by  Sean 2 months, 3 weeks 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.

    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.

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

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