1Passwordの情報取得の自動化について調べた

はじめに

1Passwordでの情報取得について調べたのでメモです。

なお、下記の図はC4 Diagrams | Mermaidで書いています。書き方はオライリーのC4モデルで読みました。

WSLからWindowsの1Password CLIで1Passwordアプリから情報を取得する構成

  C4Context
title WSLからWindowsの1Password CLIを使うシステムコンテキスト図
Person(User, "ユーザー")
System_Boundary(BndPC, "PC") {
  System_Boundary(BndWSL, "WSL") {
    System(SysBash, "bash")
  }
  System_Boundary(BndWin, "Windows") {
    System(SysOpExe, "1Password CLI (op.exe)")
    System(SysOpApp, "1Password App")
  }
}

Rel(User, SysBash, "requests item to")
UpdateRelStyle(User, SysBash, $offsetX="-10", $offsetY="-80")
Rel(SysBash, SysOpExe, "requests item to")
UpdateRelStyle(SysBash, SysOpExe, $offsetX="-50", $offsetY="-15")
Rel(SysOpExe, SysOpApp, "reads item from")
UpdateRelStyle(SysOpExe, SysOpApp, $offsetX="5", $offsetY="0")

WSLで1Password CLIのLinux版を使うのではなく、WSLからWindows版の1Password CLIを実行し、Windowsの1Passwordアプリから情報を取得します。 情報取得の際に「アイテムリクエスト」ダイアログが表示され、ユーザーが許可すると情報を取得できます。

SSHトンネルを使って踏み台サーバーに1Passwordの情報を渡す構成

  C4Context
title SSHトンネルを使って踏み台サーバーに1Passwordの情報を渡すシステムコンテキスト図
Person(User, "ユーザー")
System_Boundary(BndBastion, "Bastion host") {
    System(SysRemopRead, "remop read")
}
System_Boundary(BndPC, "PC") {
  System_Boundary(BndWCL, "WSL") {
    System(SysRemopTun, "remop tunnel")
  }
  System_Boundary(BndWin, "Windows") {
    System(SysOpExe, "1Password CLI (op.exe)")
    System(SysOpApp, "1Password App")
  }
}

Rel(User, SysRemopRead, "requests item to")
UpdateRelStyle(User, SysRemopRead, $offsetX="0", $offsetY="-40")
Rel(SysRemopRead, SysRemopTun, "requests item to")
UpdateRelStyle(SysRemopRead, SysRemopTun, $offsetX="-80", $offsetY="-20")
Rel(SysRemopTun, SysOpExe, "requests item to")
UpdateRelStyle(SysRemopTun, SysOpExe, $offsetX="-50", $offsetY="-15")
Rel(SysOpExe, SysOpApp, "reads item from")
UpdateRelStyle(SysOpExe, SysOpApp, $offsetX="5", $offsetY="0")

ekovshilovsky/op-forwardという先人がいたのですが、ssh -Rでポートフォワードするというアイディアだけ頂いてhnakamur/remopというCLIを自作しました。

最初にremop tunnel 対象サーバーでPCから踏み台サーバーにSSHトンネルを作成します。そして踏み台サーバー上でremop read op://...で情報取得を依頼すると、WSL上のremop tunnelのプロセスがop.exeを実行して情報を取得して返します。

この構成でも情報取得の際に「アイテムリクエスト」ダイアログが表示され、ユーザーが許可すると情報を取得できます。

この許可が必要とはいえ、踏み台サーバーが侵害されたり何らかのミスでremop readで想定外のアイテムの情報の取得を依頼されるリスクを考えて、取得可能なアイテムの許可リストをremop tunnel側で持つようにし、許可リストにないアイテムは情報を返さないようにしています。

1Passwordのサービスアカウントを使う構成

  C4Context
title 1Passwordのサービスアカウントで情報を取得するシステムコンテキスト図
Person(User, "ユーザー")
System_Boundary(BndMyCompanyLAN, "My company LAN") {
  System_Boundary(BndBastion, "Bastion host") {
    System(SysOpCLI, "1Password CLI (op)")
  }
}
System_Boundary(BndInternet, "Internet") {
  Enterprise_Boundary(BndOnePasswordCom, "1Password.com") {
    System(SysOpServer, "1Password server")
  }
}

Rel(User, SysOpCLI, "requests item to")
UpdateRelStyle(User, SysOpCLI, $offsetX="0", $offsetY="-60")
Rel(SysOpCLI, SysOpServer, "reads item from")
UpdateRelStyle(SysOpCLI, SysOpServer, $offsetX="-60", $offsetY="-15")

ブラウザで1password.comにサインインし、サービスアカウントを作成します。 作成時にそのアカウントがアクセスできる保管庫を指定しておきます。 アクセストークンが発行されるので、情報取得の際にはそのトークンで認証します。

1Passwordのサーバーから情報を取得するので、1Passwordアプリの「アイテムリクエスト」ダイアログで許可を求められることはありません。1Passwordアプリを起動していない状態でも取得できました。

無人での完全自動処理ができるという利点がありますが、一方でトークンが盗まれるとそのトークンでアクセスできる保管庫の情報を取得され放題になるというリスクがあります。

トークンが盗まれた可能性が発覚したら、1password.comにサインインして対象アカウントのトークンを取り消すかローテーションする対応が必要です。さらにアクセスできた可能性のあるアイテムのパスワードなどの機密情報も変更が必要です。

