We automate the Cisco AnyConnect connection on Mac OS

It so happened that at my work I use Cisco AnyConnect Secure Mobility Client. From time to time you have to connect to a working VPN using this program. Each time you need to manually perform the following steps:

  1. Clicking the “Connect” button.

  2. Entering a permanent password, then clicking the “OK” button.

  3. Enter TOTP and clicking the “Continue” button.

As a person who constantly works in the terminal, I wanted to have a console utility on hand that would perform all the described actions for me. Of course, we are not talking about significant time savings. At the same time, I’m tired of looking at TOPT from my phone every time, waiting until the time comes during which this code will not have time to expire while I enter it manually. And of course, I was curious about how you can programmatically interact with different GUI applications.

AppleScript

For simplicity, we will first assume that you have a permanent password and the current TOTP value in your environment variables. How to programmatically make several clicks and also fill in the corresponding fields? After a short search on the Internet, a solution was found: the AppleScript scripting language built into MacOS, which allows you to manage other GUI applications. It seems that everything is very simple, I thought, having almost 10 years of programming experience in various languages. But this language turned out to be poorly documented, inconsistent and causing a bit of frustration. It took me about 3 hours to make a working version of the script. To write in AppleScript there is a built-in Script EditorI used it to write and debug my craft.

Let's automate step 1

At this stage, you just need to activate the program and click the “Connect” button.

Main screen of the program

Main screen of the program

set appName to "Cisco AnyConnect Secure Mobility Client"
set vpnPwd to system attribute "VPN_PWD"
set vpnTotp to system attribute "VPN_TOTP"

tell application appName
	activate
end tell

tell application "System Events" to tell process appName
	repeat with appWindow in (every window whose subrole is not "AXUnknown")
		perform action "AXRaise" of appWindow
	end repeat

	set cw to first window whose description is "standard window"
	click button "Connect" of cw
end tell

In order not to write a long program name every time, I immediately created the appName variable. The permanent password and TOTP are taken from the environment variables VPN_PWD and VPN_TOTP, respectively. In lines 5-7 we activate the application. However, if it is already active and minimized to the Dock, then nothing will happen. In order to press the button in the future, the application must be open. Therefore, in the block starting on line 9 we will act on the desired window (there are 2 of them at this stage, because the icon in the Tray is considered a separate window and has a subrole equal to AXUnknown). To remove a minimized window from the Dock, you need to perform the AXRaise action on it. And finally, in lines 14 and 15 we find the window we need and click the button with the description “Connect”.

Let's automate step 2

You need to enter a permanent password from the environment variable and click “OK”.

Permanent password entry screen

Permanent password entry screen

...
set vpnPwd to system attribute "VPN_PWD"
...

tell application "System Events" to tell process appName
	...
	click button "Connect" of cw
	set wcount to count (window)
	repeat until wcount > 2
		delay 0.2
		set wcount to count (window)
	end repeat
	
	set cw to first window whose description is "dialog"
	set value of text field 2 of cw to vpnPwd
	click button "OK" of cw
end tell

At the last step we clicked the button, but we need to wait until the next window appears (3rd in a row). I didn’t come up with anything other than the classic check-wait-repeat loop in lines 8-12. Again, select the desired window, fill in the text field and click the button.

Quirks with cycles

The seemingly simpler loop below for some reason doesn't work in AppleScript, even though that's what I tried first.

repeat until count (window) > 2
    delay 0.2
end repeat

error “Can't make window into type number, date or text.” number -1700 from window to number, date or text

Let's automate step 3

You must enter TOTP from the environment variable and click the treasured “Continue” button.

TOTP input screen

TOTP input screen

...
set vpnTotp to system attribute "VPN_TOTP"
...
tell application "System Events" to tell process appName
	...
	click button "OK" of cw
	delay 0.3
	set wcount to count (window)
	repeat until wcount > 2
		delay 0.2
		set wcount to count (window)
	end repeat
	set cw to first window whose description is "dialog"
	set value of text field 1 of cw to vpnTotp
	click button "Continue" of cw
end tell

return

After clicking the button in the previous step, you must wait until the old window disappears and a new one appears. We wait 300ms and again use the loop from the previous step. Then we repeat the steps already described above. Return with an empty value is needed so that the last value does not end up in stdout when run from the terminal.

Full version of the script – ciscovpn.applescript
set appName to "Cisco AnyConnect Secure Mobility Client"
set vpnPwd to system attribute "VPN_PWD"
set vpnTotp to system attribute "VPN_TOTP"

tell application appName
	activate
end tell


tell application "System Events" to tell process appName
	repeat with appWindow in (every window whose subrole is not "AXUnknown")
		perform action "AXRaise" of appWindow
	end repeat
	
	set cw to first window whose description is "standard window"
	click button "Connect" of cw
	set wcount to count (window)
	repeat until wcount > 2
		delay 0.2
		set wcount to count (window)
	end repeat
	
	set cw to first window whose description is "dialog"
	set value of text field 2 of cw to vpnPwd
	click button "OK" of cw
	delay 0.3
	set wcount to count (window)
	repeat until wcount > 2
		delay 0.2
		set wcount to count (window)
	end repeat
	set cw to first window whose description is "dialog"
	set value of text field 1 of cw to vpnTotp
	click button "Continue" of cw
end tell

return

Getting TOTP

To store tokens and receive TOTP from them, I use the one I purchased once Yubikey 5c nano(available on Ozon and Yandex market at a price of around 7,000 rubles), then we will talk about it. If you don't have one, you can use totp-cli the only difference is that it will store the tokens on the disk of your computer. To work with yubikey it is used ykman. First we need to add our token. Pay attention to the –touch option, it is needed to require a physical touch of the key while receiving TOTP.

ykman oath accounts add --touch <token_name> <token_value>

Next, TOTP can be obtained with the following command, while the yubikey will blink indicating that a touch is required. I keep it in the side USB-C slot of my monitor, which is very convenient.

ykman oath accounts code -s <token_name>

Conclusion

For everything to work, you need to add the following lines to .zprofile (or another file if you prefer a shell other than zsh). After rebooting the shell, you can connect with one command, connect and touching yubikey. Oh yes, the final touch is to add the necessary security permissions for osascript (the built-in AppleScript interpreter).

export VPN_PWD=<static_password>
function connect() {
        VPN_TOTP=$(ykman oath accounts code -s <token_name>) osascript ~/ciscovpn.applescript
}

PS Well, where would we be without this: subscribe to my telegram channelwhere I hallucinate on various topics, I will be glad to new readers.

Similar Posts

Leave a Reply

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