会社で作業をするために使うスクリプトをOCamlで書いてやりました。

で、この過程でrequrettyに苦しめられたのでその辺のメモ。
requiretty外せないサーバ上でsudoしてからコマンド実行しないといけない、呪われた運命を背負った ">魔法少女 開発者の助けになればな、と。

うちの会社で開発者が本番サーバに入るためには、一旦踏み台サーバを介する必要があります。
また、踏み台サーバから本番へ入るためのssh鍵はsuperユーザしか持っていないため、一旦

$ sudo su - super

を行う必要があります。
あ、ちなみにsuperユーザはrootという意味ではなく、開発者が共通で使うsshログイン不可のユーザです。
なお、開発者のアカウントはsuperになるためのsudoについてはno passwordで行うことができます。
まぁ、これはよくある構成ですね。

Untitled (1)

で、ここからが問題なのですが、この方法で本番に入れる開発者が限られていたりします。
この辺は色々事情があるのですが、まぁよくある権限的な問題ということでその辺は置いておいて。
本番に入れない開発者が本番のログを見る必要がある場合、本番に入れる人に取得をお願いすることになります。
取得したものは共通で見れる場所に置いたほうが都合がいいので、開発サーバ上に置く習わしです。

そして当然ながら、踏み台サーバと開発サーバは別ネットワークになっており、疎通しません。
まぁ、システム管理の観点からすれば当然ですね。

Untitled(1) (1)

さて、この場合、どうやって本番のログを開発サーバに転送するかというと…

Untitled(2)

こんな感じになります。
黒の実線がリモートログインの流れ、青の点線がデータの流れです。
雑な図ですが、単純なファイル転送でscp3回、計6回のコマンドを打っています。
しかも一時ファイルが manage:/tmp/log と local:/tmp/log に残っているので、こいつらも消さないといけません。
これが、「今月分の hogehoge.log(日付別にファイルが分かれている) から FugaException をgrepした結果をください!」とか言われるわけです。
正直、ソウルジェムが濁ります。

開発者たるもの、ルーチンワークなんてこなすわけがありません。
こういう時はスクリプトを書きましょう。
イメージとしては、こんな感じのコマンド群を実行できれば、ローカルに置いたスクリプトで全作業をこなせそうです。

ssh manage sudo su - super -c scp prod:log /tmp
scp manage:/tmp/log /tmp
scp /tmp/log develop:/tmp

試しに、これらのコマンドをターミナルから直接打ってみます。

$ ssh manage sudo su - super -c scp prod:log /tmp
sudo: sorry, you must have a tty to run sudo

が…… 駄目っ……!
1番目のコマンドが引っかかってしまいます。

最近のCentOSではデフォルトで、requirettyという「tty持ってないユーザにはsudoさせないよ☆」というオプションが全ユーザ有効になっています。
「requiretty sudo」でググると、ssh&sudoでエラーになったらなど「/etc/sudoers弄れば簡単に解除できるよ」というのが引っかかります。
というわけでシステム管理者にお伺いを立てるも、「セキュリティ上requirettyを切るのは避けたい」という渋いお返事。
そりゃそうだ、CentOSでシステムデフォルトになってるのはセキュリティ強化のためなので、本番間近のサーバでホイホイとは切ってもらえないでしょう。
しかし、ローカルからコマンド一発でログ転送はしたい所…
ここから、なんとかrequirettyを突破してコマンドを通せないかの試行錯誤が始まります。

とりあえずttyの定義について改めてWikipedia先生にお伺いし、問題なのは「sshコマンドに引数としてコマンドを渡すと、ttyが消えてしまうせいではないか」と推測。
一応実験してみます。

$ tty
/dev/pty0
$ ssh manage tty
not a tty

やっぱり、ここが問題なようです。
ググること数分、sshに-tオプションを付けることで、強制的に仮想端末を付けられることに気づきます。

$ ssh manage -t tty
/dev/pts/1
Connection to manage closed.

