.npmrc と kubeconfig を、まだ手で渡している ── ファイルごと配る push と、commit の前に止める scan

上原正吉(EarthLink Network Co., Ltd.)。AI会社・AI社員による業務自動化と、人の逐次介入なしに24時間自動開発を回す仕組みを自社で実践しています。開発ノウハウと具体的な設定方法、運用して分かった改善点を公開しています。

.npmrc と kubeconfig を、まだ手で渡している ── ファイルごと配る push と、commit の前に止める scan

.npmrc と kubeconfig を、まだ手で渡している ── ファイルごと配る push と、commit の前に止める scan

この記事は、連載「dotvault の使い方 ── .env を手で配るのをやめ、CI・チーム・AI エージェントに 1 コマンドで配るまで」の第 5 回です。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。

新しい人が入った日に、Slack の DM で .npmrc を送る。社内の npm registry のトークンが入ったファイルです。.env は dotvault のような置き場に移しても、.npmrc は手で渡したまま、ということはよくあります。kubeconfig も同じです。クラスタに入るための認証情報が、誰かのノート PC の ~/.kube/config から、別の誰かの ~/.kube/config へ、コピーで渡っていきます。

「.env さえ管理すれば済む」と考えがちですが、key と値に分かれないファイルが、.env と同じくらいあります。そして、そういうファイルほど、うっかり commit に入ります。

この回で分かるのは、.npmrc や kubeconfig も 1 コマンドで預けて、受け取る側は .env と同じ pull か run で受け取れる、ということです。扱うのは、ファイルをそのまま配る dotvault push と、commit する前に secret の混入を見つける dotvault scan です。2 回目で作った demo-app に .npmrc と kubeconfig を push し、別のマシンで pull と run で受け取り、最後に、3 つの secret を含んだ commit を scan が止めるところまで進めます。

結論

  • key と値に分かれないファイルは dotvault push <ファイル> で登録します。 .npmrc、kubeconfig、YAML の設定のような、中身を丸ごと持ちたいテキストのファイルが対象です。4 回目の import や Upload タブは、ファイルの中の KEY=VALUE を 1 つずつ登録するもので、別の仕組みです。
  • 置き先と、環境変数での指し方を一緒に登録できます。 --as .kube/config で受け取る側の置き先を、--pointer KUBECONFIG で run の時に一時パスを入れる環境変数を指定します。.npmrc は何も指定しなくても NPM_CONFIG_USERCONFIG が付きました。
  • 受け取る側は dotvault pull か dotvault run です。 pull は .env と一緒に、push したファイルも置き先に書きます。run は一時ディレクトリに書いて環境変数を設定し、コマンドが終わると消します。pull --file は .env.local のような key のファイルを 1 つ受け取る指定です。
  • 画面の Files タブは、一覧・別の環境へのコピー・削除だけです。 追加とダウンロードは CLI からです。
  • dotvault scan . は、login しなくても動きます。 AWS のアクセスキー、GitHub のトークン、dotvault のトークンのような、形の決まった secret を探し、見つかれば終了コード 1 で止まります。.git/hooks/pre-commit から dotvault scan . を呼ぶと、commit が止まります。
  • scan は .env の中身の守りではありません。 素の .env の SMTP_PASSWORD=… は拾いません。.env を git に入れないのは .gitignore の仕事で、scan はコードや設定に紛れ込んだトークンを見るものです。

本文(読了 約13分)

前提と注意

  • 読者。 4 回目の「値を間違えて上書きしてしまった ── key を書く・取り出す・前の値に戻す」で、demo-app の development に .env(5 key)と .env.local(1 key)がある状態から続けます。4 回目を読んでいなくても、画面の流れは追えます。
  • 環境。 画面は、本番の app.dotvault.io と、公開されている CLI の 0.15.7 で確かめました。端末は 2 回目と同じ Node 20 の Linux コンテナです。「別のマシン」は、同じコンテナの別のディレクトリ(project-config.json だけを置いた machine-b)で代用しています。
  • 端末の画面は再構成です。 端末に出た文字をそのまま写し、見やすい形に組み直しています。生の出力はそのまま保存しています。
  • 画面は英語です。 管理コンソールの表示言語は、2 回目の付録の手順で日本語にできます。
  • デモのアカウントです。 project 名、ファイルの中身、トークンの文字列は、この連載のために作ったデモのものです。ghp_demo… や AKIAIOSFODNN7EXAMPLE は、どこにも使えない文字列です。
  • 用語。 「key」は DATABASE_URL のような名前と値の組、「ファイル」は push で丸ごと登録したものを指します。「pointer」は、run の時にファイルの一時パスを入れる環境変数の名前です。「終了コード」は、コマンドが終わる時に返す数で、0 が成功、それ以外が失敗です。

