恥ずかしながら、私はパスワードマネージャーというものをずっと避け続けてきた。
というのも、ここ数年、おそらく3, 4年はずっとVivaldiというブラウザを使い続けていて、パスワード管理はすべてVivaldiに任せっきりにしていたからである。

VivaldiにはVivaldi用のアカウントにログインすることでパスワードをデバイス間で共有する機能がある。まあ普通にApple IDとかGoogleでログインしておけばiPhoneなりGoogle Chromeが勝手にやってくれることと全く持って同じなのだが、昔から使い続けているそのソフトウェアを離れることは考えられなかったし、何より昔よりソフトウェアが大きくなって要求するスペックは高くなってきたものの、Vivaldiから別のブラウザに乗り換える選択肢は私の中にはなかった。最近はArcとかBraveとか流行りのブラウザがあるわけだが、LinuxでもWindowsでもMacOSでも同じユーザー体験を提供してくれるブラウザというのは実はそんなに無い。だからこそVivaldiを使い続けていたわけだが、まあなにはともあれ、Vivaldiをおそらく今後も使い続ける以上、パスワード管理をVivaldiに任せるのは自明の理だった。

しかし問題があった。私はVivaldiを使い続けすぎたのである。
約5年間のうちで私を取り巻くサービスたちは大きく様変わりした。毎日無料のFPSゲームにログインし続けたゲーマーも今では最終ログインが数年前、大学1年の頃に登録させられたWebサイトは当該の授業が終わるとともにアクセスしなくなった、あるサイトでは度重なるメールアドレス+パスワード変更により同じWebサイトにパスワードのサジェストが数件表示される始末 (しかも正解は一つしかない)。ひどいものではパスワードマネージャーが知っているパスワードが最新のパスワードではなく、私がデスクトップパソコンのDドライブの奥深くに配置したtxtファイルに最新のパスワードが記述されているという場合もある。
明らかにそのパスワードマネージャーに記録されたパスワードたちはレガシー化していた。

そこでだ、昨今では、というか本来はそっちが元来のパスワードマネージャーなのだが、ブラウザに依存しない、独立したパスワードマネージャーが存在する。有名所ではBitWardenや1Passwordなどである。
私は満を持して、そのような独立したパスワードマネージャーへの移管を心に決めたのである。

目次は以下の通り。技術や構築方法にだけ興味がある人は前半を飛ばしても構わない。逆に経緯にだけ興味がある人や、あなたの環境にこの方法が適するかどうかを判断したい人は前半部分だけを読むのが良いと思う。

  • 前半
    • VaultWardenについて
    • セキュリティについて
    • 構築するネットワークの概要
  • 後半
    • tailnetの構築
    • nginxの設定
    • VaultWardenのデプロイ
    • DNS (Cloudflare) の設定
    • 証明書の設定
    • パスワードの移管
  • おわりに

VaultWardenについて

といっても、単にBitWardenや1Passwordなどに移行するにしても懸念点がある。それらのサービスにパスワードを移管することの是非についてである。
4年以上Vivaldiにパスワードを預けていた輩が今更何を言うのかという話ではある。話ではあるのだが、この際パスワードは私企業ではなく、私自身で管理したいという欲求がある。じゃあ全部txtファイルなりExcelで管理すればいいだろという話ではあるのだが、過去にそれでパスワード管理に失敗した中学生の頃の記憶があるので、あまりやりたくない。

そこで目をつけたのがパスワードマネージャーサーバーのセルフホストである。

先程パスワードマネージャーとして例を上げたBitWardenはクライアントサーバー方式を採用している。難しい話は抜きにすると、クライアントとサーバーがあって、サーバーがユーザーのパスワード情報などを保持している。クライアントはサーバーにログイン情報を送り、認証されるとパスワード情報にアクセスすることができる。なんてことのないWebサービスである。BitWardenの面白いところは、そのサーバーのセルフホストができる点である。これはいわゆる企業向けに用意されたオプションのようなもので、社員にパスワード管理にBitWardenを使うように促し、社員が使うクライアントからは企業が用意したオンプレミスなりAWSのインスタンスの上に乗っかったBitWardenのサーバーにアクセスするという仕組みである。