キタコレ!
いけそうな雰囲気が漂ってきました。

$ ssh -t manage sudo su - super -c scp prod:log /tmp
[sudo] password for kokuyou:

が…… やっぱり駄目っ……!
なぜかパスワードを聞かれてしまいました。
どこでパスワードが必要になるのか確認するために、普通にsshしてから残りのコマンドを打ってみます。

$ ssh manage
$ sudo su - super -c scp prod:log /tmp
[sudo] password for kokuyou:

あ、どうやらsuコマンドに-cオプションでコマンドを渡すとパスワードが必要になってしまうようです。
おそらく、su – superに限ってsudoのnopasswordが有効になっており、-cをつけるとそれが効かなくなってしまう、といったところでしょうか。
ちなみに踏み台サーバへの ssh は鍵認証で行っており、パスワードはnilっているので、この方法だと詰みです。

ここで問題なのは、suのオプションを使ってコマンドを実行しようとしていることです。
ならば、sudo su – super に標準入力としてコマンドを渡してやればどうでしょうか?
そうです、パイプです。

$ echo "scp prod:log /tmp" | sudo su - super
log                                                                      100%  221     0.2KB/s   00:00

来た! これでかつる!
全部繋げたコマンドをローカルから実行してみましょう。

$ ssh -t manage echo "scp prod:log /tmp" | sudo su - super
zsh: command not found: sudo
Connection to manage closed.

あ、これだと ssh コマンドの結果を sudo にパイプしてしまいますね…
ちなみに手元で試しているのが cygwin なので sudo が not found になっています。
では、ssh してから行うコマンドを全部クオートしてみましょう。

$ ssh -t manage "echo \"scp prod:log /tmp\" | sudo su - super"
log                                                                      100%  221     0.2KB/s   00:00
Connection to manage closed.

勝った! 第三部完!
というわけで、requiretty なサーバに ssh してから sudo su して更にコマンドを実行する場合、

  • ssh コマンドには -t を付けて仮想端末を強制する
  • sudo su コマンドの -c オプションは使わず、パイプして標準入力を流し込む

とすることで幸せになることができました。

なお、scp ではなく本番ホスト上で何らかのコマンドを実行する場合、

$ ssh -t manage "echo \"ssh pwd\" | sudo su - super"
/home/super
Connection to manage closed.

という感じで、こちらは普通に ssh コマンドに引数で渡してしまえばオッケーでした。
ただ、このコマンド部分にダブルクォーテーションが出てきたりすると、エスケープ自体もエスケープする必要がありなかなか悲惨な事になります。
まぁシングルクォーテーションも組み合わせれば多少マシにはなりますが、それはそれでややこしいので、自分はOCamlでスクリプト書いて String.escaped を噛ませつつ合成していくようにしました。

またコマンド実行結果をリダイレクトする場合若干注意が必要で、

# これだとローカルの /tmp/log に出力される
# 一番わかり易いので、問題なければこれが一番手っ取り早い
ssh -t manage "echo \"ssh pwd\" | sudo su - super" > /tmp/log
# これは踏み台サーバの /tmp/log に(superではなく)自身の権限で出力される
# そのためsuperの書き込み権限のある場所でもpermission_denied になる場合がある
ssh -t manage "echo \"ssh pwd\" | sudo su - super > /tmp/log"
# これは踏み台サーバの /tmp/log にsuperの権限で出力される
# おそらく望む動作
ssh -t manage "echo \"ssh pwd\" > /tmp/log | sudo su - super"
# これは本番サーバの /tmp/log に出力される
# おそらく望まない動作
ssh -t manage "echo \"ssh pwd > /tmp/log\" | sudo su - super"

と、書く場所によって挙動が大きく異なります。
当たり前といえば当たり前なのですが、さっくり書くと割と引っかかりやすいです。
自分は引っかかりました。

というわけで、requirettyに悩まされる方の参考になれば幸いです。
">え、requiretty外してもらえるよう交渉したほうが生産的だって? せ、せやな…