Palo Alto Automation: License Devices without Internet Access

Another entry for Palo Alto today. This a direct response to an issue I have come across. How do you license a Palo Alto device that cannot contact the Internet, without relying on significant manual intervention? Following in the footsteps of our previous entries, we want our script to be homogeneous and use PowerShell to complete this task.

Licensing API

Request Preparation

The solution takes shape through multiple phases. Our first step is to contact the Palo Alto Licensing API. To successfully make a request of the API, we need a few pieces of information. For all requests, we need to include the API key that can be generated within the Palo Alto portal, and the Auth Code for the license we want to pull from, also within the Palo Alto portal.

The next information we will need to gather is a question of whether our license is new, or existing. If we had previously licensed a device, but perhaps only uploaded a handful of its licenses, we will want to come back and finalize the deployment. In this scenario, we can send our request to the licensing API with the serial number from the device, and get the relevant license information returned for upload. In the case of a new deployment, we will need to gather the UUID and CPUID of the appliance, and send them to the licensing API.

Thankfully, the serial number, UUID, and CPUID can easily be gathered with our previously created function, Request-PaloApi. Sending an Operation request with a Show System Info returns the details of the appliance, and will contain the relevant details we need. At which time, we can pull from our results for our variables, example below.

$systemInfo = Request-PaloApi -ipAddress $ipAddress `
  -paloKey $paloKey `
  -apiType 'op' `
  -apiCmd '<show><system><info></info></system></show>'

$uuid = $systemInfo.system.'vm-uuid'
$cpuid = $systemInfo.system.'vm-cpuid'

With the details of that request, we can send it on to the licensing API and retrieve the details necessary.

Generate License Files

We can use another function to our advantage, Get-PaloLicense, to retrieve information, and generate license files.

function Get-PaloLicense{
  [CmdletBinding()]
  param (
    [string] $vmName,
    [string] $uuid,
    [string] $cpuid,
    [string] $licenseApiKey,
    [string] $authcode
  )

  <# Test Folder Paths for Licenses #>
  $paloParentPath = "$env:HOMEDRIVE\palo"
  $paloLicensePath = "$env:HOMEDRIVE\palo\$vmName"
  $paloFolderTest = Test-Path -Path $paloParentPath
  if ($paloFolderTest -eq $False){New-Item -Path "$env:HOMEDRIVE\" -Name 'palo' -ItemType 'Directory' -Force}
  $deployPaloFolderTest = Test-Path -Path $paloLicensePath
  if ($deployPaloFolderTest -eq $False){New-Item -Path "$env:HOMEDRIVE\palo" -Name "$vmName" -ItemType 'Directory' -Force}

  <# Generate the license via the public API #>
  $licenseUrl = 'https://api.paloaltonetworks.com/api/license/activate'
  $licenseHeader = @{
    apikey="$licenseApiKey"
    'content-type'='application/x-www-form-urlencoded'
  }
  $licenseBody = @{
    uuid="$uuid"
    cpuid="$cpuid"
    authcode="$authCode"
  }
  $licenseUrl = 'https://api.paloaltonetworks.com/api/license/activate'
  $licenseResponse = Invoke-RestMethod -Uri $licenseUrl -Method Post -Headers $licenseHeader -Body $licenseBody
  $licenses = ($licenseResponse | Select-Object -Property partidField,serialnumField,featureField,keyField)

  foreach ($license in $licenses){
    $fileName = $partidField + '_' + $serialnumField + '.key'
    $fileBody = $keyField
    $fileBody | Out-File -FilePath $paloLicensePath\$fileName -Force -Encoding ascii
  }
}

The function requests a few things, VM Name, Licensing API Key, Auth Code, UUID, and CPUID. We have the latter from our previous request and the portal, and are necessary for generation, and the former is for logical separation of our key files.

Our command above confirms the existence of two directories, a ‘Palo’ directory for navigation, and a sub-directory with the VM Name for placing the files. If these are not found, they will be created.

We generate our request by crafting our header and body. The Licensing API Key must be in the header, to authorize the request to the API, and to retrieve the data, we have our UUID, CPUID, and Auth Code in the request body. Using Invoke-RestMethod, we send our request to the licensing API, and send its results to a variable.

That variable will be an array we can parse for all relevant information, and we do so using our foreach. Parsing the array, we set the name to be descriptive of what the license is for, and what serial it’s attached to, and then export the file as ASCII, a requirement for uploading back to Palo Alto.

From here, you can simply log into your Palo Alto device, and manually upload the licenses. It’s requested that you install the licenses with the VM license last, as it will trigger a reboot of the device. Alternatively, why log back in, when we can upload with PowerShell.

License Import

We will be using two functions here. In the below example, their list order is to show the process. If they are implemented into a script, they should be reversed when placed inside. Lets cover Generate-PaloImport and Request-PaloImport.

function Generate-PaloImport{
  [CmdletBinding()]
  param(
    [string] $partidField,
    [string] $serialnumField,
    [string] $ipAddress,
    [string] $paloKey   
  )
  $fileName = $partidField + '_' + $serialnumField + '.key'
  $filePath = "$paloLicensePath\$fileName"
  $fileRead = [IO.File]::ReadAllBytes($filePath);
  $fileEncoded = [Text.Encoding]::GetEncoding('ascii').GetString($fileRead);
  $boundary = [guid]::NewGuid().ToString();
  $lineFormat = "`r`n";
    
  $bodyContent = ( 
    "--$boundary",
    "Content-Disposition: form-data; name=file; filename=$fileName",
    "Content-Type: application/octet-stream$lineFormat",
    $fileEncoded,
    "--$boundary--$lineFormat" 
  ) -join $lineFormat
  $import = Request-PaloImport -ipAddress $ipAddress -paloKey $paloKey -Verbose
  return $import
}

function Request-PaloImport{
  [CmdletBinding()]
  param(
    [string] $ipAddress,
    [string] $paloKey
  )
  try {
    Write-Verbose -Message 'Importing License'
    $licenseResponse = Invoke-RestMethod -Uri $licenseUploadUrl -Method Post -ContentType "multipart/form-data; boundary=$boundary" -Body $bodyContent -ErrorVariable errorMessage
    Write-Verbose -Message 'Request Sent - Testing Success'
    $paloLicenseCheck = $licenseResponse.response.status
    if ($paloLicenseCheck -ne 'success'){
      throw [Net.NetworkInformation.NetworkInformationException]::New('an error occurs while retrieving network information.')
    }
    else{
      $result = 'License Imported Successfully'
    }
  }
  catch {
    Write-Verbose -Message 'Device not responding - Waiting 30 Seconds'
    Write-Verbose -Message ('Message: {0}' -f $errorMessage)
    $result = 'Error on Request - Enable Verbose'
  }
  return $result
}

Here we begin by requesting the IP address of the Palo Alto we are importing licenses to, a key to access it, and the serial number, and Part ID from the keys we generated. With this information, we read in the key information, and pre-process it for upload, wrapping it to present to the API for import.

From there, we use that information as part of an Invoke-RestMethod to the Palo Alto API, requesting an import. We confirm that the license imported correctly, and return a failure or success message. After that, we can repeat the process for the other licenses, and begin receiving traffic on our Palo Alto appliance.

Thanks for reading, hope to provide you with more information moving forward.

Palo Alto Automation: License Devices without Internet Access

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.