
概要#
最近数多のSaaSが値上げしており、パスワード管理ツールの大御所でもある1Passwordがまさかの年額47.88ドルまであがるとのこと。
1Password、3月27日から値上げ。個人プランは年額35.88ドル→47.88ドルに
円安もあいまって、4/30日時点で1ドル160円となったので、約7,660円/年となる。
ソースネクスト経由であれば3年で12800円なので、約4267円/年となる。
私自身は1Passwordは使っておらず、Bitwardenの無料版を使っているが無料版も添付ファイルは添付できなかったり、スマホは結局優良プランに入らないとダメそうだった。
ChromeでBitwardenで管理したり、Chromeで管理したりしていてそろそろ本腰あげてちゃんと管理することにした。
色々検討したが、自宅サーバー勢なのでセルフホストなパスワード管理ツールを検討することにした。
要件#
要件としては、この辺りが抑えられていればある程度運用するメリットがありそうと考えた。
- 無料であること(買い切りならヨシ)
- Dockerイメージがあって、簡単に管理できること
- ブラウザの拡張があること(これはMUSTじゃない、最悪自分で作れば良い)
- スマホアプリが使えること(これもMUSTじゃないけど、あったほうが嬉しい)
- インターネット経由でアクセスできること(場合によっては無しでもOK)
- できればMFAも管理できること
Bitwarden, Vaultwarden, Keepass#
ChatGPTに聞いたらこの辺りが出てきたので、比較表を作成してもらった。
| 項目 | Bitwarden | Vaultwarden | KeePass |
|---|---|---|---|
| 種別 | OSS + 公式サービス | OSS(Bitwarden互換サーバ) | OSS(ローカルDB型) |
| セルフホスト | 可能(公式サーバあり) | 可能(軽量) | 不要(ファイル管理) |
| スマホアプリ | 公式あり(iOS / Android) | Bitwardenアプリを使用 | サードパーティ(KeePassium等) |
| クライアントUX | 非常に良い | 非常に良い(Bitwarden同等) | やや弱い |
| セットアップ難易度 | 中(公式はやや重い) | 低(Dockerですぐ) | 低(ファイルだけ) |
| リソース消費 | 高め | 低い(軽量) | ほぼゼロ |
| 同期方式 | サーバ経由 | サーバ経由 | 自分で同期設計(S3, Nextcloud等) |
| オートフィル | 対応(ブラウザ/スマホ) | 対応 | 限定的(環境依存) |
| チーム利用 | 強い(共有/権限あり) | 制限あり(基本個人向け) | 弱い |
| セキュリティ | 高(ゼロ知識暗号) | 高(Bitwarden互換) | 高(ローカル管理) |
| 監査/信頼性 | 高(企業利用多数) | 中(非公式実装) | 高(実績長い) |
| 運用コスト | 中 | 低 | 低(ただし同期設計必要) |
| 拡張性/API | あり | あり(Bitwarden互換) | ほぼ無し |
| バックアップ | サーバ側で管理 | サーバ側で管理 | ファイルコピーのみ |
| 向いている用途 | 個人〜チーム | 個人・小規模 | 完全ローカル主義 |
セルフホストをしないというKeePassも検討にあがったが、GoogleDriveとかでの同期はなにかと面倒なのでセルフホストできたほうが逆にいいかなと考えた。
あと、今はどうかわからないけど昔KeePassXを一時的に使っていたのもあり、便利なのはわかっていたけどUIがあまりカッコよくない印象があった(今はもしかしたら違うかもしれないが)
機能差#
特に要件を満たしているかはこの辺りの機能差を重視することとした。
トータルではBitwardenをセルフホストできたらいいかと考えたが、OSSでないと無料版に制限がかかったりしそうなので食指が動かなかった。
また、無料版だとBitwardenのソフトから見るとTOTP保存が出来なかったので、この辺りがわからなかった。
なんとなくできなそうには見える。
Any way to selfhost bitwarden with tOTP support?
| 機能 | Bitwarden | Vaultwarden | KeePass |
|---|---|---|---|
| パスワード保存/検索 | 〇 | 〇(完全互換) | 〇 |
| 自動入力(ブラウザ/スマホ) | ◎(標準機能) | ◎(Bitwardenアプリ経由) | △(環境依存) |
| クロスデバイス同期 | ◎(公式サーバ) | ◎(自前サーバ) | △(手動 or 外部同期) |
| オフライン利用 | 〇(キャッシュあり) | 〇 | ◎(完全ローカル) |
| パスワード生成 | 〇 | 〇 | 〇 |
| 2FA(TOTP保存) | ◎ | ◎ | △(プラグイン等) |
| 生体認証(FaceID/指紋) | ◎ | ◎ | △(アプリ依存) |
| セキュアノート | 〇 | 〇 | 〇 |
| 添付ファイル保存 | ◎(公式対応) | △(制限あり) | △(DB肥大化) |
| パスワード共有 | ◎(細かい権限管理) | △(簡易的) | ✕(基本不可) |
| 組織/グループ管理 | ◎ | △(制限あり) | ✕ |
| アクセス権限(RBAC) | ◎ | △ | ✕ |
| 監査ログ | ◎(企業向け機能) | ✕ | ✕ |
| API/CLI | ◎ | ◎(互換API) | ✕ |
| Web UI | ◎ | ◎ | △(専用ツール必要) |
| ブラウザ拡張 | ◎ | ◎ | △ |
| パスワード漏洩チェック | ◎(HaveIBeenPwned連携) | △(制限あり) | ✕ |
| Emergency Access | ◎ | ✕ | ✕ |
| Send機能(安全な共有リンク) | ◎ | △(制限あり) | ✕ |
| SSO連携 | ◎(企業向け) | ✕ | ✕ |
| カスタムフィールド | ◎ | ◎ | 〇 |
Vaultwarden#
結果、Vaultwardenを検証することにした
- 個人利用のみであること
- RUSTで軽量であること
- Dockerコンテナで管理が容易であること
- TOTP管理ができること(Bitwardenだと有償ライセンスぽい)
- SQLiteなのでバックアップが容易であること
Vaultwarden構築環境#
我が家にあるNASではDockerを動かすための専用のアプリケーションがあり、compose.ymlを設定するとそれを常時動かすことが可能である。
左が昨年導入したUGREENのNAS。
OSがDebianでrootまで取れるので、なんでも出来ちゃうやつ。
8TB2本のRAID1+256GBのM.2 SSDのキャッシュ付き。
右が10年くらい動かしていた旧NAS。2TBx2 RAID1で運用していた。

UGREENのNASはいろいろなアプリケーションがあり、その中にDockerアプリがある。
そこで画像のように時前でアプリケーションをDockerで動かしたりが簡単にできるので、ローカルだけで参照できる簡易なサイトを動かたりしている。

なお、Vaultwardenをただ動かすなら下記だけでOKである。
docker pull vaultwarden/server:latest
docker run --detach --name vaultwarden \
--env DOMAIN="https://vw.domain.tld" \
--volume /vw-data/:/data/ \
--restart unless-stopped \
--publish 127.0.0.1:8000:80 \
vaultwarden/server:latest
ローカル内サイトと同じようにやったらいけるかなと思って手元でDockerを動かすのは秒でできたものの、アカウントを作成しようとしたところ、HTTPSでないとErrorになってしまって作成ができなかった。
アカウント作成だけならやりようがあるかもしれないが、どちらにしろスマホ対応するならインターネット越しにアクセスしないといけないのでHTTPSは必須と考えた。
そこで、このブログが動いているk8sでHTTPSプロキシすればよいと考えた。
構成としてはこんな感じ。
コンテナで動かすのにステートフルになるのは微妙なため、データ領域としてNFSマウントを有効化することとした。

自宅サーバーでk8sクラスターをホストしており、そこですでに数サイト動いている。
ASROCK Deskmini X300というベアボーンキットで動き、余ったVESAマウントでマウントして空中に浮かせている。

