Home > AutoPkg, Mac administration, macOS, Packaging > Using AutoPkg to build installers for Palo Alto’s GlobalProtect VPN software

Using AutoPkg to build installers for Palo Alto’s GlobalProtect VPN software

As part of some recent testing, I needed to do some work with Palo Alto’s GlobalProtect VPN software. Palo Alto provides an installer package for GlobalProtect, but it has some interesting characteristics as the installer includes three installation options. One is enabled by default and the other two are disabled by default.

The first configuration is the option to install GlobalProtect, the default enabled configuration:

Screenshot 2022 12 08 at 3 47 44 PM

The second configuration is the option to uninstall GlobalProtect, which is disabled by default:

Screenshot 2022 12 08 at 3 49 18 PM

The third configuration is the option to enable the System Extension for GlobalProtect, which is disabled by default:

Screenshot 2022 12 08 at 3 50 35 PM

Note: In the image above, I’ve done some photoshopping because checking the third option to enable the System Extension for GlobalProtect also enables the option to install GlobalProtect. I made the change to the image to hopefully make more clear which option I was discussing.

The options to uninstall GlobalProtect and enable the System Extension for GlobalProtect can be managed by using an installer choices XML file to selectively enable only the desired option. For example, here’s the installer choices XML file for enabling only the option to uninstall GlobalProtect:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<array>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>second</string>
</dict>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>com.paloaltonetworks.globalprotect.uninstall.pkg</string>
</dict>
</array>
</plist>

Here’s the installer choices XML file for enabling only the option to enable the System Extension for GlobalProtect:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<array>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>third</string>
</dict>
<dict>
<key>attributeSetting</key>
<integer>1</integer>
<key>choiceAttribute</key>
<string>selected</string>
<key>choiceIdentifier</key>
<string>com.paloaltonetworks.globalprotect.systemext.pkg</string>
</dict>
</array>
</plist>

Using these options, I was able to build recipes for AutoPkg which would automatically build three installer packages:

  • An installer which installs GlobalProtect.
  • An installer which uninstalls GlobalProtect.
  • An installer which enables the System Extension for GlobalProtect.

The reason I chose to do this is that using AutoPkg to create these additional installer packages should help ensure any changes that Palo Alto makes to GlobalProtect’s uninstall and System Extension enablement will automatically be available whenever a new version of GlobalProtect is picked up by AutoPkg. In turn, this should save work for those deploying GlobalProtect because now they don’t need to figure out what may have changed between GlobalProtect releases. For more details, please see below the jump.

There is an existing AutoPkg .download recipe for GlobalProtect, available via the link below:

https://github.com/autopkg/peshay-recipes/blob/master/PaloAlto/GlobalProtect.download.recipe

