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:
The second configuration is the option to uninstall GlobalProtect, which is disabled by default:
The third configuration is the option to enable the System Extension for GlobalProtect, which is disabled by default:
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"> | |
<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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"> | |
<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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"> | |
<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:
- An installer choices XML file
- 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"> | |
<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><?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"> | |
<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></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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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"> | |
<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><?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"> | |
<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></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> |