1. key とファイルの違い

dotvault には、置き方が 2 つあります。

keyファイル
単位名前と値の組(DATABASE_URL=…)ファイルの中身を丸ごと
登録画面の + Add key / Upload、CLI の set / importCLI の push だけ
受け取りpull(.env などに書く)/ run(環境変数に入れる)pull(置き先に書く)/ run(一時ディレクトリに書いて pointer を設定)
画面Keys タブ(見る・書く・消す)Files タブ(一覧・別の環境へコピー・消す。追加とダウンロードは不可)
変更の履歴versions で一覧、rollback で前の値に戻すpush のたびに変更の回数が 1 つ増える(画面の VERSION 列)
向くもの.env、.env.local、1 行ずつ意味のある設定.npmrc、kubeconfig、YAML の設定(テキストのファイル)

.npmrc は KEY=VALUE の形をしていますが、//npm.pkg.github.com/:_authToken=… のような行は key の名前として扱いにくく、ファイルごと置く方が素直です。

2. ファイルを push する

demo-app のディレクトリで、.npmrc と ~/.kube/config を push します。

dotvault push の端末画面(再構成)。cat .npmrc で registry・@acme:registry・_authToken の 3 行、dotvault push .npmrc で「pushed .npmrc (v1) → .npmrc [run sets NPM_CONFIG_USERCONFIG] (project=demo-app, env=development)」、dotvault push ~/.kube/config --as .kube/config --pointer KUBECONFIG で「pushed .kube/config (v1) → .kube/config [run sets KUBECONFIG]」、dotvault files で .kube/config と .npmrc の 2 行(括弧に pointer の名前)。

出力の読み方です。pushed .npmrc (v1) → .npmrc は、「.npmrc という名前で v1 を登録し、受け取る側では .npmrc に書く」という意味です。[run sets NPM_CONFIG_USERCONFIG] は、run の時に NPM_CONFIG_USERCONFIG という環境変数に一時パスを入れる、という意味です。.npmrc には何も指定していません。よく使うファイルは、pointer が最初から決まっています。

kubeconfig は、置き先と pointer を自分で指定しました。--as .kube/config で、受け取る側の置き先を project のディレクトリの中の .kube/config に、--pointer KUBECONFIG で、run の時に KUBECONFIG を設定するようにしています。~/.kube/config のように project の外にあるファイルは、--as で置き先を、--pointer で環境変数を、こうして明示しておきます。

dotvault files で、この環境に登録されたファイルの一覧が出ます。「名前 → 置き先 (pointer)」の順です。同じファイルをもう一度 push すると v2 になります。

3. 画面の Files タブ

管理コンソールの project に Files タブがあります。push する前は空で、「Add one from the CLI with dotvault push」とだけ出ます。

demo-app の Files タブの空の状態(英語 UI)。環境の切り替え(development が選ばれている)、右に「Copy all files in this env…」のボタン、本文に「No files in this env yet. Add one from the CLI with dotvault push」。

ファイルの追加とダウンロードは CLI から行います(追加は push、受け取りは pull か run)。push した後に開くと、一覧になります。

push 後の Files タブ(英語 UI)。development (3) が選ばれ、FILE PATH・VERSION・UPDATED の表に .kube/config(1)・.npmrc(2)・cert.der(1)の 3 行。各行の右に Copy… と Delete のボタン。

表の cert.der は、9 節で push が text 専用であることを確かめるために入れたもので、この後で消します。.npmrc の VERSION が 2 なのは、撮影の途中で 1 行足してもう一度 push したからです。

「Copy all files in this env…」を押すと、別の環境へまとめてコピーする画面が出ます。

Copy all files in development… の画面(英語 UI)。Copy to の選択(staging)、「Overwrite files that already exist in staging」のチェック(外れている)、Cancel と Copy のボタン。

3 回目の Compare envs と同じ考え方です。コピー先に同じ名前のファイルがある時は、チェックを入れない限り上書きしません。この回では Cancel で閉じています(staging に何も作りたくないためです)。