Since that part of the recipe setup is already done, I focused on building AutoPkg .pkg recipes. For the example recipe shown below which handles creating the installer which installs GlobalProtect, the recipe won’t make any changes to the downloaded installer package beyond renaming it. This is because by default, the GlobalProtect installer package installs GlobalProtect.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<dict>
<key>Description</key>
<string>Downloads the latest version of Palo Alto's GlobalProtect installer package and renames the package with version. Requires the use of HOSTNAME to point at your GlobalProtect instance. Use pkg_path in parent recipes for package with version.</string>
<key>Identifier</key>
<string>com.company.pkg.GlobalProtect</string>
<key>Input</key>
<dict>
<key>NAME</key>
<string>GlobalProtect</string>
<key>VENDOR</key>
<string>PaloAlto</string>
<key>SOFTWARETITLE</key>
<string>GlobalProtect</string>
</dict>
<key>MinimumVersion</key>
<string>1.0.0</string>
<key>ParentRecipe</key>
<string>com.company.download.GlobalProtect</string>
<key>Process</key>
<array>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/unpack</string>
<key>flat_pkg_path</key>
<string>%pathname%</string>
</dict>
<key>Processor</key>
<string>FlatPkgUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>pattern</key>
<string>%RECIPE_CACHE_DIR%/unpack/*gp.pkg</string>
</dict>
<key>Processor</key>
<string>FileFinder</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app</string>
<key>pkg_payload_path</key>
<string>%found_filename%/Payload</string>
</dict>
<key>Processor</key>
<string>PkgPayloadUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>input_plist_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app/Contents/Info.plist</string>
<key>plist_version_key</key>
<string>CFBundleShortVersionString</string>
</dict>
<key>Processor</key>
<string>Versioner</string>
</dict>
<dict>
<key>Processor</key>
<string>PkgCopier</string>
<key>Arguments</key>
<dict>
<key>source_pkg</key>
<string>%pathname%</string>
<key>pkg_path</key>
<string>%RECIPE_CACHE_DIR%/%VENDOR%_%SOFTWARETITLE%_%version%.pkg</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PathDeleter</string>
<key>Arguments</key>
<dict>
<key>path_list</key>
<array>
<string>%RECIPE_CACHE_DIR%/unpack</string>
<string>%RECIPE_CACHE_DIR%/payload</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

The second and third .pkg recipes will wrap the downloaded installer package inside a second installer package, along with the following files which will also be stored in the second installer package:

  1. An installer choices XML file
  2. A postinstall script which will install the downloaded installer using the options configured by the installer choices XML file.

AutoPkg recipe to create an installer package which uninstalls GlobalProtect:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<dict>
<key>Description</key>
<string>Downloads the current release version of the Global Protect VPN client and builds an installer package which uninstalls Global Protect.</string>
<key>Identifier</key>
<string>com.company.pkg.GlobalProtect.uninstall</string>
<key>Input</key>
<dict>
<key>NAME</key>
<string>GlobalProtect</string>
<key>VENDOR</key>
<string>PaloAlto</string>
<key>SOFTWARETITLE1</key>
<string>GlobalProtect</string>
<key>SOFTWARETITLE2</key>
<string>Uninstaller</string>
</dict>
<key>MinimumVersion</key>
<string>1.0.0</string>
<key>ParentRecipe</key>
<string>com.company.download.GlobalProtect</string>
<key>Process</key>
<array>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/unpack</string>
<key>flat_pkg_path</key>
<string>%pathname%</string>
</dict>
<key>Processor</key>
<string>FlatPkgUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>pattern</key>
<string>%RECIPE_CACHE_DIR%/unpack/*gp.pkg</string>
</dict>
<key>Processor</key>
<string>FileFinder</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app</string>
<key>pkg_payload_path</key>
<string>%found_filename%/Payload</string>
</dict>
<key>Processor</key>
<string>PkgPayloadUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>input_plist_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app/Contents/Info.plist</string>
<key>plist_version_key</key>
<string>CFBundleShortVersionString</string>
</dict>
<key>Processor</key>
<string>Versioner</string>
</dict>
<dict>
<key>Processor</key>
<string>PkgRootCreator</string>
<key>Arguments</key>
<dict>
<key>pkgroot</key>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<key>pkgdirs</key>
<dict>
<key>Scripts</key>
<string>0755</string>
</dict>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileMover</string>
<key>Arguments</key>
<dict>
<key>source</key>
<string>%RECIPE_CACHE_DIR%/pkgroot/Scripts</string>
<key>target</key>
<string>%RECIPE_CACHE_DIR%/Scripts</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgCopier</string>
<key>Arguments</key>
<dict>
<key>source_pkg</key>
<string>%pathname%</string>
<key>pkg_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/GlobalProtect.pkg</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileCreator</string>
<key>Arguments</key>
<dict>
<key>file_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/uninstall_global_protect.xml</string>
<key>file_mode</key>
<string>0755</string>
<key>file_content</key>
<string>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;array&gt;
&lt;dict&gt;
&lt;key&gt;attributeSetting&lt;/key&gt;
&lt;integer&gt;1&lt;/integer&gt;
&lt;key&gt;choiceAttribute&lt;/key&gt;
&lt;string&gt;selected&lt;/string&gt;
&lt;key&gt;choiceIdentifier&lt;/key&gt;
&lt;string&gt;second&lt;/string&gt;
&lt;/dict&gt;
&lt;dict&gt;
&lt;key&gt;attributeSetting&lt;/key&gt;
&lt;integer&gt;1&lt;/integer&gt;
&lt;key&gt;choiceAttribute&lt;/key&gt;
&lt;string&gt;selected&lt;/string&gt;
&lt;key&gt;choiceIdentifier&lt;/key&gt;
&lt;string&gt;com.paloaltonetworks.globalprotect.uninstall.pkg&lt;/string&gt;
&lt;/dict&gt;
&lt;/array&gt;
&lt;/plist&gt;</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileCreator</string>
<key>Arguments</key>
<dict>
<key>file_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/postinstall</string>
<key>file_mode</key>
<string>0755</string>
<key>file_content</key>
<string>#!/bin/bash
PKG="${0%/*}/GlobalProtect.pkg"
ChoiceChangesXMLFile="${0%/*}/uninstall_global_protect.xml"
ERROR=0
if [[ -f "$PKG" ]]; then
/usr/sbin/installer -pkg "$PKG" -applyChoiceChangesXML "$ChoiceChangesXMLFile" -target "$3"
if [[ $? -ne 0 ]]; then
/usr/bin/logger -t "${0##*/}" "ERROR! Installation of package $PKG failed"
ERROR=1
fi
else
/usr/bin/logger -t "${0##*/}" "ERROR! Package $PKG not found"
ERROR=1
fi
exit $ERROR</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgCreator</string>
<key>Arguments</key>
<dict>
<key>pkg_request</key>
<dict>
<key>pkgroot</key>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<key>pkgname</key>
<string>%VENDOR%_%SOFTWARETITLE1%_%SOFTWARETITLE2%_%version%</string>
<key>pkgtype</key>
<string>flat</string>
<key>id</key>
<string>com.company.GlobalProtectUninstall.pkg</string>
<key>options</key>
<string>purge_ds_store</string>
<key>scripts</key>
<string>Scripts</string>
<key>version</key>
<string>%version%</string>
</dict>
</dict>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>path_list</key>
<array>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<string>%RECIPE_CACHE_DIR%/payload</string>
<string>%RECIPE_CACHE_DIR%/Scripts</string>
<string>%RECIPE_CACHE_DIR%/unpack</string>
</array>
</dict>
<key>Processor</key>
<string>PathDeleter</string>
</dict>
</array>
</dict>
</plist>

