NixOSとシークレット
(isabelroses.com)- NixOSでシークレットを Nix 設定、非公開 Git リポジトリ、
git-cryptに平文で置くと、Nix store は誰でも読めるため、マシンにアクセスできる人がシークレットを読めてしまう - sops-nix は
.sops.yamlのルールとsopsの編集フローでシークレットファイルを暗号化し、有効化時にホストの SSH キーで復号して/run/secrets/<name>の tmpfs に平文を置く - agenix は
secrets.nixでシークレットごとの受信者公開鍵を指定し、.ageファイルを/run/agenix/<name>の tmpfs に復号するが、新しいホストの追加や鍵のローテーション時には再キーイングが必要になる - ファイルシステムにシークレットを直接置く方式は単一サーバーやノート PC では可能だが、
builtins.readFile "/var/lib/myservice/token"のように評価時点で読むと値が Nix store に入ってしまうため避けるべき - 始めたばかりで独立したトークンが数個なら agenix のほうが手順が少なく、メールサーバーのように関連するシークレットが多いホストなら、複数の値を 1 ファイルにまとめられる sops-nix のほうが向いている
NixOSでシークレットを扱うときの基本的なリスク
- NixOS におけるシークレット管理は、
sops-nix、agenix/ragenix、ファイルシステム活用、非公開 Git リポジトリ、git-crypt、Nix 設定に直接書く方式に分けられる - 非公開 Git リポジトリ、
git-crypt、Nix 設定への直接記述は、マシンを共有していたり設定を公開する予定があるなら使ってはいけない- Nix store は誰でも読めるため、マシンへのアクセス権を持つ人がシークレットを読める
- このリスクは、CVE-2026-31431(copyfail) や CVE-2026-43284 および CVE-2026-43500(dirtyfrag) のような脆弱性がある時点では特に重要になる
- 公開された設定からシークレットが少なくとも 2 回漏えいしたことがあり、例は 1、2 に残っている
sops-nix
- sops-nix は最初は設定が難しそうに感じるかもしれないが、ドキュメントが大きく改善され、
sopsが SSH キーによるシークレットの暗号化・復号を標準でサポートするようになったのは大きな改善点 - ただし
sops-nixはこの SSH キー対応ではまだ後れを取っており、sops-nix#779、sops-nix#922 で関連作業が進んでいる - 利用フローは、
.sops.yamlに暗号化・復号ルールを作成し、sopsコマンドでシークレットファイルを編集する形- 例としては、
secrets/*.yamlパスに対してageの受信者として SSH 公開鍵を指定する sops secrets/shush.yamlを実行すると選択したエディタが開き、hello: sopsのような YAML 値を書ける- エディタを終了すると値は
ENC[AES256_GCM,...]の形で暗号化され、同じコマンドで再編集できる
- 例としては、
- NixOS 設定では
sops-nixモジュールがほとんどの作業を処理するdefaultSopsFile = ./secrets/shush.yaml;でデフォルトのシークレットファイルを指定するage.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];でホスト SSH キーを指定するsecrets."hello"にowner、group、modeを指定してファイル権限を設定する
- 有効化時に
sops-nixがホストの SSH キーでファイルを復号し、平文を/run/secrets/<name>に置く- このパスは tmpfs なので、シークレットがディスクに触れることはない
- 値を必要とするサービスはそのパスを読めばよい
- テンプレート 機能は、共有設定や他のユーザーが参照する設定で便利
- サービス設定ファイルが平文と一部のシークレット値を同時に必要とする場合、ファイル全体を暗号化しなくてよい
- たとえば
SMTP_USER=isabelは平文のままにし、SMTP_PASSWORD=${config.sops.placeholder."mailserver/smtp_password"}のようにシークレットのプレースホルダーを入れられる
agenix
- agenix は
sops-nixと異なり、secrets.nixでシークレットとアクセス可能な鍵を設定するため、より Nix らしい使い心地がある secrets.nixではユーザーとホストの SSH 公開鍵を束縛し、各.ageファイルにどの公開鍵がアクセスできるかを指定する- たとえば
"secret1.age".publicKeys = [ isabel host1 ];、"secret2.age".publicKeys = [ isabel host2 ];のように、シークレットごとに異なる受信者リストを持たせられる
- たとえば
- シークレットファイルは
agenixCLI で作成する必要があるagenix -e secret1.ageコマンドでsecret1.ageを作成したり、後で再編集したりできる
- ホスト設定へ接続する方法は
sops-nixと似ているが、各シークレットが別ファイルなので表面積はより小さいage.secrets.secret1.file = ./secrets/secret1.age;でファイルを指定するowner、group、modeで所有者と権限を指定する
- 起動時にホストの SSH キーで各
.ageファイルを復号し、/run/agenix/<name>に置く- このパスも tmpfs 上にある
- 最もよくつまずく部分は 再キーイング(rekeying)
- 新しいホストを追加したり鍵を交換したりすると、
secrets.nixでpublicKeysリストが変わったすべてのシークレットを再暗号化しなければならない agenix --rekeyがこれを処理するが、既存の暗号文を読むために現在の受信者のうち少なくとも 1 つの秘密鍵が必要になる- 実際には、新しく追加するホストではなく、最も信頼しているマシンで再キーイングを行うことになる
- 新しいホストを追加したり鍵を交換したりすると、
ファイルシステムだけを使う方式
- ファイルシステムにシークレットを直接置く方式には、設定だけではマシンを完全に記述できなくなるというコストがある
- 再インストール時には、すべてのファイルを正しい場所と所有権で再配置しなければならない
- 深夜 2 時にサーバーを復旧するような場面では、復旧作業にとって大きな災難になりうる
- 避けるべきパターンは
builtins.readFile "/var/lib/myservice/token"のような形- この方式は評価時点でファイルを読み、その内容を Nix store に含めてしまう
- Nix store は誰でも読めるため、最初に警告した失敗パターンとまったく同じになる
- 代わりに、サービスには値そのものではなくパスを渡すべき
- 例として services.*.environmentFiles のようなオプションを使える
- 単一サーバーやノート PC では問題ないこともあるが、フリート運用だったり、設定だけでゼロから再構築したい環境なら
sops-nixやagenixのほうが適している - マシンごとに本当にリポジトリへ入れてはいけない値が 1 つか 2 つだけなら問題ないが、あとでそれを再投入しなければならない責任は将来の自分に残る
sops-nix と agenix の比較
sops-nixを選ぶ主な理由は、できるだけ多くのデータを 1 つのファイルにまとめられる点- メールサーバーのように関連シークレットが多い場合、
agenixのように 5 個前後のファイルへ分けるのではなく、1 ファイルにより多くのシークレットを入れられる - 強力なツールとして継続利用に向いており、メールサーバーのようにシークレットが非常に多いホストではまず選択肢に入る
- メールサーバーのように関連シークレットが多い場合、
agenixは シンプルさ が強み- 学ぶべき YAML スキーマがない
- 同期が必要な
.sops.yamlがない secrets.nixはただの Nix なので、ホストやユーザーで使っていたlet ... in束縛を鍵にもそのまま使える- 思考モデルは「シークレット 1 つ、ファイル 1 つ、受信者リスト 1 つ」で、アクセス制御の方式とうまく噛み合う
- 可動部分が最も少ない部類でありながら、ホストごとの鍵アクセス制御を提供するため、NixOS マシンのシークレット管理を初めて尋ねる人に勧めやすい選択肢になっている
- どちらのツールも問題を解決でき、現時点での差はほとんど 使い勝手 に近い
- 始めたばかりで、複数サービスが関連シークレットのまとまりを必要とするなら
sops-nixのほうがより拡張しやすい - 始めたばかりで、ほとんどが独立したトークン数個だけなら
agenixのほうが少ない手順で目的に到達できる - 最初のシークレットツールを選ぶなら、まず
agenixで流れに慣れ、「シークレット 1 つにつきファイル 1 つ」という方式の不便さを実際に感じたときにsops-nixへ移るのがよい
- 始めたばかりで、複数サービスが関連シークレットのまとまりを必要とするなら
- 耐量子性に関する内容は修正済み
1件のコメント
Lobste.rs のコメント
暗号化されたシークレットとその鍵の両方がディスク上にあるのではないか? それともどちらか一方が TPM のような場所に保存されるのだろうか?
Nix を使い始めたばかりで、小規模なセルフホスティング環境では単純なので
scpでファイルシステムに入れる方法を使っている少し調べると SSH 秘密鍵を TPM で保護できるようで、VM でも可能なのか気になっていたが、vTPM のサポートがあり得ると分かって自分で答えを見つけた形になった
サーバー側には NixOps のアクセス手段もある。Colmena のやり方を見ればよい: https://colmena.cli.rs/0.4/features/keys.html
要するに、シークレットを持つ信頼済みマシンがリモートサーバーへそれをプッシュする構成だ。今
scpでやっていることに近いが、その過程をより Nix らしく動かしているここ数晩かけて自分のシステム flake に agenix 系を再設定したばかりなので、agenix についてだけ話せる。興味がある人向けには agenix-rekey を選んだ。シークレットを入れた
.ymlファイルを置く必要がなく、すでにしているようにシークレットをすべて Nix の中で設定できるからだ暗号化されたシークレットは Nix store にあり、Nix store の他のものと同様に全体で読み取り可能である。そのシークレットを開く SSH 秘密鍵は通常、実際の SSH サーバーの秘密鍵であり、必要なら そうしないこともできる。シークレットはアクティベーション時、だいたい起動時に復号されて
/run/agenix/<user>に置かれるsecrix というツールはさらに進んで、シークレットを systemd サービスに結び付け、そのサービスをシークレットを必要とするサービスにさらに結び付ける。つまりそのサービスが動作している間だけシークレットが復号される。ただ実際には、NixOS ユーザーがサービスを頻繁に起動停止することはあまりないので、多くの場合はずっと実行中という意味になりがちだ。ユーザー作成のようなシステムアクティベーション用シークレットには向いているかもしれない
agenix-rekey は rekey をそれほど面倒でなくし、
secrets.nixの代わりに flake 出力で置き換えてくれる。鍵の片側の半分として YubiKey 分割 ID を使う。rekey はその鍵ともう片側の半分で認証してシークレットを復号し、その後サーバーの SSH 公開鍵で再び鍵を掛ける工程だ。公開鍵はシステムインストール時に生成され、自分は nixos-anywhere に--copy-host-keysを使って、インストールクロージャで生成された鍵を取得している。暗号化されたシークレットはリポジトリ内に置くが、信頼済みビルダーだけがアクセスできる別のサブモジュールに置いている参考までに vTPM はたいていハードウェアベースではなく、多くのプロバイダーは TPM を提供していても大半は TPM v2 ではなく TPM v1.2 しか提供していない。自分のプロバイダーである BinaryLane もそうだ。セキュリティ層が一つ増えるのは確かだが、本物の TPM のような魔法の HSM ではなく、プロバイダーやホストノードの侵害から守ってくれるわけでもない。本物の HSM ベース vTPM を許可するには、プロバイダー側にとってはかなり高コストになりそうで、AWS は AWS 価格でそれを提供しているのだと思う
agenix+agenix-rekey+age-plugin-1pを使っているマスター鍵は 1Password の中にあるので、ノートPCのディスクにどんな認証情報も置かずに、どのサーバーのパスワードでも編集・閲覧・rekey できる
実行時に鍵アクセスが必要なサーバーには権限を与えられる。agenix-rekey にそのサーバーがどの鍵を見られるかを知らせて
agenix rekeyを実行すると、そのサーバーが復号できる暗号化鍵バージョンが Nix store に記録される。そのサーバーの SSH 秘密鍵はそのサーバー上にのみ存在し、決して外へ出ない。agenix-rekey が rekey に必要とするのは公開鍵だけだしたがってシークレットが漏れるのは、自分の 1Password アカウントが侵害された場合か、そのシークレットを使うサーバーが侵害された場合である
/etc/ssh/ssh_host_ed25519_keyで復号し、/run/agenix.dにマウントされた ramfs に置くなのでその通り。暗号化された内容、暗号化鍵、復号済みの内容のすべてにファイルシステムからアクセス可能である
https://github.com/oddlama/agenix-rekey
そのうえ長期に維持される認証情報も減ってきている。長期認証情報のコピー から離れて、ハードウェアベースの認証情報へ移行しつつあり、それを直接使うか、短命な認証情報と交換する方向へ進んでいる