systemd サービスユニットにおける環境変数の安全な管理

Environment、EnvironmentFile、ドロップイン、より安全なシークレット処理を用いて systemd 環境変数を設定します。

systemd サービスユニットにおける環境変数の安全な管理

環境変数は便利ですが、自動的にプライベートになるわけではありません。systemd サービスでは、ユニットファイル、ドロップイン、systemctl show、プロセス検査ツール、クラッシュレポート、デバッグログ、コピーされたサポートバンドルに表示される可能性があります。これは、環境変数を決して使用できないという意味ではありません。何を環境変数に入れるか、そしてそれらを定義するファイルを誰が読めるかを慎重に検討すべきだという意味です。

このガイドでは、Environment=EnvironmentFile= という 2 つの一般的なディレクティブについて説明し、パッケージ管理されたユニットからローカル設定を分離するためにドロップインを使用する方法を示します。


systemd における環境変数の役割

環境変数は、コードを変更せずにサービスを設定するための簡単な方法を提供します。systemd がサービスを開始すると、プロセス環境を構築し、ExecStart= を実行する前にユニットで定義された変数を適用します。

systemd は、ユニットファイルの [Service] セクション内で、これらの変数を管理するための 2 つの主要なディレクティブを提供します。

1. 直接定義: Environment ディレクティブ

この方法では、systemd ユニットファイル内で直接変数を定義できます。これは、めったに変更されない機密性の低い設定パラメータに適しています。

使用法と構文

Environment ディレクティブは、"KEY=VALUE" 形式の変数代入のスペース区切りリストを受け入れます。

# /etc/systemd/system/my-app.service

[Unit]
Description=My Application Service

[Service]
User=myuser
WorkingDirectory=/opt/my-app

# ユニットファイル内で直接変数を定義
Environment="APP_PORT=8080" "NODE_ENV=production"

ExecStart=/usr/local/bin/my-app --start

[Install]
WantedBy=multi-user.target

制限とセキュリティ

便利ではありますが、Environment ディレクティブはパスワード、トークン、データベース認証情報を保存するには不適切な場所です。ユニットファイルは、構成管理システムに保存されたり、チケットにコピーされたり、サービスの動作を検査する必要があるがシークレットを見るべきではないオペレーターによって読み取られたりすることがよくあります。ポート、フィーチャーフラグ、ログレベル、パスなどの値に使用してください。

2. 外部設定: EnvironmentFile ディレクティブ

より大規模な構成の場合、外部ファイルから変数をロードする方が通常はよりクリーンです。これにより、メインのユニットファイルとは独立して変数ファイルの権限を管理できます。また、パッケージが提供するユニットを読み取り可能に保ちながら、ローカル設定は /etc に置くことができます。

使用法と構文

EnvironmentFile ディレクティブは、設定ファイルへの絶対パスを取ります。systemd はこのファイルを行ごとに読み取り、各行を潜在的な KEY=VALUE 代入として扱います。

[Service]
# 外部ファイルから変数をロード
EnvironmentFile=/etc/config/my-app-settings.conf

ExecStart=/usr/local/bin/my-app --start

環境ファイルの形式

外部ファイルは、単純なシェルライクな形式に従う必要があります。

  • # で始まる行はコメントとして扱われます。
  • 空の変数代入 (VAR=) で始まる行は、変数が以前に設定されていた場合、その変数をクリアします。
  • 変数は KEY=VALUE として定義されます。
  • 値の引用符 (KEY="VALUE WITH SPACES") はサポートされています。
# /etc/config/my-app-settings.conf

# 機密性の低い変数
MAX_WORKERS=4
LOG_LEVEL=INFO

# 機密性の高い変数(厳格なファイル権限と注意深いアクセス制御が必要)
DB_PASSWORD=SecureRandomString12345

