サービスを非特権ユーザーで実行する方法
サービスを root で実行すると、1つのバグがサーバー全体への侵入につながります。専用アカウントを作るか、systemd の DynamicUser を使って被害を限定します。
root で実行するべきでない理由
root はマシン上のすべてを操作できます。すべてのファイルを読み取り、任意の設定を変更し、システム全体を削除できます。サービスを root で実行すると、そのサービスにこれらすべての権限を与えることになります。サービスに攻撃者が悪用できるバグがある場合、攻撃者が取得するのはサービスだけではありません。root 権限を取得し、サーバー全体を操作できるようになります。権限のないユーザーで実行すれば、被害を限定できます。制限されたアカウントで実行するサービスにバグがあっても、攻撃者が操作できるのは、そのアカウントがアクセスできる範囲だけです。その範囲は、ほとんど何もできない状態にする必要があります。
これは最小権限の原則です。システムの各部分には、処理に必要なアクセス権だけを与え、それ以上は与えません。侵害時の被害範囲を抑えるうえで、最も効果的な基本習慣です。最新のサーバーでは、適用しても運用上の負担はほとんどありません。
サービスごとに専用アカウントを作成する
従来の方法は、サービスごとに個別の system user を作成することです。そのアカウントには対象サービスのファイルだけを所有させ、ログインは許可しません。Web アプリケーション用の system account は、次のようになります。
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvc各 flag には意味があります。--system は人間がログインするためのアカウントではなく、service account として作成します。--no-create-home は不要な home directory の作成を省略します。--shell /usr/sbin/nologin により、攻撃者が何らかの方法でそのアカウントを取得しても、そのアカウントで shell を起動できません。このアカウントは、process とそのファイルの所有者としてのみ存在します。
次に、その user には必要なファイルだけを与え、それ以外は与えません。
sudo chown -R appsvc:appsvc /opt/myappこれで、サービスは自身の directory の読み書きだけを行い、disk 上の他の場所にアクセスする必要がなくなります。サービスが侵害された場合でも、攻撃者が変更できるファイルは /opt/myapp に限定されます。そのアカウントは world-readable なファイルを読み取れますが、system の他の部分を変更することはできません。
systemd でそのユーザーとして実行する
アカウントを作成したら、systemd にそのアカウントでサービスを実行するよう指定します。unit ファイルに次の 1 行を追加します。
[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvcUser=appsvc により、プロセスは root の権限ではなく、そのアカウントの制限された権限で起動します。これは systemd でアプリケーションを実行する標準的な方法です。unit を作成するすべてのサービスで実施する価値があります。ただし、systemd が監視するプロセスを誤っている場合、権限を下げても問題は解決しません。デーモンがひそかに終了した後も unit が active のままになる場合は、プロセスの起動方法に合った 正しい Type= を選択しているか確認してください。
または DynamicUser でアカウント自体を省略する
systemd では、サービスの実行中だけ存在する一時的なユーザーを作成することもできます。DynamicUser=yes を設定すれば、アカウントを管理する必要はありません。
[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myapp起動時に systemd が未使用のユーザー ID を割り当て、停止時に解放します。サービスには専用の /tmp も割り当てられます。ファイルシステムの大部分は読み取り専用で見え、StateDirectory= が用意して引き渡す /var/lib/myapp 配下の状態ディレクトリには書き込めます。SysV init では、このような仕組みは利用できませんでした。権限の降格は、各サービスの起動スクリプトが実装している方法に任されていたためです。この差は、ディストリビューションが systemd に移行した主な理由の 1 つです。専用の状態ディレクトリだけを必要とする自己完結型サービスでは、DynamicUser=yes により強力な分離を最小限の手間で実現できます。攻撃者が標的にできる長期的なアカウントが存在しないためです。
unit を手作業で記述するのは手間がかかります。ハードニング用ディレクティブを正しく設定することが、作業の大部分を占めます。systemd のサービスとタイマーのガイドにあるジェネレーターを使えば、これらのオプションを自動入力できます。そのため、最初から正しい unit を作成できます。
他の対策との関係
最小権限は 1 つの層であり、他の対策を置き換えるものではありません。デフォルト拒否ファイアウォールはサービスに到達できる通信を制御します。非特権ユーザーで実行すると、サービスが侵害された場合にサービスが実行できる操作を制限できます。強化した SSHは、そもそも攻撃者がサーバーに侵入することを防ぎます。これらの対策は、どれか 1 つだけでは不十分です。組み合わせることで、1 つのサービスのバグがサーバー全体の侵害につながることを防げます。秘密情報を保護するサービスをホスティングすると、これらの層にも限界があることが分かります。制限されたアカウントを使えば、侵害されたプロセスがアクセスできる対象を制限できます。しかし、Vaultwarden のような自ホスト型パスワードマネージャーは、管理トークンとバックアップファイルをどのように保護するかに依然として左右されます。ユーザー分離では、これら 2 つは保護されません。
次に進む前に、サーバー全体のハードニングチェックリストを確認し、作業用の個別化したコピーを生成します。
FAQ
サービスを root として実行してはいけないのはなぜですか?
root はマシン上のすべてを操作できます。そのため、root として実行しているサービスが侵害されると、攻撃者はそのサービスだけでなくサーバー全体を掌握できます。サービスを制限付きの非特権アカウントで実行すれば、そのアカウントがアクセスできる範囲に被害を限定できます。root は管理作業に限定し、長時間稼働するすべてのサービスを制限ユーザーで実行してください。
ログインできないユーザーを作成するにはどうすればよいですか?
sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME を実行します。nologin シェルを指定すると、認証情報が盗まれてもアカウントで対話セッションを開始できません。--system はサービスアカウントであることを示し、--no-create-home は不要なホームディレクトリの作成を省略します。chown を使用して、そのアカウント自身のファイルだけを所有させてください。
systemd DynamicUser とは何ですか?
DynamicUser=yes を指定すると、systemd はサービスの実行中だけ存在する一時ユーザーを作成します。長期間存続するアカウントを管理する必要はありません。また、サービス専用の /tmp、ほぼ読み取り専用のファイルシステムビュー、管理対象の状態ディレクトリも提供します。使い捨ての低権限 ID で自己完結型サービスを実行する最も手間の少ない方法です。
非 root ユーザーで実行すればファイアウォールは不要ですか?
いいえ。両者が保護する対象は異なります。非特権ユーザーで実行すると、サービスが侵害された場合に実行できる操作を制限できます。一方、ファイアウォールはサービスに到達できる通信自体を制限します。強化した SSH とともに両方を使用し、それぞれの層で他の対策では防げない範囲を補ってください。
サービスユーザーに所有させるべきファイルはどれですか?
サービスが実際に必要とするファイルだけです。それ以外は所有させないでください。アカウントには専用の作業ディレクトリとデータの所有権だけを与え、その他のファイルは root が所有する状態にします。一般的な構成では、アプリケーションディレクトリに sudo chown -R svc-app:svc-app /opt/svc-app を使用し、/etc 配下の設定は root が所有し、サービスからは読み取りだけ可能にします。プロセスが侵害されても変更できるファイルを自身のデータに限定し、システムの他の部分に及ばないようにすることが目的です。