電通総研 テックブログ

電通総研が運営する技術ブログ

SOPSで暗号化した .env をGit管理し、チーム開発の環境変数管理を標準化する

こんにちは。クロスイノベーション本部 AIデータテクノロジーユニット AIトランスフォーメーションセンターの青木 尚人です。

本記事では、SOPS を利用してチーム開発の環境変数管理を標準化する方法を紹介します。

はじめに

チーム開発で .env を使っていると、次のような運用になりがちです。

  • .env の実際の値を Slack や Teams で共有する
  • 新規メンバーが入るたびに、誰かが .env を手作業で渡す
  • .env.example はあるが、実際の値とはずれている
  • どの値が最新なのかわからない
  • 秘密情報とそうでない値が混ざっている
  • .env を誤って Git にコミットしてしまう

そこで今回は、SOPS + Azure Key Vault + mise を使って、暗号化された .env を Git 管理できるようにします。

この記事で紹介するのは、次のような手順での環境変数の管理方法です。

  • SOPS で .env 形式のファイルを暗号化する
  • Azure Key Vault の Key を SOPS の暗号化・復号に使う
  • mise で sopsazure-cli を導入する
  • mise task で .env の復号・生成コマンドを標準化する

この記事の前半では、まず SOPS を利用して暗号化された環境変数を作成します。
その後、mise task を使って .env の生成手順を標準化します。

本記事で紹介するプロジェクトで想定している最小の構成は以下です。

Existing Git Repository
├── .env.sops.env    # SOPSで暗号化された.env。Git管理する
├── .env             # 復号して生成する.env。Git管理しない
├── .sops.yaml       # SOPSの暗号化設定。Git管理する
└── mise.toml        # ツールとタスク定義。Git管理する

Azure Key Vault
└── Key Vault Key    # SOPSの暗号化・復号に使う鍵

SOPS とは

SOPS は、YAML、JSON、ENV、INI などのファイルを暗号化して管理するためのツールです。

一般的な暗号化ツールと違い、ファイル全体を単純にバイナリ化するのではなく、設定ファイルとして扱いやすい形で暗号化できます。
例えば .env 形式のファイルであれば、暗号化後もキー名は読める状態にしつつ、値だけを暗号化できます。

似た選択肢としては、Azure Key Vault Secret に環境変数を1つずつ保存する方法、.env.example で項目だけ共有する方法、.env ファイルの暗号化に特化したツールとして dotenvx もあります。
dotenvx は、既存の .env 運用に近い形で暗号化された .env ファイルを扱えるため、.env を中心にシンプルに管理したい場合は有力な選択肢です。

一方、今回の構成では Azure Key Vault の Key を使って復号権限を Azure 側で制御したかったため、SOPS を採用しています。
SOPS は .env だけでなく YAML、JSON、INI など複数の設定ファイル形式を扱えるため、環境変数以外の設定ファイル管理にも広げやすい点もメリットです。

暗号化前の .env が次のような内容だったとします。

DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db
API_TOKEN=dummy-api-token
JWT_SECRET=dummy-jwt-secret

SOPS で暗号化すると、次のようなファイルになります。

DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]

この状態なら、暗号化された .env ファイルを Git 管理できます。
値は暗号化されているため、リポジトリに置いても平文の secret は見えません。

Azure Key Vault は何に使うのか

今回、Azure Key Vault は、環境変数そのものの保存先ではなく、SOPS がデータキーを保護するための Key Vault Key の管理基盤として使います。

Azure Key Vault では、主に次の3種類のオブジェクトを管理できます。

  • Key
  • Secret
  • Certificate

このうち、今回使うのは Secret ではなく Key です。
環境変数を Azure Key Vault Secret に1つずつ保存する構成ではありません。
SOPS が暗号化・復号に使う鍵を、Azure Key Vault Key として管理します。

Azure Key Vault
└── Key
    └── SOPSが.env.sops.envを暗号化・復号するために使う

