1Password Service Accounts を使って AWS Lambda で秘密情報を取得する

本記事は [KDDIアジャイル開発センター（KAG） Advent Calendar 2024](https://qiita.com/advent-calendar/2024/kag) の23日目です。

パスワード管理サービスである [1Password](https://1password.com/jp) には、トークンを用いて保管庫の情報にアクセスできる **Service Accoutns** という仕組みがあります。

https://developer.1password.com/docs/service-accounts/

これを用いることで 1Password に保存した秘密情報を Lambda から取得できます。
この機能を試してみました。

# Service Account を作成する

https://developer.1password.com/docs/service-accounts/get-started

公式ドキュメントに従って進めます。
まずは Lambda が読み取りたいアイテムを保管する保管庫を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/10b3e514-7bc5-c44b-a7ab-84afded18a23.png)

次に 1Password.com にサインインし、**デベロッパーツール**を選択して、**アクセストークン**の**サービスアカウント**を選択します。

> すでに Service Account を作成している場合などは、上部の**ディレクトリ**を選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/4643d6fc-c7a0-eb2b-2616-36226165be19.png)

Service Account 名を決めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/eb0129d5-0ba5-0fd5-1914-29fc0b71d56a.png)

Service Account のパーミッションを決めます。ここで決めたパーミッションは後で変更できません。

- 保管庫を作成できるか
- 選択した保管庫について
    - 読み取り
    - 書き込み
    - 共有


> 変更するには Service Account を作り直す必要があります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/53681ffe-0502-647a-3915-4359aecf6a54.png)

Service Account トークンが発行されるので保存します。(上記のトークンは実際のトークンの一部分です)
このトークンはこの場でしか閲覧できません。
**1Password に保存** を押すとトークンを 1Password のアイテムとして保存することができます。

これで Service Account のトークンを発行できました。

# 1Password SDK を使って Lambda から保管庫のアイテムを読み取る

https://developer.1password.com/docs/sdks/setup-tutorial#part-2-install-and-configure-a-1password-sdk

こちらも基本的には公式ドキュメントに沿って進めます。

## Service Account トークンを Secrets Manager に保存する

公式ドキュメントでは先程発行した Service Account トークンを環境変数から取得していますが、Lambda の場合は環境変数にトークンなどの秘密情報を保存するのは推奨されていないので、代わりに [AWS Secrets Manager](https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/intro.html) にトークンを保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/3ba3e66d-b6c1-d24e-5ab8-1c1b3784347f.png)

Secrets Manager の画面から**新しいシークレットを保存する**を押します。

シークレットのタイプは**その他のシークレットのタイプ**を選び、キー/値のペアで**プレーンテキスト**を選んで Service Account トークンを貼り付けます。
暗号化キーはデフォルトでOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/8c5fc62e-924d-8a55-ab9d-6e484f316f96.png)

次にシークレットの名前を入力します。
その他は必要に応じて追加します。(何も入力しなくてもOKです)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/781f1e1b-9198-1c4e-3ba3-f32db9350cb4.png)

ローテーションの設定は、必要に応じて編集します。(何も入力しなくてもOKです)

次の画面へ移り、内容を確認した後保存をすることで Secrets Manager にトークンが保存されます。

## 秘密情報を取得する Lambda を作成する

npm パッケージを作成し、`package.json` に `type: "module"` を追加します。

```diff_json
{
    "name": "Tutorial",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
+   "type": "module",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}
```

1Password SDK と AWS SDK for JavaScript をインストールします。

```
npm install @1password/sdk @aws-sdk/client-secrets-manager
```

次に `index.js` を下記のようにします。

```javascript
import {
  SecretsManagerClient,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
import { createClient } from "@1password/sdk";

// Secrets Manager から 1Password Service Account トークンを取得
const secret_name = "<your secret name>";
const awsSecretsManagerClient = new SecretsManagerClient({
  region: "ap-northeast-1",
});

let response;

try {
  response = await awsSecretsManagerClient.send(
    new GetSecretValueCommand({
      SecretId: secret_name,
    })
  );
} catch (error) {
  throw error;
}

const opServiceAccountToken = response.SecretString;

// 認証済みの 1Password クライアントを作成する
const client = await createClient({
    auth: opServiceAccountToken,
    // 下記は自由に設定
    integrationName: "My 1Password Integration",
    integrationVersion: "v1.0.0",
});

// 1Password に保存した秘密情報を取得
// "op://~" は秘密情報への秘密参照
const secret = await client.secrets.resolve("op://vault/item/field"); 
```

ここで `client.secrets.resolve()` に渡す**秘密参照**とは下記のように取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408546/20a17a3f-7b46-477e-6ed6-e0080eba9ac4.png)

最後に、Lambda が Secrets Manager にアクセスできるようにするため、Lambda にアタッチする IAM ロールに下記を追加します。

```diff_yaml
{
    "Version": "2012-10-17",
    "Statement": [
        ..., 
+        {
+            "Action": [
+                "secretsmanager:GetSecretValue"
+            ],
+            "Resource": "<Secrets Manager の ARN>",
+            "Effect": "Allow"
+        }
    ]
}
```

以上で Lambda から 1Password に保存した秘密情報にアクセスできるようになります。

# まとめ

1Password Service Accounts を使って AWS Lambda から 1Password に保存した秘密情報を取得する方法を試しました。

一見、秘密情報を AWS Secrets Manger に保存するだけで良いのでは？と思いますが、秘密情報が 1Password に一元化されることで下記のメリットがあると考えられます。

- 秘密情報が複数の場所から参照される場合、秘密情報の更新が1箇所のみの変更で済む
- 秘密情報が 1Password にアイテムとして保存されていると、開発時に 1Password CLI と組み合わせた開発ができる
- 秘密情報へのアクセス権を誰が持っているかを 1Password で一元管理できる

用途に合わせて、活用していきたいです。