こんな感じのシンプルな構成。
このクラスターでは、幸いすでにcert-managerコンテナが動いているので、証明書の更新実績もあり利用は簡単に行える。
NASの設定変更#
ControlPlaneの証明書切れ対応#
自宅は固定IPアドレスではないので、定期的に今のIPアドレスをRoute53にレコードを上書きするcronjobを動かしているのだけど、そこで今回アクセスするためのレコードを更新しようとしたらk8sの証明書周りがみな期限切れていた。
ちょうど1年前に構築したのでその時から1年経過したらしい。
以下はVaultwardenは無関係だけど備忘録。
kubectl apply -n update-ddns -f update-dns.yaml
error: error validating "update-dns.yaml": error validating data: failed to download openapi: Get "https://192.168.x.x:6443/openapi/v2?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2026-04-29T12:25:57+09:00 is after 2026-04-11T03:30:39Z; if you choose to ignore these errors, turn validation off with --validate=false
ControlPlaneにアクセスして証明書を確認。
❯ ssh 192.168.x.x
root@k8s-control-vm01:~# kubeadm certs check-expiration
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[check-expiration] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.
[check-expiration] Error reading configuration from the Cluster. Falling back to default configuration
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Apr 11, 2026 03:30 UTC <invalid> ca no
apiserver Apr 11, 2026 03:30 UTC <invalid> ca no
apiserver-etcd-client Apr 11, 2026 03:30 UTC <invalid> etcd-ca no
apiserver-kubelet-client Apr 11, 2026 03:30 UTC <invalid> ca no
controller-manager.conf Apr 11, 2026 03:30 UTC <invalid> ca no
etcd-healthcheck-client Apr 11, 2026 03:30 UTC <invalid> etcd-ca no
etcd-peer Apr 11, 2026 03:30 UTC <invalid> etcd-ca no
etcd-server Apr 11, 2026 03:30 UTC <invalid> etcd-ca no
front-proxy-client Apr 11, 2026 03:30 UTC <invalid> front-proxy-ca no
scheduler.conf Apr 11, 2026 03:30 UTC <invalid> ca no
super-admin.conf Apr 11, 2026 03:30 UTC <invalid> ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Apr 09, 2035 03:30 UTC 8y no
etcd-ca Apr 09, 2035 03:30 UTC 8y no
front-proxy-ca Apr 09, 2035 03:30 UTC 8y no
証明書を更新とチェック。
root@k8s-control-vm01:~# kubeadm certs renew all
[renew] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[renew] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.
[renew] Error reading configuration from the Cluster. Falling back to default configuration
root@k8s-control-vm01:~# kubeadm certs check-expiration
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[check-expiration] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Apr 29, 2027 03:28 UTC 364d ca no
apiserver Apr 29, 2027 03:28 UTC 364d ca no
apiserver-etcd-client Apr 29, 2027 03:28 UTC 364d etcd-ca no
apiserver-kubelet-client Apr 29, 2027 03:28 UTC 364d ca no
controller-manager.conf Apr 29, 2027 03:28 UTC 364d ca no
etcd-healthcheck-client Apr 29, 2027 03:28 UTC 364d etcd-ca no
etcd-peer Apr 29, 2027 03:28 UTC 364d etcd-ca no
etcd-server Apr 29, 2027 03:28 UTC 364d etcd-ca no
front-proxy-client Apr 29, 2027 03:28 UTC 364d front-proxy-ca no
scheduler.conf Apr 29, 2027 03:28 UTC 364d ca no
super-admin.conf Apr 29, 2027 03:28 UTC 364d ca no
systemctl restart kubelet は実行したはず
下記コピーしてMacの ~/.kube/configに上書き
root@k8s-control-vm01:~/.kube# cat /etc/kubernetes/admin.conf
これでkubectlが動くようになった。
vaultwarden用のserviceとdeployment等を用意して適用。
ingressで証明書を参照できるようにした。この時点では共通のingressのまま。
❯ kc apply -f vaultwarden.yaml
persistentvolume/vaultwarden-data-pv unchanged
persistentvolumeclaim/vaultwarden-data created
deployment.apps/vaultwarden created
service/vaultwarden created
❯ kc apply -f ingress.yaml
ingress.networking.k8s.io/web-ingress unchanged
ingress.networking.k8s.io/redirect-www-inamuu-com created
ingress.networking.k8s.io/redirect-kazuma-tokyo created
NASをNFSマウントするのでNFS Clientをインストール#
sudo apt update
sudo apt install -y nfs-common
which mount.nfs
NAS側でNFSサービスを有効化。

最初はPodがNFSマウントできずにErrorになっていたが、上記サービスを有効化したあとマウントされるようになった。
MacのFinderでも確認できるようになった。

この時点でVaultwardenへインターネット越しにアクセスできるようになった。

