自宅k8sにBitwarden互換なOSS Vaultwardenをセルフホストして、ベンダーに非依存なパスワードレスログインを実現する


きっかけ

サービスへのログインを色々なベンダに依存しているのを、予予気持ちが悪いと思っていた。

最近Yubikey 5 NFCを買ったことをきっかけに、重い腰を上げて、普段使っているサービスをベンダ依存しないようにしつつ、パスワードレスでログイン出来るように設定してみた。

現状どのようなサービスをベンダ依存しているか整理してみると、以下の通り。

  • パスワード
    • Safari/Chromeのパスワードマネージャ
  • TOTP(二段階認証コード)
    • MS/GoogleのAuthenticator
  • Passkey
    • iOS パスキー/MS Authenticator

ここから、

  • パスワード
    • 各パスワードマネージャから、Vaultwardenに一本化
    • Vaultwardenはマスターパスワード+Yubikey(iOSではFaceID)で保護
  • TOTP
    • VaultwardenのビルトインTOTP機能
    • 万が一Vaultwardenが使えない際のバックアップとして、Yubikeyの組み込みTOTP機能を利用(Yubikey 5の指紋認証モデルでは使えないので注意)
  • Passkey
    • マルチデバイス対応のため原則Vaultwardenにパスキーを保存
    • サービス側がセキュリティキーに対応している場合、2本目のパスキーとしてYubikeyを登録

を目指す。

表にするとこんな感じ。Vaultwardenが死んでいる時のバックアップとして、Yubikeyを持っておく感じ。

サービスの種類メイン(Vaultwarden)バックアップ(非Vaultwarden)
パスキー対応サイトVaultwardenに保存Yubikeyを直接登録 (物理セキュリティキー非対応のサイトの場合、iOSのデフォルトパスキーアプリで登録)
TOTPのみ対応サイトVaultwardenで管理**Yubikey本体(Yubico Auth)**に保存
Vaultwarden自体マスターパスワードYubikeyをFIDO2として登録 + リカバリコード

そもそもパスキーとは

さすがにこのご時世にパスキーを聞いたことがない人はいないと思うが、パスキーが他の認証手段に比べてなぜセキュアなのか。それはパスキーやYubikey等のFIDO2規格は「共有された秘密」を持たない仕組みだから。 と言える。

従来型の認証はユーザとサーバが同じ秘密を保持し、それが一致するかを確認する仕組みだが、この方式ではサーバ側から漏洩する、通信経路で盗まれるという構造的な欠陥が存在する。

対してパスキー(FIDO2)では「公開鍵暗号方式」を利用するので、デバイス内に保管された秘密鍵は外に出ることがない。 デバイスを盗まれたら終わりと思うかもしれないが、FIDO2準拠デバイスにはソフトウェア的な暗号化だけではなく、物理的な防御1(耐タンパ性などと言ったりする)が備わっているため、中の秘密鍵を取り出すことは不可能に近い。

FIDO2の認証フローはざっくりと以下の通りである。

sequenceDiagram participant U as ユーザー (生体認証/PIN) participant D as デバイス (Yubikey/スマホ) participant S as サーバー (サービス側) U->>D: 1. 本人確認 (指紋/顔認証) D->>S: 2. ログイン要求 S->>D: 3. チャレンジコード送信 D->>D: 4. 内部の「秘密鍵」で署名 D->>S: 5. 署名済みのデータを送信 S->>S: 6. 「公開鍵」で署名を検証 S-->>U: 7. ログイン完了

加えて、FIDO2が他の認証方式に比べてセキュアだと言える根拠として、フィッシング耐性がある。

パスキー等で認証する際は、「アクセスしているドメイン(URL)」を厳格にチェックする。フィッシングサイトが署名を求めてきても、デバイス側が「URLが違う」と判断して拒否するため、騙される余地がない。

以上の要素から、パスキーを標準の認証方法として設定できるようにしているサイトが増えている背景がある。

Vaultwardenのインストール

非公式だが、それなりにメンテされているhelmチャートがあるのでそれ経由で

helm repo add vaultwarden https://guerzon.github.io/vaultwarden
helm repo update
kubectl create namespace vaultwarden

# argon2でトークン生成(/adminページにアクセスする際のパスワード)
echo -n "your-admin-password" | argon2 "$(openssl rand -base64 32)" -id -t 3 -m 16 -p 4 -l 64 -e

# シークレット作成
kubectl create secret generic vaultwarden-secret \
  --namespace vaultwarden \
  --from-literal=ADMIN_TOKEN='$argon2id$...' # 上記出力を貼る

helm install vaultwarden vaultwarden/vaultwarden \
  --namespace vaultwarden \
  --values values.yaml

storageやingressは適宜読み替えて

values.yaml
domain: "https://vaultwarden.4nm1tsu.com"

adminToken:
  # 事前にargon2でシークレット作成
  existingSecret: "vaultwarden-secret"
  existingSecretKey: "ADMIN_TOKEN"

# 初期設定後falseに
signupsAllowed: true

storage:
  existingVolumeClaim:
    claimName: "pvc-vaultwarden-data"
    dataPath: "/data"
    attachmentsPath: "/data/attachments"

