限定公開URLについて適当に書く

過去に限定公開URLっぽい機能を実装したので、その時考えていたことを書こうと下書きを始めてから1年以上経ったものを手直しして公開しています。具体的なコードとかは出てきませんし、終始フワフワした感じで歯がゆい感じかもしれませんが、ごめんなさい。

はじめに

まず、限定公開URLについては、渋川よしき先生のスライドが詳しいです。特にURLに使う識別子(?)の長さにおけるセキュリティ強度の各サービスの比較と検討に関するページは貴重なまとめだと思います。 (URLの性質を考えればできることは知っていたが、Capability URLといった呼び方があることを知ったのがこのスライドでした)

docs.google.com

またスライドでも触れられているW3CのCapability URLのデザインに関する記事もわかりやすいです。 どういうものか、使い時、注意点といった話もすべて書かれています。

w3ctag.github.io

ここを読んでおけば何かしら指針は見えるはずです。以上です。ご清聴ありがとうございました。

さて本記事では、限定公開URLについて私が雑に書き、IDベースの実装、署名を使った実装について書きます。おまけに限定公開URLを短いURLにしたい場合の話も少し書きます。

限定公開URLの意図

https://w3ctag.github.io/capability-urls/2014-07-23.html#advantages

W3Cの記事にも書かれている通りなんですが、私は「URLを知っていることを認可に使いたい」という要件があるのだと思います。

あまりこの言葉だとピンとこないと思いますが、具体的なサービスの利用例を見るとすぐにわかるかと思います。カッコ内は「URLを知っている人に~を認める」という感じで認可っぽい言い回しにしてます。

  • パスワードリセット(ユーザAのパスワードを変更することを認める)
  • Discordの招待(サーバへ参加することを認める)
  • Youtubeの限定公開(視聴できることを認める)
  • Google docs等の限定公開(閲覧できることを認める)
  • Wandbox, CodeSandbox, JsFiddleなどのオンラインなサンドボックスサービスのコード共有(コードを閲覧できることを認める)
  • Miroなどのツールのコラボレート機能(閲覧・編集できることを認める)
  • zoomのミーティング(ミーティングに参加できることを認める)

このほかにもたくさんあるでしょう。もう1サービスにつき限定公開URLを使った仕組みは1個はあるんじゃないでしょうか。

もちろん、意図していないがURLを知らないとアクセスが困難になっているケースがあります。例えばTwitterの過去のツイートは頑張って発掘するかしてURLを知るという流れになります。まぁこれは違うことは分かるでしょう。

限定公開URLの注意点

限定公開URLを知られたら誰でもアクセスできてしまう点でしょう。URLを知っている人はURLを共有した人だけとは限りません。上で「認可」と言いだして「こいつ何を急に通ぶってんだ?」という感じになったのはこの点があるからです。こんなこと言われるまでもないとは思いますが。

