0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIからも人間からもアクセスできるNASなS3をさくらレンサバにつなげた話 #3

0
Last updated at Posted at 2026-06-04

公開はするが、誰にも生では触らせない — セルフホストS3の多層防御という設計判断

はじめに

自宅NAS上のセルフホスト S3互換ストレージ(Garage)を外部公開するにあたって、「公開はするが、誰にも生のままでは触らせない」という方針で多層防御を組みました。

この記事は、手順やコードではなく なぜその積み方にしたのか、設計判断の理由を書きます。具体的な構築手順は別記事(インフラ編・アプリ実装編)にあります。

結論を先に言うと、守りは次の積層になりました。

  1. Cloudflare Access — 誰がそのホストに到達できるか(IPと、必要ならSSO)
  2. Garage の S3署名(SigV4) — 到達した先で、鍵を持つ者だけが操作できる
  3. 自前API + presigned URL — クライアントに生の鍵を渡さず、短命の署名URLだけを配る

それぞれが独立した関門で、1枚破られても次がある。そして重要なのは、各層が「何を守るか」を明確に分担していることです。

出発点の問い:公開と秘匿は両立するか

セルフホストのオブジェクトストレージを外から使いたい。でも素朴にポートを開けて公開すれば、それは攻撃対象です。かといって完全に閉じれば、外部のツールやクラウド上のアプリから使えない。

ここで陥りがちなのが「秘匿していれば安全」という発想です。URLを知られなければ大丈夫、というのは防御ではありません(security by obscurity)。実際、本構成の S3 エンドポイントは Cloudflare 配下でインターネットに出ています。隠していません。隠す代わりに、到達できても何もできないようにする方針を採りました。

「公開 vs 秘匿」の二択ではなく、「公開した上で、各層でクレデンシャルを要求する」。これが出発点です。

層1:Cloudflare Access — 「誰が到達できるか」

最前段は Cloudflare Access(Zero Trust)です。ここで「そもそもそのホスト名に到達できる相手」を絞ります。NAS のポートはルーターに一切開けず、Cloudflare Tunnel の outbound 接続だけで公開しているので、攻撃者が直接NASを叩く経路は存在しません。すべて Cloudflare のエッジを通ります。

Allow と Bypass — ここが一番の設計判断

Cloudflare Access のポリシーには Allow と Bypass があり、この使い分けが本構成の肝でした。

  • Allow = 条件(IP等)に合う相手を通すが、ログイン(SSO)を要求する
  • Bypass = 条件(IP/CIDR)に合えば、ログイン無しで素通りさせる

そして決定的なのは、守る対象が「アプリ自身の認証を持つか持たないか」で、どちらを使うべきかが変わるという点です。

S3 API → Bypass

S3 API のホストは、Garage 自身が S3署名(SigV4)という認証機構を持っています。つまり認証は Garage に任せられる。ここで Access に SSO ログインまで要求させる(Allow)と、curl や自動化ツール、presigned URL での直アクセスがログイン画面に弾かれてしまいます。

なので S3 側は 許可IPを Bypass(素通り) にして、認証は後段の Garage 署名に委ねる。Access は「許可IPかどうか」だけを見る門番に徹します。

これは実際にハマったポイントでもあります。当初 Allow にしていて、IPが一致しているのに presigned URL が 302 でSSOログインに飛ばされ続けました。Bypass に変えて初めて、署名付きURLが素通り→Garage の署名ゲートに到達、という意図どおりの挙動になりました。「Access の 302 が消えて、Garage の 403 が返る」が、正しく到達した合図です。

管理UI → Allow + SSO

一方、Garage の管理UI(サードパーティの garage-webui)には、アプリ自体にログイン認証がありません。素のまま公開すると、URLを知る者は誰でもバケットやキーを操作できてしまう。

ここでは認証を Cloudflare Access に肩代わりさせます。許可IP「かつ」特定ユーザーのSSOログイン(Allow + ユーザー固定)という二重の門。管理UIは鍵そのものを発行・閲覧・削除できる最も危険な画面なので、S3 API より一段厳しくするのが筋です。

対比:Access の役割は固定ではない

S3 API 管理UI
アプリ自身の認証 あり(S3署名) なし
Access の役割 門番(IPを通すだけ) 認証本体
ポリシー Bypass Allow + SSO