service:
  type: ClusterIP

ingress:
  enabled: true
  class: "nginx"
  hostname: "vaultwarden.4nm1tsu.com"
  path: "/"
  pathType: "Prefix"
  tls: true
  tlsSecret: "letsencrypt-cert-vaultwarden"
  nginxIngressAnnotations: true
  additionalAnnotations:
    cert-manager.io/cluster-issuer: letsencrypt-issuer

確認

$ kubectl get pods -n vaultwarden
NAME            READY   STATUS    RESTARTS   AGE
vaultwarden-0   1/1     Running   0          68s

Vaultwardenでの設定

アカウント新規作成

初回ログインすると以下のような画面が出てくる。

アカウントの作成をクリックして、マスターパスワードを設定したら、速やかにvalues.yamlsignupsAllowed:falseに設定する。

拡張機能インストール

このタイミングでchromeにBitwardenの拡張機能を入れておく。 自鯖に接続するにはポップアップ下部のbitwarden.comをクリックして、「自己ホスト型」を選択後、URLとマスターパスワードを入力する必要がある。

Chromeからcsvエクスポート

chrome://password-manager/settingsにアクセス。「パスワードをエクスポート」を選択して、ダウンロードしたファイルをVaultwardenの「ツール」>「Import」からインポートすると、Bitwarden拡張機能から、Chromeのパスワードマネージャから補完させていたパスワードはすべて補完されるようになる。※クレカの補完は含まれていない。

Yubikeyでの認証を必須化

「設定」>「セキュリティ」からマスターパスワード+FIDO2 WebAuthnでのログインを必須にする。

画面の指示に従ってYubikeyの設定を完了する。Yubikeyを使うのが初めての場合、この画面でPINの設定も行う必要がある。

Chromeでの補完を無効に

ChromeとBitwarden拡張機能で、ともにパスワード補完されてうざいので、Chrome側のパスワードを全削除する。

chrome://password-manager/settingsから「パスワードとパスキーを保存するか確認する」/「自動ログイン」をそれぞれOFFにしてもChromeのパスワード補完は消えなかった。環境イシューかも

iOSでの設定

スマホにもBitwarden authenticator/password managerアプリを入れる。

「設定アプリ」>「一般」>「自動入力パスワードとパスキー」からBitwardenをチェックし、ChromeはOFFに。

「確認コードを設定するアプリ」をBitwardenにすると、TOTPのデフォルトアプリがBitwardenになる。

また、そのままだと毎回マスターパスワードの入力を求められるので、password managerの設定から「Face IDでロック解除」をONに

Authenticatorとの同期を許可するをONにすると、TOTPコードの補完をしてくれるようになるが、2FAの意味がなくなるので、OFFにしておく。

各種サービスでPasskey/Yubikey登録

主要なサービスでPasskey/Yubikeyを登録していく

AWS

AWSコンソールログイン後、右上のユーザ名をクリックして、「セキュリティ認証情報」>「多要素認証(MFA)」の「MFAデバイスの割り当て」

MFAデバイスとして、「パスキーまたはセキュリティキー」を選択

ブラウザの指示に従って、パスキー・セキュリティキーをそれぞれ追加する。

ちなみに、AWSのrootユーザのログインをパスワードレスにはできなかった。

Google

https://www.google.com/intl/ja/account/about/passkeys/にアクセス後、「パスキーを作成」をクリック

通常の方法でログイン後、「パスキーとセキュリティキー」というパスキー管理の画面になるので、「パスキーを作成」をクリックしてそれぞれの認証器を追加

GibHub

https://github.com/settings/securityにアクセスし、Passkeys > Add passkeyからそれぞれの認証器を追加

Authentik

Adminユーザでログイン後、右上の設定アイコンから「MFAデバイス」を選択し、「登録」>「WebAuthn Device」から各種認証器を追加。

この時点でパスワード+WebAuthnの2FAになっているが、完全なパスワードレスを実現したい場合はdefault-authentication-flowを編集する必要がある。時間があるときにパスワードレス化してみようと思う。

NAS

QNAPのページにパスキー認証の設定方法が記載されていたが、手持ちのNASは対応するOSにアップグレードできなそうだったので泣く泣く諦めた。

おわりに

これまで各ベンダの作法にバラバラに預けていた認証情報をVaultwardenという「自分専用のセキュアな拠点」に集約したことで、ようやく管理の主導権を取り戻せた実感が持てた。

単に一箇所にまとめただけではなく、そこにYubikey 5 NFCという物理的な「マスターキー」を組み合わせたのが今回の構成の肝だ。Vaultwarden自体のロック解除には生体認証やPINを使い、万が一のデバイス紛失やシステムトラブルに備えてYubikeyにバックアップを逃がしておく。

結果として、PCでもiPhoneでも、どのブラウザを開いても生体認証のみで安全にログインできる環境が整った。パスワードの使い回しや複雑な文字列の記憶といった不毛な運用から解放され、セキュリティレベルを底上げしつつ、「楽」なログイン体験を手に入れることができた。


  1. デバイスに搭載されるTPM(Trusted Platform Module)や、セキュアエレメント等 ↩︎