AutoPkg recipe to create an installer package which enables the System Extension for GlobalProtect:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;
<plist version="1.0">
<dict>
<key>Description</key>
<string>Downloads the current release version of the Global Protect VPN client and builds an installer package which enables the Global Protect system extension.</string>
<key>Identifier</key>
<string>com.company.pkg.GlobalProtect.systemextension</string>
<key>Input</key>
<dict>
<key>NAME</key>
<string>GlobalProtect</string>
<key>VENDOR</key>
<string>PaloAlto</string>
<key>SOFTWARETITLE1</key>
<string>GlobalProtect</string>
<key>SOFTWARETITLE2</key>
<string>System</string>
<key>SOFTWARETITLE3</key>
<string>Extension</string>
<key>SOFTWARETITLE4</key>
<string>Enabler</string>
</dict>
<key>MinimumVersion</key>
<string>1.0.0</string>
<key>ParentRecipe</key>
<string>com.company.download.GlobalProtect</string>
<key>Process</key>
<array>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/unpack</string>
<key>flat_pkg_path</key>
<string>%pathname%</string>
</dict>
<key>Processor</key>
<string>FlatPkgUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>pattern</key>
<string>%RECIPE_CACHE_DIR%/unpack/*gp.pkg</string>
</dict>
<key>Processor</key>
<string>FileFinder</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>destination_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app</string>
<key>pkg_payload_path</key>
<string>%found_filename%/Payload</string>
</dict>
<key>Processor</key>
<string>PkgPayloadUnpacker</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>input_plist_path</key>
<string>%RECIPE_CACHE_DIR%/payload/GlobalProtect.app/Contents/Info.plist</string>
<key>plist_version_key</key>
<string>CFBundleShortVersionString</string>
</dict>
<key>Processor</key>
<string>Versioner</string>
</dict>
<dict>
<key>Processor</key>
<string>PkgRootCreator</string>
<key>Arguments</key>
<dict>
<key>pkgroot</key>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<key>pkgdirs</key>
<dict>
<key>Scripts</key>
<string>0755</string>
</dict>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileMover</string>
<key>Arguments</key>
<dict>
<key>source</key>
<string>%RECIPE_CACHE_DIR%/pkgroot/Scripts</string>
<key>target</key>
<string>%RECIPE_CACHE_DIR%/Scripts</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgCopier</string>
<key>Arguments</key>
<dict>
<key>source_pkg</key>
<string>%pathname%</string>
<key>pkg_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/GlobalProtect.pkg</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileCreator</string>
<key>Arguments</key>
<dict>
<key>file_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/install_system_extensions.xml</string>
<key>file_mode</key>
<string>0755</string>
<key>file_content</key>
<string>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;array&gt;
&lt;dict&gt;
&lt;key&gt;attributeSetting&lt;/key&gt;
&lt;integer&gt;1&lt;/integer&gt;
&lt;key&gt;choiceAttribute&lt;/key&gt;
&lt;string&gt;selected&lt;/string&gt;
&lt;key&gt;choiceIdentifier&lt;/key&gt;
&lt;string&gt;third&lt;/string&gt;
&lt;/dict&gt;
&lt;dict&gt;
&lt;key&gt;attributeSetting&lt;/key&gt;
&lt;integer&gt;1&lt;/integer&gt;
&lt;key&gt;choiceAttribute&lt;/key&gt;
&lt;string&gt;selected&lt;/string&gt;
&lt;key&gt;choiceIdentifier&lt;/key&gt;
&lt;string&gt;com.paloaltonetworks.globalprotect.systemext.pkg&lt;/string&gt;
&lt;/dict&gt;
&lt;/array&gt;
&lt;/plist&gt;</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>FileCreator</string>
<key>Arguments</key>
<dict>
<key>file_path</key>
<string>%RECIPE_CACHE_DIR%/Scripts/postinstall</string>
<key>file_mode</key>
<string>0755</string>
<key>file_content</key>
<string>#!/bin/bash
PKG="${0%/*}/GlobalProtect.pkg"
ChoiceChangesXMLFile="${0%/*}/install_system_extensions.xml"
ERROR=0
if [[ -f "$PKG" ]]; then
/usr/sbin/installer -pkg "$PKG" -applyChoiceChangesXML "$ChoiceChangesXMLFile" -target "$3"
if [[ $? -ne 0 ]]; then
/usr/bin/logger -t "${0##*/}" "ERROR! Installation of package $PKG failed"
ERROR=1
fi
else
/usr/bin/logger -t "${0##*/}" "ERROR! Package $PKG not found"
ERROR=1
fi
exit $ERROR</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>PkgCreator</string>
<key>Arguments</key>
<dict>
<key>pkg_request</key>
<dict>
<key>pkgroot</key>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<key>pkgname</key>
<string>%VENDOR%_%SOFTWARETITLE1%_%SOFTWARETITLE2%_%SOFTWARETITLE3%_%SOFTWARETITLE4%_%version%</string>
<key>pkgtype</key>
<string>flat</string>
<key>id</key>
<string>com.company.GlobalProtectSystemExtensionEnable.pkg</string>
<key>options</key>
<string>purge_ds_store</string>
<key>scripts</key>
<string>Scripts</string>
<key>version</key>
<string>%version%</string>
</dict>
</dict>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>path_list</key>
<array>
<string>%RECIPE_CACHE_DIR%/pkgroot</string>
<string>%RECIPE_CACHE_DIR%/payload</string>
<string>%RECIPE_CACHE_DIR%/Scripts</string>
<string>%RECIPE_CACHE_DIR%/unpack</string>
</array>
</dict>
<key>Processor</key>
<string>PathDeleter</string>
</dict>
</array>
</dict>
</plist>

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: