efcl.info Open in urlscan Pro
188.114.97.3  Public Scan

URL: https://efcl.info/2023/01/31/remove-secret-from-local/
Submission: On August 27 via manual from JP — Scanned from NL

Form analysis 1 forms found in the DOM

GET https://www.google.co.jp/search

<form class="search-box" method="get" action="https://www.google.co.jp/search">
  <label for="search-box-label">検索:</label>
  <input type="hidden" name="q" value="site:efcl.info">
  <input id="search-box-label" type="search" name="q">
  <input type="submit" value="search">
</form>

Text Content

WEB SCRATCH

ブラウザ/JavaScript等についてのブログ


AZU


検索:


最近の投稿

 * Node.jsで型安全な環境変数を扱うスニペット
 * モバイル端末でのウェブアプリのデバッグ方法、Safari on iOS/Chrome on Android
 * JavaScript PrimerのES2024対応を手伝ってくれるContributorとSponsorを募集しています
 * Kagi Searchをメインの検索エンジンとして使っている


カテゴリ一覧

 * JavaScript[212]
 * イベント[107]
 * 雑記[90]
   その他のタグ…


1PASSWORDを使って、ローカルにファイル(~/.CONFIGや.ENV)として置かれてる生のパスワードなどを削除した

2023年01月31日 Edit on GitHub

最近、コミットはされないがローカルのディレクトリに置かれている.envのようなファイルから生のパスワードやAPI Tokenを削除しました。
これは、ローカルでマルウェアを実行した場合に、ローカルに置かれている生のパスワードやAPI Tokenを盗まれる可能性があるためです。 最近は、npm
install時のpostinstallでのデータを盗むようなマルウェアを仕込んだりするソフトウェアサプライチェーン攻撃が多様化しています。

 * Compromised PyTorch-nightly dependency chain between December 25th and
   December 30th, 2022. | PyTorch
 * What’s Really Going On Inside Your node_modules Folder? - Socket
 * Microsoft spots malicious npm package stealing data from UNIX systems | ZDNET
 * GitGot: GitHub leveraged by cybercriminals to store stolen data

これは、次の記事とも関係しています。

 * パスワード管理/MFA管理の戦略 | Web Scratch
 * Secretlint 6リリース: .bash_historyや.zsh_historyに残ったトークンをマスキングする | Web Scratch

自分は、1Passwordを使っているので、.envに書くようなパスワードやAPI Tokenも1Passwordに集約することで、
ファイルとして置かれている生のパスワードやAPI Tokenの大部分を削除しました。

 * 1Password for Open Source Projectsの申請をした | Web Scratch

ローカルにファイルとして置かれる生のパスワードやAPI Tokenにはいくつか種類があるので、それぞれの対応を書いていきます。
実際には全てのものは対応できていませんが、できるだけファイルに生のパスワードやAPI Tokenを置かないようにしていきます。


SSHの秘密鍵を1PASSWORDに移行する

1Passwordには、SSH Agentがあります。 これを使うとSSHの秘密鍵を1Passwordに保存できるため、~/.sshから秘密鍵を削除できます。

 * 1Password for SSH & Git | 1Password Developer

ついでに、GitHubのSigning Keyも追加すると、Gitでのコミット時に自動的に署名してくれます。

 * Sign your Git commits with 1Password | 1Password

ブラウザに1Passwordの拡張が入ってるなら、https://github.com/settings/keysにアクセスして、ポチポチするだけで終わるので、~/.sshに秘密鍵を置くのはやめましょう。

Bitwardenでも似たようなことができます(仕組みは違います)。

 * Bitwarden SSH Agent

macOSだとsecretiveも同様のことができます。ただし、仕組み的にキーをバックアップはできないため、マシンを変更する場合はSSHキーの再発行が必要です。

 * maxgoedjen/secretive: Store SSH keys in the Secure Enclave


CLIが利用するCREDENTIALを1PASSWORDに移行する