systemd の環境ファイルパーサーが期待通りにサポートしないシェルの習慣は避けてください。export KEY=value と書かないでください。等号の周りにスペースを入れないでください。値にスペースが含まれる場合は、引用符で囲んでください。値にリテラルの引用符、バックスラッシュ、または改行が含まれる場合は、本番環境でそれに依存する前にテストしてください。

ファイルが見つからない場合の処理

デフォルトでは、EnvironmentFile で指定されたファイルが存在しない場合、systemd はサービスの起動を失敗させます。環境ファイルがオプションの場合は、ファイルパスの前にハイフン (-) を付けることができます。

EnvironmentFile=-/etc/config/optional-settings.conf

ファイルの前に - が付いている場合、systemd はファイルが存在しないことによるエラーを無視します。

ベストプラクティス: 機密データにはドロップインユニットを使用する

コアユニットファイル(例: /usr/lib/systemd/system/my-app.service)を変更することは、特にファイルがパッケージマネージャーによって管理されている場合、一般的には推奨されません。代わりに、ドロップインユニットファイル を使用して、設定のオーバーライドや追加を適用します。

このプラクティスが重要なのは、ベンダーのデフォルトとローカル設定を分離するためです。また、監査が容易になります。ユニットは設定がどこからロードされるかを示し、そのファイルの権限は誰がそれを読めるかを示します。

ステップバイステップのドロップイン設定

1. ドロップインディレクトリを見つける/作成する

my-app.service という名前のサービスの場合、ドロップインディレクトリは my-app.service.d/ という名前で、/etc/systemd/system/ 階層内に存在する必要があります。

sudo mkdir -p /etc/systemd/system/my-app.service.d/

2. 設定オーバーライドを作成する

ドロップインディレクトリ内にファイル(例: secrets.conf)を作成します。このファイルには、[Service] セクションと、オーバーライドまたは追加する特定のディレクティブのみが必要です。

# /etc/systemd/system/my-app.service.d/secrets.conf

[Service]
# 安全な認証情報ファイルをロード
EnvironmentFile=/etc/secrets/my-app-credentials.env

3. 外部環境ファイルを保護する

これは最も重要なセキュリティ手順です。シークレットを含む外部ファイルが制限的な権限を持っていることを確認してください。理想的には、root:root が所有し、root ユーザーまたはサービスユーザーのみが読み取り可能である必要があります。

# シークレットファイルを作成
sudo touch /etc/secrets/my-app-credentials.env

# ファイルにシークレットを入力
sudo sh -c 'echo "DB_PASS=S3cr3tP@ssw0rd" >> /etc/secrets/my-app-credentials.env'

# 制限的な権限を設定
sudo chmod 600 /etc/secrets/my-app-credentials.env

EnvironmentFile によって参照されるファイルに認証情報が含まれている場合は、サービスを管理する必要があるアカウントのみが読み取り可能にしてください。systemd が User= で権限をドロップする前にファイルを読み取る場合、0600 root:root が一般的ですが、一部の運用モデルでは、専用の root 所有グループと 0640 を使用します。重要なのは、通常のユーザーがファイルを読み取れないことです。

また、残りのリスクについて正直になってください。環境変数は、ハードコードされたコマンドライン引数よりも処理が簡単ですが、それでも完全なシークレット管理システムではありません。リスクの高い認証情報の場合は、専用のシークレットストア、短期間の認証情報、新しいディストリビューションの systemd クレデンシャル、または保護されたファイルを直接読み取るアプリケーション固有のメカニズムを検討してください。

トラブルシューティングと検証

ユニットファイルまたはドロップインに変更を加えた後は、systemd マネージャー設定をリロードする必要があります。

sudo systemctl daemon-reload
sudo systemctl restart my-app.service

実行中のサービスに対して systemd が正常にロードした環境変数を確認するには、systemctl show コマンドを使用し、特に Environment プロパティをクエリします。

systemctl show my-app.service --property=Environment

出力例(ロードされた変数を表示):

