いしぐめも

プログラミングとかしたことを書きます。

WebDAVをマウントするための代理で認証を行うプロキシを書いた

PersoniumのBox(データ保存領域)はWebDAVという仕様を踏襲しており、 単純なファイル操作などはWebDAVのライブラリを流用することができます。

リモートのWebDAVサーバーをあたかもローカルのファイルのように振舞わせるようなクライアントアプリが世の中にはあり、 それを使うことでPersoniumのデータはもっと便利に扱うことができます。

しかし、使うには認証周りに問題があり、以下のような課題がありました。

課題:Basic認証やDigest認証でないケースへの対応

一般のWebDAVサーバーで、BASIC認証をして、ファイルを読み書き という方式はどうやらメジャーなようで、検索すると複数のクライアントアプリが対応しているようでした。

しかし、Personiumは事前にユーザー名・パスワードを使用して認証・アクセストークンを取得して(ROPC)、取得したアクセストークンを Bearer <ACCESS_TOKEN> という形式でAuthorizationヘッダに指定する方式 を取ります。こういったケースの場合、一般的なWebDAVクライアントアプリが対応していることは望み薄です。

解決法①:WebDAVクライアントを1から作る

この解決策をちょっと考えたこともあるにはありました。世の中にはWebDAV用のクライアントライブラリのOSSを開発している人がいて、それらをお借りすることで自分好みのクライアントアプリを作成することができます。が、まぁ1から作るってことでかなり時間はかかりそうです。

解決法②:認証を代替するサーバーを立てる

WebDAVというプロトコルは結局HTTPなので、HTTPをプロキシするサーバーを立て、ヘッダを弄ったり向き先を変えたりすることができます。

今回はそれを応用して、localhostをリッスンするサーバーに対してリクエストを送ると目的のWebDAVサーバーに対してAuthorizationヘッダを付与してリクエストしなおす、ということをやってみることにします。

今回作成したもの

今回はDenoで実装してみました。Denoは最近出たJavaScript/TypeScriptのランタイムですが、パッケージ周りでnpm依存がなくなっていて、従来のnodejsに比べて配布が楽になっています(個人の感想)

github.com

実行コマンド

deno run --allow-net https://raw.githubusercontent.com/yoh1496/personium-webdav-proxy/main/index.ts <CELL_FQDN> <CELL_USERNAME> <CELL_PASSWORD>

実行コマンドはこんな感じです。denoがインストールされている環境であれば、スクリプトをローカルに手動でダウンロードする必要がなく、スクリプトのソースをURLで指定することができます。クール!

本来であれば、mainブランチのソースではなく、Releaseなどで管理すべきなんでしょうが、それは改版するようなことがあれば考えたいと思いますw

ちょっと解説

今回はオシャレに for await なんて使ってしまいました。こうすると、サーバーにリクエストが来るまで待機し、来たら順次処理を行っていくというコードが書けます。スゴイ。

const s = serve({ port: LISTEN_PORT });

/** 中略 */

for await (const req of s) {
  // リクエスト内容を弄る
  const tokens = await adapter.getToken();
  const { access_token } = tokens;

  const header = new Headers(req.headers);
  header.set("Authorization", `Bearer ${access_token}`);
  header.delete("host");
  // リクエストを目的のWebDAVサーバーに対して投げる
  /** 中略 */
  // レスポンスを返す
  /** 中略 */
}

また、adapter.getToken() を実行して、WebDAVサーバーに向ける新しいリクエストのヘッダにアクセストークンを設定するわけですが、ここにトークン取得のロジックを色々書きました。例えば、getTokenが呼ばれた際に有効期限が切れていそうだったらトークンを取り直す、とか。重複してトークンを取りに行こうとしたら前に実行したリクエストが終わるのを待ってその結果を参照するようにする、とか。認証方式を変えたりする場合はココを編集してください。

使い方

これはあくまで、「認証を代理で行うツール」なので、クライアントアプリと併用して使用します。

今回は以下のような3段構えの構成を取りました。

f:id:yoh1496:20210830193849p:plain
三段構え

まず、エディタで開くために、フォルダとしてマウントします。これにはWindows 10に標準で備わっている機能を使っていきます。

f:id:yoh1496:20210830194056p:plain
WindowsWebDAVをフォルダとしてマウントする機能がある

この機能、便利なんですが、実装がどうやらPersoniumとは合っていないらしく、バグって使い物になりません。そこで救世主的に出てくるのが CarotDAV です。

麗の小屋 - WebDAV Client CarotDAV -

このツール、単体でもファイルエクスプローラとして使えて便利なんですが、

f:id:yoh1496:20210830194557p:plainf:id:yoh1496:20210830194604p:plain
エクスプローラーとして便利

なんと、簡易WebDAVサーバーの機能も備えており、このサーバーに対してWindowsの機能でマウントを試みると非互換が吸収されているらしく(?)、無事エクスプローラーで閲覧することができました。(作者のホームページに載っている通り、Windowsのクライアント実装的に限界がある模様)。

そして、CarotDAVの向き先を今回作成したプロキシツールに向けることで、アクセストークンを使うタイプの認証も突破できるというわけです。

終わりに

以上、WebDAVの認証を代理で行うプロキシサーバーを実装してみた、という記事でした。

やりたかったことは、VSCodeを使ったリモートWebDAVサーバーのファイル編集なので、以下のような拡張機能もいいなぁと思ったんですが、

marketplace.visualstudio.com

どうも上記のような拡張機能(仮想ワークスペースというらしい)はESLintなどの他の拡張機能が使えなくなるようで、スクリプトも編集したいなぁと考えていた自分には合わないシロモノでした。

記事内のやり方の通り、Windowsファイルシステムとして振舞えるようにすることで、VSCode拡張機能を使いつつ編集することが可能になりました。ひとまずはこれでOKとしたいと思いますが、リモートのファイルが他の要因で書き換わり、ローカルの内容と食い違ってしまった場合にどういう挙動を示すのかはまだ確認できていないため、問題が発生するようだったら対処を考えたいと思います。

f:id:yoh1496:20210830200051p:plain
拡張機能がconfigファイルをチェックしまくる図

・・・あとは拡張機能が存在しないconfigファイルをチェックしまくってサーバーにリクエストが飛びまくるので、DropBoxのクライアントのようなリモートとローカルのフォルダを同期させられるようなソフトがあればこれも解決できないかなぁと思ったり。