`sudo` の代替技術としての SSH
(whynothugo.nl)sudoとdoasは setuid バイナリ と権限昇格に依存するため、ローカルsshdを Unix ドメインソケットに束縛し、root コマンド実行を任せる実験である- 目標は、許可されたユーザーだけが root コマンドを実行できるようにしつつ、ユーザーセッション全体に権限昇格能力を残さないことにある
- 実装は、root 専用 SSH キーファイル、アクセス制限された
/run/sshd/sshd.sock、AuthorizedKeysFile、PermitRootLogin=yesオプションを持つ別個のsshdインスタンスで構成される sshには既存ソケットを直接渡すオプションがないため、当初はProxyCommandとsocatを使い、その後 ProxyUseFdpass と短い Python スクリプトでソケットのファイルディスクリプタを渡すようにした- この方式は動作し、セキュリティ処理の大半は OpenSSH に依存するが、日常利用では
passfd.pyを小さな実行ファイルにし、sshコマンド全体をラッパーで包むのが適している
sudo と doas が持つ構造的制約
sudoとdoasは、コマンドを root として実行するためにsetuidバイナリと 権限昇格 に依存している- この設計にはいくつかの限界がある
- ユーザーセッション全体が権限昇格を実行できる能力を保持しなければならない
- 制限されたユーザーネームスペース内でユーザーセッション全体を実行する場合に動作しない
setuidバイナリはシステム全体のセキュリティ構成に制約を与える
s6-sudodは、権限のあるサーバーと権限のないクライアントにプログラムを分ける代替案である
実験の目標
- ローカルで
sshを使ってsudoのような役割を果たさせるが、そのsshdインスタンスはネットワークに公開しない - 中核となる条件は 2 つある
- 許可されたユーザーだけ が root としてコマンドを実行できる
- 権限昇格を使わない
ローカル root ログイン用 sshd 構成
- root 認証にのみ使う専用 SSH キーを作成し、通常の
authorized_keysではなく/root/.ssh/local_keysに保存する - 別個の
sshdインスタンスを Unix ドメインソケットにバインドし、許可されていないユーザーがソケットにアクセスできないよう/run/sshd/の権限を制限する - 実行例は
s6-ipcserverで/run/sshd/sshd.sockを作り、sshdに次のオプションを渡す形であるAuthorizedKeysFile=/root/.ssh/local_keysPermitRootLogin=yes
/etc/ssh/sshd_configは変更しない- ネットワークにバインドされた既存の
sshdで root ログインを許可したくないためである - 既存設定では
PermitRootLogin noを維持する
- ネットワークにバインドされた既存の
root アカウントのロックとパスワードログイン処理
- root アカウントは、どの方法でもログインできないようロックされており、これはパスワードハッシュの先頭に
!を付ける方式だった sshdはこの!接頭辞をアカウントロックと解釈し、root ログインを許可しない/etc/passwdの root パスワード値を変更し、!を*に置き換えるsshdは*を特別なロック値として解釈しない*はどのパスワードハッシュとも一致しないため、パスワードログインは事実上無効のままになる
- 別途
sshd_configではPasswordAuthentication noを設定しており、sshd側でパスワードベース認証を無効にする構成が安全である
ssh で Unix ソケットに接続する
sshdには既存ソケットを渡せる-iフラグがあるが、sshには同じ役割のフラグがない- 当初は
ProxyCommandでsocatを使って/run/sshd/sshd.sockに接続した - 接続コマンドは次の要素で構成される
ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'- 専用 root キーを
-i .ssh/root-key.pubで指定 root@rootに接続- 現在のディレクトリへ移動した後、ログインシェルを実行
- 使用した SSH キーは ハードウェアバインドキー であり、接続承認のために物理デバイスをタップする必要がある
- 明示的な承認後にのみキーを公開する
ssh-agent、たとえばhissh-agentも代替になりうる
socat のオーバーヘッドと ProxyUseFdpass
socatはsshのすべての入出力を中継してからソケットに書き込むため、接続オーバーヘッドが実質的に重複するProxyUseFdpassは、コマンドがソケットのファイルディスクリプタをstdout経由でsshに渡し、sshがそのソケットに接続する仕組みである- 2016 年の OpenSSH ProxyUseFdpass 使用例 をもとに、Python スクリプト
passfd.pyを作成した- Unix ドメインソケット
/run/sshd/sshd.sockに接続する sendmsgとSCM_RIGHTSでファイルディスクリプタを渡す
- Unix ドメインソケット
- 以後の接続コマンドは、
ProxyCommandにpassfd.pyを指定し、ProxyUseFdpass=yesを追加する形になった nc -FU /run/sshd/sshd.sockも使えそうだが、マニュアルでは-Fは-Uと一緒に使えないと明記されている
結論と実運用の形
- この手法は動作し、センシティブなセキュリティ処理の大半は OpenSSH に任せられる
- OpenSSH は良好な実績を持ち、ハードウェアベースの SSH キーを含む複数の認証方式を利用できる
- 新しいホストで設定する過程にも複雑な手順はなく、
ipcserverコマンドはシステムのサービスマネージャー経由で実行できる passfd.pyは実験を進めるための手早いハックに近い- 日常利用では、同じ役割を担う小さな実行ファイルを作って
/usr/local/binに置き、sshコマンド全体も小さなラッパーで包む方式のほうが適している
1件のコメント
Hacker Newsのコメント
この方式への最大の反対理由は、複雑さの増加であること。設定ファイルを読み込んで
exec()を呼ぶ単一のsuidバイナリの代わりに、root として動作して UNIX ソケットを待ち受けるバイナリ1つと、そのソケットに接続するバイナリ1つが必要になり、しかも両方が非対称暗号まで処理しなければならないsudo/doas に対する中核的な反論が「すべてのユーザーがアクセス可能な
suidバイナリがあり、バグがあれば権限昇格に悪用されうる」というものなら、次のようにすればよいchgrp wheel /usr/bin/sudochmod o-rwx /usr/bin/sudosudo 利用者を
wheelグループに入れておけば、sudo 可能なユーザーだけがディスク上のファイルのバイト列を読み取れ、実行もできる。UNIX ソケットをwheelユーザーだけが利用可能にする sshd 方式と、アクセス制御の観点ではほぼ同等の安全性だが、複雑さははるかに低いそのうえ sshd 方式では sudo のように root アクセスを特定のコマンドに制限できないため、たとえ sudo のコマンド制限を回避するバグがあったとしても、sshd 方式以上の権限を与えることにはならない
パッケージマネージャーが
/usr/bin/sudoの権限を壊してしまうのが心配なら、cron で定期的に修復させるか、sudo を完全に削除してソースから別の場所へ手動インストールすることもできる。もちろん、その場合は保守とアップグレードも自分で行う必要がある/etcのすべての変更を追跡している。ソフトウェアのインストールやアップグレードでも、人間が行った変更でも記録され、新しいリリースへアップグレードするときもローカル変更分のパッチを作ってクリーンインストールに適用し、その後 3-way マージで競合を確認できるので便利https://etckeeper.branchable.com/
wheelグループに入れて、sudo 可能なユーザーだけがファイルを読んで実行できるようにするのはかなり合理的。なぜこれがどこでもデフォルト設定ではないのか気になる/usr/bin/sudoを immutable にすることもできるのではないか? そうすればパッケージマネージャーが触るのを防ぐ助けになりそうこれは今の systemd run0 がやっていることではないのか? systemd には
run0という新しいツールがあるが、実際には完全に新しいツールというより、以前からあるsystemd-runをrun0という名前のシンボリックリンク経由で呼び出すと sudo の代替のように振る舞う仕組み中核的な違いは、これが実際には
SUIDではない点。サービスマネージャーに対して対象ユーザーの UID でコマンドやシェルを実行するよう要求し、新しい PTY を割り当てたうえで、元の TTY とその PTY の間でデータを中継するつまり対象コマンドはクライアントのコンテキストを継承せず、PID 1 から新たに fork された隔離済みの実行コンテキストで動作する。
$TERMは渡すが、これは明示的な例外であり、ブロックリストというより許可リストに近い多くの点で
run0はsudoよりsshの挙動に近いと言えるhttps://mastodon.social/@pid_eins/112353324518585654
何か見落としているのだろうか? root での ssh ログイン が sudo を使うよりどう安全なのか分からない。常に許可すべきではないと教わってきたので、実際どれほど危険なのかもよく分からない
ここではリモートユーザーを防ぐための話が入っているようなので、そのセキュリティ面を言っているのではない
sudo の限界の3番目に関係するのかもしれないが、少なくとも1番目と比べて利点が見えない
実験だというのは理解できるが、sudo より脆弱性が少ないというより、むしろ脆弱に思える。開いたソケットプロキシは中間者攻撃に弱そうに見える
それでも古いツールでできるいくつかの技術は学べたし、新しいものを見せてくれた点はよかった
sudoバイナリは suid root であり特権バイナリなので、信頼できないユーザーに直接さらされる。sudo の内部で何か問題が起きれば、ユーザーの環境全体が攻撃面となって悪用されうるssh 方式は
suidバイナリを露出させず、ssh のネットワーク層を使う。したがって、ネットワーク経由で ssh にアクセスされる場合より安全性が低いわけではなく、これはかなり安全と見なされているsudo bashのようなものを起動するのではなく、個別のコマンドを sudo で実行して 監査可能性 を高められることそのデーモンにも設定ミスや脆弱性がありうるし、ユーザーベースの認可レイヤーをいくつか単一の root レベルへと減らしてしまう
それにもかかわらず、より安全だと見なされているようだ。合理的なセキュリティの観点では、より安全とは言えず、単に別のやり方にすぎない
実際には、リモート SSH でユーザーとしてログインしてから
sudoを使うより、最初から root で SSH ログインする方が厳密には安全であるuser@homeがroot@serverに ssh する場合、root@serverが侵害されるのはuser@homeが侵害されたときだけ一方で
user@homeがuser@serverに ssh してから sudo でroot@serverになる場合、user@homeまたはuser@serverのどちらか一方が侵害されるだけで root が侵害される。特にuser@serverではデーモンや cron ジョブのような別のソフトウェアがよく動いているそうした経路で侵入に成功した相手に、ただで root 権限昇格を与えるべきではないし、パスワード再利用によってしばしば起こるラテラルムーブメントまで許すべきではない
もちろん、sudo が許可リストにあるコマンド専用モードで使われており、パスワードやリモートホストから完全にアクセス可能な認証情報を受け取らない場合はこの限りではない
起動時に ssh が開始されなかったらどうなるのか? 一般的な構成ではネットワークに依存して起動すると記憶している。そうなると failsafe コンソールでもログインできない
sudo や su と比べて実際に何が得られるのか分からない。setuid バイナリを避ける代わりに、root 権限でネットワークサービスを、たとえソケットにしか接続しないとしても、実行することになる
SSD が壊れたらどうする? そのときも failsafe コンソールにはログインできない
30年間 Linux を使ってきて、ハードディスクが壊れたことは sshd デーモンが起動しなかったことよりはるかに多く、比率で言えば 0 で割るようなものだ
OS の sshd デーモンがランダムに起動しないのなら、もっと安定した OS に移るべきだというサインだと考える
sudo や su より得られるのは、ローカル権限昇格エクスプロイトがはるかに難しくなる点だ
ttyS*デバイスでは sudo や su は使わないこうしたコンソールでは
gettyのようなプログラムやウィンドウマネージャを使い、これらは root で起動される非 suid プログラムだコンソールログイン用の root パスワードは設定しておくべきだ
kubectlエンドポイントを使えばよいログインできるなら root に setuid できるようにしている。k8s ボックスならそれはプラットフォームインフラチームで、その上のサービスへのアクセスは k8s 権限プロバイダ経由だ
プラットフォームインフラチームの観点で指標やログだけが必要なら、すでにボックスの外にあるし、何らかの作業やワークフローをトリガーする必要があるならパイプラインを使えばよい
それでも誰かがログインして root 作業をするなら、監査ログは残したい
自分が持っているボックスで、ログイン権限がある人が完全な root 権限を持たないケースは思い浮かばない
もちろんサービスが setuid するケースは理解できるが、サービスの場合は通常 systemd が権限を上げるためではなく下げるために setuid を行う
systems.unit=emergency.target、init=/bin/bash、rd.break=pre-pivotを設定したり、live CD 環境で起動したりできる。一般的な 緊急復旧オプションはすべて使えるそれほど致命的でない緊急時なら、この sshd インスタンスがネットワークにバインドされている必要もないように思う
このアプローチの欠点がすべて含まれていないのは少し残念だ。sudo なら、どのグループがどのコマンドを実行できるか、そのコマンドがどんな引数を受け取れるか、サブシェルの生成を許可するかどうかなどを制御できる
この方式ではそうした きめ細かな制御の多くが失われ、sudoers ファイルの編集より管理しづらい信頼鍵に依存することになる
sudo ができる驚くべき機能を知るには、Sudo Mastery の本を本当に勧めたい
これは Systemd の run0 に近い発想だ: https://news.itsfoss.com/systemd-run0/
ssh の問題の1つは、プロセス生成がプロトコルの一部ではないことだ。そしてリモートプロトコルなので、ローカル資源を子プロセスに渡せない
そのため、null 区切りの引数配列を渡したり、追加のファイルディスクリプタを渡したり、実行ファイルを指定したりできない
代わりにサーバーに設定されたシェルへ文字列1つを渡す。するとシェルエスケープが必要になり、サーバー側でどのシェルが動いているかも知っていなければならない
SSH をまともな sudo 代替として使うには、拡張として
posix_spawnに近い機能が必要だhttps://bugzilla.mindrot.org/show_bug.cgi?id=2283
完全に同意する。自分も似たようなことをしていて、以前 HN のコメントでも説明したことがある
自分のやり方は少し違う。物理的な SSH コンソール用に専用マシンを使っていて、このマシンは家のほかのマシンとは分離されたプライベート LAN 上にある。管理スイッチではなく普通のスイッチを使い、Ethernet ケーブルを使い、トランクはない
ログインは SSH でしかできず、ここに Yubikey を付けている
デスクトップ PC は独自のファイアウォールを持っていて、この SSH コンソールの IP/MAC アドレスから来る SSH トラフィックだけを許可している。これは両者が共有するプライベート LAN 上でのみで、別の物理 LAN ではデスクトップはインターネットにアクセスできる
sshd デーモンは公開鍵/秘密鍵ログインのみを許可し、パスワードログインは無効にしてある
root が必要なら「SSH コンソール」を起動する。ほとんど何も入っていないマシンなので非常にすばやく起動する。ログインして上矢印で
ssh root@...の行を呼び出し、Enter を押して Yubikey をタッチするその SSH コンソールとキーボードは机の上にあり、常に手の届くところにある
プライベート LAN、それもほかのプライベート LAN と物理的に分離された場所にある iptables/nftables + sshd が sudo バイナリや su より安全かどうかは、各自で判断すればよい
「なぜ」と聞かれたら「できるから」と答えるだろう。かなり前に設定したので、いつやったのかも覚えていない。たぶん2年ほど前にこのアイデアをいじり始め、それ以来ずっと使っている。問題はまったくなかった
この問題に対するエレガントな解決策だ。ユーザーを子どものように扱う必要はないが、同時に合理的なデフォルトで潜在的なミスを避けるべきだ
root が必要ならコンソールで root ログインすればよいので、
suすら不要だと思う。この方式はコンソール tty で root としてログインすることにできるだけ近い1つ目は、sudo と違ってすべてのユーザーが root パスワードを知っていなければならないこと
2つ目は、全員がそのまま root でログインしてしまうと、実際に誰がログインして何をしたのかを監査する方法がないこと
10年前に似たようなことをやったことがある。UNIXソケットの部分はなく、localhostでのみ待ち受ける別個の
sshdを立ち上げていたので、SCM_RIGHTSを扱う必要もなかった特に良くも悪くもならず、単に興味が薄れて次のマシンへ設定を移さなかった