SOPS は、ファイル本体の値を暗号化し、その暗号化・復号に必要な情報をファイル内に保持します。
ただし、その復号には Azure Key Vault Key へのアクセス権が必要です。

そのため、次のような管理ができます。

  • Git には暗号化済みの .env.sops.env を配置する
  • 復号できる人は Azure Key Vault の権限で制御する
  • チャットで .env の平文を共有しない
  • メンバー追加・削除時は Azure 側の権限を見直す

ここが、この構成の重要なポイントです。

mise とは

mise は、プロジェクトで使う CLI ツールのバージョン管理や、タスク定義をまとめて扱えるツールです。

SOPS と Azure CLI を各メンバーが個別にインストールしても、この構成は実現できます。
ただし、それだと次の問題が残ります。

  • SOPS がインストールされていない
  • Azure CLI がインストールされていない
  • メンバーごとにバージョンが異なる
  • .env を生成するコマンドを毎回説明する必要がある

mise を使うと、プロジェクトに必要な CLI ツールとタスクを mise.toml にまとめられます。

[tools]
sops = "3.12.2"
azure-cli = "2.84.0"

これをプロジェクトに置いておけば、メンバーは次のコマンドで必要なツールをそろえられます。

mise install

さらに、.env を生成する処理も mise task にできます。

mise run env:render

つまり mise を使う理由は、単にツールを入れたいからではありません。
チーム全員が同じコマンドで、同じ手順を実行できるようにするためです。

ここからは、mise のインストール手順から、Azure Key Vault と SOPS を使った環境変数の管理手順までを順に紹介します。

mise をインストールする

macOS では Homebrew でインストールできます。

brew install mise

zsh を使っている場合は、シェルに mise を有効化する設定を追加します。

echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

インストールできたか確認します。

mise --version

macOS 以外のインストール方法は、公式ドキュメントを参照してください。

https://mise.jdx.dev/getting-started.html

mise.tomlsopsazure-cli を追加する

既存プロジェクトのルートに mise.toml を用意します。
すでに mise.toml がある場合は、既存の [tools] に追記してください。

[tools]
sops = "3.12.2"
azure-cli = "2.84.0"

コマンドで追加する場合は、以下のようにします。

mise use sops@3.12.2
mise use azure-cli@2.84.0

その後、ツールをインストールします。

mise install

確認します。

sops --version
az version

Azure にログインする

SOPS が Azure Key Vault を使って復号するには、ローカル端末が Azure に認証済みである必要があります。

まず Azure にログインします。

az login

現在のサブスクリプションを確認します。

az account show -o table

必要であれば、利用するサブスクリプションに切り替えます。

az account set --subscription "<subscription-id-or-name>"

Azure Key Vault と Key を作成する

すでにチームで利用している Key Vault がある場合は、既存の Key Vault に SOPS 用の Key を追加しても構いません。

Key Vault の作成

Azure Portal から操作する場合は以下のようになります。

トップ画面からキーコンテナーを選択します。

作成ボタンを押下して、キーコンテナーを作成します。

基本タブでリソースグループとリージョンを選択します。

アクセス制御タブでは「Azureロールベースのアクセス制御(RBAC)」を選択してください。

ネットワークタブでは必要に応じてアクセス制御を設定します。
ここではデフォルトの「すべてのネットワークからのアクセスを許可する」を選択します。

※実運用では要件に合わせて制限してください。特に本番 secret に関わる Key Vault では、ネットワーク制限を含めた設計が必要です。

最後に確認タブで設定を確認し、作成ボタンを押下してキーコンテナーを作成します。

Key Vault のアクセス制御で権限を付与する

次に、Key Vault の Key を使えるように、アクセス制御で権限を付与します。

アクセス制御(IAM)タブを開き、ロールの割り当てを追加します。

Key Vault の Key を作成・編集する管理者には「キー コンテナー暗号化責任者」を割り当てます。
一方で、一般的な開発メンバーが環境変数の暗号化・復号に利用するだけであれば、「キー コンテナー暗号化ユーザー」を割り当てるのが適切です。