行の Delete を押すと、ブラウザの確認(「Delete development/cert.der? This cannot be undone.」)が出て、OK で消えます。消した後は「Deleted development/cert.der.」の 1 行が出て、一覧が 2 行になります。

cert.der を消した後の Files タブ(英語 UI)。development (2) が選ばれ、表に .kube/config(1)と .npmrc(2)の 2 行。

4. 別のマシンで pull する

受け取る側です。project-config.json だけを置いた空のディレクトリで、dotvault pull を実行します。

別のディレクトリで dotvault pull した端末画面(再構成)。mkdir -p .kube の後の ls -la は .kube と project-config.json、dotvault pull で .env(5 keys)と .env.local(1 keys)の 2 行と「2 files synced, 0 excluded, 6 keys total」、ls -la で .env・.env.local・.npmrc が増え .npmrc は -rw-------、ls -la .kube で .kube の中に書かれた config、head -3 .kube/config で apiVersion: v1 / kind: Config / clusters:。

pull の出力に出るのは .env と .env.local の 2 行だけですが、ls -la を見ると .npmrc と .kube/config も書かれています。push したファイルは、pull のたびに、置き先に一緒に書かれます。新しく書かれたファイルのパーミッションは .env と同じ -rw-------(本人だけ読み書き)です。

最初の mkdir -p .kube が要る理由は、9 節で説明します。置き先のディレクトリが無いと、pull はファイルを書かずに、同じ出力を返します。

5. run で一時ディレクトリに書く

pull でファイルを置くと、そのマシンにファイルが残ります。CI や、短い作業なら、run の方が向いています。

dotvault run の端末画面(再構成)。dotvault run -- sh -c '…' で KUBECONFIG=/tmp の下の一時パスの config と、そのパスの ls -la(-rw-------・279 bytes)と head -3(apiVersion: v1 / kind: Config / clusters:)、終了後に同じパスを ls -la すると No such file or directory、2 つ目の run で NPM_CONFIG_USERCONFIG=/tmp の下の一時パスの .npmrc と npm config get @acme:registry で https://npm.pkg.github.com/。

run は、pointer のあるファイルを /tmp の下の一時ディレクトリに書き、pointer の環境変数にそのパスを入れて、コマンドを実行します。コマンドが終わると、一時ディレクトリごと消えます。2 つ目の ls -la が「No such file or directory」なのは、そのためです。

.npmrc の方は、npm config get @acme:registry が https://npm.pkg.github.com/ を返しています。push した .npmrc の 2 行目が、NPM_CONFIG_USERCONFIG 経由で npm に読まれています。project のディレクトリには .npmrc を置いていません。

pointer が無いファイル(9 節の cert.der のような、--pointer も既定の pointer も無いもの)は、run の間だけ置き先に書かれ、終わると消されます。手元に残したいファイルは、pull で受け取ります。

6. scan で secret の混入を見つける

ここからは、配る話ではなく、漏らさない話です。dotvault scan <パス> は、指定したファイルとディレクトリを読み、形の決まった secret を探します。サーバーには何も送りません。login していなくても動きます。撮影の最後に logout して(whoami が「Not logged in.」になる状態で)、secret を直した後のリポジトリに実行しても、login を求められずに「No secrets found.」が返りました。

試しに、3 つの secret を含むリポジトリを作りました。src/s3.js に AWS のアクセスキー、deploy.sh に GitHub のトークン、.github/workflows/ci.yml に dotvault のサービストークンです。node_modules の中にも、同じ AWS のキーを置いてあります。

dotvault scan の端末画面(再構成)。find . -type f | sort で 5 ファイル(node_modules/some-dep/index.js を含む)、dotvault scan . で .github/workflows/ci.yml:2 [dotvault-service-token]・deploy.sh:2 [github-pat]・src/s3.js:3 [aws-access-key-id] の 3 行(値は est_...ue (72 chars) のように先頭 4 文字と末尾 2 文字だけ)と「3 potential secret(s) found.」、echo $? で 1、dotvault scan src/ で 1 件、machine-b での dotvault scan . で「No secrets found.」と 0。

出力は「ファイル:行 [ルール名] 説明 — 値の一部」の形です。値は、先頭 4 文字と末尾 2 文字と文字数だけが出ます。端末の画面や CI のログに、secret そのものを出さないためです。node_modules の中のキーは出ていません。読まないディレクトリが決まっています(8 節)。

