Skip to content

Instantly share code, notes, and snippets.

@kaleksandrov
Last active November 13, 2024 15:20
Show Gist options
  • Save kaleksandrov/3cfee92845a403da995e7e44ba771183 to your computer and use it in GitHub Desktop.
Save kaleksandrov/3cfee92845a403da995e7e44ba771183 to your computer and use it in GitHub Desktop.
Simple script that starts and stops GlobalProtect.app on Mac OSX.
#!/bin/bash
case $# in
0)
echo "Usage: $0 {start|stop}"
exit 1
;;
1)
case $1 in
start)
echo "Starting GlobalProtect..."
launchctl load /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist
launchctl load /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist
echo "Done!"
;;
stop)
echo "Stopping GlobalProtect..."
launchctl remove com.paloaltonetworks.gp.pangps
launchctl remove com.paloaltonetworks.gp.pangpa
echo "Done!"
;;
*)
echo "'$1' is not a valid verb."
echo "Usage: $0 {start|stop}"
exit 2
;;
esac
;;
*)
echo "Too many args provided ($#)."
echo "Usage: $0 {start|stop}"
exit 3
;;
esac
@EvilJordan
Copy link

Hi, Friends.

I found that none of the above worked for me on MacOS Ventura 13.2.1 running GlobalProtect 6.0.5, so I created a simple script that does.

Perhaps I am different in that I have also disabled the underlying system LaunchDaemon service (pangpsd) when this thing is not running because I like to know what's happening on my machine at all times, to the best extent possible. I have no Login Items enabled and modified the plists directly with a text editor to keep them from launching on startup.

This quick script is part of my ~/.bash_profile and invoked by calling vpn on the command line and will start the requisite services, and then launch GlobalProtect. If you have a different install location, you may need to change the open -a /Applications/GlobalProtect.app line to the appropriate directory.

To quit, vpn unload will stop and disable all services and kill the app as well. Errors are suppressed, with the routing > /dev/null 2>&1 because I don't care if the shutdown process isn't clean. Your milage may vary.

vpn() {
	if [ "$1" = "unload" ]; then
		launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist > /dev/null 2>&1
		launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist > /dev/null 2>&1
		launchctl bootout gui/$(id -u) /Library/LaunchDaemons/com.paloaltonetworks.gp.pangpsd.plist > /dev/null 2>&1
		PID="$(launchctl list | grep palo | cut -f 1)"
		kill $PID
		echo "VPN unloaded"
	else
		launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist
		launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist 
		launchctl bootstrap gui/$(id -u) /Library/LaunchDaemons/com.paloaltonetworks.gp.pangpsd.plist
		open -a /Applications/GlobalProtect.app
	fi
}

@reynardmarx
Copy link

Is there a way to fill in your 2FA code when prompted for this when connecting to a specific VPN portal?

@kaleksandrov
Copy link
Author

Is there a way to fill in your 2FA code when prompted for this when connecting to a specific VPN portal?

This script doesn't supports this. All it does is to register/unregister the global protect application as an autostart service. It doesn't use any global protect API.

@reynardmarx
Copy link

@kaleksandrov thank you, I understand that. However, do you think it would be possible, seeing that some of the scripts above are using the click functionality?

@erseco
Copy link

erseco commented Mar 11, 2024

This is a easier version that works for me in macOS Sonoma

vpn() {
    if [ "$1" = "stop" ]; then
        launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist > /dev/null 2>&1
        launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist > /dev/null 2>&1
        echo "VPN unloaded"
    elif [ "$1" = "start" ]; then
        #statements
        launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist
        launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist
        echo "VPN loaded"
    fi
}

@damosse31
Copy link

damosse31 commented Apr 1, 2024

I adapted the script to kill globalprotect icon in tray on stop and open globalprotect app when start :

# Add the function below to your .zsh_env or .bash_profile
# Usage : globalprotect start or globalprotect stop

globalprotect() {
    if [ "$1" = "stop" ]; then
        launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist > /dev/null 2>&1
        launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist > /dev/null 2>&1
        PID="$(launchctl list | grep palo | cut -f 1)"

        # Kill the processes IDs only if found
        if [ ! -z "$PID" ]; then
                kill -9 $PID
        fi

        echo "VPN unloaded"
    elif [ "$1" = "start" ]; then
        launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist
        launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist
        open -a /Applications/GlobalProtect.app
        echo "VPN loaded"
    fi
}

@githubrobbi
Copy link

githubrobbi commented Jun 26, 2024

Just to share:

Here is my version of start / stop script

# echo "START"
# set -e
# set -x

PORTAL="vpn.nios.net"
MAX_ATTEMPTS=10
SLEEP_INTERVAL=5
WAIT_FOR_APP_TO_BE_READY=5
WAIT_FOR_CONNECTION=15

function check_setup() {
    if [ ! -e "/Applications/GlobalProtect.app" ]; then
        echo "GlobalProtect app is not installed."
        exit 1
    fi

    if [ ! -e "/Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist" ] || [ ! -e "/Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist" ]; then
        echo "GlobalProtect launch agents are not properly set up."
        exit 1
    fi
}

function ensure_app_running() {
    for ((i=1; i<=$MAX_ATTEMPTS; i++)); do
        if osascript -e 'tell application "System Events" to (name of processes) contains "GlobalProtect"' &>/dev/null; then
            sleep $WAIT_FOR_APP_TO_BE_READY
            return 0
        fi
        echo "Waiting for GlobalProtect app to start..."
        sleep $SLEEP_INTERVAL
    done

    echo "Failed to start GlobalProtect app."
    exit 1
}