ここでは、Key を作成するために「キー コンテナー暗号化責任者」のロールを自身に割り当てます。

SOPS 用の Key の作成

[オブジェクト > キー] のタブから「+ 生成/インポート」を押下します。

名前や RSA キーサイズを選択して、環境変数を暗号化する SOPS 用の Key を作成します。

作成した Key の ID を取得します。

出力例は以下のとおりです。

https://kv-sops-xxxx.vault.azure.net/keys/sops-env-key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

この Key ID を SOPS の設定で使います。

.sops.yaml を追加する

プロジェクトルートに .sops.yaml を追加します。

creation_rules:
  - path_regex: \.env\.sops\.env$
    azure_keyvault:
      - https://kv-sops-xxxx.vault.azure.net/keys/sops-env-key/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

この設定により、.env.sops.env という名前のファイルを SOPS で作成するときに、Azure Key Vault の Key が使われます。

暗号化前の .env を用意する

ここでは、既存の .env から secret を含む値を暗号化する想定で進めます。
記事用の例ではダミー値を使います。

.env.plain という名前で、次のような内容を用意します。

DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db
API_TOKEN=dummy-api-token
JWT_SECRET=dummy-jwt-secret

SOPS で .env を暗号化する

.env.plain を SOPS で暗号化し、.env.sops.env を作成します。

sops encrypt \
  --filename-override .env.sops.env \
  --input-type dotenv \
  --output-type dotenv \
  .env.plain > .env.sops.env

暗号化後のファイルを確認します。

cat .env.sops.env

次のように値が ENC[...] 形式になっていれば成功です。

DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]

暗号化前の一時ファイルは削除します。

rm .env.plain

この時点で、Git 管理する対象は .env.sops.env です。
平文の .env.env.plain ではありません。

復号して .env を生成する

復号できるか確認します。

sops decrypt .env.sops.env

以下のような出力が得られれば成功です。

DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db
API_TOKEN=dummy-api-token
JWT_SECRET=dummy-jwt-secret

問題なければ、.env に出力します。

sops decrypt .env.sops.env > .env
chmod 600 .env

これでアプリケーションが読む .env が生成されます。

cat .env

出力例は以下のとおりです。

DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db
API_TOKEN=dummy-api-token
JWT_SECRET=dummy-jwt-secret

mise task で .env 生成を標準化する

毎回 sops decrypt .env.sops.env > .env と入力するのは手間です。
そこで、mise.toml にタスクを追加します。

[tasks."env:render"]
description = "Generate .env from encrypted env file"
run = '''
set -euo pipefail
sops decrypt .env.sops.env > .env
chmod 600 .env
echo "generated: .env"
'''

[tasks."env:decrypt"]
description = "Print decrypted env to stdout"
run = "sops decrypt .env.sops.env"

これで、開発者は次のコマンドだけで .env を生成できます。

mise run env:render

以下のような出力が得られれば成功です。

% mise run env:render
[env:render] $ sops decrypt .env.sops.env > .env
generated: .env

復号結果を標準出力で確認したい場合は、次を使います。

mise run env:decrypt

以下のような結果が得られれば成功です。

% mise run env:decrypt
[env:decrypt] $ sops decrypt .env.sops.env
DATABASE_URL=postgresql://demo_user:demo_password@localhost:5432/demo_db
API_TOKEN=dummy-api-token
JWT_SECRET=dummy-jwt-secret

VSCode で暗号化された .env を編集する

暗号化済みファイルを編集する場合は、sops edit を使います。
VSCode で編集する場合は、次のようにします。

SOPS_EDITOR='code --wait' sops edit .env.sops.env

実行すると /var/folders/s7/86599dyd1g5clgw4f7l3d2840000gn/T/3982786129/.env.sops.env のような一時ファイルが VSCode で開かれます。
適宜編集して保存した後、ファイルを閉じると .env.sops.env が再暗号化されます。

これも mise task にしておくと便利です。