Environment=APP_PORT=8080 NODE_ENV=production DB_PASS=S3cr3tP@ssw0rd

このコマンドはデバッグに役立ちますが、注意点もあります。適切な検査コマンドを root として実行できる人は誰でも値を確認できます。この出力を、共有チャット、チケット、公開バグレポートに、編集せずに貼り付けないでください。

サービスが起動に失敗した場合は、journalctl -xeu my-app.service を使用してサービスのログを確認してください。環境変数に関連する失敗の一般的な理由は次のとおりです。

  1. EnvironmentFile のファイルパスが間違っている。
  2. ファイルが見つからない(そしてパスの前に - が付いていなかった)。
  3. 外部環境ファイルの変数構文が間違っている(例: = 記号の周りのスペース)。

機能する実用的なパターン

シナリオ 使用するディレクティブ 場所のベストプラクティス セキュリティに関する考慮事項
静的で機密性の低い設定 Environment 直接ユニットファイルまたはドロップイン セキュリティリスクは低い。
機密性の高い認証情報(シークレット) EnvironmentFile 外部ファイル、ドロップイン (*.service.d/) を介して参照 重要: 環境ファイルは 0600 の権限が必要。
モジュール性とオーバーライド EnvironmentFile ドロップインユニットファイル 設定をベンダーのデフォルトから分離します。

専用のドロップインユニット内で EnvironmentFile ディレクティブを活用し、厳格なファイル権限を確保することにより、管理者は最小権限と関心の分離の原則に従って、安全かつ柔軟にサービス設定を管理できます。

小規模な内部サービスの場合、妥当な設定は次のようになります。

# /etc/systemd/system/my-app.service.d/env.conf
[Service]
Environment="APP_ENV=production"
EnvironmentFile=/etc/my-app/runtime.env
EnvironmentFile=-/etc/my-app/local.env

runtime.env には必要な値が含まれています。local.env はオプションであり、オペレーターがメインのユニットを編集せずにメンテナンスウィンドウ中に設定をオーバーライドできるようにします。変更後:

sudo systemctl daemon-reload
sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

最も安全な習慣は簡単です。機密性の低いデフォルトはユニットまたは通常の設定ファイルに保持し、シークレットはパッケージが所有するユニットの外に保持し、認証情報を含むファイルをロックダウンし、ロードされた環境を確認して、それが属さない場所に漏洩しないようにします。

避けるべきよくある間違い

最初の間違いは、ExecStart= にシークレットを入れることです。

ExecStart=/usr/local/bin/my-app --db-password=s3cret

急いでいるときは無害に見えますが、コマンドライン引数は環境ファイルよりも露出しやすいことがよくあります。プロセスリスト、監視ツール、シェル履歴、クラッシュレポート、またはコピーされたサービス定義に表示される可能性があります。アプリケーションが保護された設定ファイルの読み取りをサポートしている場合は、通常はその方が優れています。環境変数を期待する場合は、保護された EnvironmentFile= を使用し、値をコマンドラインの外に保持してください。

2 番目の間違いは、ベンダーユニットを直接編集することです。パッケージのアップグレードによりファイルが置き換えられ、次の再起動で環境設定が静かに失われる可能性があります。ドロップインを使用してください。

sudo systemctl edit my-app.service

次に、ローカルのオーバーライドのみを追加します。

[Service]
EnvironmentFile=/etc/my-app/my-app.env

3 番目の間違いは、サービスがターミナルで表示されるのと同じシェル環境を認識すると想定することです。通常は認識しません。インタラクティブシェルには、.bashrc.profile、SSH セッション、またはデプロイツールからの変数がある場合があります。システムサービスは、systemd の管理環境から開始されます。アプリが PATHJAVA_HOMENODE_ENVLD_LIBRARY_PATH、または同様の値を必要とする場合は、明示的に定義するか、絶対パスを使用してください。

たとえば、これは脆弱です。

ExecStart=npm start

