1Password CLIを使った環境変数の管理とDocker Composeでの読み込み方法

1PasswordのCLIを使って、環境変数の安全な管理ができるようになった。さらに、1Passwordの値を直接読み取って、docker composeも実行できるようにした。

# TL;DR
- 1Passwordに現在の環境変数をインポートする
    - 手動でもできるが、opコマンド(1PasswordのCLI)を使うと楽
- .env.localから1Passwordの値を書き換える
```.env.local
OP_ROOT_ENV_PATH="hoge-vault/local/root-env"
MYSQL_ROOT_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_ROOT_PASSWORD"
MYSQL_DATABASE="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE"
```
- .envから1Passwordの値を読み込める形でdocker composeを実行する
```
cp .env.local .env && \
op run --env-file="./.env" -- docker compose up -d && docker compose ps
```

# はじめに
ローカル環境変数の値を.env.localにベタ書きしていたが、git管理されてしまうため、セキュリティ上の問題がある。以前勤務していた会社では、
- 社内wikiの記述や特定の管理者のみが管理
- 必要になればslackのDMでのやりとり
- サーバーで直接環境変数を設定する

とかなり手間をかけて環境変数を扱っていた。セキュリティや事故防止を考えれば苦肉の策だったかもしれないが、当時の自分にもいい代替案は提示できなかった。

その後転職先で1Passwordを知った。家族との諸々のアカウント共有にも便利で家庭内でも導入した。いろいろ試しているうちに、開発にも利用できることがわかったため、さっそく導入してみた。

# 移行前の状況
## 環境変数
.env.localにベタ書きで保持されている
```.env.local
# MySQL 環境変数
MYSQL_ROOT_PASSWORD=hogeroot
MYSQL_DATABASE=hogedb
MYSQL_DATABASE_TEST=test_hoge
MYSQL_USER=hogedb
MYSQL_PASSWORD=hogepassword
```

## コンテナ起動コマンド
.env.localを.envにコピーして、コンテナを起動
```
cp .env.local .env && docker compose up -d && docker compose ps
```

# 1Passwordの開発者設定

下記の[記事](https://zenn.dev/lambdasawa/articles/op-cli-and-environment-variables#cli%E3%81%AE%E8%A8%AD%E5%AE%9A)を参考にした。

https://zenn.dev/lambdasawa/articles/op-cli-and-environment-variables

1Password＞設定＞開発者で
`1Password CLIと連携`
にチェックを入れる

# 現行の.envの内容を1Passwordにインポートする
## vault、セクション、環境の構成を決める
私は下記の構成にした。
- vault: hoge-vault
- アイテムタイプ: セキュアノート
- アイテム名: local(環境)
- セクション: root-env

他の記事を見ると、環境変数ごとにアイテムを作成し、セクションと環境を割り当てる方法もあるようだ。

## vault、アイテムを作成する
私は1PasswordのGUIからアイテムを作成した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263051/4b3b504e-e3aa-45e9-90b3-f454f344dd4e.png)

## 現行の.envから、opコマンドを作成する
.env.localから各環境変数ごとのopコマンドを作成するために、雛形用ファイルを作成する。
```linux
cp .env.local .env_to_op_command
```

VSCodeの置換機能を使って、各環境変数ごとのopコマンドを作成する。下記の置換方法は一例だ。作成したコマンドや.envの内容によって、置換方法は異なる。
```
検索：
^(.+)$
置換：
op item edit --vault hoge-vault local root-env.$1
```

```.env_to_op_command
op item edit --vault hoge-vault local root-env.MYSQL_ROOT_PASSWORD=hogeroot
op item edit --vault hoge-vault local root-env.MYSQL_DATABASE=hogedb
op item edit --vault hoge-vault local root-env.MYSQL_DATABASE_TEST=test_hoge
op item edit --vault hoge-vault local root-env.MYSQL_USER=hogedb
op item edit --vault hoge-vault local root-env.MYSQL_PASSWORD=hogepassword
```
作成したコマンドをターミナルにコピペして実行すると、1Passwordに.env.localの内容がインポートされる。

インポートが確認できたら、.env_to_op_commandは削除する。

# 現行の.env.localが1Passwordの値を読み取れるようにする

## 1Passwordの秘密参照を.env.localに書き換える
1Passwordのセクションまでのパスを`OP_ROOT_ENV_PATH`として設定する。まずVSCodeの置換機能を使って、既存の.env.localの値を書き換える。
```
検索：
^(.+?)=(.+)$
置換：
$1="op://${OP_ROOT_ENV_PATH}/$1"
```

