Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
028ba23
feat: 機密を平文ファイルを介さずコンテナへ渡す経路へ移行する
takemi-ohama Aug 12, 2026
4b48806
fix: env encrypt/decrypt が途中で失敗しても中間状態を残さないようにする
takemi-ohama Aug 12, 2026
e86127b
fix: 部分復号・空行・注入スキップ・インライン env_file の取りこぼしを直す
takemi-ohama Aug 12, 2026
3833287
fix: scale 生成で非機密 environment を残し、機密以外の env_file 欠落を隠さない
takemi-ohama Aug 12, 2026
7b2ab87
fix: 移行の中断条件を厳しくし、compose.yml の書き込みと機密の渡し先を直す
takemi-ohama Aug 12, 2026
08834d8
fix: 機密は「そのサービスが元々参照していた由来」のキーだけに絞って渡す
takemi-ohama Aug 12, 2026
c5df236
fix: env_file ブロック内のコメント行で走査を止めない
takemi-ohama Aug 12, 2026
540003d
fix: env_file の long syntax・クォート付きサービス名・CRLF を取りこぼさない
takemi-ohama Aug 12, 2026
088207f
fix: 単一文字列の env_file を中止せず移行対象に含める
takemi-ohama Aug 12, 2026
0b599c9
fix: 書き換え後の compose.yml を YAML として検証し機密参照の残りを検出する
takemi-ohama Aug 12, 2026
8ddca44
fix: 退避先を排他的に作り、機密は原文のバイト列のまま往復させる
takemi-ohama Aug 12, 2026
9544212
fix: プロジェクト切替で切替元の機密が環境変数に残らないようにする
takemi-ohama Aug 12, 2026
9425648
fix: 注入履歴を対象の環境マッピングごとに持たせる
takemi-ohama Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions bin/devbase
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ env_var_keys() {
# Environment setup
export DOCKER_GID=$( [ "$(uname)" = "Darwin" ] && echo "0" || grep docker /etc/group | cut -d: -f3 )
export COMPOSE_PROJECT_NAME=$(basename "$PWD")
# devbase root の .env (AWS / BigQuery 等の devbase ツール用変数) を読み込む。
# devbase root の非機密設定 (env) を読み込む。
#
# 機密 (認証情報) はここでは読まない。暗号化された機密は Python 本体が必要に
# なった時点で復号し、値を必要とする処理へ環境変数として渡す (plan35 §4.4)。
# シェルから読める場所に機密を置かないことが暗号化の前提なので、ここで
# `${DEVBASE_ROOT}/.env` を source すると意味が無くなる。
#
# project ディレクトリで実行された場合に Laravel ランタイム用 .env を bash で
# source すると、CRLF 改行や `|` / `&` 等の特殊文字を含む値で syntax error に
# なる。compose は同階層の .env を自動で読むため wrapper 側で project .env を
# source する必要は無い。
[ -f "${DEVBASE_ROOT}/.env" ] && set -a && source "${DEVBASE_ROOT}/.env" && set +a
[ -f "${DEVBASE_ROOT}/env" ] && set -a && source "${DEVBASE_ROOT}/env" && set +a

# 呼び出し元 (初期 CWD) の env で定義された変数キーを記録しておく。
# project 切替 (maybe_cd_project) 時に「呼び出し元プロジェクトにしか無い変数」を
Expand All @@ -61,6 +67,15 @@ export DEVBASE_ROOT
# Function definitions
# ===================================================================

# Docker Compose の変数展開が機密を必要とする場合があるため、compose の呼び出しは
# Python 経由で機密を注入して実行する (plan35 §4.4 / §11.2)。復号結果は子プロセスの
# 環境変数としてだけ渡り、ファイルには書き出されない。
compose_with_secrets() {
ensure_uv
PYTHONPATH="${DEVBASE_ROOT}/lib:$PYTHONPATH" \
uv run --project "$DEVBASE_ROOT" python -m devbase.cli env exec -- "$@"
}

cmd_build() {
echo "=== Building devbase images ==="

Expand Down Expand Up @@ -154,7 +169,7 @@ cmd_build() {

echo ""
echo "[2/2] Building project image without cache..."
if docker compose build "${DEV_SERVICE_NAME:-dev}" --no-cache "$@"; then
if compose_with_secrets docker compose build "${DEV_SERVICE_NAME:-dev}" --no-cache "$@"; then
echo ""
echo "✓ All images built successfully"
else
Expand All @@ -176,7 +191,7 @@ cmd_build() {

echo ""
echo "[2/2] Building project image..."
if docker compose build "${DEV_SERVICE_NAME:-dev}" "$@"; then
if compose_with_secrets docker compose build "${DEV_SERVICE_NAME:-dev}" "$@"; then
echo ""
echo "✓ All images built successfully"
else
Expand Down Expand Up @@ -209,7 +224,7 @@ cmd_build() {

echo ""
echo "[2/2] Building project image..."
if docker compose build "${DEV_SERVICE_NAME:-dev}" "$@"; then
if compose_with_secrets docker compose build "${DEV_SERVICE_NAME:-dev}" "$@"; then
echo ""
echo "✓ All images built successfully"
else
Expand Down
72 changes: 72 additions & 0 deletions docs/user/cli-reference/03-env.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,78 @@ devbase env keygen --force

> **鍵のバックアップは必須です。** この鍵を失うと、暗号化した機密は誰にも復号できません(devbase 側にも復旧手段はありません)。生成後に表示される鍵ファイルを、パスワード管理ツールなど端末とは別の場所へ必ず複製してください。鍵は全ワークスペース共通のため、`--force` で作り直すと他のワークスペースで暗号化した機密も復号できなくなります。

## `devbase env encrypt`

平文で保存されている設定を、暗号化ストア (`$DEVBASE_ROOT/secrets/`) へ移します。事前に `devbase env keygen` で鍵を作っておく必要があります。

```
devbase env encrypt [--project NAME]... [--dry-run] [-y|--yes]
```

| オプション | 説明 |
|-----------|------|
| `--project NAME` | 対象を指定プロジェクトだけに絞る(繰り返し指定可)。指定すると共通設定は対象外になります |
| `--dry-run` | 変更内容と構成ファイルの差分を表示するだけで、何も書き換えません |
| `-y`, `--yes` | 確認プロンプトを省略 |

実行すると次の 3 つが行われます。

1. 平文の設定を暗号化して `secrets/` 配下へ保存する
2. **暗号化した内容を読み戻して元と一致することを確認**してから、元の平文を `backups/env-encrypt/<日時>/` へ退避する
3. 各プロジェクトの `compose.yml` から機密ファイルの参照をコメントアウトする(元の行はコメントとして残るため、`decrypt` で復元できます)

```bash
# 何が変わるかを先に確認する
devbase env encrypt --dry-run

# 共通設定とすべてのプロジェクトを暗号化する
devbase env encrypt

# 特定プロジェクトだけを暗号化する
devbase env encrypt --project web
```

> 退避した平文は**自動では消しません**。内容を確認したうえで、案内された `backups/env-encrypt/<日時>/` を削除してください。削除するまでは端末上に平文の認証情報が残ったままです。

> 退避先は毎回新しく作られます。同じ秒に再実行して名前が衝突した場合は `<日時>-2`, `<日時>-3` … と別のディレクトリになり、**過去の退避物を上書きすることはありません**。

## `devbase env decrypt`

暗号化された設定を平文へ戻します。`encrypt` と対になる退避コマンドです。

```
devbase env decrypt [--project NAME]... [--dry-run] [-y|--yes]
```

オプションは `encrypt` と同じです。`compose.yml` のコメントアウトも元に戻るため、暗号化前の状態へそのまま復帰します。機密ファイルは `KEY=VALUE` の一覧へ畳まず原文のバイト列のまま暗号化しているので、コメント・空行・`export KEY=...` 表記・値のクォートもそのまま戻ります。

> 原文が保たれるのは**値を書き換えるまで**です。暗号化した状態で `devbase env set` などを実行すると、内容は `KEY=VALUE` を昇順に並べた書式へ正規化され、コメントは残りません(平文だけを使っていた頃と同じ挙動です)。

```bash
devbase env decrypt --dry-run
devbase env decrypt
```

## `devbase env exec`

復号した機密を環境変数として渡した状態で、任意のコマンドを実行します。値はその子プロセスの環境変数としてのみ渡り、ファイルには書き出されません。

```
devbase env exec -- CMD [ARGS...]
```

起動ラッパーは共通の機密ファイルを読み込まないため、ホスト側で機密を必要とする処理(Docker Compose の変数展開など)はこのコマンドを通します。devbase 自身の `devbase build` も内部でこれを使っています。

```bash
# コンテナに渡る値を確認する
devbase env exec -- printenv ANTHROPIC_API_KEY

# 機密を必要とする compose 操作を手で実行する
devbase env exec -- docker compose config
```

> `devbase env exec -- printenv` のように値を表示するコマンドは、画面共有や端末ログに認証情報がそのまま残ります。実行する場面に注意してください。

## `devbase env export`

複数プロジェクトの `.env` 群を暗号化したまま 1 つのバンドルにまとめて書き出します。
Expand Down
6 changes: 4 additions & 2 deletions docs/user/cli-reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ devbase の全コマンドの構文、オプション、使用例をまとめた
|---------|------|
| [トップレベルコマンド](01-toplevel.md) | `init` / `status` / `bin/rc` |
| [project グループ](02-project.md) | コンテナのライフサイクル管理・一覧(`up` / `down` / `login` / `ps` / `logs` / `scale` / `build` / `rebuild` / `list`)と非推奨の `container` グループ |
| [env グループ](03-env.md) | 環境変数の管理(`init` / `sync` / `list` / `set` / `get` / `delete` / `edit` / `project` / `keygen` / `export` / `import`) |
| [env グループ](03-env.md) | 環境変数の管理(`init` / `sync` / `list` / `set` / `get` / `delete` / `edit` / `project` / `keygen` / `encrypt` / `decrypt` / `exec` / `export` / `import`) |
| [plugin グループ](04-plugin.md) | プラグインの管理(`list` / `install` / `uninstall` / `update` / `info` / `sync` / `migrate` / `repo *`) |
| [snapshot グループ](05-snapshot.md) | スナップショットの管理(`create` / `list` / `restore` / `copy` / `delete` / `rotate`) |

Expand All @@ -26,7 +26,9 @@ graph TD
D --> D3["login [index]"]
D --> D4["build [image] / rebuild [name]"]
D --> D2["list [--no-interactive]"]
E --> E1[init / sync / list / set / get / delete / edit / project / export / import]
E --> E1[init / sync / list / set / get / delete / edit / project]
E --> E2[keygen / encrypt / decrypt / exec]
E --> E3[export / import]
F --> F1[list / install / uninstall / update / info / sync / migrate]
F --> F2[repo add / repo remove / repo list / repo refresh]
G --> G1[create / list / restore / copy / delete / rotate]
Expand Down
12 changes: 12 additions & 0 deletions etc/_devbase
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ _devbase() {
'export:Export .env files as an encrypted bundle (age)'
'import:Import .env bundle (age decrypt + merge)'
'keygen:Generate the devbase age key used by the secret store'
'exec:Run a command with the decrypted secrets in its environment'
'encrypt:Move plaintext settings into the encrypted store'
'decrypt:Move encrypted settings back to plaintext'
)

plugin_subcommands=(
Expand Down Expand Up @@ -288,6 +291,15 @@ _devbase() {
'--backup-dir[Override backup directory]:dir:_files -/' \
'--keep-last[Keep only the last N backup directories]:n:'
;;
exec)
_arguments '*:command:_command_names -e'
;;
encrypt|decrypt)
_arguments \
'*--project[Limit to the specified project (repeatable)]:name:' \
'--dry-run[Show what would change without writing]' \
'--yes[Skip the confirmation prompt]' '-y[Skip the confirmation prompt]'
;;
keygen)
_arguments \
'--force[Overwrite an existing key]' \
Expand Down
2 changes: 1 addition & 1 deletion etc/devbase-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ _devbase_completions() {
# project / container は同じサブコマンド群 (container は非推奨だが補完は維持)。
local project_subcommands="up down ps login logs scale build rebuild list"
local container_subcommands="up down ps login logs scale build rebuild"
local env_subcommands="init sync list set get delete edit project export import keygen"
local env_subcommands="init sync list set get delete edit project export import keygen exec encrypt decrypt"
local plugin_subcommands="list install uninstall update info sync repo"
local repo_subcommands="add remove list refresh"
local snapshot_subcommands="create list restore copy delete rotate"
Expand Down
16 changes: 13 additions & 3 deletions issues/plan35.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ chmod 600 ~/.config/devbase/age/keys.txt
- **コンテナ環境の可視性**: コンテナの詳細情報を参照する権限があれば、注入済みの環境変数は読める。devbase は開発コンテナに Docker の制御ソケットを渡す構成を既定に含むため、**コンテナ内から他コンテナの環境変数も参照できる**
- **構成の展開結果**: 構成の確認コマンドは変数名だけの列挙を実際の値へ解決して表示する
- **利用者権限を得た攻撃者**: 既定の鍵保管では鍵も同時に読める
- **同名キーの由来分離**: 生成する構成はサービスごとに「元々参照していた由来(共通/プロジェクト)のキーだけ」を列挙するが、同じキーが共通機密とプロジェクト機密の両方にある場合、値は devbase 自身の環境変数から解決されるため 1 つ(プロジェクト側が優先)に定まる。結果として共通側だけを参照していたサービスにも合成後の値が渡る。サービスごとに異なる値を渡すには生成ファイルへ値を書き込むしかなく、「生成物に機密の値を残さない」という本方針の前提と矛盾するため受け入れる

実行時の露出を下げる手段(コンテナの秘密情報機能によるファイル渡し、クラウドの一時認証、認証エージェントの転送)は、本方針の範囲外として将来の課題に置く。

Expand All @@ -255,9 +256,18 @@ chmod 600 ~/.config/devbase/age/keys.txt

## 10. 未確認事項・残リスク

- **値を持たない変数名の列挙の挙動**: 実行プロセス側で変数が未設定だった場合に、Docker Compose の版によって警告のみか失敗かが分かれる可能性がある。実装前に対象版で確認する
- **ホスト側処理の機密依存範囲**: 起動ラッパーが読み込んだ環境変数に依存する処理を全件は洗い出せていない。段階 3 の着手時に、機密を必要とする処理の一覧化を先に行う
- **コンテナ台数を増やした構成での上書き順序**: 台数拡張時に生成される構成ファイルと、機密を渡す上書き構成の適用順序は未検証
- ~~**値を持たない変数名の列挙の挙動**~~: 確認済み。Docker Compose v5.1.4 では、実行プロセス側で未設定の変数は失敗ではなく空 (`null`) として扱われ、その変数はコンテナへ渡らない。

```console
$ DEFINED_VAR=hello docker compose config
environment:
DEFINED_VAR: hello
UNDEFINED_VAR: null
```

同時に、構成の確認コマンドが設定済みの値をそのまま表示することも確認できた (§7「守れないもの」に挙げた挙動)。
- ~~**ホスト側処理の機密依存範囲**~~: 洗い出し済み。§11.2 を参照
- ~~**コンテナ台数を増やした構成での上書き順序**~~: 別ファイルの上書きを重ねる方式をやめ、台数拡張時に生成する構成そのものへ変数名の列挙を書き込む方式にした。適用順序の問題自体が発生しない
- **復号の実行回数**: 現在は devbase の実行ごとに設定ファイルを読み込んでいる。復号を毎回行う場合の所要時間は未計測であり、体感が悪ければ実行単位での保持を検討する
- **バックアップ機能との関係**: バックアップ取得がボリューム内の機密を平文で保存するかは未確認

Expand Down
Loading