AWS や Google 等で利用可能な MFA の仮想デバイスとしては、複数デバイス間の同期や Chrome Extension を擁する Authy が便利なわけですが、PC 特に CLI を使ってるといちいちデバイス or Chrome Extension を開く必要があり、かなり面倒です。
CLI から token を生成する OATH Toolkit の oathtool を使えば、この面倒さが少しは解消され、スクリプト化による自動処理も可能になります。
ただし、oathtool 自体は毎回 SecretKey が必要になるため、mfacodegen という名の wrapper script を用意して便利に利用できるようにします。
利用イメージは以下の通り。
Authy Desktop から SecretKey を取得する
MFA では
が使われていますが、これらの token を生成するには SecretKey が必要となります。
SecretKey は MFA 設定時の QR code 内に仕込まれているのですが、既に Authy を設定済の場合は アプリ上から SecretKey を見ることが出来ません。
ただし Authy は定期的に token を生成するために内部的には当然 SecretKey を保持しています。
Authy Desktop は JS で token を生成しているため、デバッグポートを有効にした状態でアプリを起動し、Developer Tools で内部を覗けばバッチリ取得できます。
Mac の場合は Terminal を開き、以下のコマンドで Authy Desktop を開きます。
open -a "Authy Desktop" --args --remote-debugging-port=5858
Windows の場合は Authy Desktop のショートカットを作成し、右クリック -> プロパティ -> ショートカット -> リンク先 の最後に --remote-debugging-port=5858
を付加して保存。作成したショートカットからアプリを開きましょう。
まず、Authy Chrome Extension を開き、ログインした後のアカウント一覧画面で右クリック、Inspect(検証)を開きます。
Authy Desktop を起動したら、Chrome で http://localhost:5858 を開きます。
デバッグポートに正常に接続できれば以下のような画面が表示されます。
Twilio Authy をクリックすると、次のように Developer Tool が使えるようになります。
Developer Tools が開いたら、Console タブを開き、以下のコードを入力して実行します。
appManager.getAuthenticatorApps().forEach(app=>console.log(app.name,app.originalName,app.decryptedSeed))
すると、アカウント名、SecretKey が Console に出力されるはずです。
得られた SecretKey は、パスワード管理アプリ等を用いて安全に保管しておきましょう。
oathtool で MFA Token を生成してみる
Mac であれば、brew を使えば簡単にインストールできます。
brew install oath-toolkit
以下のコマンドで token が生成できることを確認します。
stty -echo; echo -n 'Enter SecretKey: '; read skey; echo; stty echo; oathtool -b --totp $skey
oathtool -b --totp [SecretKey]
だけでも確認できますが、その場合は shell の history に SecretKey が残ってしまうので、後で .bash_history や .zsh_history から削除する必要があります。
wrapper script (mfacodegen) を作成する
このままだと SecretKey の扱いが面倒で非常に使いにくいので、
- Service ID、SecretKey を書いた key list を用意する
- key list は openssl で encrypt しておく
- wrapper script が key list を参照、decrypt
- wrapper script の引数で渡された Service ID のエントリを元に oathtool で token 生成
のような処理を行います。
暗号化した SecretKey リストファイルを作成する
一行が [ServiceID]\t[SecretKey]
となるようなリスト mfalist.txt
を適当な場所に生成します。
ServiceID は CLI のオプションとして無難な文字列にしておいてください。
S/MIME 用の秘密鍵と証明書を生成します。
openssl req -x509 -days 3650 -newkey rsa:2048 -keyout /path/to/mfa.key -out /path/to/mfa.crt -subj '/'
作成した証明書を用いて、openssl コマンドで encrypt します。
openssl smime -encrypt -aes256 -in /path/to/mfalist.txt -out /path/to/mfalist.dat -binary -outform PEM /path/to/mfa.crt
念のため、decrypt 可能かどうか、確認しておきましょう。
以下のコマンドで、標準出力に decrypt 結果が表示されれば成功です。
openssl smime -decrypt -in /path/to/mfalist.dat -inkey /path/to/mfa.key -binary -inform PEM
元の /path/to/mfalist.txt は不要なので削除しておきます。
mfacodegen を設置する
以下のファイルを path の通った適当な場所に設置します。
最初の
LIST_PATH
およびKEY_PATH
は適宜修正してください。
また、chmod +x
を忘れずに。
#!/bin/bash
set -e
LIST_PATH=/path/to/mfalist.dat
KEY_PATH=~/path/to/mfa.key
usage() {
echo "Usage: $0 [-clh] [-s service]" 1>&2
exit 1
}
while getopts cls:h OPT; do
case $OPT in
c) mode='copy'
;;
l) cmd='show'
;;
s) cmd='generate'
service=$OPTARG
;;
h) usage
;;
\?) usage
;;
esac
done
[ -z "$cmd" ] && usage
generateToken() {
list=$(loadList)
seckey=$(echo "${list}" | awk "\$1==\"${service}\"{print \$2}")
[ -z "$seckey" ] && { echo "Service ${service} not found." >&2; exit 1; }
if [ "$(uname)" = 'Darwin' -a "$mode" = 'copy' ]; then
echo -n $(oathtool -b --totp $seckey) | pbcopy
else
oathtool -b --totp $seckey
fi
}
loadList() {
if [ -p /dev/stdin ]; then
stdin=$(cat -)
openssl smime -decrypt -inkey $KEY_PATH -in $LIST_PATH -binary -inform PEM -passin "pass:$stdin"
else
openssl smime -decrypt -inkey $KEY_PATH -in $LIST_PATH -binary -inform PEM
fi
}
showServiceList() {
list=$(loadList)
echo "${list}" | cut -f1 | sort
}
case "$cmd" in
'show' )
showServiceList
;;
'generate' )
generateToken
;;
esac
動作確認
-l
オプションで、サービスの一覧が表示されます。
mfacodegen -l
-s [ServiceID]
オプションで、指定のサービス ID の token を生成します。
mfacodegen -s github
Mac の場合は、-c -s [ServiceID]
で token がクリップボードにコピーされます。
mfacodegen -c -s github
これで、CLI で MFA token が取得できるようになりました。