サービスアカウントでの情報取得などのリクエストには1時間毎と24時間毎のレートリミットがあります。

詳細は1Password Service Accounts - 1Password Developerを参照してください。

1PasswordのConnectサーバーを使う構成

  C4Context
title 1PasswordのConnectサーバー経由で情報を取得するシステムコンテキスト図
Person(User, "ユーザー")
System_Boundary(BndMyCompanyLAN, "My company LAN") {
  System_Boundary(BndBastion, "Bastion host") {
    System(SysHTTPClient, "a HTTP client")
  }
  System_Boundary(BndConnectServer, "Connect Server host") {
    System(SysConnectServer, "Connect Server")
  }
}
System_Boundary(BndInternet, "Internet") {
  Enterprise_Boundary(BndOnePasswordCom, "1Password.com") {
    System(SysOpServer, "1Password srver")
  }
}

Rel(User, SysHTTPClient, "requests item to")
UpdateRelStyle(User, SysHTTPClient, $offsetX="0", $offsetY="-80")
Rel(SysHTTPClient, SysConnectServer, "reads item from")
UpdateRelStyle(SysHTTPClient, SysConnectServer, $offsetX="-60", $offsetY="-15")
Rel(SysConnectServer, SysOpServer, "reads item from")
UpdateRelStyle(SysConnectServer, SysOpServer, $offsetX="-60", $offsetY="-15")

サービスアカウントのレートリミットが心配だったり、毎回1Password社のサーバーにアクセスするレイテンシーが気になる場合は、LAN内でConnectサーバーを動かしてそれ経由で情報を取得するという選択肢もあります。

ブラウザで1password.comにサインインし、Connectサーバーを作成します。実際にConnectサーバーのシステムが作られるわけではなく、Connectサーバー用の設定(資格情報ファイル)が作られます。Connectサーバーも作成時にアクセス可能な保管庫を指定します。

次にConnectサーバーへのアクセストークンを作成します。アクセストークンでもトークン毎にアクセス可能な保管庫を指定します。

するとConnectサーバー用の資格情報ファイル(1password-credentials.json)とアクセストークンが作成されるので1Passwordに保存します。

ConnectサーバーはDocker ComposeまたはKubernetesで動作します。

docker-compose.ymlの例のままだと1password-credentials.jsonをマウントする際にnot a directoryというエラーになりました。 op-connect-apiとop-connect-syncサービスのvolumesで1password-credentials.jsonをマウントする箇所を以下のように修正すると正常に動作しました。

修正前

    volumes:
      - "./1password-credentials.json:/home/opuser/.op/1password-credentials.json"

修正後

    volumes:
      - type: bind
        source: ./1password-credentials.json
        target: /home/opuser/.op/1password-credentials.json
        read_only: true
        bind:
          create_host_path: false

Connectサーバーの作成時にへのアクセストークンも作成します。アイテム取得はそのトークンを使って通常のHTTPクライアントで1Password Connect Server APIにアクセスします。

例:

curl -sS \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $OP_API_TOKEN" \
  http://localhost:8080/v1/vaults/<your_vault_id_here>/items/<your_item_here> \
  | jq -r '.fields[] | select(.id == "password") | .value'

アクセストークンが流出した可能性がある場合は、1password.comで対象のConnectサーバーの設定で対象のアクセストークンを取り消し、アクセス可能だった保管庫のアイテムの内容を更新します。

1password-credentials.jsonが流出した可能性がある場合は、設定画面を見た感じではConnectサーバーの設定ごと消す必要があるようです。そしてアクセス可能だった保管庫のアイテムの内容の更新も必要です。

Connectサーバーの詳細は1Password Connect - 1Password Developerを参照してください。

GoのSDK

2つのSDKがあります。

1Password Go SDK

1Password SDKs - 1Password DeveloperにGo、JavaScript、PythonのSDKがあります。

Goの1Password/onepassword-sdk-goを見ると2つのオプションがあります。

デスクトップアプリ連携

1つめは1Passwordデスクトップアプリと連携する選択肢です。

この選択肢ではCGoを有効にしてビルドが必要です。無効にした場合はinternal/shared_lib_core_no_cgo.go#L8 at v0.4.1のエラーが出ます。

WSLから使う場合でもGOOS=windowsを指定してWindowsの実行ファイルをビルドする必要があります。そして実行にはshared_lib_core.go#L53-L59のop_sdk_ipc_client.dllが必要です。これはWindows版の1Password CLIには含まれないので、1Passwordアプリの開発者向け連携機能の設定で「1Password SDKsとの連携」にチェックをつける必要があります。

サービスアカウント

もう1つはサービスアカウントを使う選択肢です。こちらはCGo無効でビルドできてスタティックリンクな実行ファイルが生成されました。Linuxで使う場合に加えてWSLで使う場合もLinux用のビルドでOKです。

1Password Connect SDK for Go

Connectサーバーへのアクセスは上記のcurlでの例からわかるようにGoの標準ライブラリーのnet/httpやencoding/jsonパッケージだけでも可能です。

が、1Password/connect-sdk-go: Go SDK for 1Password Connectなら1Password Connect Server APIをより簡単に使えそうです(実際に試してはいないです)。