これは推論が容易です。

WorkingDirectory=/opt/my-app
Environment="NODE_ENV=production"
ExecStart=/usr/bin/npm start

4 番目の間違いは、環境ファイルをサービスユーザーが書き込み可能にすることです。独自の環境ファイルを上書きできる Web アプリは、通常のアプリケーションバグを永続性の問題に変える可能性があります。多くの設定では、サービスユーザーはアプリケーションデータを読み取り、ログまたはアップロードを書き込む必要がありますが、サービスの開始に使用される認証情報を書き換えることはできないはずです。

環境変数が適切なツールではない場合

環境変数はシンプルであるため人気がありますが、常に最適なインターフェースであるとは限りません。値が大きい、構造化されている、頻繁にローテーションされる、または複数のサービスで共有される場合、実際の設定ファイルまたはシークレットストアの方が通常は管理が容易です。

データベース URL は妥当な環境変数です。

DATABASE_URL=postgresql://[email protected]:5432/app

完全な JSON サービスアカウントドキュメントは、扱いにくくなります。引用符が扱いにくくなり、偶発的な改行が障害を引き起こし、デバッグ中にログに貼り付ける可能性が高くなります。その場合は、JSON を保護されたファイルに保存し、ファイルパスを渡します。

GOOGLE_APPLICATION_CREDENTIALS=/etc/my-app/google-service-account.json

次に、JSON ファイルを個別に保護します。

sudo chown root:my-app /etc/my-app/google-service-account.json
sudo chmod 640 /etc/my-app/google-service-account.json

これにより、シークレットが魔法のように安全になるわけではありません。アプリケーションは依然としてそれを読み取ることができます。root は依然としてそれを読み取ることができます。しかし、複雑なシークレットを systemd の環境パーサーに詰め込むことを回避し、ファイルレベルの監査をより明確にします。

より安全なレビューチェックリスト

環境変数を使用するサービスを再起動する前に、4 つのことを確認してください。

systemctl cat my-app.service
sudo ls -l /etc/my-app/my-app.env
sudo systemd-analyze verify /etc/systemd/system/my-app.service
sudo systemctl daemon-reload

systemctl cat は、どのドロップインがアクティブかを確認します。ls -l は、権限が意図したものであることを確認します。systemd-analyze verify は、再起動する前にいくつかのユニット構文の問題をキャッチできます。アプリケーション固有のすべての設定を検証するわけではありませんが、それでも有用なガードレールです。

再起動後、ジャーナルで起動エラーを確認します。

sudo systemctl restart my-app.service
sudo journalctl -u my-app.service -n 100 --no-pager

変数がロードされたことを確認する必要がある場合は、慎重にクエリを実行し、共有する前に出力を編集してください。機密性の高いサービスの場合は、最初に APP_ENVLOG_LEVEL などの非シークレット変数を確認することをお勧めします。それが同じファイルからロードされた場合、ファイルパスとパーサー構文はおそらく正しく、シークレットを含む値を出力する必要はないかもしれません。

最後に実用的なポイント: 必要になる前にローテーションを計画してください。パスワードまたはトークンが環境ファイルに保存されている場合は、値が変更された後にどのサービスを再起動する必要があるか、およびその再起動がダウンタイムを引き起こすかどうかを書き留めてください。設定は簡単でもローテーションが難しい認証情報は、最終的にインシデントになります。小規模なサービスの場合、ローテーションのランブックは 4 行だけかもしれません。

sudoedit /etc/my-app/my-app.env
sudo systemctl restart my-app.service
sudo systemctl status my-app.service
sudo journalctl -u my-app.service -n 50 --no-pager

誰もが影響範囲を知っていれば、これで十分です。大規模なシステムの場合は、ローテーション中に重複できる認証情報を優先して、新しい値をデプロイし、確認し、古い値を削除して、急いでダウンタイムウィンドウを設ける必要がないようにします。