[tasks."env:edit"]
description = "Edit encrypted env file with VSCode"
run = "SOPS_EDITOR='code --wait' sops edit .env.sops.env"

実行します。

mise run env:edit

EMBEDDING_API_TOKEN などの値を適宜書き換えて保存し、VSCode を閉じます。
.env.sops.env が再び暗号化された状態で保存されれば成功です。

DATABASE_URL=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
EMBEDDING_API_TOKEN=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
JWT_SECRET=ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]

最終的な mise.toml

最小構成の mise.toml は次のようになります。

[tools]
sops = "3.12.2"
azure-cli = "2.84.0"

[tasks."env:render"]
description = "Generate .env from encrypted env file"
run = '''
set -euo pipefail
sops decrypt .env.sops.env > .env
chmod 600 .env
echo "generated: .env"
'''

[tasks."env:decrypt"]
description = "Print decrypted env to stdout"
run = "sops decrypt .env.sops.env"

[tasks."env:edit"]
description = "Edit encrypted env file with VSCode"
run = "SOPS_EDITOR='code --wait' sops edit .env.sops.env"

このファイルをプロジェクトに置いておけば、開発者が覚えるコマンドは少なくなります。

mise install
mise run env:render

新規メンバーが入ったときの手順

新規メンバーが入ったときは、次の手順でセットアップできます。

git clone <repository-url>
cd <repository-name>
mise install
az login
mise run env:render

もちろん、「キー コンテナー暗号化ユーザー」などの Key Vault 権限は事前に必要です。

発展:shared / secret / local に分ける

実際のチーム運用では、すべての値を1つの .env.sops.env に入れるより、値の性質ごとに分けたほうが扱いやすい場合があります。

例えば、次のように分けます。

.env.shared              # チームで共有してよい非秘密情報
.env.secret.sops.env     # SOPSで暗号化した秘密情報
.env.local               # 個人用の上書き設定
.env                     # 最終的に生成されるファイル

この場合の考え方は次の通りです。

ファイル Git管理 用途
.env.shared する チームで共有してよい非秘密情報
.env.secret.sops.env する SOPSで暗号化した秘密情報
.env.local しない 個人のローカル上書き設定
.env しない アプリケーションが読む生成物

この構成にすると、非機密情報の差分が読みやすくなります。
一方で、ファイルが増えるため、最小構成よりは運用ルールが必要になります。

例えば、mise task は次のようになります。

[tasks."env:render"]
description = "Generate .env from shared, encrypted secret, and local env files"
run = '''
set -euo pipefail

tmp_secret="$(mktemp)"

cleanup() {
  rm -f "$tmp_secret"
}
trap cleanup EXIT

sops decrypt .env.secret.sops.env > "$tmp_secret"

{
  cat .env.shared
  printf "\n"
  cat "$tmp_secret"

  if [ -f .env.local ]; then
    printf "\n"
    cat .env.local
  fi
} > .env

chmod 600 .env
echo "generated: .env"
'''

まとめ

SOPS を使うと、.env 形式のファイルを暗号化して Git 管理できます。
Azure Key Vault を組み合わせることで、復号できる人を Azure 側の権限で制御できます。
さらに mise を使うことで、SOPS と Azure CLI の導入、そして .env の生成コマンドをチームでそろえられます。

最小構成は次の通りです。

.env.sops.env  # 暗号化された.env。Git管理する
.env           # 復号して生成する.env。Git管理しない
.sops.yaml     # SOPS設定
mise.toml      # ツールとタスク定義

開発者が実行するコマンドは、最終的にはこれだけにできます。

az login
mise install
mise run env:render

環境変数の管理はチーム開発において重要な課題です。
いざ開発に入ると、.env の値をチャットで共有してしまったり、誰が最新の値を持っているのかわからなくなったりします。
今回の構成を使うことで、Git 管理と Azure Key Vault の権限管理を組み合わせながら、チームで同じ手順で .env を生成できるようになります。

参考資料

執筆:@aoki.naoto
レビュー:@yamada.y
Shodoで執筆されました