function start_vpn() {
    echo -e "\nStarting GlobalProtect...\n"
    launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist
    launchctl bootstrap gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist

    ensure_app_running

    connect_vpn

    sleep $WAIT_FOR_CONNECTION

    if check_vpn_status; then
        echo -e "\nVPN is connected to $PORTAL\n"
        return 0
    fi

    for ((i=1; i<=$MAX_ATTEMPTS; i++)); do
        connect_vpn
        sleep $SLEEP_INTERVAL
        if check_vpn_status; then
        echo -e "\nVPN is connected to $PORTAL\n"
            return 0
        fi
    done

    echo -e "\nFailed to connect to VPN after $MAX_ATTEMPTS attempts\n"
    exit 1
}

function connect_vpn() {
    osascript <<EOF &>/dev/null
    tell application "System Events"
        tell process "GlobalProtect"
            if not (exists window 1) then
                click menu bar item 1 of menu bar 2 -- Activates the GlobalProtect "window" in the menubar
                delay 2 -- Wait for 2 seconds
            end if
            set frontmost to true -- keep window 1 active
            tell window 1
                if exists (first button whose title is "Connect") then
                    tell (first button whose title is "Connect") to if exists then click
                end if
            end tell
        end tell
    end tell
EOF
}

function check_vpn_status() {
    VPN_STATUS=$(osascript <<EOF 2>/dev/null
    tell application "System Events"
        tell process "GlobalProtect"
            set frontmost to true -- keep window 1 active
            tell window 1
                if exists (first button whose title is "Disconnect") then
                    return "Connected"
                else
                    return "Not Connected"
                end if
            end tell
        end tell
    end tell
EOF
    )
    echo "VPN status: $VPN_STATUS"
    if [ "$VPN_STATUS" == "Connected" ]; then
        return 0
    elif [ "$VPN_STATUS" == "Not Connected" ]; then
        return 1
    else
        return 1
    fi
}
function stop_vpn() {
    echo -e "\nStopping GlobalProtect...\n"

    if ! launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist 2>/dev/null || \
       ! launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist 2>/dev/null; then
        echo "Failed to bootout as current user, trying with sudo..."
        sudo launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangps.plist 2>/dev/null
        sudo launchctl bootout gui/$(id -u) /Library/LaunchAgents/com.paloaltonetworks.gp.pangpa.plist 2>/dev/null
    fi

    PIDS=$(launchctl list | grep -E 'com\.paloaltonetworks\.gp\.pangpa|com\.paloaltonetworks\.gp\.pangps' | awk '{print $1}')
    if [ ! -z "$PIDS" ]; then
        echo "Killing GlobalProtect processes..."
        for PID in $PIDS; do 
            if sudo kill -9 $PID; then
                echo "Killed process $PID"
            else
                echo "Failed to kill process $PID"
            fi
        done
    fi

    echo -e "\nVPN stopped\n"
}

case $1 in
    start)
        check_setup
        start_vpn
        ;;
    stop)
        stop_vpn
        ;;
    *)
        echo "'$1' is not a valid verb."
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac

@nirazul
Copy link

nirazul commented Jun 27, 2024

I adapted the script to kill globalprotect icon in tray on stop and open globalprotect app when start :

@damosse31 THANK YOU! That's exactly what I was looking for and it works perfectly on macOS Sonoma and GlobalProtect 6.1.4 🫶

@oneisall8955
Copy link

I adapted the script to kill globalprotect icon in tray on stop and open globalprotect app when start :

@damosse31 Thanks, it works perfectly!

macOS Ventura 13.6.6 & GlobalProtect Version: 5.2.13-48

@deltaex1
Copy link

@githubrobbi Thanks for the clearly written code, I adapted your script's connect_vpn function!

As my org requires manual user name and password entry plus a 2FA, I added rudimentary scripts to advance the GP login screens and entering those values via blind keystrokes. The 2FA key is generated by a separate python script that returns the TOTP value using the python lib pyotp by printing it within the python function, and again entering those value via blind keystrokes.

There are definitely better ways to not hardcode the user name, password, and TOTP secret key as well as advancing the screen based on the available buttons rather than blind keystrokes... but it got too late at night and this works for now. Would love it if someone can improve upon it!

MacOS Sonoma 14.7.1 (23H222) & GlobalProtect 6.2.1-132

.zshenv

function vpn() {
    osascript <<EOF &>/dev/null
    tell application "System Events"
	tell process "GlobalProtect"
		if not (exists window 1) then
			click menu bar item 1 of menu bar 2 -- Activates the GlobalProtect "window" in the menubar
			delay 2 -- Wait for 2 seconds
		end if
		set frontmost to true -- keep window 1 active
		tell window 1
			if exists (first button whose title is "Connect") then
				tell (first button whose title is "Connect") to if exists then click
			end if
			
			delay 3
			set textToType to "USERNAME"
			keystroke textToType
			keystroke "	"
			set textToType to "PASSWORD"
			keystroke textToType
			if exists (first button whose title is "Connect") then
				tell (first button whose title is "Connect") to if exists then click
			end if
			
		end tell
		
		delay 10
		set totp to do shell script "python $HOME/TOTP.py"
		keystroke totp
		keystroke return
		-- if exists (first button whose title is "Verify") then
		-- 	tell (first button whose title is "Verify") to if exists then click
		-- end if
		
	end tell
end tell
EOF
}

TOTP.py

import pyotp

secret_key = "SECRET_KEY"

def getToken(secret_key):
    totp = pyotp.TOTP(secret_key)
    token = totp.now()
    print(token) # This is what's returning the value in a shell script execution
    # return token

getToken(secret_key)

Special thanks to @kaleksandrov for starting this gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment