なぜ環境変数によるシークレット管理は危ういのか
データベースのパスワードやAPIキーを Environment=DB_PASS=... としてユニットファイルに直書きしたり、EnvironmentFile=/etc/myapp/secrets に平文で置いたりする運用は、今もよく目にします。手軽に動くという理由から、スタートアップフェーズのサービスがそのまま本番に昇格してしまうケースも少なくありません。
しかしこの構造には根本的な脆弱性があります。systemctl show や cat /proc/<pid>/environ を実行できる権限さえあれば、平文のシークレットが容易に参照できます。さらに厄介なのは、環境変数がプロセスをフォークするたびに子プロセスへ継承される点です。アプリケーションが外部ライブラリや子コマンドを呼び出す際、意図せずシークレットを渡してしまうリスクは常にあります。
コンテナ環境では Kubernetes の Secret リソースや Vault エージェントによる注入が普及しましたが、ベアメタルや VM 上で動く systemd サービスには同等の仕組みが長らく欠けていました。この空白を埋める機能として設計されたのが、systemd 資格情報ストア(Credentials API)です。
systemd 資格情報ストアの仕組みと設計思想
資格情報ストアは systemd v247(2020年12月)で導入され、暗号化資格情報は v250(2021年12月)で追加されました。2026年現在、Ubuntu 22.04 以降・Debian 12 以降・RHEL 9 系・Fedora 37 以降では標準搭載済みです。
基本的な動作原理は次のとおりです。サービスが起動する際、systemd はユニットファイルに記述された LoadCredential= または LoadCredentialEncrypted= の指示に従って、指定したファイルをサービス専用の一時ディレクトリ /run/credentials/<unit-name>/ へコピーします。このディレクトリはサービスプロセスからのみアクセス可能なパーミッション(0700、所有者は当該サービスの実行ユーザー)で作成されます。サービスが停止するとディレクトリは消去されます。
アプリケーション側は環境変数ではなく、$CREDENTIALS_DIRECTORY 環境変数が指すパス(実体は /run/credentials/<unit-name>)以下のファイルを読み込みます。ファイルはサービス起動時にしか存在せず、プロセスをまたいで継承されません。これにより「秘密はファイルとして読む」「読めるのは当該サービスのみ」という原則が実現されます。
さらに LoadCredentialEncrypted= を使うと、TPM2 チップまたはホスト固有の鍵で暗号化されたブロブをユニットファイルや /etc 以下に安全に置けます。暗号化・復号には systemd-creds コマンドを使います。ブロブ自体が漏洩してもホスト外では復号できないため、リポジトリや設定管理ツールへのコミットすら選択肢に入ります。
本番環境への適用手順
ここでは既存の環境変数ベースのサービスを資格情報ストアへ移行する流れを示します。対象は PostgreSQL 接続パスワードを扱う Web アプリケーションサービスを想定しています。
1. 既存のシークレットファイルを暗号化する
まず systemd-creds でシークレットを暗号化します。TPM2 が利用可能な環境では自動的に TPM2 バインドが使われます。
echo -n "your_db_password" | systemd-creds encrypt --name=db-password -p - -
出力された暗号化ブロブを /etc/myapp/db-password.cred として保存します。このファイルは平文ではないため、通常の設定管理リポジトリへのコミットも運用ポリシー次第で許容されます。
2. ユニットファイルを編集する
systemctl edit myapp.service でドロップインファイルを作成し、以下のように記述します。
[Service]
# 旧来の環境変数指定を削除
# EnvironmentFile=/etc/myapp/secrets
LoadCredentialEncrypted=db-password:/etc/myapp/db-password.cred
3. アプリケーションコードを修正する
アプリケーション側では $CREDENTIALS_DIRECTORY/db-password を読み込むよう変更します。Python を例にすると次のようになります。
import os, pathlib
cred_dir = os.environ.get("CREDENTIALS_DIRECTORY", "")
db_password = pathlib.Path(cred_dir, "db-password").read_text().strip()
多くの主要フレームワーク(Django、Spring Boot、Node.js の dotenv 代替ライブラリ等)では、環境変数のフォールバックとして資格情報ファイルを読む設定を追加するだけで対応できます。
4. サービスをリロードして起動する
systemctl daemon-reload
systemctl restart myapp.service
動作確認と検証
起動後、資格情報が正しく展開されているかを確認するには次のコマンドを使います。
systemd-creds --system list
このコマンドは現在起動中のサービスに渡されている資格情報の一覧と、暗号化の有無・サイズを表示します。内容そのものは表示されないため、オペレーターが意図せずシークレットを閲覧することも防げます。
実行ユーザー以外からの隔離を確認するには、別ユーザーで /run/credentials/myapp.service/ へのアクセスを試みます。
sudo -u nobody ls /run/credentials/myapp.service/
# ls: cannot open directory '/run/credentials/myapp.service/': Permission denied
root ユーザーはアクセス可能です。root アクセスを制限するセキュリティ要件がある場合は、ProtectSystem=strict や NoNewPrivileges=true などの他の hardening オプションと組み合わせることで多層防御を構成します。
また、journalctl -u myapp.service でサービスログを確認し、資格情報ファイルの読み込みエラーが出ていないことを必ず確認します。暗号化ブロブの復号に失敗するとサービスは起動拒否されます。これは意図した動作ですが、本番投入前にステージング環境での検証が不可欠です。
切り戻しと注意点
切り戻しが必要になった場合、旧来の EnvironmentFile= 形式に戻す手順は単純です。ドロップインファイルを削除して systemctl daemon-reload と再起動を行えばよく、アプリケーション側の変更を元に戻すだけです。資格情報ストアはオプト・イン設計なので、移行は段階的に進められます。
注意が必要な点がいくつかあります。
- TPM2 非搭載環境での暗号化:TPM2 がない VM やコンテナホストでは、
systemd-creds encryptはホスト固有の鍵(/var/lib/systemd/credential.secret)を使ったソフトウェア暗号化にフォールバックします。この場合、マシンを移行すると鍵が合わず復号に失敗します。暗号化ブロブと秘密鍵の移行手順をあらかじめ定めておく必要があります。 - 一時ファイルシステムへの依存:
/run/credentials/はtmpfs上に存在します。ディスクへの書き込みは行われませんが、システムが/runを別パーティションにマウントしている場合や、コンテナ環境でtmpfsのサイズが制限されている場合は事前確認が必要です。 - 資格情報サイズの上限:単一の資格情報は 1 MiB が上限です。証明書チェーンのような大きなファイルを渡す場合は注意が必要です。
- SetCredential= のインライン記述:
SetCredential=key:valueでユニットファイルに直接値を書く方式もありますが、これは暗号化されていない形でユニットファイル内に残ります。設定管理リポジトリを使っている環境では、誤って平文シークレットをコミットするリスクが残るため、本番ではLoadCredentialEncrypted=またはLoadCredential=でファイルパスを参照する形が推奨されます。
2026 年時点の対応ディストリとバージョン差分
2026 年 9 月現在、主要ディストリビューションの対応状況は以下のとおりです。
- Ubuntu 22.04 LTS(systemd 249)・24.04 LTS(systemd 255):いずれも
LoadCredentialEncrypted=およびsystemd-credsコマンドに対応。Ubuntu 24.04 では TPM2 LUKS 連携も標準化されており、ホスト固有鍵の信頼性がより高まっています。 - Debian 12 Bookworm(systemd 252):同様に対応済み。Debian 11 Bullseye(systemd 247)は
LoadCredential=のみで暗号化には非対応のため、Bullseye 環境ではプレーンな資格情報ファイルとディレクトリパーミッションによる隔離にとどまります。 - RHEL 9 / Rocky Linux 9 / AlmaLinux 9(systemd 252):暗号化資格情報を含めフルサポート。SELinux との組み合わせでさらに細粒度のアクセス制御が可能です。RHEL 8 系(systemd 239)は資格情報ストア自体が未実装のため対象外です。
- Fedora 40 以降(systemd 255+):最新機能が最も早く取り込まれます。
ImportCredential=(v254 で追加)による外部資格情報プロバイダーとの統合も試せます。
既存の HashiCorp Vault や AWS Secrets Manager などの外部シークレット管理基盤との共存も現実的です。外部プロバイダーから取得したシークレットをファイルに書き出すエージェント(Vault Agent、External Secrets Operator の node-level agent 等)を別ユニットとして動かし、その出力を LoadCredential= で参照する構成にすると、systemd の隔離境界と外部管理基盤の両方を活かせます。サービス停止と同時に /run/credentials/ が消えるため、外部エージェントが次回起動時にシークレットを再取得する設計との相性も良好です。
systemd 資格情報ストアは「別途ツールを導入せずに、OS 標準の機能だけでサービス単位のシークレット隔離を実現する」という点で、現時点で最も導入コストが低い選択肢の一つです。環境変数依存からの脱却を検討している現場では、まずステージング環境で LoadCredential= から始め、TPM2 環境が整い次第 LoadCredentialEncrypted= へ段階的に移行するアプローチが堅実です。
「コマンドは打てる。次は”現場で通用する型”を最短で身につけたい」——そんなあなたへ。
ネットの断片的なTipsをコピペするだけでは、体系的な運用スキルはなかなか積み上がりません。リナックスマスター.JPの無料メルマガ「リナマガ」では、現場で実際に使うLinuxサーバー運用の考え方を、順を追って実務目線でお届けしています。いま登録された方には、安全なサーバー構築の「型」をまとめた『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼント中です。
>> 図解60P『Linux入門マニュアル』を無料で受け取る
(メルマガ登録は10秒・いつでも解除OK)
※ 「独学の時間がもったいない」「プロから現場の技術を最短で学びたい」という本気の方には、2日で実務レベルが身につく初心者向けハンズオンセミナーもご用意しています。
PR・広告
[試して理解]Linuxのしくみ 増補改訂版(Amazon)
プロセス・権限・ファイルシステムなどLinux内部を図解で理解できる一冊。運用判断の勘所に。
※ Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。