GitHub CLIは~/.config/gh/に、AWS CLIは ~/.aws/credentials にCredentialをデフォルトで配置します。
色々なCLIがそれぞれのサービスのAPIトークンをホームディレクトリ以下に配置しています。

1Password Shell Pluginsを使うことで、ghやawsコマンドを実行するときに、
トークンを1Passwordがロードできるようにコマンドをラップしてくれます。

サービスごとにプラグインが実装されていて、Go言語でプラグインを書けるようになっています。



 * Use 1Password Shell Plugins to securely authenticate third-party CLIs |
   1Password Developer

導入方法は簡単で1PasswordのCLIであるopコマンドをインストールして、プラグインを追加するだけです。

# opコマンドをインストール
$ brew install --cask 1password/tap/1password-cli
# GitHubのプラグインを追加
$ op plugin init gh


 * Get started with 1Password CLI | 1Password Developer
 * Use 1Password to securely authenticate GitHub | 1Password Developer

これで、~/.aws/credentialsなどに置かれているトークンは不要となるため、単純に削除できます。


.ENVに入っているCREDENTIALを1PASSWORDに移行する

.envには生のパスワードやトークンが入ってることが多いです。

.envは普通はコミットしませんが(Secretlintにも検知ルールがあります)、.envからパスワードなどが消えれば、コミットしても大丈夫な状態まで持っていけます。(実際にはコミットはしてないですが)

ghqを使ってリポジトリはまとめられているので、次のコマンドで .env を検索して1コずつ移行していきました。

❯ find . -name ".env" -not -path "*/node_modules/*"


.envの中の特定のキーだけ保存するなら、エディタのプラグインを使うと便利です。

 * 1Password for VS Code | 1Password Developer
 * 1Password - IntelliJ IDEs Plugin | Marketplace

.envの中身を1つのアイテムにまとめるにはop item createコマンドなどを使ってまとめる必要がありました。

例えば、次のような.envファイルがあったとします。

APP_CF_NAMESPACE_USER="SECRET_XXXXXXXX"
APP_CF_AUTHKEY="SECRET_XXXXXXXX"
APP_CF_ACCOUNT_ID="SECRET_XXXXXXXX"
APP_CF_AUTH_EMAIL="SECRET_XXXXXXXX"
APP_LOGFLARE_API_KEY="SECRET_XXXXXXXX"
APP_SESSION_COOKIE_SECRET="SECRET_XXXXXXXX"
APP_STATS_AWS_ACCESS_KEY_ID="SECRET_XXXXXXXX"
APP_STATS_AWS_SECRET_ACCESS_KEY="SECRET_XXXXXXXX"
APP_GOOGLE_OAUTH_CLIENT_ID="SECRET_XXXXXXXX"
APP_GOOGLE_OAUTH_CLIENT_SECRET="SECRET_XXXXXXXX"
APP_OAUTH_REDIRECT_URL="SECRET_XXXXXXXX"
APP_PROD_CF_NAMESPACE_USER="SECRET_XXXXXXXX"
APP_PROD_GOOGLE_OAUTH_CLIENT_ID="SECRET_XXXXXXXX"
APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET="SECRET_XXXXXXXX"


これを次のようなシェルスクリプトに変換して、それぞれの値をアイテムにまとめて1Passwordに保存します。

op item create --category=login --title='My Example'
op item edit 'My Example' APP_CF_NAMESPACE_USER="SECRET_XXXXXXXX"
op item edit 'My Example' APP_CF_AUTHKEY="SECRET_XXXXXXXX"
op item edit 'My Example' APP_CF_ACCOUNT_ID="SECRET_XXXXXXXX"
op item edit 'My Example' APP_CF_AUTH_EMAIL="SECRET_XXXXXXXX"
op item edit 'My Example' APP_LOGFLARE_API_KEY="SECRET_XXXXXXXX"
op item edit 'My Example' APP_SESSION_COOKIE_SECRET="SECRET_XXXXXXXX"
op item edit 'My Example' APP_STATS_AWS_ACCESS_KEY_ID="SECRET_XXXXXXXX"
op item edit 'My Example' APP_STATS_AWS_SECRET_ACCESS_KEY="SECRET_XXXXXXXX"
op item edit 'My Example' APP_GOOGLE_OAUTH_CLIENT_ID="SECRET_XXXXXXXX"
op item edit 'My Example' APP_GOOGLE_OAUTH_CLIENT_SECRET="SECRET_XXXXXXXX"
op item edit 'My Example' APP_OAUTH_REDIRECT_URL="SECRET_XXXXXXXX"
op item edit 'My Example' APP_PROD_CF_NAMESPACE_USER="SECRET_XXXXXXXX"
op item edit 'My Example' APP_PROD_GOOGLE_OAUTH_CLIENT_ID="SECRET_XXXXXXXX"
op item edit 'My Example' APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET="SECRET_XXXXXXXX"


