はじめに
最近サプライチェーンアタックとか物騒なんで、自衛のためにローカルの環境変数に散らばりがちな平文クレデンシャルは撲滅しましょう。
保存先はいろいろあるとは思いますが、私は普段1Passwordを使っているので、1Passwordに開発用のVaultを作って、1Password CLIやEnvironments経由で環境変数に注入するようにしています。
本稿執筆時点では、1Password Environmentsとdirenvとの組み合わせ方法が自明ではなかったので、そのためにこのメモを書いてますが、説明の都合上、1Password CLIのインストールやop readコマンドなど基本から順に説明していく。
環境
手元の環境は以下のとおりです。
- 1Password for Mac: v8.12.12
- 1Password CLI: v2.33.1
- direnv: v2.37.1
前提として、1Password自体の導入は割愛します。
1Password CLIのインストール
1Passwordには公式のCLIがあります。
macOSの場合は以下のコマンドでインストールできます。
$ brew install 1password-cli
macOS以外の人は公式のインストールガイドを参照して下さい。
インストールするとopコマンドが使えるようになりますが、1Passwordのアプリ側の設定も必要です。
「1Password」 > 「設定」 > 「開発者」 > 「1Password CLIと連携」にチェックを入れます。
op vault list コマンドを実行すると認証を求められるので、許可します。
もし仕事用と個人用で複数のアカウントがある場合は、 op signin コマンドでアカウントを選択できます。
op read コマンドで1Passwordのitemを読み込むことができます。
$ op read op://Dev/Foo/FOO_TOKEN
op://〜 の参照URLは、itemのプルダウンメニューから「秘密参照をコピーする」で取得できます。
この例では、 Dev 保管庫の Foo というitemの FOO_TOKEN という項目から値を取得します。
1Password CLIから環境変数を読み込む
コマンドの実行時に環境変数を注入する方法はいくつかありますが、例えば必要な環境変数が1つであれば、以下のようなシェル関数を書くことで、実行時に透過的に環境変数を差し込むことができます。
1点注意点として、無限ループしないように、 シェルビルトインの command コマンドで、シェル関数への解決を抑止します。
foo(){ env FOO_TOKEN="$(op read op://Dev/Foo/FOO_TOKEN)" command foo "$@" }
1Password Environmentsで.envファイルをマウントする
複数の環境変数を読み込む場合は、シェル関数よりも、シェルスクリプトなどに切り出した方が見通しがよいかもしれません。
ただ、op readコマンドの読み込みオーバーヘッドが体感まぁまぁ遅いので、読み込む環境変数の数が多い場合は、1Password Environments経由で読み込むことで、複数の環境変数を一括で読み込めます。
1Password Environmentsは.envファイルを1Passwordで管理する機能です。.envファイルとは、環境変数KEY=VALUEのペアを書いたファイルで、多くのツールやライブラリがこのファイル形式に対応しています。
本稿執筆時点では、1Password Environmentsはまだベータ版ですが、有効化するには、「1Password」 > 「設定」 > 「開発者」 > 「1Password開発者エクスペリエンスを表示する」にチェックを入れます。
1Passwordのアプリの左のメニューバーに開発者メニューが表示されるので、
「開発者」 > 「環境」 > 「新しい環境」で適当な名前を付けて、「保存」を選択します。
「変数」タブ > 「.envファイルをインポート」 > 既存の.envファイルを選択して「インポート」 > 「保存」します。
「保存先」タブ > 「保存先を追加する」 > 「ローカル.envファイル」 > 「保存先の設定」 > 「ファイルパスを選択する」 > 既存の.envファイルを「選択」します。
「"."(ピリオド)で始まる名前を使ってもよいですか?」 と確認が出た場合は、 「"."」を使用を選択します。
「".env"ファイルは既に存在しています。置き換えてもよろしいですか?」 と確認が出た場合は、 「置き換え」を選択します。
最後に「.envファイルをマウントする」を押すと.envファイルがマウントされます。
この状態で、cat .env などで.envファイルを読み込もうとすると、認証を求められるので許可すると読み込めます。また1PasswordのVaultをロックしてから、再度.envファイルにアクセスしようとすると、再度認証が求められることが確認できます。これで許可したときだけ読み込める.envファイルができました。
1Password Environmentsから環境変数を読み込む
1Password Environmentsの使用上の注意点として、この.envファイルはファイルシステムレベルでは普通のファイルではなく、実体はNamed Pipeとして実装されており、.envを直接読み込むアプリケーション側がNamed Pipeに対応している必要があります。
例えばpython-dotenvは v1.2.1以降でNamed Pipeに対応しています。ライブラリのバージョンが古いと読み込めない可能性があるので注意して下さい。主要な言語のライブラリの対応状況は以下のとおりです。
シェルスクリプトの中から読み込む場合は、cat .envしてevalで読み込めます。
eval "$(cat .env)"
アプリケーションが直接.envファイルの読み込み対応しておらず、環境変数経由で渡されることを想定している場合は、 op run --env-file=.env -- コマンドでラップすると、コマンド実行時に.envファイルを読み込んで環境変数を注入することが可能です。
1Password Environmentsとdirenvを組み合わせる
op run コマンドを使った方法は、汎用的な方法ではありますが、アプリケーションの起動コマンドを変更する必要があるので、ラッパースクリプトやエイリアスなどで隠蔽することはできるものの、若干手間です。
特定のプロジェクトディレクトリ配下でだけで使いたい場合はdirenvと組み合わせる方が簡単です。
direnvは.envrcファイルを配置したディレクトリにcdすると自動で環境変数を読み込んでくれるツールです。
macOSの場合は以下のコマンドでインストールできます。
$ brew install direnv
macOS以外の人は公式のインストールガイドを参照して下さい。
と言いつつ、1Password Environmentsは.envファイルをマウントできますが、direnvの.envrcファイルには直接対応していません。
ところで、direnvには.envファイルの読み込む dotenv 機能があるので、これを組み合わせられるのでは?と思ったのですが、残念ながら本稿執筆時点では、direnvはまだNamed Pipeに対応していませんでした。
回避策として、以下を.envrcに書くとdirenvでも読み込めました。
set -a; source .env; set +a
direnvは特定のプロジェクトにcdするだけでシェルに環境変数が読み込まれるので、1Password Environmentを挟んでも、cdするタイミングで認証を求められるだけで、あとは既存の開発フローにそのまま組み込みやすいです。若干怠惰な方法ではありますが、常時ローカルに平文クレデンシャルがあるよりもずいぶんマシです。
まとめ
マルウェアは踏んじゃう想定で、被害を最小化するためにローカルの平文クレデンシャルを撲滅しましょう。