これはW3Cの4章あたりにも書かれている通り、意図せず不特定多数な人に漏れることとかを想定したほうがよいということです。うっかりURLを共有してしまうケースや、今どきなら画面共有の映り込みなどが上がるでしょう。(スタバとか電車内といった出先で作業している人らは他人に見られた時にどうなるかというリスクを考えてなさそう

後は単純に限定公開URLの実装が脆弱だと、総当たりなどで正規なURLを見つけられてしまう可能性があります。

限定公開URLは馴染み深い存在になってきていますが、このリスクは昔から変わっていません。本当に必要かどうかを含めて考え、リスクの軽減措置(期限を付ける、失効できるようにするなど)は検討したほうがよいでしょう。Microsoft 365の共有URLはクソ長いURLにして画面に収まらないようにすることでリスク軽減しています。これは要件によるので明確な答えはないでしょう。

一方でそれをあまりにも気にしすぎると限定公開URLを使えなくなってしまいます。利便性とのバランスを考えてみましょう。そもそも、大事な会議をzoomのミーティングURLをクリックして始めたりしていますよね。もし知らん人が入ってきてもミーティングを作り直すとか運用でカバーできます。それぐらいのレベル感でいいんじゃないでしょうか。悩み過ぎると禿げますよ。私は禿げた。

どう実装するか

色々見て感じた結果としては、IDか署名を使う感じかと思います。もちろんデメリットがありますが、小手先の技で改善を狙うといったカスタマイズはできるでしょう。

  • ID
    • 永続的なURLを払い出したい時(ブックマークできるコンテンツなど)
  • 署名
    • 一時的なURLを払い出したい時(画像の配信、一時的なコンテンツの配信など)

ただ結局は要件次第なので、他の事例を比べながらいい塩梅の落としどころを見つける必要はあるかと思います。

IDベース(トークンベース)

何かしらID(トークン)を払い出し、そのIDをサーバサイドで管理する方法でしょう。

  • メリット
    • 単純?
  • デメリット
    • IDの設計
    • IDの衝突時の考慮
    • コンテンツのIDとは別に限定公開用のIDを払い出しデータストアで管理する必要がある
    • データストアに突き合せることによる負荷

この方式はIDをどう払い出すかが大事になってきます。IDが漏れたら限定公開URLの信頼性が失われるからです。

渋川よしき先生のスライドにも書かれているように、どれぐらいの長さにすべきか明確な指針がないという課題があります。というのも扱う要件にもよりますから長さが充分かどうかは要件次第です。とにかく、W3Cの記事にも書かれているように、推測困難な形が望ましいです。

UUIDといったメジャーなID生成のロジックを借りるのも手です。この辺りkawasimaさんのID生成大全を参考にするとよいでしょう。限定公開URLにおいては順序性や生成速度は不要で推測困難性が重視されるでしょう。

qiita.com

また、IDで管理する仕組みを取る場合、どうしてもIDとコンテンツを対応付けるデータストアが必要で、リクエストの都度アクセスしないといけません。限定公開URLの性質から通常利用で高負荷になることは無いと思いますが、総当たりでリクエストを投げてくる不逞な輩に対しては耐性がありません。緩和策を狙いたい場合、ちょっとしたパリティを仕込んでみるのもいいかもしれません。ただパリティは生成の傾向を見るとバレるので過信はできません。でもこういうのは後から入れると過去IDの扱いをどうするかを考える必要があり面倒ですし、小手先で出来ることなので突っ込んでおくのもいいと思います。本当に小手先であまり役立たずに終わる可能性もありますが…。

署名方式

HMACなどで署名をURLに付ける方式です。

  • メリット
    • データストアの負荷軽減
    • コンテンツに対して別のIDを振らなくてよくなる
    • パラメータでちょっとした制御ができる(期限、読み書き権限、IP制限など)
  • デメリット
    • 失効について検討が必要
    • URLが長くなる
    • 署名に使う鍵の管理が面倒(どう連携するか、ローテーションをどうするか)

Azure Blob StorageではShared Access Signatures (SAS)という、Blob Storageに対して「どのprefixに対して、どのIPが、読み書きできるか」などのパラメータを付与して署名を生成し、それをURLにくっつけて操作用のURLを払い出すという仕組みがあります。

Cloudflareも画像配信に署名付きのURLを発行するといったことができます。これも有効期限などの情報がURLに含まれておりそれを署名しています。署名は署名に使う鍵がわからないと作れないため、これで外部サイトからの直リンクが不可能になります(仮に直リンクしても有効期限内しか見れない…直リンクって伝わるのかな…)

これは限定公開URLに使えそうだと思って、過去に実装した時はこれを採用しました。

例えば、読み取り専用のURLと読み書きできるURLを払い出したいとき、署名ベースならパラメータ一つで表現できます。IDベースだと2つIDを払い出して管理することになります。

署名の簡単な実装は秘密鍵(共通鍵)を用意し、主要な情報(署名を除いたURL)をHMAC-SHA256などで署名してURLにくっつけてやることでしょう。共通鍵の管理のめんどくささはあったりURLが長くなりがちですが、ステートレスな管理ができます。

データストアに持たなくてもパラメータで必要な情報が揃うのでデータストアが不要となります。もちろん署名計算のコストはかかりますがデータストアの通信のほうが圧倒的に遅いので許せるはずです。今の時代ならCDNエッジとかにオフロードもできます。

ただ、デメリットはあります。

秘密鍵が漏れたケースを想定してローテーションすることを検討する必要があります。秘密鍵を変えた場合、過去に発行した限定公開URLはすべて失効することとなってしまいます。なので永続的なURLに価値がある場合は署名を使う方式は向いていません。

また、URLが漏れた場合に失効ができない点も要件によっては厳しいでしょう。署名の場合はそのコンテンツが公開中であるかどうかのデータストア上で状態管理をする必要があります。

また、既にritou先生が「それJWT(JWS)でできるよ」と言う話をされております。

Capability URLsをBearer Tokenと捉えた場合のJWT適用の可能性 - r-weblife

Capability URLs も Bearer Token の要件に近いのではないか

という記述をみて「あー、そうかも」と思いました(小並感)

JWTに関しては何度も話題に上がっており皆さん大好きだと思いますので説明はしません。

JWTやJWSは様々な言語でライブラリが提供されていますので、それに乗っかれるメリットはあるでしょう。 ただ個人的にはオーバーキル感を感じていて、URLをHMACで署名したりURLを分解して署名を検証したりするぐらいなら標準ライブラリでも実装してしまえる範囲だと思うので「そこまでせんでも…」という感じです。(それでみんな好き勝手やっていたから規格化されたと思いますが…)

やり方は1つじゃない

IDと署名の方法を書きましたが、上記の通り、要件にマッチしないところを少し拡張したり妥協して補ったりすることは可能だと思います。

他にも画期的なやり方があるかもしれません。教えてください。

おまけ:短さを求めたい

私の結論を言えば、短さを優先するとセキュリティ強度はどうしても落ちます。なのでもし短さに価値がある場合は、意図しない誰かにアクセスされることを想定し、問題がある場合はその問題を軽減する施策があるか検討し、現実的でない場合は「短さ」諦めたほうがいいのではないでしょうか。

まず公開されても問題ないものの具体例として、Stackoverflowの回答のシェアに使われるURLです。割と短く数値のみで構成されています。質問も回答も普通に見れる公開された情報なのでそれに対して短いURLを使うのは何の問題もないでしょう。(しかしこれは限定公開URLと言えないでしょう)

他にもDiscordの招待URLも比較的短いです。招待URLを知ったら誰でもサーバーに入れてしまうのでしょうか? もちろんそんなことはなく、期限を指定、一時的なメンバーとして招待などのようにリスクに対する対策を取っています。

ngrokやcodesandboxは短さを優先しています。ngrokはそもそも起動のたびに変動しますから長期間同じURLで公開し続けることは無いのでリスクは低いと判断しているでしょう。codesandboxは意図しない誰かに見られるかもしれないよりも簡単に別端末からアクセスできるような短いURLのほうが価値があるからでしょう。

「セキュアにしつつ短くしたいなー」と考えているときはクレジットカードを想像してください。クレジットカードは数字16桁のカード番号の他に、セキュリティコード、暗証番号、所有していることによる認証、3Dセキュア、数回でロックアウト等のように色々やっています(それでも被害は出続けておりセキュアとは何かわからなくなりそうですが)。機能性の違いもありますが、とにかく短くしたいというなら相応の対策は必要です。

今どきはURLの共有手段はあふれています。チャットなどを通してコピペで共有できるケースも多く、QRコードで伝えてもいいでしょう。本当に短さは必要でしょうか?

それでもやはり短くしたい場合は短くするとよいです。

例えばシーケンス番号で扱い、URLで扱えるBase64 URLエンコード, 記号を含まないBase62, 識字のしやすい(?)Base32で符号化するとかなり短くなります。順序性(規則性)が気になる場合はある程度ビットを入れ替えたりXORしたりして分かりにくくするしかないでしょう。ロジックがバレるとお終いなのであまり意味はないですが。

そういえば、限定公開URLとは関係ないですが過去に「なるべく短くてユニークなIDを作りたかった - 日々量産」という記事を書いていたことを思い出しました。何の面白みもない内容だ。

おわり

何か書き足りない感じはしますが、思いつかなかったのでここまで。文章を書くのが下手で辛い。