This page looks best with JavaScript enabled

リモートのサーバを雑に操る

 ·  ☕ 3 min read

なんとなく以前作ったものをGoで書き直したので供養します。リモートのサーバを操作できます。
GitHub - ebiiim/cmdproxy: Run any commands on remote hosts via HTTP(S).

使い方

  1. 事前に決めた共通のパスワードでサーバcmdprxを起動しておき、
1
cmdprx -tls -host="isekai.ebiiim.com" -secret="tensei"
  1. クライアントcmdprx-cliから実行したいコマンドを送ると、
1
cmdprx-cli -cmd="date" -url="https://isekai.ebiiim.com" -secret="tensei"
  1. 実行結果が返ってきます。
1
2
3
4
5
6
Error: 
ExitCode: 0
Stdout: Sun Feb 14 12:34:56 JST 2021

Stderr: 

プログラムから呼び出す場合はこんな感じです。

クリックで展開
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cmd := `date +"%Y-%m-%d %H:%M"`
client := cmdproxy.NewClient("https://isekai.ebiiim.com", "tensei")
res, err := client.Run(strings.Split(cmd, " "), 5*time.Second)
if err != nil {
  panic(err)
}
fmt.Printf("Error: %+v\n", res.Error)
fmt.Printf("ExitCode: %d\n", res.ExitCode)
fmt.Printf("Stdout: %s\n", res.Stdout)
fmt.Printf("Stderr: %s\n", res.Stderr)

何をしているの?

特にすごいことはしてなくて、JSONをリクエストボディに入れてやりとりするだけです。

これだけだとただの踏み台なので、クライアントはハッシュしたパスワードをリクエストボディに含めて、サーバはそれを検証します。(リクエストヘッダに入れたほうがキレイですね……)

URLのパスも同様です。(これはリクエストボディなどに入れるのと本質的に変わらないので、なくてもいいかもしれません)

通信路を暗号化しないとtcpdumpされちゃうのでHTTPSに対応します。

FAQ

Q. リモートのサーバでコマンドを実行したい
A. 次からお選びください
  • SSH
  • IPsecなどVPNを使う
  • stunnelをいい感じに使う
  • 専用線接続
  • ちゃんとしたAPIを生やす
Q. では、なぜこんなものを?
A. いろいろあった
クリックで展開

「異世界の時刻を知ること」が現実世界でお金になるとします。あなたは、異世界にサーバを設置し、リモートでdateコマンドを実行できるようにする仕事をしています。あなたが上司の魔法で異世界に転生し、サーバを設置してSSHの動作を確認しようとしたそのとき、上司からテレパシーでメッセージが届きました。

 「そういえばうちはHTTP(S)しか使えないよ」
 「異世界はヤバいから1回の通信は数秒しか維持できないよ」
 「あと30分で帰れなくなるから急いでね!」

そこで、あなたはテキストエディタを開き(以下略)

※フィクションです

Q. もうちょっと良くできない?
A. こんなのがあったらいいよね
  • クライアント証明書認証
    • 安心感がすごい
    • Go 1.16から使えるらしいgo:embedでいい感じにシングルバイナリにまとめられてカッコイイ
  • 送信元ポート制限
    • 送信元IPでフィルタできるのが理想ですが、送信元IPが一定じゃない環境ってよくあるので……
    • 送信元ポートを制限したら変な通信あまり来ないのでは?(要検証)
  • stdinとstdoutのストリーミング
    • フィルタ処理とかで使う、stdinにデータを入力し続けて、完了したものが順次stdoutに出力されるやつ
    • 自分で実装すると大変だけど、gRPC bidirectional streaming RPCでできそう
Q. 本番環境で使っていい?
A. ダメです、インターネットの治安維持に協力しましょう

1点付け加えると、443番ポートにbindするためにsudo cmdprx ...で起動するとroot権限でコマンドを実行するので大変ヤバいです。setcapでwell-known portsにbindできるようにしましょう。

1
sudo setcap CAP_NET_BIND_SERVICE=+eip /path/to/cmdprx

おわりに

HTTP越しになんか呼び出さなきゃいけなくて、しかも納期は30分後!ってときにこのページを思い出していただければと思います。そんな場面に遭遇しないのが一番ですが。