Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion containers/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ RUN set -eux; \
curl -LsSf https://astral.sh/uv/install.sh | sh; \
# npm グローバルパッケージ(umask 0002 で npm グループにも書き込み権を残す)
umask 0002; \
npm i -g yarn @playwright/test aws-cdk aws-cdk-lib typescript @google/gemini-cli @openai/codex; \
npm i -g yarn @playwright/test aws-cdk aws-cdk-lib typescript @google/gemini-cli @openai/codex @googleworkspace/cli; \
# root が作ったファイルも ubuntu が上書きできるように揃える。
# install レイヤーと同じ RUN なのでレイヤーの複製は発生しない。
chown -R "$USERNAME":npm "$NPM_CONFIG_PREFIX"; \
Expand Down
124 changes: 90 additions & 34 deletions containers/base/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,91 @@ devbase_setup_ai_settings() {
done
}

# ===================================================================
# PLAN39: GCP の認証モードと gcloud / gws の設定ディレクトリ
# ===================================================================
# 設定ディレクトリはグループボリューム配下 (CLOUDSDK_CONFIG /
# GOOGLE_WORKSPACE_CLI_CONFIG_DIR) をホストから渡される。これにより
# credentials.db / access_tokens.db / application_default_credentials.json と
# gws の credentials.enc / .encryption_key がグループ単位に分かれる。
#
# 変数そのものはホスト側 (生成 compose) が渡す。entrypoint の export は PID 1 の
# 子プロセスにしか効かず、docker exec のシェルには届かないため。ここでは
# **ディレクトリの用意**だけを行う。

# gcloud / gws の設定ディレクトリを用意する (空の named volume は root 所有)。
devbase_setup_cloud_config_dirs() {
local owner="${1:-${USERNAME:-ubuntu}}"
local dir

for dir in "${CLOUDSDK_CONFIG:-}" "${GOOGLE_WORKSPACE_CLI_CONFIG_DIR:-}"; do
[ -n "$dir" ] || continue
devbase_ensure_persistent_root "$dir" "$owner"
done
}

# サービスアカウント鍵を env から書き出す (鍵モードのみ)。
#
# `adc` では鍵を書かず、GOOGLE_APPLICATION_CREDENTIALS / BIGQUERY_KEY_FILE を
# unset する。値だけ残して実体が無いと ADC はユーザー認証へフォールバックせず
# DefaultCredentialsError で落ちる。ただし **unset が効くのは PID 1 の子孫だけ**で、
# docker exec のシェルから消すのはホスト側の役目 (lib/devbase/env/gcp_auth.py)。
# ここでの unset は、ホストが古い場合や env ファイル直書きに対する保険である。
#
# 鍵の出力先 ~/.config/gcloud は CLOUDSDK_CONFIG を向け直した後は
# **gcloud の設定ディレクトリではなく単なる鍵の置き場**であり、コンテナ層に残る。
# したがって鍵は毎起動 env から書き直され、永続領域には残らない。
devbase_setup_gcp_credentials() {
local home_root="${1:-/home/${USERNAME:-ubuntu}}"
local mode="${GCP_AUTH_MODE:-}"
local profile="${GCP_ACTIVE_PROFILE:-default}"
local var="GCP_CREDENTIALS_BASE64__${profile}"
local creds_b64="${!var:-${GOOGLE_APPLICATION_CREDENTIALS_BASE64:-}}"

# 未設定・未知の値は auto 判定 (鍵の env があれば key、無ければ adc)
if [ "$mode" != "key" ] && [ "$mode" != "adc" ]; then
if [ -n "$creds_b64" ]; then
mode="key"
else
mode="adc"
fi
fi

if [ "$mode" = "key" ] && [ -z "$creds_b64" ]; then
echo "Warning: GCP_AUTH_MODE=key ですが ${var} が設定されていません。ADC へ切り替えます"
mode="adc"
fi

if [ "$mode" = "adc" ]; then
unset GOOGLE_APPLICATION_CREDENTIALS
unset BIGQUERY_KEY_FILE
echo "GCP auth mode: adc (サービスアカウント鍵は書き出しません)"
return 0
fi

echo "GCP auth mode: key (profile: ${profile})"
local default_creds_path="${home_root}/.config/gcloud/credentials.json"
local creds_content gac_path bq_path

creds_content=$(printf '%s' "$creds_b64" | base64 -d)

gac_path="${GOOGLE_APPLICATION_CREDENTIALS:-$default_creds_path}"
mkdir -p "$(dirname "$gac_path")"
printf '%s' "$creds_content" > "$gac_path"
chmod 600 "$gac_path"
export GOOGLE_APPLICATION_CREDENTIALS="$gac_path"
echo "Google Cloud credentials saved to: $gac_path"

bq_path="${BIGQUERY_KEY_FILE:-$default_creds_path}"
if [ "$bq_path" != "$gac_path" ]; then
mkdir -p "$(dirname "$bq_path")"
printf '%s' "$creds_content" > "$bq_path"
chmod 600 "$bq_path"
echo "BigQuery key file saved to: $bq_path"
fi
export BIGQUERY_KEY_FILE="$bq_path"
}