見つかると終了コードが 1、見つからなければ No secrets found. で 0 です。パスを複数渡せます(scan src/ tests/)。

7. commit の前に止める

git は、commit の直前に .git/hooks/pre-commit というファイルがあれば実行し、終了コードが 0 でなければ commit をやめます。scan は見つかると 1 を返すので、そこに置くだけで止まります。

pre-commit フックで commit が止まる端末画面(再構成)。cat .git/hooks/pre-commit で #!/bin/sh と exec dotvault scan . の 2 行、git add src deploy.sh .github .gitignore README.md && git commit で 6 節と同じ 3 件の検出と「3 potential secret(s) found.」、git log --oneline で「fatal: your current branch 'main' does not have any commits yet」、3 つの値を環境変数に置き換えてもう一度 git commit すると「No secrets found.」と「[main (root-commit) f2748e0] add deploy script」「5 files changed, 11 insertions(+)」。

フックの中身は 2 行です。

#!/bin/sh
exec dotvault scan .

1 回目の commit は、3 件を出して止まります。git log は「commits yet」で、何も記録されていません。3 つの値を process.env.AWS_ACCESS_KEY_ID や ${{ secrets.DOTVAULT_TOKEN }} に置き換えて、もう一度 commit すると通ります。

このフックは、ステージした差分ではなく、作業ツリー全体を読みます。.gitignore で外した .env も読むので、手元の .env に Stripe の本番 key のような形の決まった値があると、commit のたびに止まります。その時と、大きなリポジトリでは、exec dotvault scan src/ .github/ のように .env を含まない範囲に絞ります。チームで共有するなら、.git/hooks ではなく、lefthook や husky のようなフックの管理ツールの設定に同じ 1 行を書きます。

8. scan が見ているもの・見ていないもの

scan の検出は、形の決まった文字列との照合です。0.15.7 の CLI に入っている規則は 10 個です。

ルール名探すもの
aws-access-key-idAKIA + 16 文字
aws-secret-access-keyaws の近くに引用符で囲んだ 40 文字
private-key-----BEGIN PRIVATE KEY-----(RSA / EC / OPENSSH / DSA / PGP 付きも。ENCRYPTED PRIVATE KEY は対象外)
github-patghp_ + 36 文字
github-fine-grained-patgithub_pat_ + 60 文字以上
slack-tokenxoxb- などで始まる文字列
stripe-secret-keysk_live_ + 24 文字以上
google-api-keyAIza + 35 文字
dotvault-service-tokenest_ + 32 文字 + . + 20 文字以上
generic-assignmentsecret / token / password / passwd / api_key(api-key・apikey も)の後に = か : と、引用符で囲んだ空白なしの 16 文字以上(大文字小文字は区別しない)

読まないものも決まっています。

読まないもの条件
ディレクトリnode_modules・.git・dist・build・.next・coverage・cdk.out・.turbo
大きいファイル5MB を超えるもの
binary先頭 8KB に NUL 文字があるもの

.gitignore は見ません。読まないディレクトリは名前で決まっています。

この 2 つの表は、公開されている CLI のコードを読んで書き、ディレクトリ・5MB・binary の 3 つは実測しました。6MB のテキストと、NUL 文字を含む 27 バイトのファイルの両方に AKIAIOSFODNN7EXAMPLE を入れて scan しても、No secrets found. です。

scan の限界の端末画面(再構成)。ls -la で big.txt(6000021 bytes)と blob.bin(27 bytes)、dotvault scan big.txt blob.bin で「No secrets found.」、cat .env で SMTP_PASSWORD=very-secret-mail-password-2026 と STRIPE_KEY=sk_live_demo…、cat docker-compose.yml で POSTGRES_PASSWORD: "${POSTGRES_PASSWORD_FROM_ENV}"、dotvault scan . で .env:2 [stripe-secret-key] と docker-compose.yml:4 [generic-assignment] の 2 件。

同じ画面の後半が、この回でいちばん伝えたいところです。.env に SMTP_PASSWORD=very-secret-mail-password-2026 と書いても、scan は拾いません。generic-assignment は引用符で囲んだ値だけを見るので、.env の素の書き方は対象外です。同じ .env の sk_live_… は、Stripe の形なので拾います。