そして、op:// URLの形式で.envに保存していた値を置き換えます。 次のようなコマンドを使うと、op://
URLを取得しながら.env形式で取得できます。

$ op item get --format json 'My Example' | jq -r '.fields[] | select(.value) | (.label) + "=\"" + (.reference) + "\""'
APP_CF_NAMESPACE_USER="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_NAMESPACE_USER"
APP_CF_AUTHKEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_AUTHKEY"
APP_CF_ACCOUNT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_ACCOUNT_ID"
APP_CF_AUTH_EMAIL="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_AUTH_EMAIL"
APP_LOGFLARE_API_KEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_LOGFLARE_API_KEY"
APP_SESSION_COOKIE_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_SESSION_COOKIE_SECRET"
APP_STATS_AWS_ACCESS_KEY_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_STATS_AWS_ACCESS_KEY_ID"
APP_STATS_AWS_SECRET_ACCESS_KEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_STATS_AWS_SECRET_ACCESS_KEY"
APP_GOOGLE_OAUTH_CLIENT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_GOOGLE_OAUTH_CLIENT_ID"
APP_GOOGLE_OAUTH_CLIENT_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_GOOGLE_OAUTH_CLIENT_SECRET"
APP_OAUTH_REDIRECT_URL="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_OAUTH_REDIRECT_URL"
APP_PROD_CF_NAMESPACE_USER="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_CF_NAMESPACE_USER"
APP_PROD_GOOGLE_OAUTH_CLIENT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_GOOGLE_OAUTH_CLIENT_ID"
APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET"


これで、.envの中身は次のようになりました(SectionでProductionとDevelopを同じアイテム内で分けて保存とかもできます)。

APP_CF_NAMESPACE_USER="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_NAMESPACE_USER"
APP_CF_AUTHKEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_AUTHKEY"
APP_CF_ACCOUNT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_ACCOUNT_ID"
APP_CF_AUTH_EMAIL="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_CF_AUTH_EMAIL"
APP_LOGFLARE_API_KEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_LOGFLARE_API_KEY"
APP_SESSION_COOKIE_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_SESSION_COOKIE_SECRET"
APP_STATS_AWS_ACCESS_KEY_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_STATS_AWS_ACCESS_KEY_ID"
APP_STATS_AWS_SECRET_ACCESS_KEY="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_STATS_AWS_SECRET_ACCESS_KEY"
APP_GOOGLE_OAUTH_CLIENT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_GOOGLE_OAUTH_CLIENT_ID"
APP_GOOGLE_OAUTH_CLIENT_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_GOOGLE_OAUTH_CLIENT_SECRET"
APP_OAUTH_REDIRECT_URL="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_OAUTH_REDIRECT_URL"
APP_PROD_CF_NAMESPACE_USER="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_CF_NAMESPACE_USER"
APP_PROD_GOOGLE_OAUTH_CLIENT_ID="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_GOOGLE_OAUTH_CLIENT_ID"
APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET="op://Private/My Example/Section_azkwev6t5djujcfqbb6hpmyk3m/APP_PROD_GOOGLE_OAUTH_CLIENT_SECRET"


このままではアプリケーションの環境変数にはop://の値がそのまま入ります。 そのため、実際にアプリケーションを実行する際には、op
runというコマンドを経由します。