# テストは関数定義だけを使う (source 時のみ有効な return で以降を読み飛ばす)。
if [ -n "${DEVBASE_ENTRYPOINT_LIB_ONLY:-}" ]; then
return 0 2>/dev/null || exit 0
Expand All @@ -419,40 +504,11 @@ fi
# Setup authentication credentials from environment variables
USERNAME="${USERNAME:-ubuntu}"

# 1. Setup Google Cloud credentials from base64 encoded environment variable
# New format: GCP_CREDENTIALS_BASE64__{profile} with GCP_ACTIVE_PROFILE
# Legacy format: GOOGLE_APPLICATION_CREDENTIALS_BASE64
_GCP_PROFILE="${GCP_ACTIVE_PROFILE:-default}"
_GCP_VAR="GCP_CREDENTIALS_BASE64__${_GCP_PROFILE}"
_GCP_CREDS_B64="${!_GCP_VAR:-$GOOGLE_APPLICATION_CREDENTIALS_BASE64}"

if [ -n "$_GCP_CREDS_B64" ]; then
echo "Setting up Google Cloud credentials (profile: ${_GCP_PROFILE})..."
DEFAULT_CREDS_PATH="/home/${USERNAME}/.config/gcloud/credentials.json"

# Decode base64 content once
CREDS_CONTENT=$(printf '%s' "$_GCP_CREDS_B64" | base64 -d)

# Output to GOOGLE_APPLICATION_CREDENTIALS path
GAC_PATH="${GOOGLE_APPLICATION_CREDENTIALS:-$DEFAULT_CREDS_PATH}"
GAC_DIR=$(dirname "$GAC_PATH")
mkdir -p "$GAC_DIR"
printf '%s' "$CREDS_CONTENT" > "$GAC_PATH"
chmod 600 "$GAC_PATH"
export GOOGLE_APPLICATION_CREDENTIALS="$GAC_PATH"
echo "Google Cloud credentials saved to: $GAC_PATH"

# Output to BIGQUERY_KEY_FILE path if different
BQ_PATH="${BIGQUERY_KEY_FILE:-$DEFAULT_CREDS_PATH}"
if [ "$BQ_PATH" != "$GAC_PATH" ]; then
BQ_DIR=$(dirname "$BQ_PATH")
mkdir -p "$BQ_DIR"
printf '%s' "$CREDS_CONTENT" > "$BQ_PATH"
chmod 600 "$BQ_PATH"
echo "BigQuery key file saved to: $BQ_PATH"
fi
export BIGQUERY_KEY_FILE="$BQ_PATH"
fi
# 1. Setup Google Cloud credentials / auth mode (PLAN39)
# 設定ディレクトリ (CLOUDSDK_CONFIG / GOOGLE_WORKSPACE_CLI_CONFIG_DIR) と
# 解決済みの GCP_AUTH_MODE はホスト側 (生成 compose) が渡す。
devbase_setup_cloud_config_dirs "$USERNAME"
devbase_setup_gcp_credentials "/home/${USERNAME}"