```.env.local
# MySQL 環境変数
MYSQL_ROOT_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_ROOT_PASSWORD"
MYSQL_DATABASE="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE"
MYSQL_DATABASE_TEST="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE_TEST"
MYSQL_USER="op://${OP_ROOT_ENV_PATH}/MYSQL_USER"
MYSQL_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_PASSWORD"
```

## 1Passwordの秘密参照を取得
1PasswordのGUIで、各変数の値を右クリックし、`秘密参照をコピー`を選択して、秘密参照を取得する。`"op://hoge-vault/local/root-env/MYSQL_ROOT_PASSWORD"`のような形式だ。

.env.localに上記1Password秘密参照のセクション部分までを変数として設定する。

```.env.local
# 1Passwordのパス
OP_ROOT_ENV_PATH="hoge-vault/local/root-env"

# MySQL 環境変数
MYSQL_ROOT_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_ROOT_PASSWORD"
MYSQL_DATABASE="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE"
MYSQL_DATABASE_TEST="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE_TEST"
MYSQL_USER="op://${OP_ROOT_ENV_PATH}/MYSQL_USER"
MYSQL_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_PASSWORD"
```

### 補足
ここで`OP_ROOT_ENV_PATH`は文字列として扱いたいので、`op://`をつけない。

もし、
``` 
 OP_ROOT_ENV_PATH="op://hoge-vault/local/root-env"
``` 
と`op://`をつけると、1Passwordの読み込みが始まってしまう。

公式ドキュメント

https://developer.1password.com/docs/cli/secret-reference-syntax

によれば、秘密参照のルールは
```
op://<vault-name>/<item-name>/[section-name/]<field-name>
```
となっているが、上記例ではセクション名の`root-env`で終わっている。
そのためエラーが出てしまうので、`op://`をつけないようにする。


# 1Password CLI起点のdocker composeコマンドを作成
既存のコマンドは
```
cp .env.local .env && docker compose up -d && docker compose ps
```
となっているが、このまま実行すると、.envで1Passwordの値を読み取れない。そのため、1Passwordの値を読み取れるように、docker composeコマンドを修正する。

## op runオプションの追加

[公式ドキュメント](https://developer.1password.com/docs/cli/secrets-scripts/#option-1-use-op-run-to-load-secrets-into-the-environment)

https://developer.1password.com/docs/cli/secrets-scripts/

を参考に下記のオプションを利用する
- `--env-file="./.env"`
    - 環境変数を読み取るファイルを相対パスで指定する
- `--`
    - 1Passwordの環境変数をロードして実行したいコマンドを指定する
上記を使い、元のコマンドを下記のように修正する。
```
cp .env.local .env && \
op run --env-file="./.env" -- docker compose up -d && docker compose ps
```

これで、1Passwordに格納されている値を利用して、コンテナ実行を行うことが可能となった。


# 今後やってみたいこと

## 1Passowrd Service Accountの導入
公式ドキュメントを読んでいると、

https://developer.1password.com/docs/cli/secret-references/


 > We recommend using 1Password Service Accounts to follow the principle of least privilege. Service accounts support restricting 1Password CLI to specific vaults, so that processes in your authorized terminal session can only access items required for a given purpose.


> 最小限の権限の原則に従うために、1Passwordサービスアカウントを使用することをお勧めします。サービスアカウントは、1Password CLIを特定のVaultに制限することをサポートします。したがって、承認されたターミナルセッション内のプロセスは、特定の目的に必要なアイテムにのみアクセスできます。

とあった。個人開発のローカル環境作成だと大げさかもしれないが、web公開する場合は導入してみたい。
またこれを利用すれば、理屈上実際の値を意識、一切利用しない開発が可能になるかもしれない

## .env.localの環境変数をまとめる

```
# MySQL 環境変数
MYSQL_ROOT_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_ROOT_PASSWORD"
MYSQL_DATABASE="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE"
MYSQL_DATABASE_TEST="op://${OP_ROOT_ENV_PATH}/MYSQL_DATABASE_TEST"
MYSQL_USER="op://${OP_ROOT_ENV_PATH}/MYSQL_USER"
MYSQL_PASSWORD="op://${OP_ROOT_ENV_PATH}/MYSQL_PASSWORD"
```
の書き方であれば、これらの値は環境を意識する必要がないので.env.localからは切り離し、

環境を切り替える仕組みは
```.env.local
# 1Passwordのパス
OP_ROOT_ENV_PATH="hoge-vault/local/root-env"
```
だけに集約できないかなと考えている