これは何?
macOS でこれの入力を自動化するやつ。
Cisco AnyConnect クライアントは VPN 接続で Multi-Factor Authentication (MFA) をサポートしているが, これを毎回接続時に実行するのは死ぬほど面倒くさい。というわけで自動化を試みてみた。
当初は AppleScript を使って GUI 操作の自動化を試みていたが,このアプリケーションのクセが非常に強かったので断念。 CLI からの自動化を試みて,後から GUI 版を起動するという解決策を採る。
準備手順
Cisco AnyConnect のインストール
通常,企業から支給された PC にはインストールされていると思います。未インストールの場合は各自ダウンロードしてきてインストールしてください。
GUI アプリケーションには CLI コマンドも同梱されているので, これを vpn
コマンドで呼び出せるようにパスを通すかエイリアスを張るかしておいてください。自分の環境では以下の場所にインストールされていました。
/opt/cisco/anyconnect/bin/vpn
oathtool
コマンドのインストール
brew install oath-toolkit
MFA の TOTP 発行のために必要です。
キーチェーンへの登録
macOS の 「キーチェーンアクセス」にあらかじめ以下の秘密情報を登録しておいてください。
- パスワード
- MFA のトークン発行元となるシークレットの BASE 32 エンコード値
- 「アカウント名」はここでは使用しないので何でもいいです。
- もしシークレットが QR コードで提供されている場合は,
zbarimg
およびphp
を使って読み取ってください。
# インストール
brew install zbar
# パースして出力
zbarimg -q 画像ファイルへのパス | php -r 'parse_str(parse_url(fgets(STDIN), PHP_URL_QUERY), $_); echo $_["secret"];'
スクリプトの整備
以下のコマンドを,普段お使いのシェルが Zsh の場合には ~/.zshrc
に, Bash の場合には ~/.bashrc
にて登録してください。
vpn() (
# エラー対応
set -eu
# パラメータの設定
export VPN_HOST='接続先'
export VPN_GROUP=${VPN_GROUP:=デフォルトのグループ番号}
export VPN_USERNAME='ユーザ名'
export VPN_PASSWORD_KEYNAME='パスワードキーチェーン登録名'
export VPN_SECOND_USERNAME='第2ユーザ名'
export VPN_TOTP_SECRET_KEYNAME='シークレットキーチェーン登録名'
# "vpn" "vpn connect" 以外の呼び出しは元のコマンドに流す
if [[ "$#" -ne 0 && ( "$#" -ne 1 || "$1" != 'connect' ) ]]; then
exec command vpn "$@"
fi
# GUI 版が起動していたら一度強制終了する
killall 'Cisco AnyConnect Secure Mobility Client' >/dev/null 2>&1 || :
# 既に VPN に接続していたら強制切断する
expect -c '
set log_user 0
set timeout 5
spawn vpn disconnect
expect ">> state: Disconnected"
interact
'
# キーチェーンからの取得
VPN_PASSWORD="$(security find-generic-password -s "$VPN_PASSWORD_KEYNAME" -w)"
VPN_TOKEN="$(oathtool --totp --base32 "$(security find-generic-password -s "$VPN_TOTP_SECRET_KEYNAME" -w)")"
export VPN_PASSWORD
export VPN_TOKEN
# 接続
expect -c '
set log_user 0
set timeout 5
spawn vpn connect $env(VPN_HOST)
expect "Group:"
send "$env(VPN_GROUP)\r"
expect "Username:"
send "$env(VPN_USERNAME)\r"
expect "Password:"
send "$env(VPN_PASSWORD)\r"
expect "Second Username:"
send "$env(VPN_SECOND_USERNAME)\r"
expect "Second Password:"
send "$env(VPN_TOKEN)\r"
interact
'
# GUI 版を起動
open '/Applications/Cisco/Cisco AnyConnect Secure Mobility Client.app'
)
- 環境変数を利用して,エスケープ処理などを考慮せずに簡単に下位のプロセスに値を渡せるようにしています。
- シェルを単なるサブシェル
{}
ではなく別プロセス()
として起動させることで,export
やset -eu
の影響が外に出ないようにしています。 - GUI 版起動状態では CLI 版での操作は行えませんが, CLI 版で接続済みの状態で GUI 版を起動すると状態の引き継ぎが行えるようです。ここではその性質を利用して最後に GUI アプリケーションの起動を行っています。
やっぱり接続状態が視覚的に見えるほうがいいですよね↓
使い方
-
vpn
またはvpn connect
で接続- パスワード入力を求められます。 残念ながら Touch ID は使えないようなので, 利便性を考えると常に許可を押しておくのが現状妥当な選択肢かなと思います。
- グループ番号を変更したいときは
VPN_GROUP=xxx vpn
-
vpn disconnect
で切断 -
vpn state
で接続状態確認 - 元のコマンドをそのまま実行したいときは
command vpn