2014年8月29日金曜日

Python から sshpass を実行する時に安全にパスワードを渡す方法について

リモートのサーバーで ssh 経由でコマンドを実行することを Python からやろうとしてまして、sshpass を使うことにしました。

sshpass は ssh 実行時にプロンプトでのパスワード入力を省略したい時等に使えます。例えば

$ sshpass -p hogehoge ssh worker@RemoteServer ls -la

とすると、プロンプトでのパスワード入力を求められることなく RemoteServer に worker ユーザーでログイン (パスワードは hogehoge) して、ls -la を実行した結果を得ることができます。

パスワード入力を省略したい場合は ssh で公開鍵認証方式を使うこともよくありますが、サーバーに公開鍵をインストールしていない場合でも ssh できるように sshpass を使うことにします。

なお、サーバーによっては ID/Password での ssh 認証方式を設定によって禁止しているものもありますが、その場合は sshpass は使えません。

また、パスワードを取り扱うので、他のユーザーにある程度パスワードが漏れないように気を付ける必要があります。

sshpass にパスワードを渡す方法はいくつかあるので、それぞれについてどうやって他のユーザーに漏れてしまうのかを調べてみました。

まず、sshpass にパスワードを渡す方法はヘルプで確認することができます。
$ sshpass -h
Usage: sshpass [-f|-d|-p|-e] [-hV] command parameters
   -f filename   Take password to use from file
   -d number     Use number as file descriptor for getting password
   -p password   Provide password as argument (security unwise)
   -e            Password is passed as env-var "SSHPASS"
   With no parameters - password will be taken from stdin
... snip ...

こんな感じです。

  • -f オプション: ファイルにパスワードを書いて、ファイル名を sshpass に知らせる
  • -d オプション: ファイルディスクリプタ経由
  • -p オプション: パスワードをベタ書き
  • -e オプション: SSHPASS 環境変数を使う
  • オプション無し: 標準入力からパスワードを流し込む

それぞれについて調べてみました。

-p オプションの場合

一般にコマンドラインにパスワードをベタ書きするのが最も漏れやすいと言われている (ような気がします。)

まずは sshpass を実行します。

$ sshpass -p hogehoge ssh worker@RemoteServer sleep 300

ローカルサーバー側 (sshpass を実行した側) で ps コマンドを打つと

$ ps auxwww
... snip ...
worker   26378  0.0  0.0  12764   748 pts/7    S+   13:19   0:00 sshpass -p zzzzzz ssh worker@RemoteServer sleep 300
... snip ...

パスワードを覗き見できてしまいま。。。せん。。。

最近は zzzzzz というようにパスワードをマスクして見えなくしてくれるようです。

しかし、マスクしてくれないケースもあるかと思われますし、ps コマンドで見れてしまうということは良く言われているので -p オプションは避けるべきです。

-f オプションの場合

ファイルにパスワードを書いて -f オプションでファイル名を指定します。

ファイルへのアクセス権を制限して意図しないユーザーがファイルに書いたパスワードを読めないようにする必要があります。

ファイルにパスワードを書くのがなんとなく好きでないので私は -f も使いません。

-e オプションの場合

SSHPASS 環境変数にパスワードを設定します。

$ export SSHPASS='hogehoge'
$ sshpass -e ssh worker@RemoteServer sleep 300

export はシェルの組み込みコマンドなので ps の出力結果には export SSHPASS=’hogehoge’ は出てこないようです。

しかし、ps コマンドで実行中のプロセスの環境変数も表示させることができます。

$ ps auxwwwe
... snip ...
worker    1529  0.0  0.0  12764   744 pts/7    S+   20:03   0:00 sshpass -e ssh worker@RemoteServer sleep 300 LESS_TERMCAP_mb=?[01;31m X
..............................................................................
........................ SSHPASS=hogehoge ....................................
..............................................................................
... snip ...

実際にやってみると少しわかりにくいですが、たくさん環境変数が表示されてその中に SSHPASS もあります。

オプション無しの場合

標準入力からパスワードを流し込むとのことで、パスワードが他者に漏れそうな要因が思い浮かびません。 (コアダンプとられたら漏れるのかな?)

以下、Python のサンプルコードです。 (RemoteServer で hostname コマンドを実行します)

subprocess.check_output() の例

#!/usr/bin/env python3

import getpass
import os
import subprocess

iPipe_r, iPipe_w = os.pipe()

sPasswd = getpass.getpass()
os.write(iPipe_w, '{}\n'.format(sPasswd).encode())

bOutput = subprocess.check_output(
    ['sshpass', 'ssh', 'worker@RemoteServer', 'hostname'],
    stdin=iPipe_r)

print(bOutput.decode())

subprocess.Popen() の例

#!/usr/bin/env python3

import getpass
import os
import subprocess

iPipe_r, iPipe_w = os.pipe()

oSub = subprocess.Popen(
    ['sshpass', 'ssh', 'worker@RemoteServer', 'hostname'],
    stdout=subprocess.PIPE, stdin=iPipe_r)

sPasswd = getpass.getpass()
os.write(iPipe_w, '{}\n'.format(sPasswd).encode())

print(oSub.stdout.read().decode())

-d オプションの場合

オプション無しの場合と同様に、パスワードが他者に漏れそうな要因が思い浮かびません。

以下、Python のサンプルコードです。 (RemoteServer で hostname コマンドを実行します)

subprocess.check_output() の例

#!/usr/bin/env python3

import getpass
import os
import subprocess

iPipe_r, iPipe_w = os.pipe()

sPasswd = getpass.getpass()
os.write(iPipe_w, '{}\n'.format(sPasswd).encode())

bOutput = subprocess.check_output(
    ['sshpass', '-d', str(iPipe_r), 'ssh', 'worker@RemoteServer', 'hostname'],
    pass_fds=(iPipe_r,))

print(bOutput.decode())

subprocess.Popen() の例

#!/usr/bin/env python3

import getpass
import os
import subprocess

iPipe_r, iPipe_w = os.pipe()

oSub = subprocess.Popen(
    ['sshpass', '-d', str(iPipe_r), 'ssh', 'worker@RemoteServer', 'hostname'],
    stdout=subprocess.PIPE, pass_fds=(iPipe_r,))

sPasswd = getpass.getpass()
os.write(iPipe_w, '{}\n'.format(sPasswd).encode())

print(oSub.stdout.read().decode())

-d オプションでパスワードを渡す方法がわからず、かなりはまりました。

結論としては subprocess.chck_output(), subprocess.Popen() の引数に pass_fds=(iPipe_r,) を追加すれば良いのですが、ここまでたどり着くのにいろいろ調べました。

おかげでファイルディスクリプタを子プロセスに引き継ぐ場合の挙動に少し詳しくなることができました。

忘れないように次回の記事に詳しく書いとく予定です。

参考:


0 件のコメント:

コメントを投稿