# 2. Setup Git configuration
if [ -n "$GIT_USER_NAME" ]; then
Expand Down
25 changes: 25 additions & 0 deletions docs/user/container-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,31 @@ AI CLI ツールの設定や認証情報は、コンテナを再生成しても
> **Note:** symlink 対象は entrypoint にビルド時 `COPY` で焼き込まれます。エントリを増減した場合は
> イメージの再ビルドが必要です(`devbase up` 単体では反映されない場合があります。[CLI リファレンス: project グループ](cli-reference/02-project.md#devbase-project-up) の `devbase project up` の注記参照)。

### gcloud / gws の設定はどこにあるか

gcloud と gws は symlink ではなく **環境変数で設定ディレクトリごと差し替え**ています。

| 変数 | 向き先 | 入るもの |
|---|---|---|
| `CLOUDSDK_CONFIG` | `/persistent/group/gcloud` | `credentials.db` / `access_tokens.db` / `legacy_credentials/` / `configurations/` / `application_default_credentials.json`(ADC ファイル) |
| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | `/persistent/group/gws` | `credentials.enc` / `.encryption_key` |

`CLOUDSDK_CONFIG` は gcloud CLI 専用の仕組みではなく `google.auth` の探索経路そのものなので、
BigQuery クライアント等のライブラリも同じ場所を見ます。

> **Warning:** この差し替えにより、`~/.config/gcloud` は **gcloud の設定ディレクトリでは
> なくなりました**。鍵モード(`GCP_AUTH_MODE=key`)で書き出されるサービスアカウント鍵の
> 置き場でしかなく、コンテナ層(揮発)に残ります。したがって鍵は毎起動 `env` から書き直され、
> 永続領域には残りません。設定を見たいときは `$CLOUDSDK_CONFIG` を参照してください。

認証モードの切り替えは [環境変数ガイド](environment-variables.md) の `GCP_AUTH_MODE`、
実際の認証手順は [Google 認証ガイド](google-auth.md) を参照してください。

> **Warning:** gcloud は**並行実行を想定していません**(公式ドキュメント: "Parallel execution of
> multiple gcloud CLI commands is not supported.")。`credentials.db` は SQLite なので、
> 同じアカウントグループの複数コンテナが同時に `gcloud` を叩くと `database is locked` が
> 出ることがあります。恒久対策は取っていないので、その場合は少し待って再実行してください。

## コンテナイメージ階層

devbase のコンテナイメージは用途に応じた階層構造になっています。
Expand Down
53 changes: 51 additions & 2 deletions docs/user/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,64 @@ devbase はホストマシンの認証情報を自動収集し、コンテナ内
| `GCP_ACTIVE_PROFILE` | アクティブなプロファイル名 |
| `GOOGLE_CLOUD_PROJECT` | GCP プロジェクト ID |
| `GOOGLE_CLOUD_LOCATION` | GCP リージョン |
| `GOOGLE_APPLICATION_CREDENTIALS` | サービスアカウントキーのパス |
| `GOOGLE_APPLICATION_CREDENTIALS` | サービスアカウントキーのパス(鍵モードのみ。下記参照) |
| `BIGQUERY_PROJECT` | BigQuery プロジェクト |
| `BIGQUERY_DATASETS` | BigQuery データセット |
| `BIGQUERY_LOCATION` | BigQuery ロケーション |
| `BIGQUERY_KEY_FILE` | BigQuery キーファイルパス |
| `BIGQUERY_KEY_FILE` | BigQuery キーファイルパス(鍵モードのみ。下記参照) |

ソースファイル: `~/gcp-credentials/`
ソースタイプ: `named_profiles`

##### `GCP_AUTH_MODE` -- 認証モードの切り替え

Google はサービスアカウント鍵を非推奨とし、ローカル開発には
`gcloud auth application-default login`(ユーザー認証 = ADC)を推奨しています。
devbase は gcloud の設定ディレクトリをアカウントグループごとに永続化するため、
ADC を既定の経路にできます。鍵が要る場面のために切り替えを残しています。

`GCP_AUTH_MODE` はプロジェクトの `env` かグローバル `env` に手書きします。

| 値 | 挙動 |
|---|---|
| `adc` | 鍵を書かない。`GOOGLE_APPLICATION_CREDENTIALS` と `BIGQUERY_KEY_FILE` を**コンテナへ渡さない**。認証は `$CLOUDSDK_CONFIG/application_default_credentials.json`(= `gcloud auth application-default login` の結果)に委ねる |
| `key` | `GCP_CREDENTIALS_BASE64__<profile>` を復号して書き、上記 2 変数を渡す(従来どおり) |
| 未設定 | 鍵の env があれば `key`、無ければ `adc`(既存プロジェクトは従来どおり動きます) |

鍵の有無は **`GCP_ACTIVE_PROFILE`(未設定なら `default`)のプロファイル** 1 本だけで
判定します(無ければ後方互換の `GOOGLE_APPLICATION_CREDENTIALS_BASE64`)。別プロファイル
の鍵があっても、アクティブなプロファイルの鍵が無ければ `adc` です。`GCP_AUTH_MODE=key` を
明示していても同じで、鍵が無ければ `adc` として構成します(警告を出します)。ホスト側と
コンテナ側で判定が食い違うと、実体の無いパスだけがコンテナへ残るためです。

`adc` で 2 変数を**渡さない**のが要点です。値だけ残して実体が無いと、ADC は
ユーザー認証へフォールバックせず `DefaultCredentialsError` で落ちます。元の
`compose.yml` の `environment:` にパスが直書きされている場合も、`adc` では生成 compose
から取り除きます。

```bash
# ADC を使う(推奨)
echo 'GCP_AUTH_MODE=adc' >> projects/<name>/env
devbase project up <name>
```

切り替えには `devbase up` が必要です(コンテナへ渡す環境変数が変わるため)。
手順の全体は [Google 認証ガイド](google-auth.md) を参照してください。

##### gcloud / gws の設定ディレクトリ

| 変数 | 値 | 意味 |
|---|---|---|
| `CLOUDSDK_CONFIG` | `/persistent/group/gcloud` | gcloud の設定ディレクトリ。`credentials.db` / `access_tokens.db` / `application_default_credentials.json` がここに入る |
| `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` | `/persistent/group/gws` | gws(Google Workspace CLI)の設定ディレクトリ |

いずれも devbase が生成 compose で渡すため、`env` に書く必要はありません。

> **Warning:** `CLOUDSDK_CONFIG` を向け直したあとの `~/.config/gcloud` は
> **gcloud の設定ディレクトリではありません**。鍵モードで書き出される
> サービスアカウント鍵の置き場でしかなく、コンテナ層(揮発)に残ります。
> gcloud の実際の設定を見たいときは `$CLOUDSDK_CONFIG` を参照してください。

#### git -- Git 認証

| キー | 説明 |
Expand Down
30 changes: 25 additions & 5 deletions lib/devbase/env/collectors/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def collect_google_credentials(env_file: EnvFile) -> None:

if not profiles:
existing = env_file.get(keys.gcp_credentials_key("default"))
has_key = bool(existing)
if existing:
logger.info("%s: 設定済み", keys.gcp_credentials_key("default"))
else:
Expand All @@ -83,9 +84,10 @@ def collect_google_credentials(env_file: EnvFile) -> None:
creds_path = Path(creds_path_str).expanduser()
if creds_path.exists():
_register_profile(env_file, 'default', creds_path)
has_key = True
else:
logger.error("ファイルが見つかりません: %s", creds_path)
_collect_common_settings(env_file)
_collect_common_settings(env_file, has_key=has_key)
return

print(f"\n検出されたcredential ({len(profiles)}件):")
Expand All @@ -111,7 +113,7 @@ def collect_google_credentials(env_file: EnvFile) -> None:
env_file.set(keys.BIGQUERY_PROJECT, project_id)
logger.info("%s: %s", keys.GOOGLE_CLOUD_PROJECT, project_id)

_collect_common_settings(env_file)
_collect_common_settings(env_file, has_key=True)


def _register_profile(env_file: EnvFile, name: str, file_path: Path) -> None:
Expand All @@ -125,8 +127,17 @@ def _register_profile(env_file: EnvFile, name: str, file_path: Path) -> None:
logger.error("credentialファイルの処理に失敗: %s", e)


def _collect_common_settings(env_file: EnvFile) -> None:
"""GCP共通設定を収集"""
def _collect_common_settings(env_file: EnvFile, has_key: bool = False) -> None:
"""GCP共通設定を収集

Args:
env_file: 書き込み先
has_key: サービスアカウント鍵を登録したか。鍵モード専用の変数
(``GOOGLE_APPLICATION_CREDENTIALS`` / ``BIGQUERY_KEY_FILE``) は
鍵があるときだけ書く。実体の無いパスが env に残っていると ADC が
ユーザー認証へフォールバックせず ``DefaultCredentialsError`` で
落ちるため (PLAN39 / 前提 10)。
"""
collect_key(env_file, keys.GOOGLE_CLOUD_LOCATION, auto_value="global", mask_after=0,
prompt=f"{keys.GOOGLE_CLOUD_LOCATION} (デフォルト: global): ")

Expand All @@ -136,7 +147,16 @@ def _collect_common_settings(env_file: EnvFile) -> None:
collect_key(env_file, keys.BIGQUERY_LOCATION, auto_value="asia-northeast1", mask_after=0,
prompt=f"{keys.BIGQUERY_LOCATION} (デフォルト: asia-northeast1): ")

# コンテナ内パス(devbaseコンテナイメージの仕様に依存)
if not has_key:
logger.info(
"サービスアカウント鍵が未登録のため %s / %s は設定しません "
"(ADC を使う場合は不要。詳細: docs/user/google-auth.md)",
keys.GOOGLE_APPLICATION_CREDENTIALS, keys.BIGQUERY_KEY_FILE)
return

# コンテナ内パス(devbaseコンテナイメージの仕様に依存)。
# CLOUDSDK_CONFIG を向け直したあとの ~/.config/gcloud は gcloud の設定
# ディレクトリではなく、単なる鍵の置き場になる (PLAN39)。
env_file.set(keys.BIGQUERY_KEY_FILE, "/home/ubuntu/.config/gcloud/credentials.json")
env_file.set(keys.GOOGLE_APPLICATION_CREDENTIALS, "/home/ubuntu/.config/gcloud/credentials.json")

Expand Down
Loading