メインコンテンツにスキップ
エンジニアリングセキュリティapi
ノンカストディアル取引の設計:APIキーの権限範囲と、出金には決して触れないこと

ノンカストディアル取引の設計:APIキーの権限範囲と、出金には決して触れないこと

「ノンカストディアル(非保管型)」は、たいていマーケティング用語として使われます。しかし正確に用いれば、それはアーキテクチャ上の制約であり、この種の制約が興味深いのは、仕事を増やすのではなく減らすからです。

取引ボットは、あなたに代わって注文を発注する必要があります。取引所はAPIキーによってそれを可能にし、APIキーには権限範囲(スコープ)があります:読み取り、取引、そしてほとんどの取引所では出金です。ですから設計上の問いは、権限を要求するかどうかではありません。どの権限を要求するか、そして要求した以上のものを受け取ったときにどうするか、です。

要点

  • 権限を使わないという約束は、その権限を持たないことよりも弱い保証です。私たちは出金権限を持つキーを、受け入れて無視するのではなく、拒否します。
  • 拒否することは信頼よりも強い保証です。なぜなら、それは私たちのコードが正しいことや、私たちのチームが誠実であることに依存しないからです。
  • 接続フローがユーザーの口座に書き込みを行う場合、その書き込みは受け入れの判断より前ではなく、後に行われなければなりません。さもなければ、拒否を伝える途中で相手の設定を変更してしまいます。
  • 資金を保管しないことは、難しい問題を丸ごと一群消し去ります:保管台帳も、照合作業も、出金キューもありません。
  • その代わりに残るのは、より狭く、より鋭い要求です:一度行ったことを取り消せない以上、システムはリアルタイムで正しくなければなりません。

1. 権限を拒否することは、使わないと約束することに勝る

明白な実装方法は、読み取りと取引の権限を持つキーをユーザーに求め、出金エンドポイントを一切呼び出さないというものです。業界の大半はこの方法をとっており、それは不合理ではありません。

私たちはより厳格なことを行います。出金権限を持つキーが届いた場合、それは拒否されます。ユーザーには、何をすべきかを正確に伝えるメッセージが表示されます:

このAPIキーは出金権限が有効になっています。Freyaは資金を出金することが一切ないため、その権限を持つキーは受け入れられません。読み取りと取引のみのキーを作成してください。

同じルールが、私たちがサポートする3つの取引所すべてに適用されます。上書きも、管理者による例外もありません。

一見すると、これは無用な摩擦のように見えます。そのキーは動作するはずです。ユーザーは手間をかけてそれを作成しました。それを拒否するということは、別のキーを作るために取引所へ戻ってもらうことを意味します。

その理由は、2つの設計が異なる約束をしており、その違いは最悪の日にしか見えてこないからです。

  • 「私たちは出金エンドポイントを決して呼び出さない」というのは、私たちの振る舞いについての主張です。それは、私たちのコードが正しく、依存関係がクリーンで、デプロイ権限を持つ全員が誠実であり続ける限り成り立ちます。それらは期待して妥当なことです。しかし、ユーザーが検証できることではありません。
  • 「そのキーは出金できない」というのは、キーについての主張です。それは私たちのコードが何をしようと成り立ちます。なぜなら取引所がそれを強制し、ユーザー自身が自分の取引所の設定で確認できるからです。

後者は、より弱い要求でありながら、より強い保証です。安全性が自分以外の誰かによって強制される設計を選びましょう。とりわけ、ユーザーがあなたの側を監査する手段を持たない場合はそうです。

実務上重要な、もう少し小さな利点もあります:それは失敗を目立たせるということです。権限が広すぎるキーは接続時に捕捉されます。ちょうど設定作業中で、1分で直せるユーザーの目の前でです。もう一方の選択肢では、権限過剰なキーがそのまま問題なく動き続け、それが重大な事態になるまで誰も何も気づきません。

2. 何かに触れる前に「ノー」と言う

これは、他のエンジニアに最も持ち帰ってほしい細部です。なぜなら間違えやすく、しかも間違えてもほとんど目に見えないからです。

取引所への接続は、純粋な読み取り操作ではありません。取引所によっては、口座を使える状態にするために書き込みが必要になることがあります:口座モードの調整、ポジションモードの設定などです。これらはセットアップの一部として行われる正当な手順です。

ここで順序を考えてみましょう。もし権限チェックがそれらの書き込みの後に行われるなら、拒否されたキーであっても痕跡を残します。ユーザーは認証情報をあなたに渡し、あなたはその口座の設定を変更し、そのうえで拒否を伝えたことになります。ユーザーの側からは、なぜ取引所の設定が自分が残したときと違っているのか、何も説明がつきません。

そこで私たちが定めたルールはこうです:拒否される認証情報は、ユーザーの口座を見つけたときのまま、そっくりそのまま残さなければならない。 書き込みを行う手順はすべて、受け入れの判断より後に置かれるべきであり、決してその前には置きません。拒否は副作用なしに到達可能でなければなりません。

一般化すれば、これはトランザクション的な考え方を他人のシステムに適用しているだけです。サードパーティの口座への書き込みはロールバックできないので、順序こそが唯一の防御手段です。取り消せないなら、順序を慎重に組みましょう。

まる一群のバグから私たちを救った実装上のメモがあります:拒否は特別なフラグとしてではなく、無効な結果として表現されます。そのため、検証結果を受け取るあらゆる経路が、その存在を知らなくてもそれを処理できます。私たちには2つの接続フローがありますが、どちらも同じ拒否を継承します。なぜなら、どちらもそれについて尋ねることを覚えておく必要がなかったからです。すべての利用側がすでに読んでいる型にルールを埋め込むことは、それを文書化して期待するよりも優れています。

3. その制約が消し去るもの

ここで、このトレードオフが明らかに有利に転じます。

資金がユーザーの取引所口座から出ていくことは決してありません。ボットは読み取りと取引の権限のキーで取引し、残高は最初にあった場所にとどまります。そこから導かれるのは、私たちのシステムには単純に存在しないものの長いリストです:

  • 保管台帳がないので、内部残高と外部残高の照合もありません
  • 出金キューも、承認ワークフローも、取引資金に対する日次上限もありません
  • その資金のためのホットウォレットとコールドウォレットの分割もなく、それを取り巻く鍵管理もありません
  • 会社が消滅した場合にユーザーの資金がどうなるか、という問いもありません

これらはどれも、カストディアル(保管型)の製品では丸ごと1つのサブシステムであり、そのどれもが資金が失われうる場所です。それらがここに存在しないのは、私たちが解決したからではなく、それらを生み出すものを最初から引き受けなかったからです。

ただし、大雑把にではなく正確に言いたいのですが:これは取引資金に当てはまります。サブスクリプションに使われるプラットフォーム残高は別の話であり、独自の扱いがあります。ノンカストディアルという性質は、取引されるお金についてのものであり、それこそが最も重要なお金です。

4. それが解決しないもの

問題を取り除く制約は、間違いを直すために使うはずの道具も同時に取り除きます。その点について正直であることには価値があります。

取引は最終的です。 保管型であれば、内部送金は原則として取り消せます。他人の取引所口座で約定した注文は取り消せません。エンジニアリングの重みはすべて、リクエストが着いた後に照合することではなく、リクエストが出ていく前に正しくあることへと移ります。それは聞こえるよりも難しい規律であり、私たちの作業の多くが検証、精度、そして冪等性に関するものである理由です。

取引権限もれっきとした権限です。 漏洩した読み取りと取引のキーは、人々が実際に恐れる結末である、口座から外部アドレスへ資金を抜き取ることはできません。しかし、注文を発注することはできます。影響範囲は限定されていますが、ゼロではありません。それをゼロだと表現するのは不誠実でしょう。

ユーザーにも依然として責任があります。 キーのローテーション、サービス間でキーを使い回さないこと、そして同じ口座に他に何がアクセスできるかを見張ることは、ユーザーの責任のままです。私たちのアーキテクチャは被害を限定しますが、キーを無害にするわけではありません。

別のチームに伝えたいこと

  • 最も狭い権限範囲を要求し、そのうえで実際にそれを得たか検証しましょう。 より少なく要求することは、より少なく受け取ることと同じではありません。
  • 自分のコードの外側で強制される保証を選びましょう。とりわけ、ユーザーがあなたを信頼せずに確認できるものを選びましょう。
  • 拒否を副作用のないものにしましょう。 フローがサードパーティの口座に書き込みを行うなら、書き込むものはすべて、受け入れの判断より後に置きましょう。
  • 利用側がすでに見ている場所にルールを埋め込みましょう。 共有された型で表現されたルールは、すべての呼び出し側がそれを覚えている必要がありません。

より大きな要点はこうです:「私たちはXをしません」と「私たちはXをできません」は、ランディングページ上では似たように読めますが、インシデントのときにはまったく別物です。約束を前者のカテゴリーから後者へ動かせるときはいつでも、そのコストはたいてい小さく、その見返りは、他のすべてがうまくいかなくなったまさにそのときに現れます。


関連記事

学んだことを実践しよう。

無料アカウントを作成し、戦略をテストして、最初のボットを起動しましょう。

無料で始める
このコンテンツは教育目的のみであり、金融アドバイスではありません。仮想通貨取引には重大なリスクが伴います。失っても許容できる金額以上の投資はしないでください。