同じ Cloudflare Access でも、対象のアプリが認証を持つかどうかで役割が真逆になる。 認証を持つアプリには「素通りの門番」、持たないアプリには「認証そのもの」。これが今回いちばん腑に落ちた設計判断でした。認証のないセルフホストアプリを、ソースに一切手を入れず安全に公開できる、という汎用パターンでもあります。

層2:Garage の S3署名 — 「鍵を持つ者だけが操作できる」

Access を通った先で、Garage 自身の認証が効きます。バケットは private、匿名アクセスは拒否(403)。SigV4 署名のない素のリクエストは、たとえ許可IPから来ても弾かれます。

層1(Access)が「誰が来たか」を、層2(Garage署名)が「正しい鍵を持っているか」を見る。到達制御と操作制御を別の層に分けているわけです。許可IPの内側に万一信頼できない端末が紛れても、鍵がなければ何もできない。

層3:自前API + presigned URL — 「生の鍵を配らない」

最上層は、アプリ側の判断です。クライアントに access/secret key を直接渡すと、漏れたら全権限が漏れます。そこで、

  • 鍵はサーバ側(自前API)だけが保持する
  • クライアントはAPIに Bearer トークンで認証し、ファイルを要求する
  • APIは presigned URL(短命・例:10分) を生成して返す
  • クライアントはその署名URLで Garage へアクセスする

こうすると、クライアントに出ていくのは「10分で失効する、その1ファイル限定の署名URL」だけ。漏れても被害は限定的で短命です。生の鍵は一度もネットワークに出ません。

ただしこの「被害限定」は、クライアントに渡すものが presigned URL だけである限り、という前提つきです。Bearer トークン・サーバ側が保持する access/secret key・Cloudflare の設定そのものが漏れれば、それは別次元の話で、限定では済みません。守るべき秘密の優先順位を取り違えないことが大事です。

さらに、人間が使う認証済みUIに対しては presigned URL すら露出させず、APIサーバが中継して配信する経路も用意しました。自動化ツールには軽量な直アクセス(presigned)、人間には漏れない中継、と使い分けています(詳細はアプリ実装編)。

なぜ層を分けるのか — 単一障害点を作らない

一枚岩の防御は、その一枚が破られたら終わりです。多層にする狙いは、各層が独立して、別の観点で守ることにあります。

  • Access(IP/SSO)が誤設定で緩んでも → Garage署名が匿名を拒否する
  • Garage の鍵が漏れても → presigned 方式なら生キーは出回っていない(漏れるのは短命URL)
  • presigned URL が漏れても → 10分で失効し、許可IP外からは層1で弾かれる

どの層も「これさえあれば全部通る」という万能の穴を持たない。これが「公開はするが、誰にも生では触らせない」の実体です。

留意点 — 多層防御は運用で崩れる

層を増やすほど、設定ミスや運用のズレで崩れる箇所も増えます。実際に踏んだ/気をつけた点を挙げます。

  • Allow/Bypass の取り違え: S3 を Allow にすると自動化が止まる。管理UIを Bypass にすると認証が消える。役割を間違えると、片や使えず、片や無防備になる。
  • 許可IPの前提: IP許可方式は固定IPが前提。動的IPだと再接続でズレて、正規の相手が弾かれる(あるいは穴が開く)。固定IPか、サービストークン等のIP非依存な手段に。
  • 管理ポートを晒さない: 公開するのはS3 APIと管理UIのホスト名だけ。RPC や admin API のポートは外に出さない。
  • presigned はログ・チャットに残さない: 短命でも鍵。共有経路に貼れば失効までは流出経路になる。

多層防御は「組んで終わり」ではなく、各層が意図どおり効いているかを確認し続けて初めて成立します。

まとめ

「公開 vs 秘匿」ではなく「公開した上で、各層で別々のクレデンシャルを要求する」。これがセルフホストS3を外部公開するときに採った方針でした。

要は、

  • 層1(Cloudflare Access)が 到達を制御し、アプリの認証有無で門番にも認証本体にもなる
  • 層2(Garage署名)が 操作を制御し、鍵を持つ者だけに許す
  • 層3(自前API + presigned)が 鍵の配り方を制御し、生キーを一度も外に出さない

それぞれが独立に守るから、一枚破られても次がある。手順そのものより、この「どの層に何を守らせるか」の分担が、再利用できる設計だと思っています。

(構築手順はインフラ編・アプリ実装編に分けてあります。)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?