sshを使ってリモートマシンでコマンドを叩く際の注意点

知ってる人には当たり前なのかもしれないですが、自分用のメモです。

先に結論を書くと次の通り。

  • sshでリモートマシンにログインするのではなく<ssh command>によってコマンドを叩く場合には、ttyが割り当てられない。sshに-tオプションを付与すると、端末を無理矢理割り当てられる
  • cronなどのttyが割り当てられていない環境からは-tオプションだけでは不十分で、-ttオプションが必要

事の経緯は、固定IPアドレスが無い自宅マシン(回線はフレッツ光)にインターネットからアクセスしたいというものでした。DDNSを使うんではなく、固定IPを持っている自前VPSと"who am i"を使えば、簡単になんとかできるだろ、と思ったのが運の尽き。どハマリしました。

最初に思い描いたサービスは次の通りです。

  • 自宅マシンからVPSsshで定期的に接続して、IPアドレスを所定のファイルに書き込む。ssh接続時はパスフレーズ無しの鍵を使う。接続時に使うユーザは、上記作業以外をする以外の権限を持たない特別なものを使う
  • 自宅マシンのIPアドレスは、"who am i"の出力(例: “sat pts/8 2017-04-11 22:19 (xxx.xxx.xxx.xxx)”)の第5フィールドを整形する
  • インターネットから自宅マシンに接続する前に、VPSに自宅マシンのIPアドレスを問い合わせる。もちろんIPアドレスの問い合わせとsshによる接続は1つにまとめてスクリプト化する

うまくいかなかったのは自宅マシンのIPアドレスを取得するところです。取得したIPアドレスを書き込んでいるはずのファイルが空になっていました。問題切り分けのために、インターネット上からsshで自宅マシンに一旦ログインして、そこからIPアドレス取得コマンドを実行するとうまくいきました。

色々とぐぐった結果、sshからログインでなく直接コマンドを叩くときは、ttyが割り当てられないことがわかりました。ttyが無いとwhoコマンドの出力の元ネタであるutmpファイルにログが残りませんので、IPアドレス取得コマンド内のsshの出力も空になった、それに伴ってコマンド全体の出力も空になってしまったというわけです。回避策は、以下の通りsshに-tオプションを付けることです。

man(1) sshより抜粋:

-t Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services.

まずはインターネット上からcrontabに書いたコマンドを実行すると、正しくIPアドレスが得られました。これで解決したと思ってcrontab上のsshに-tオプションを付与したものの、またしてもIPアドレスを書き込んでいるはずのファイルは空でした。

man(1) sshを再度読んでみると、さきほど抜粋した-tオプションの説明には続きがありました。

-t Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty.

cronなどのttyを持たないプロセス(daemon)からsshを実行する場合は、-tオプションだけでは不十分で、-ttオプションが必要と書いてあります。crontab上のsshに-ttオプションを付与したところ、無事問題は解決できました。最初からmanをちゃんと読んでおけばもう少し解決が楽でした。反省。

追記。優しい人に、IPアドレスを知る目的であれば$SSH_CLIENTないし$SSH_CONNECTIONが使えるということを教えていただきました。なんてこった