しかし、このBitWardenのサーバーソフトウェアはインストールのためにインストールキーが必要なのである。企業としてそこに投資することは何も問題ないと思うが、高々一人、自分しか使わないのにインストールキーを買って、汗を流してサーバーを用意するのか?と言われると、それはちょっとやる気が失せる。

そこでだ、VaultWardenというOSSがある。これはそのBitWardenサーバーのOSS実装である。しかも本家BitWardenではお金を払わないといけない有料機能まで(セルフホストさえすれば)無料で使うことができる。
私はこのVaultWardenを自分で建てることにした。

セキュリティについて

次に問題になるのは、どうやってVaultWardenをホストするかということである。一応私が借りているVPS―post.yourein.netがホストされているサーバーだが―にはまだリソースの空きがあるにはある。しかし、Misskeyはアップデートを重ねるごとにリソース使用量を増やしているし、そのサーバー上で動いている別のサービスも存在する。それらのサービスを圧迫してまでそのVPS上にVaultWardenをデプロイすることは愚策であると思った。
また、セキュリティ的な問題もある。基本的にVaultWardenはdockerを用いてデプロイすることになるが、ご存知の通りdockerはiptablesの設定を誤るとdockerコンテナとbindされたポートが常にopenになってしまう。現にどこかのセキュリティコンテストでdockerコンテナがbindされたポートに直接アクセスしてネットワーク内部に侵入するシナリオがあったはずだ。どうせ私はまともにiptablesの設定なんてできないし、どこかでヘマをすることがわかっているので、VaultWardenのコンテナをVPS上に建てて公開したくない。

そこで考えられるのがVPNの中からしかアクセスできないようなサーバーである。これならiptablesでヘマをしてもダメージは少ないし、そもそもVPNに参加していないとサーバーのIPにアクセスできないわけだから、どのポートがdockerコンテナにbindされてopenになっているとか気にしなくても最悪良いわけだ。

構築するネットワークの概要

以上のようなことを考えた後、Tailscaleというサービスを使うことにした。
TailscaleはVPNをアカウント登録のみで構築することができるサービスで、裏ではWireGuardが動いている。
Tailscaleで構築されるVPNのことをtailnetと呼び、tailnetに参加したデバイスには当該のtailnet内で一意のIPが振られる。振られるIPは100.xxx.xxx.xxx、予約されたスペースを使っている。一応IPv6のアドレスや、device-name.hoge.ts.netみたいなドメインを振ってくれたりもするが、基本的にはIPv4のアドレスを使うことが多いと思う。

一応無料枠でも一つのTailnetにTailscaleのアカウントを3つまで紐付けられるので、自分の家族や友達を招待して、自分のtailnetの中に収容することもできる。また、無料枠ではtailnetに参加させられるデバイスの制限もあるが、100台までは行けたはずなのであまり気にすることはないと思う。

ということで、今回はオンプレミスのサーバー上でVaultWardenをデプロイし、そのサーバーにtailnet内からアクセスするというネットワークを構築してみる。ついでに、そのサーバーを私が所有しているyourein.netのドメインに紐付けてみる。これにより、あたかもインターネットに公開されたサーバーであるかのようにVPN内のサーバーにアクセスできるというわけである。当然、そのドメインに他人がアクセスしたところでVaultWardenのログイン画面にはアクセスできない。あと、所有ドメインのサブドメインでアクセスできるようにすることで後々オンプレのサーバー上に別のアプリケーションを建てたくなったときにこれと同じ手順でパッとサブドメインを切って別のサービスを生やしやすいという理由もある。

今回構築するネットワークは以下のようになる。文字が小さいので別のタブで画像を開いて見てもらうのが良いと思う。

常に同一のネットワーク上に存在するデスクトップPCなどからは、ローカルIPとポートを直接指定してアクセスし、持ち出したり、別のネットワークに接続される可能性のあるノートPCやスマートフォンからはDNSに登録したドメインからtailnetの内部IPを解決してアクセスする。
この際、全く関係ない他人のデバイスからも key.yourein.net は名前解決できるが、解決できたところでアクセス可能なサーバーが存在しないので実は問題ない。リダイレクト先をローカルIPにするようなものだと思ってもらえば良いと思う。

