ssh-agent のしくみ
ssh-agent のように daemon として起動し秘密の情報を保持しつつ別プロセスと通信するようなプログラムを書きたくて、ssh-agent はどう実装しているのかざっくり調べた。
https://github.com/openssh/openssh-portable
通信方法
これは普通に ssh-agent を使っていてもすぐ気付くことだけど、ssh-agent は UNIX domain socket を使って通信している。
eval $(ssh-agent)
のように実行すると SSH_AUTH_SOCK と SSH_AGENT_PID の2つの環境変数がセットされ、SSH_AUTH_SOCK は UNIX domain socket のパスを、SSH_AGENT_PID は daemon 化した ssh-agent の pid を指している。
SSH_AUTH_SOCK は /tmp/ssh-*/agent.${parent_pid} というパスになっている。parent_pid は daemon 化する前の pid。/tmp/ssh-* も /tmp/ssh-*/agent.${parent_pid} もオーナー以外アクセスできないようなパーミッションになっている。
このへんは /tmp 以下にできるだけ安全にプライベートなファイルを作るときの一般的な方法をそのままやっているかんじ。
https://github.com/openssh/openssh-portable/blob/V_8_5_P1/ssh-agent.c#L1517-L1543
SSH_AGENT_PID は ssh-agent -k
のときに利用される。
ssh は SSH_AUTH_SOCK が定義されていればそれを使って ssh-agent に通信しにいって、通信内容のプロトコルは独自。この部分は各プログラムが独自に決めればよいことなのであまりよく読んでない。
https://github.com/openssh/openssh-portable/blob/V_8_5_P1/ssh-agent.c#L961-L1061
daemon
daemon 化はだいたい一般的な流れ。fork して setsid して chdir して dup2 で stdin、stdout、stderr を /dev/null に向ける。
https://github.com/openssh/openssh-portable/blob/V_8_5_P1/ssh-agent.c#L1560-L1596
秘密の情報を漏らしにくく
ssh-agent は秘密鍵の情報をメモリ上に持っておく必要があるため、できるだけメモリ情報が漏れないようにする工夫が見られた。
- setrlimit でコアダンプを無効化 https://github.com/openssh/openssh-portable/blob/V_8_5_P1/ssh-agent.c#L1599-L1604
- prctl で ptrace の無効化 https://github.com/openssh/openssh-portable/blob/V_8_5_P1/platform-tracing.c#L37-L39
- これを実行すると /proc/${pid} 以下のオーナーが変わったりした
自分で同じように秘密の情報を保持するプログラムを書くときはこのへんを真似しておけばよさそう。 通信のプロトコルを gRPC にして実装したサンプルコード https://github.com/eagletmt/misc/tree/master/rust/agent-proto