クライアント証明書の導入#
ここまでは良いものの、セキュリティのことを考えることに。 VPNも検討したが、
- IP制限ではスマホからアクセスできないのと外出先からも使用できるようにしたい
- VPNだと毎回繋がないといけない
ということを鑑みてクライアント証明書を導入することにした。
クライアント証明書を作成すれば、接続元を制限できるので証明書があるクライアントだけが接続できるようになる。
導入手順#
# 自前CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes \
-key ca.key \
-sha256 \
-days 3650 \
-out ca.crt \
-subj "/CN=inamuu vaultwarden client CA"
❯ openssl x509 -in ca.crt -noout -subject -issuer -dates
subject=CN=inamuu vaultwarden client CA
issuer=CN=inamuu vaultwarden client CA
notBefore=Apr 29 05:39:37 2026 GMT
notAfter=Apr 26 05:39:37 2036 GMT
---
# クライアント証明書
openssl genrsa -out vaultwarden_client.key 4096
openssl req -new \
-key vaultwarden_client.key \
-out vaultwarden_client.csr \
-subj "/CN=vaultwarden_client"
❯ openssl x509 -req \
-in vaultwarden_client.csr \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-out vaultwarden_client.crt \
-days 3650 \
-sha256
Certificate request self-signature ok
subject=CN=vaultwarden_client
❯ openssl pkcs12 -export \
-out vaultwarden_client.p12 \
-inkey vaultwarden_client.key \
-in vaultwarden_client.crt \
-certfile ca.crt \
-name "Vaultwarden Client"
Enter Export Password:
Verifying - Enter Export Password:
Ingressを分ける#
共有Ingressになっていたので、別々に分割。
vaultwardenのときのingressだけクライアント証明書を必須とする。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vaultwarden
namespace: apps
annotations:
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/auth-tls-secret: apps/vaultwarden-client-ca
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
spec:
ingressClassName: nginx
tls:
- hosts:
- "vaults.inamuu.com"
secretName: tls
rules:
- host: vaults.inamuu.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vaultwarden
port:
number: 80
❯ kc apply -f vaultwarden.yaml
persistentvolume/vaultwarden-data-pv unchanged
persistentvolumeclaim/vaultwarden-data unchanged
deployment.apps/vaultwarden unchanged
service/vaultwarden unchanged
ingress.networking.k8s.io/vaultwarden created
❯ kc -n apps get ingress vaultwarden
NAME CLASS HOSTS ADDRESS PORTS AGE
vaultwarden nginx vaults.inamuu.com 192.168.x.x 80, 443 26s
❯ kubectl -n apps describe ingress vaultwarden
Name: vaultwarden
Labels: <none>
Namespace: apps
Address: 192.168.x.x
Ingress Class: nginx
Default backend: <default>
TLS:
tls terminates xxx.xxx.xxx
Rules:
Host Path Backends
---- ---- --------
vaults.inamuu.com
/ vaultwarden:80 (172.16.x.x:80)
Annotations: cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/auth-tls-secret: apps/vaultwarden-client-ca
nginx.ingress.kubernetes.io/auth-tls-verify-client: on
nginx.ingress.kubernetes.io/auth-tls-verify-depth: 1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 32s (x2 over 37s) nginx-ingress-controller Scheduled for sync
証明書のインポート#
MacOSについては上記で作成したp12ファイルをキーチェーンアクセスで開けばOK.

iPhone にも同じp12ファイルを送ればOK。

プロファイルとしてダウンロードされるので、それをインストールする。
クライアント証明書が無い場合は、nginxから下記のようなErrorが返される。

しかし…#
Safariではうまくいったものの、ChromeやBitwardenアプリでは何度やっても接続できず。

あとで分かったのだが、BitwardenアプリがmTLSに対応していなかったのだった。
仕方が無いのでブラウザだけで使うかと思ったら、なんとタイムリーにも3週間前にiPhoneアプリでmTLSサポートのPRがマージされていたのだった。
まだリリースはされていないようだが、これがリリースされればそのままスマホでも使えそう。
構成図#
出来上がりはこんな感じになった。

k8sが含まれているのでなんとなく複雑なように見えるが、構成としてはシンプルである。
HTTPSプロキシとしてnginxで証明書を管理して、バックエンドのvaultwardenへアクセスしているだけである。
またデータは喪失しないように、NASにマウントされている。
RAID1ではデータの損失を防げないのでバックアップジョブを別途定期的に走らせようと考えている。
作ってみて#
Vaultwardenの構築とは関係ないk8s周りでErrorになった所とか、NASでNFSマウントのサービス有効化がどこか迷ったりしたものの、比較的スムーズに構築することができた。
TLS関連がハマらなければ、結構すぐに作れてしまうのではという気がしている。
ログイン画面もインターネットフェイシングではあっても、クライアント証明書が無いと駄目なようになっている上にMFAも有効化できているのがとても安全でありがたい。

あと、何が嬉しいってTOTPの管理もできるのが良い。
もちろんスマホでもできるんだけど、バックアップとして登録しておけば安心である。
※下記はテスト

数日かかるかなと思ったけどそんなことはなく、k8s側の調整とか証明書対応も含めて数時間でできたのでわりかしコスト少なくできたと思う。
パスワード管理ツールは沢山あるが脆弱性もよくあるので自前運用はリスクも抱えることになる。
そういう意味では割とセキュアにできたのは良かったと思う。
Cloudflareを使うとプライベートなサブネットのみで運用できるらしいが、Cloudflareに依存するのは避けたくて今回はこのような構成にした。
NASのDocker起動が思った以上に便利なので、これ以外にも色々導入していこうと思う。