tailnetの構築

ということでtailnetを構築していくが、正直構築も何もない。アカウントでログインして、tailnetに参加させたいデバイスでtailscaleをセットアップするだけ。

nginxの設定

もしあなたが hoge.your-domain.com みたいな感じじゃなくて、100.xxx.xxx.xxx とか device-name.hoge.ts.net みたいな感じでアクセスできれば良いということならこのセクションは飛ばして良い。

とりあえずtailnet内から例の「Welcome to nginx!」のページが見えるまでを目指す。まあやることは Installing nginxに書いてある通りなので、その通りにやる。

nginxがインストールできて、http://localhostで「Welcome to nginx!」のページが表示されたら、次はtailnetに所属している別のデバイスから、今nginxを建てたサーバーのtailnet内のIPからアクセスしてみる。例えば、今nginxを建てたサーバーのデバイス名がserver、tailnet内のIPが100.1.2.3なら、http://100.1.2.3もしくはhttp://server.hoge.ts.netでアクセスしてみる。実際にはhoge.ts.nethogeの部分は何か別の文字列が入っているはずなので適宜置き換えする。

VaultWardenのデプロイ

dockerイメージがdocker hubにあるので、それを使う。色々オプションを指定したりすると思うので、compose.yamlを書くのが良いと思う。

compose.yaml公式のGithubにあるWikiとかを参考に書くと良いと思う。ただ、このWikiではリバースプロキシにCaddyを使っているので、その部分は今回は無視する。自分はこのWikiと、TailscaleとBitwardenで自分だけのパスワードマネージャー - izurinaの部屋という記事を参考にした。他にもtailscale + VaultWardenの組み合わせ自体は探せばいくらでも出てくるのでいい感じにcompose.yamlを書くと良いと思う。

一応私が書いたcompose.yamlを記しておく

services:
    vaultwarden:
        image: vaultwarden/server:latest
        container_name: vaultwarden
        restart: always
        environment:
            SIGNUPS_ALLOWED: "true"
            WEBSOCKET_ENABLED: "true"
            TZ: "Asia/Tokyo"
        volumes:
            - ./vwdata:/data
        ports:
            - 12345:80

余談だが、2024年のcompose.yamlにはファイル頭にversionを書かなくて良いということになっていてびっくりした。本当に久々にcompose.yamlを書いたんだ…

デプロイしたらlocalhost:12345にアクセスしてvaultwardenのログイン画面が出てくるかを確認する。確認できたらOK。

DNS(Cloudflare)の設定

ここのセクションもサブドメインでアクセスしないならいらない。

次にDNSの設定…の前にnginxのリバースプロキシの設定をしていく。
どうせあとでhttps化しないとWebVaultは使えないので、後からhttpsにリダイレクトすることを前提に適当にログイン画面が見えるようにするためだけのnginx.confを書く。

server {
    listen 80;
    listen [::]:80;
    server_name key.yourein.net;

    location / {
        proxy_pass http://127.0.0.1:12345;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_http_version 1.1;
        proxy_redirect off;
    }
}

これで一旦OK

次にCloudflareのDNS設定をする。
Cloudflare Dashboard > DNS > Recordsへ。

こんな感じにDNSの設定をする。予約されたIPアドレスに大してproxyを通すことはできないので、proxyは切ってDNS Onlyに。普通はDNS Onlyにしておくと「Proxy通したほうがいいよ!」と言ってくるけれど流石に予約IPに対してDNSを貼るような不届き者にはそういうことは言ってこない。というか、「proxyを通すことはできない」と言ったとおり、予約IPを入力してProxyをオンにしたまま設定を保存できない。

で、CloudflareのDNSの場合は設定して結構すぐに反映されるので、30秒くらい待って設定したドメインにアクセスしてみる。今回はhttp://key.yourein.netにアクセスしてみて、ログイン画面が出てきたらOK。

一応ユーザー登録画面に進むことはできるが、フォームを埋めても「httpsじゃないとユーザー作れないよ!」と怒られるのでここからはhttpsを通す作業になる。

証明書の設定

ここのセクションもサブドメインでアクセスしないならいらない。

