シェルスクリプトの中でcurlやsshなどのコマンドにパスワードを渡したい場合に平文のテキストで書き込むよりは安全にパスワードを埋め込む方法を考えてみた。
コンセプトとしてはシェルスクリプト中にはシステム固有の情報で暗号化したパスワードを埋め込んで、シェルスクリプトが流出しても、システム固有の情報によってしか復号出来ない様にすることです。 (きっと、この方法でパスワードを扱えば、シェルスクリプトを間違えてgitなどのリポジトリに上げてしまっても安心?)
ちなみにパスワードの暗号化と復号にはopensslの暗号化(encオプション)を使い、パスフレーズにはシステム固有のUUID(macOSではHardware UUID、Linuxではsystem-uuid)を使っています。
※ Linuxでsystem-uuidを取得するのに利用しているdmidecodeはroot権限でしか実行できないので、/etc/suders
などでスクリプトを実行するユーザーに/usr/sbin/dmidecode
の実行権限を与えておくひつようもあります。
somebody ALL=(ALL) NOPASSWD: /usr/sbin/dmidecode
#!/bin/sh
function encrypt_password() {
plain_password=$1
if [ "$(uname)" == 'Darwin' ]; then
system_uuid=`system_profiler SPHardwareDataType |grep "Hardware UUID: [0-9A-Z]\{8\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{12\}" | sed -e 's/^.*Hardware UUID: \([0-9A-Z]\{8\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{12\}\).*$/\1/g'`
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
system_uuid=`sudo /usr/sbin/dmidecode -s system-uuid`
else
echo Unsupported system.
exit
fi
encrypted_password=`echo "$plain_password" | openssl enc -e -des -base64 -k "$system_uuid"`
echo $encrypted_password
}
# Usage
echo `encrypt_password $1`
上記のスクリプトを使って暗号化した文字列をシェルスクリプト内に埋め込んで使えば、少なくとも何らかの手違いでシェルスクリプトが外部に流出したとしても暗号化されたパスワードはそのシェルスクリプトが実行されるべきシステム以外では復号できないので安全なはず。 (まあ、システムをクラッキングされてシステムのUUIDをクラッカーに知られてしまえば終わりだけど…)
$ encrypt_password.sh mypassword
U2FsdGVkX18m7HJ3Fbn0M3xc2+xG25DRM2b2zOhFUiA=
#!/bin/sh
function decrypt_password() {
encrypted_password=$1
if [ "$(uname)" == 'Darwin' ]; then
system_uuid=`system_profiler SPHardwareDataType |grep "Hardware UUID: [0-9A-Z]\{8\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{12\}" | sed -e 's/^.*Hardware UUID: \([0-9A-Z]\{8\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{4\}-[0-9A-Z]\{12\}\).*$/\1/g'`
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
system_uuid=`sudo /usr/sbin/dmidecode -s system-uuid`
else
echo Unsupported system.
exit
fi
plain_password=`echo "$encrypted_password" | openssl enc -d -des -base64 -k "$system_uuid"`
echo $plain_password
}
# Useage:
# echo `decrypt_password $1`
decrypt_password.sh
の最後の行のコメントアウトを外せば引数に暗号化されたパスワードを渡して試せます。
$ decrypt_password.sh U2FsdGVkX18m7HJ3Fbn0M3xc2+xG25DRM2b2zOhFUiA=
mypassword
例えば「bitbucketのリポジトリのバックアップを取得するスクリプトを書 いてみた」のスクリプトで使うならこんな感じでしょうか。
#!/bin/bash
SCRIPT_DIR=$(cd $(dirname $0); pwd)
. $SCRIPT_DIR/decrypt_password.sh
BITBUCKET_USER=bitbucket_user@email.address
BITBUCKET_PASSWORD=`decrypt_password U2FsdGVkX18m7HJ3Fbn0M3xc2+xG25DRM2b2zOhFUiA=`
BITBUCKET_OWNER=ownername
bitbucket_repositories=`curl --user $BITBUCKET_USER:$BITBUCKET_PASSWORD https://api.bitbucket.org/2.0/repositories/$BITBUCKET_OWNER | jq -r '.values[].links.clone[].href' | grep "^git"`
for repository_path in $bitbucket_repositories; do
echo $repository_path
dir=`echo "$(dirname $repository_path)" | sed -e 's/:/\//g' | sed -e 's/^git@//g'`
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
fi
repo=$(basename $repository_path)
if [ ! -d "$dir/$repo" ]; then
(cd $dir; git clone --mirror $repository_path)
else
(cd "$dir/$repo"; git fetch --all)
fi
done
まあ、いろいろと突っ込みどころもあるとは思いますが、パスワードを生のまま埋め込むよりは精神衛生上よろしいかと…
ただ、この方法の欠点としてはスクリプトは特定のシステムでしか使えないので、複数のシステムが連携して動作するシステムでは使えないのでCIシステムでのデプロイやChefやAnsibleでの設定の時も何らかの工夫が必要となるところでしょうか?
公開鍵暗号を使えばいいのかな?でも、その場合には秘密鍵はどうしよう…