次のように、op runコマンドを実行すると、op://の値が実際の値に置き換わった状態の環境変数がアプリケーションのプロセスに渡されます。

op run --env-file="./.env" -- npm start


 * run | 1Password CLI | 1Password Developer
 * Go Ahead, Delete Your .env.example File | 1Password
 * 1Password の CLI で環境変数を管理する

これでファイルとして永続的に生のパスワードなどが.envに残らないようにできます。 この移行が一番大変だったので、.env to
1Passwordの移行スクリプトを誰か作ってほしいです。

毎回コマンドを覚えるのは大変なので、自分はopr npm startとして実行できるようにしています。

opr () {
	who=$(op whoami)
	if [[ $? != 0 ]]
	then
		eval $(op signin)
	fi
	if [[ -f "$PWD/.env" ]]
	then
		op run --env-file=$PWD/.env -- $@
	else
		op run --env-file=${グローバルのenv置き場}/.env.1password -- $@
	fi
}


類似ツール:

 * zenv
 * envchain


DOTFILEにかかれたパスワードを1PASSWORDに移行する

ZshやBashなどのシェルに使ってるような、dotfileにパスワードが書かれているものがあったので、それも1Passwordに移行しました。

よく使う環境変数(GITHUB_TOKENのような決まり文句があるもの)は、グローバル用の.envを用意しoprで実行しています。
そのほかのトークンは、1Passwordに保存して、op item getやop item readで取得して利用しています。

たとえば、npm publishに必要なTOTPは次のように取得できます(npmのセキュリテキーのフローがまだ使いにくいため。パスワード管理/MFA管理の戦略
| Web Scratch)。

alias npm-get-otp='op item get --otp xxxxxxxxid'
npm publish --otp=$(npm-get-otp)



まとめ

これで、大部分のパスワードやトークンが1Passwordに移行できました。
これによって、ローカルのディレクトリを読み取られても、パスワードが漏洩するケースがかなり少なくなりました。

まだ、一部のトークンが残ったりはしています。 主に認証の実行頻度の問題で、1Passwordに移行できていないものです。

.envにop://で書く場合、それを読み取るプロセスごとに1Passwordでの認証が必要になります。
そのため、頻繁にプロセスが変わって、頻繁に.envを読み取るものは、1Passwordに移行するとノイズになります。 具体的には、次のpostcommit
hookで使ってるようなトークンです。(コミットのたびに別のプロセスになってしまう)

 * GitコミットをNotionに記録してみてる | Web Scratch

一応、.envはop://のテンプレートから作るようにしていますが、ローカルのディレクトリにトークンが残ってしまいます。

op inject --in-file .env.local.template --out-file .env.local


 * 1Password CLIで.env.localを作る - cockscomblog?

この辺の頻度が高くプロセスが変わるものをいい感じに扱える方法がほしいです。

また、このローカルディレクトリからデータを盗むのはどちらかという無差別なサプライチェーン攻撃に多いような気もします。 標的型攻撃になるとCricle
CIのインシデントのように、ブラウザのセッションを盗むことでMFA自体を回避する攻撃が増えています。 これらにも何かしらの対策が必要になっている気がします。

 * CircleCI incident report for January 4, 2023 security incident
 * Cookie stealing: the new perimeter bypass – Sophos News

修正リクエストをする
タグ:
 * Security
 * 1Password


お知らせ欄

JavaScript Primerの書籍版がAmazonで購入できます。

JavaScriptに関する最新情報は週一でJSer.infoを更新しています。

GitHub Sponsorsでの支援を募集しています。


関連記事

 * update-github-actions-permissions v2をリリース: 500種類のGitHub
   Actionsのpermissionsに対応
 * Secretlint 6リリース: .bash_historyや.zsh_historyに残ったトークンをマスキングする
 * [JavaScript] URLを文字列結合で組み立てないために、url-cheatsheetを作った
 * パスワード管理/MFA管理の戦略
 * 1Password for Open Source Projectsの申請をした

コメントを表示