※ 2018/07/21 追記。
お読みいただきましてありがとうございます!
すみません、実はタイトルの割には「現在回しているイケてない運用をちょっとした工夫でサクッと改善する」ことを主眼としており、セキュリティのベストプラクティスではありません。。。
本稿の例で挙げているコマンドのリスクにつきましては、 注意 の項を追加しましたのでご参照ください。
公開当初は「10いいねくらいもらえたら嬉しいなー」と思っていた程度だったのですが、まさかここまでたくさんの方に読んでいただけるとは。。。
もっと内容を精査しておくべきだったと反省しております。。。
いいねやコメント等反応頂けるのはとても励みになります。ありがとうございます!
TL;DR
怖いですよね、セキュリティインシデント。
インフラ系でお仕事をしていると、 Linux にログインして操作する手順書を作る事が多くなります。手順書の中には認証が必要なコマンドを含めなければならない場合もあり、そういった時にはパスワードといったクレデンシャル情報の取り扱いを考える必要性が出てきます。そのような時に最もやってはいけないことは クレデンシャル情報を手順書にべた書きする という行為です。
このような行為は
- 手順書の機密性を不必要に上昇させてしまう
- クレデンシャル情報の更新(例えばアカウントのパスワード変更)の際に漏れなく手順書のべた書き箇所を修正する手間が発生する
という問題をはらむことになります。
そこでよくあるのが、下記のようにコマンドの一部を作業実施時に作業者により読み替えさせる手法です。
$ curl -u user:{パスワード} http://example.com
※ {パスワード} は XXX を参照して読み替えてください。
この手法は手順書の機密性を不必要に上昇させませんし、クレデンシャル情報の参照先を統一しておけばクレデンシャル情報の更新時に参照先を修正するだけでよいので、上記の問題をどちらも解決できます。
しかし、これはベストな手法でしょうか?答えは No です。
それでは、どういった手法を使えばよいのか?というのが本稿の趣旨です。
何が問題か?
読み替え手法には .bash_history
にクレデンシャル情報が残ってしまう問題があります。
どういうことか見ていきましょう。
.bash_history
にクレデンシャル情報が残ってしまう問題
curl -u user:{パスワード} http://example.com
のように、読み替え手法で手順書を記載しても、実際にコマンドを打つ時にはパスワードをダイレクトに入力する必要があります。
.bash_history
にどのように記録されるか、実際に読み替え手法でコマンドを入力してみてみましょう。
下記は {パスワード}
を password
に読み替えた場合の例です。
$ curl -u user:password http://example.com
いったんログアウトしてから再度ログインして ~/.bash_history
を見てみると次のように記録されています。
curl -u user:password http://example.com
はい、当たり前ですね。普通に password
という文字列が含まれた状態で記録されてしまっています。
実際のところ、普通は .bash_history
はオーナーにしか読み書き権限がないので第三者に読み取られる可能性は低いのですが、クレデンシャル情報が平文で保存されてしまうのはいざというときに問題になりかねません。
コマンドをコピペしにくい問題
これは個人的に感じる問題です。
前出の例の curl
コマンドであれば、
-
curl -u user:
までをコピペ -
{パスワード}
に入れるべき実際のパスワードを入力 - パスワード以降の
http://example.com
をコピペ(http://example.com
の前の半角スペースも含む)
という手順を踏む必要があります。これは意外とストレスです。
更に、たった3ステップではありますが、同じことをいろいろな人間にやらせると必ずミスる人間がでてきます。1
ではどのようにするべきか?
次のように read
コマンドを利用すれば解決できます。
$ read -sp "Please input your password: " __pass; echo
※ パスワード は XXX を参照して入力してください。
$ curl -u "user:${__pass}" http://example.com
シェルスクリプトでパスワードの入力を求めるときに read
コマンドを使ったことのある方は多いかもしれません。そうです、パスワードを入力するためにその read
コマンドを手順書に記載するのです。
※ これは bash の read
の場合です。 zsh のでは動作しません。 zsh の場合は zshでも作業手順書からべた書きパスワードをなくしたい!! をご参照ください(@serinuntius さんありがとうございます!)。
read
コマンドについて軽くおさらい
read
コマンドを実行すると、キーボードからの入力待ち状態になります。オプション等の意味や効果は次の通り。
-
-p
オプションでメッセージが指定されていれば、入力待ち状態になる前にそのメッセージを表示します。上記の例ですとPlease input your password:
を表示します。 -
-s
オプションが指定されていれば、キーボードから入力した内容がエコーバックされません。2 Linux ログイン時にパスワードを入力しても何も画面に表示されないのと同じです。 -
__pass
は入力された文字列を代入する変数名です。 - 最後の
; echo
は改行を出力します。read
コマンドは最後に改行を出力しないので、このようにしておかないとプロンプトが現在の行に出力されてしまい不格好になってしまいます。
実際に read
コマンドを実行すると次のようになります。入力した値を echo
コマンドで表示させています。
$ read -sp "Please input your password: " __pass; echo
Please input your password:
$ echo "$__pass"
mypassword
.bash_history
にクレデンシャル情報が残らない
.bash_history
にどのように記録されるか、 read
コマンド手法のコマンドを入力してみてみましょう。
$ read -sp "Please input your password: " __pass; echo
$ curl -u "user:${__pass}" http://example.com
いったんログアウトしてから再度ログインして ~/.bash_history
を見てみると次のように記録されています。
read -sp "Please input your password: " __pass; echo
curl -u "user:${__pass}" http://example.com
read
コマンドと curl
コマンドの履歴が残りますが、パスワード部分に指定した環境変数が環境変数のまま記録されており、実際に入力されたパスワードが何なのかはコマンド履歴から隠蔽することに成功しています。
read
コマンド手法はクレデンシャル情報が .bash_history
に記録されるのを見事に防いでいますね。
コマンドのコピペが楽
read
コマンド手法ではどのようにコマンドをコピペするのでしょうか。
$ read -sp "Please input your password: " __pass; echo
※ パスワード は XXX を参照して入力してください。
$ curl -u "user:${__pass}" http://example.com
この手順の場合、実際にコマンドを打つ時の流れは
-
read -sp "Please input your password: " __pass; echo
を1行まるっとコピペ。 - パスワードを入力。
-
curl -u "user:${__pass}" http://example.com
を1行まるっとコピペ。
となります。先に出した例と同じ3ステップですね。読み替え手法と比べて何が良くなったのでしょうか?
...答えはコピペの粒度です。先に出した例だと、1行の中で途中まで/途中からコピペする必要があったのに対して、こちらの例だと1行まるっとコピペするだけになり、ヒューマンエラーの低減を期待できます。3
注意
例示している方法はセキュリティ的には完璧ではない
read
コマンド手法であれば、クレデンシャル情報がファイルに保存されてしまうことを防ぐことができます。
しかし、本稿で例示しているような「クレデンシャル情報を引数でコマンドに渡す」というやり方では、サーバにログイン済みの第三者にクレデンシャル情報を盗み見られてしまうリスクがあります。
どういうことか、本稿で何度も例示している下記のコマンドについて考えてみます。
$ read -sp "Please input your password: " __pass; echo
$ curl -u "user:${__pass}" http://example.com
この場合、実際に curl
コマンドを実行する時には環境変数等がシェル展開され下記のコマンドが実行されます。
curl -u user:password http://example.com
このコマンドが実行されている間に、サーバにログイン済みの第三者が ps
コマンドを叩いたらどうなるでしょうか?
引数部分に(シェル展開後の)クレデンシャル情報を含むプロセスのリストが表示されてしまうはずです。
このように、引数でクレデンシャル情報を渡すという事自体にセキュリティ的なリスクがあります。
ただ、そのリスクを軽減しているコマンドもある
但し、 ps
コマンドからクレデンシャル情報を隠匿するように実装されているコマンドもあります。
実は curl
コマンドも引数からクレデンシャル情報を隠匿するように実装されており、コマンド実行中に ps
コマンドを叩いても下記のようにクレデンシャル情報を隠匿しています。
$ curl -u user:password http://192.0.2.1/ &
[1] 22032
$ ps 22032
PID TTY STAT TIME COMMAND
22032 pts/0 S 0:00 curl -u http://192.0.2.1/
ただ、これも完ぺきではありません。
なぜなら、 クレデンシャル情報を隠匿するのはプロセス内の処理であり、プロセス起動~隠匿処理が完了するまでの間はクレデンシャル情報を盗み見られてしまう恐れがある 為です。
あくまでリスクの 軽減(≠解消) と考えるべきです。
よりセキュアな方法は?
このようなリスクを回避または軽減するためには、
-
後述する
mysql
コマンドのようにコマンドの機能でインタラクティブにパスワードを入力する(多分たいていの場合はread
コマンド手法は不可) -
パスワードを環境変数で直接受け取れるコマンドであれば、下記の例のように環境変数で渡す
$ read -sp "Please input your password: " pass; echo $ pass="$pass" COMMAND
といった方法が考えられます。
これらの方法であれば ps
コマンドでの盗聴は不可能となり、よりセキュアになります。
実際に利用するコマンドが、
- どのような手段でクレデンシャル情報を渡せるのか確認し
- ご自身の環境のセキュリティリスク(サーバ上にクレデンシャル情報を閲覧してはならないユーザのログインが許可されているか等)を考慮した上で
セキュリティリスクと作業工数等のバランスをとって頂ければと思います。
その他
パスワードの繰り返し入力が不要で楽
これはセキュリティ的なメリットではありませんが、作業時間を短縮できるメリットがあります。
パスワードを環境変数に格納するので、同じパスワードを何度も使う場合に都度入力する必要がありません。例えば次のように使えます。
$ read -sp "Please input your password: " __pass; echo
Please input your password:
$ curl -u "user:${__pass}" http://example.com/file01 -o file01
$ curl -u "user:${__pass}" http://example.com/file02 -o file02
$ curl -u "user:${__pass}" http://example.com/file03 -o file03
パスワードは3か所で使っていますが、入力しているのは read
コマンド実行時の一度のみです。
read
コマンドを使わなくても大丈夫なコマンドもある
例えば mysql
コマンドです。 mysql
コマンドは -p
オプションでパスワードを指定することができます。
$ mysql -uroot -pmysqlpassword
上記のようにコマンドラインでパスワードを指定する事もできますが、 -p
オプションの後にパスワードを指定しないとインタラクティブにパスワードを聞いてきます。
$ mysql -uroot -p
Enter password:
もちろんパスワードのエコーバックもありません。
インタラクティブにパスワードを問い合わせてくるコマンドは、ほとんどの場合パスワードに間違いがあれば直ぐに認証エラーを返してくれ、入力ミスに気づきやすいのでそのまま使った方が良いことが多いです。
まとめ
- パスワード等のクレデンシャル情報を別に安全に管理し、手順書はそれを参照させる
- パスワード等のクレデンシャル情報をインタラクティブに問い合わせてくるコマンドであれば、それを利用する
- コマンド実行時にオプションや引数で指定しなければならないコマンドであれば、
read
コマンドを利用する