つまり、scan は「.env を commit してしまう」事故を止めるものではありません。それは .gitignore に .env を書く仕事です。scan が止めるのは、コードや CI の設定や Dockerfile に、形の決まったトークンを直に書いてしまう事故です。

逆に、docker-compose.yml の POSTGRES_PASSWORD: "${POSTGRES_PASSWORD_FROM_ENV}" は、値ではなく環境変数の参照ですが、引用符の中が 16 文字以上なので拾われています。この形の時は、pre-commit で渡すパスをそのファイルを含まない範囲に絞るか、引用符を外します。

9. うまくいかない時の出力

この回で出た失敗と、その時の出力と終了コードをまとめます。

うまくいかない時の端末画面(再構成)。rm -rf .kube .npmrc && dotvault pull で 4 節と同じ 2 行と「2 files synced」、ls -la .npmrc .kube で両方 No such file or directory、dotvault pull --file .kube/config で「unexpected: Invalid env file name: ".kube/config"」と 99、dotvault pull --file .npmrc で PROJECT_NOT_FOUND(server: No DDB secrets for .npmrc)と 5、dotvault push .yarnrc で VALIDATION_ERROR(Could not read .yarnrc: ENOENT)と 6、dotvault files --env staging で「No files synced.」、dotvault scan nope/ で「No secrets found.」と 0。

操作出力終了コード
置き先のディレクトリ(.kube)が無い状態で pull.env の 2 行と「2 files synced」だけ。.npmrc も .kube/config も書かれない0
pull --file .kube/configunexpected: Invalid env file name99
pull --file .npmrcPROJECT_NOT_FOUND(server: No DDB secrets for .npmrc)5
push .yarnrc(無いファイル)VALIDATION_ERROR(Could not read … ENOENT)6
files --env staging(ファイルの無い環境)No files synced.0
scan nope/(無いパス)No secrets found.0

1 行目が、4 節で mkdir -p .kube をした理由です。置き先にディレクトリを含めるファイルは、受け取る側でそのディレクトリを先に作っておきます。無い時は、push したファイルはどれも書かれません(.kube/config の後の .npmrc も書かれていません)。「2 files synced」の 2 は .env と .env.local の数で、push したファイルは数に入らないので、受け取れたかは ls で確かめます。

2 行目と 3 行目は、push したファイルに pull --file を使った時の出力です。pull --file は .env.local のような key のファイルを 1 つ受け取る指定です。push したファイルは、pull(全部)か run で受け取ります。

最後の行は、パスを打ち間違えても「No secrets found.」で 0 が返る、という出力です。pre-commit に scan src/ と書いて src が無ければ、常に通るので、pre-commit に書くパスは、リポジトリにあるディレクトリかを確かめておきます。

もう 1 つ、push はテキストのファイル用です。

binary を push した端末画面(再構成)。od -c cert.der で 211 P N G で始まる 16 バイト、dotvault push cert.der で「pushed cert.der (v1) → cert.der」、dotvault run -- sh -c 'od -c cert.der …' で先頭が 357 277 275 P N G に変わり 18 バイト、run の中の ls -la cert.der で 18 バイト。

PNG の先頭 1 バイト(0x89)が、文字コードの置き換え文字(3 バイト)に変わっています。push はファイルを文字列として読むので、DER 形式の証明書のような binary は、そのままでは配れません。

次回

6 回目は、複数の project で同じ値を使う key を 1 か所に置き、project ごとに結び付ける Global Keys を、画面と CLI の global で進めます。


dotvault は、.env や設定ファイルの secret をサーバー側に置き、権限のある人・CI・AI エージェントが 1 コマンドで受け取るサービスです(https://dotvault.io)。この連載が終わる時に、dotvault を誰でも無料で使える形で公開する予定です。公開したら、この連載の手順どおりに使い始められます。

興味のある方は、ぜひ「いいね」と記事の購読をお願いいたします。

自社プロダクトは https://www.eln.ne.jp/products にまとめています。

筆者について

上原正吉。EarthLink Network Co., Ltd. 代表。AI社員に日々の業務を任せ、開発では人が逐一指示しなくても作業が進むHOOTL(Human Out Of The Loop)の仕組みを自社で実践しています。このブログでは、業務自動化と24時間自動開発のノウハウ、具体的な設定方法、日々の運用で見つかった問題と改善を共有します。

また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。


EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。

→ EarthLink Network が自社でつくっている18のプロダクト

会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。