https通信をするためにSSL証明書を取得する必要がある。
せっかくCloudflareを使っているし、Cloudflareの証明書を使うか!という気持ちになるが、残念ながらProxyを通していないサイトに対しては証明書が使えないので、素直にlet's encryptで証明書を取ることにする。一応オレオレ証明書で通すこともできるらしいが、私にはよくわからなかったので普通にレックリで取ることにした。

といっても、普通にいつものコマンドを打てばいいだけ…ではないので注意。

いつものレックリのコマンドはローカルにサーバーを建てて、インターネット上のサーバーと通信して証明書を取得する。このローカルにサーバーを建ててというところが問題で、今回のケースではローカルにサーバーが建ったところでインターネット側から疎通できないのである。ということで別の方法で証明書を取得する必要があるが、これは意外と簡単にできる。DNS-01チャレンジを行う。

詳しい方法はここでは解説しないが、まあいい感じにコマンドを打つと意味ありげな文字列がコンソールに出てくるので、その文字列をCloudflare Dashboardで_acme-challengeという名前でTypeをTXTにして登録する。30秒ぐらい待ってEnterキーを押下すると、ちゃんと設定がされていれば証明書が取得できる。

ただし、このやり方は証明書の自動更新ができないため、また90日後あたりに同じ手順で証明書を取得し直す必要があることに注意が必要である。私は60日ごとにGoogleカレンダーの予定が追加されるようにイベントの設定をしておいた。

証明書が取得できたら、今度はnginxの設定ファイルをhttps用に書き足していく。正直nginxのことをよく理解しているわけではないので、よくhttpsサーバーを建てるときに使うオプションを適当に列挙している感じなので、あまり下の設定は参考にしないほうが良いと思う。

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name key.yourein.net;

    root /var/www/html;
    location /.well-known/acme-challenge/ { allow all; }
    location /.well-known/pki-validation/ { allow all; }
    location / { return 301 https://$server_name$request_uri; }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name key.yourein.net;

    ssl_session_timeout 1d;
    ssl_session_cache shared:ssl_session_cache:10m;
    ssl_session_tickets off;

    ssl_certificate /your/domain/publickey.pem;
    ssl_certificate_key /your/domain/privatekey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {
    	proxy_pass http://127.0.0.1:50000/;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_http_version 1.1;
        proxy_redirect off;
    }
}

ここまで設定できたらnginxのserviceをrestartして、https://key.yourein.netでアクセスしてみる。アクセスできて、証明書の警告などが表示されなければOK。

パスワードの移管

ということで、サーバーが建ったので、次にパスワードを移していく。

なんにせよユーザー登録をしないといけないので、それは適宜やってもらうとして、パスワードはインポートができるので各自でいい感じにエクスポートしておくと良いと思う。Vivaldiの場合は設定画面のパスワードセクションにパスワードをエクスポートするボタンがあるので、そこからできる。中身はChromeのやつと同じフォーマットを使っているので、多分Chromeにも似たようなオプションがあるのだと思う。

これでパスワードの移管自体は終了。あとはブラウザの拡張機能なり、スマホアプリなどからログインすれば良いだけである。ログインの際はログイン先のサーバーを「セルフホスト」にして、自分で建てたサーバーのドメインを入力する。ちゃんと設定できていれば、普通に疎通するはずだ。

心配なら、この時点でtailnetに入っていないデバイスからhttpsでアクセスをしてみると良いと思う。

おわりに

ずいぶん長い記事になってしまったが、無事VaultWardenをセルフホストすることができた。これにより、アカウント管理がより柔軟になったと同時に、パスワード管理がネットワーク的にセキュアになったのが良かった。さらに自分向けのサービスを無限にtailnet内で独自ドメインを用いてホストできるということもわかったので、今後の活動の幅が広がりそうだと思った。

この記事の一連の活動はリアル1週間半程度の期間に設定した負債返却アワーの取り組みであった。久々にデカイ負債を返却することができたので、個人的にはかなり満足である。ということで、夏休みの自由研究報告的な意味も込めて、今回は一連の流れを記事にしてみたということだ。この記事が誰かの参考になったら嬉しい。