2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「HTML共有くん」を OCI の Object Storage と API Gateway で作ってみた

2
Last updated at Posted at 2026-08-22

1. はじめに

@minorun365 さんの「HTML共有くん」1を、Oracle Cloud Infrastructure(OCI)に移してみました。

AI エージェントが生成した HTML を自分専用の一覧に保存して、スマホから見たり、期限つき URL で他人に渡したり、外出先からスマホで指示を返したりする仕組みです。元は S3 と CloudFront で作られています。

2026-08-23 更新: 管理面(一覧とインボックス)の画面を、自前の 3 画面から AWS 版のリポジトリにある web/ をそのまま使う形に差し替えました(4.6 章)。配信・認証・インボックスの仕組みと PC 側の操作は変わっていません。スクリーンショットも差し替えています。

スマホ実機の一覧画面。赤いヘッダに「HTML共有くん」と検索・インボックス・更新のボタンがあり、その下に「今週」の区切りで「サンプル」の 2 件、「昨日」の区切りで 2 件が並ぶ。各行に更新時刻とスターが付く

移植にあたって参考にしたのは、HTML共有くんの記事本文と、公開されているリポジトリ2の実装です。AWS 側の説明は、記事の文章ではなくリポジトリのコードに基づいて書いています。以下、@minorun365 さんの HTML共有くんを AWS 版、本記事で作ったものを OCI 版と書きます。

CloudFront に相当するサービス(配信とエッジでのコード実行を 1 つで担うもの)は、OCI 自身のサービスとしてはありません。そこで Object Storage の期限つき URL(事前認証済リクエスト、以下 PAR。URL を知っていれば認証なしで読み書きできる仕組み。3.2 章で説明)と API Gateway のログイン機能を組み合わせています。CDN とエッジの機能は、OCI のコンソールから Cloudflare のサービスを使う Cloudflare@OCI として提供されていますが3、本記事では使っていません。

最初は関数なしで作りました。次に、ゲートウェイの設定に PAR を書くと設定を読める権限で PAR を取り出せる問題を避けるため、API Gateway の後ろに Functions を挟むフル構成まで作りました。しかし関数のコールドスタートが 13.9 秒から 168.2 秒までばらつきました。すべて 180 秒で失敗する時間帯もありました。そこで、利用者が自分だけという要件に戻すと関数は不要と判断し、関数を置かない構成にしました。本記事はその最小構成の作り方です。フル構成から何を削ったか、なぜ削れたかは 6.1 章に書いています。

本文に出てくる 3 つの構成を整理しておきます。

呼び方 構成 本記事での扱い
AWS 版 @minorun365 さんの HTML共有くん。S3・CloudFront・Lambda・DynamoDB・Cognito 3.1 章で構成を確認し、対比の基準にする
OCI 版(フル構成) API Gateway の後ろに Functions を置き、関数が Object Storage を読む。途中まで作って取り下げた 6.1 章で削った理由だけを書く
OCI 版(最小構成) API Gateway から Object Storage の PAR へ直結。関数なし 本記事の手順(4 章)と結果(5 章)。以下「OCI 版」と書けばこの構成

1.1. 結論(先出し)

  • 利用者が自分だけなら、Object Storage の PAR と API Gateway のログインで HTML共有くんの 3 つの目的(スマホで見る・期限つき URL で渡す・スマホから指示を返す)は満たせた。取り除いた部品は関数(と付随する Container Registry)で、ログインを受け持つ API Gateway は最小構成でも使う
  • 部品は Object Storage・API Gateway・Identity Domains・Vault・VCN(仮想クラウドネットワーク)の 5 つ。フル構成にあった Functions と Container Registry は使わない
  • 管理面の画面は、AWS 版のリポジトリにある web/ を無改造で流用できた。足したのはパスの書き換えと、画面が呼ぶ API 8 本を Object Storage の読み書きに変換する JavaScript 1 ファイルだけ(4.6 章)。見た目と操作感の差は部品の差からは生じず、それでも違うのはセッション長・画面からの共有 URL 発行・PC の登録の 3 つ(6.3 章)
  • ゲートウェイの設定と実行ログに PAR が書き込まれる構成なので、それらを読める権限をどこまで絞るかが設計の中心になる。実行ログは INFO のままだと転送先 URL がそのまま記録される
  • つまずいたのは、公式ドキュメントの例がそのままではエラーになる箇所(responseType の大文字小文字、ログイン用ルートに付けてはいけない認可ポリシー)、Content-Type の指定漏れ、初回ログインの 401、IdP のリダイレクト先を決める loginPath が今回の CLI では無視されることの 4 点。4 章と 5 章に書いた

1.2. 検証ゴール

# ゴール 達成できたと言える状態
1 AI が生成した HTML をスマホから見られる 実機のブラウザで一覧が開き、成果物を読める
2 他人に期限つき URL で渡せる 期限内は開き、期限切れと失効で開かなくなる
3 外出先から PC のエージェントとやり取りできる スマホで承認や依頼を置き、PC 側で受け取れる。逆向きもできる

2. 検証環境

項目 内容
OCI CLI 3.86.0(WSL2 の Ubuntu 上)
リージョン ap-tokyo-1
Identity Domains この用途専用に作ったドメイン(ライセンス種別 Free)
Object Storage Standard 層、パブリックアクセス無効、バケット 1 つ
API Gateway パブリック、ルート 6 本、OIDC(OpenID Connect)ログイン、Cookie セッション
管理面の画面 AWS 版リポジトリの web/(コミット 5d64724、2026-08-20 版)を流用
VCN API Gateway を置くためのリージョナルなパブリックサブネット 1 つ(TCP 443 のみ)
Vault OIDC のクライアントシークレットの保管先(ゲートウェイから参照する)
クライアント PC のブラウザと、iPhone の実機ブラウザ

Object Storage(20 GB まで)・Vault(150 シークレットまで)・VCN は Always Free の枠に入ります4。Identity Domains は Free 種別のドメインです。API Gateway は Always Free の一覧に無く、価格表では 100 万 API コールあたり 3 ドルの従量課金です5。無料トライアルの終了後にアップグレードしていないアカウントでは Always Free 以外のリソースは回収されるため6、Always Free の範囲だけで作る場合は API Gateway を利用できません。その場合はログインを付けず、管理面も含めて PAR だけで配ることになります。PAR だけの配布では URL を知っていることが認証の代わりになり、本記事の要件には足りないと判断してログインを付けています。検証中に確認した利用明細(8 月 14〜17 日)に API Gateway の行は現れませんでしたが、呼び出し数が少なく金額に表れなかったためと考えられ、従量課金の対象であることは変わりません。


3. 構成・実装

3.1. HTML共有くん(AWS 版)の構成

@minorun365 さんの HTML共有くんは、リポジトリ2を読むと S3・CloudFront・Lambda・DynamoDB・Cognito で構成されています。

  • S3 バケット 2 つ。管理面(一覧とインボックスの画面)と閲覧面(成果物の HTML)を別ホスト名で配る
  • CloudFront ディストリビューション 2 つ。閲覧面は Key Group による署名必須
  • Lambda 2 本(認証用・レビュー API 用)と DynamoDB 1 つ
  • Cognito ユーザープール。サインイン要素はパスワードとメール OTP(ワンタイムパスワード)

共有 URL は CloudFront の署名付き URL です。RSA-2048 の鍵をローカルで生成し、秘密鍵を SSM(AWS Systems Manager)Parameter Store に置き、公開鍵を CloudFront に登録しています。署名は URL 単位で、社内限定の場合はポリシーに送信元 IP の条件も入ります。

スマホと PC のやり取りは、DynamoDB に「カード」を 1 レコードずつ置く方式です。PC 側は端末トークンで API を呼びます。トークンはスマホでペアリングコードを発行して引き換える方式で、DB にはハッシュだけが保存されます。

3.2. OCI 版の部品対応

OCI 版の中心になるのは PAR です。PAR は Object Storage の機能で、バケットやオブジェクト、プレフィックスに対して期限つきの URL を発行します7。URL を知っていれば認証なしで読み書きできます。読み取りだけ・読み書き・一覧の可否は発行時に決めます。期限は日付で指定し、今回はゲートウェイ用と PC 用が 3 か月、ページ用が 1 週間です。ページは成果物の HTML ファイル 1 つ(pages/<slug>/index.html。一覧の 1 項目)を指します。PAR は Object Storage 側にリソースとして作られるので一覧と削除ができ、削除すればその場で使えなくなります。

この PAR を軸に、役割ごとに AWS 版の部品と OCI 版の部品を並べます。

役割 AWS 版 OCI 版
置き場 S3 バケット 2 つ Object Storage バケット 1 つ(プレフィックスで分離)
配信と共有 URL CloudFront + 署名付き URL PAR
本人認証 Cognito + 署名 Cookie API Gateway + IAM Identity Domains
API とデータ Lambda 2 本 + DynamoDB API Gateway の HTTP ルートから Object Storage を直接読み書き(関数なし)
管理面の画面 web/ の静的 HTML と JavaScript 同じ web/ を流用し、API の呼び出しだけ変換する(4.6 章)
PC の資格情報 端末トークン(ペアリング) inbox/ 限定の PAR
カードの寿命 DynamoDB の TTL(有効期限) ライフサイクルポリシー

次の図は、同じ対応に、置き換えで得られるものと失うものを役割ごとに書き添えたものです。色を付けた②(配信と共有 URL)が、本記事の主題になる置き換えです。

役割ごとの部品対応を左右に並べた図。左が AWS 版(HTML共有くんのリポジトリ)、右が OCI 版(本記事)。①置き場は S3 バケット 2 つに対し Object Storage バケット 1 つ、②配信と共有 URL は CloudFront + 署名付き URL(RSA 鍵を自分で管理。URL 単位で署名、IP 条件も付けられる)に対し PAR(鍵を持たない。ページ単位で発行。IP 条件は付けられない)、③本人認証は Cognito + 署名 Cookie(ログイン後のセッションは 30 日)に対し API Gateway + Identity Domains(API は 24 時間を超える値を拒否)、④API とデータは Lambda 2 本 + DynamoDB に対し API Gateway の HTTP ルート + Object Storage(関数なし)、⑤PC の資格情報は端末トークン(ペアリング)に対し inbox/ 限定の PAR、⑥カードの寿命は DynamoDB の TTL に対しライフサイクルポリシー

署名鍵の管理が要らなくなる代わりに、権限の粒度(URL ごとの送信元 IP の条件など)は細かく持てなくなります。

部品の対応だけでは、どの経路で何を配るのかが分からないので、構成図も添えます。

OCI 版の構成図。左に「使う人」としてスマホと PC、中央に「入口(本人だけ通す)」として API Gateway と Identity Domains、右に「置き場」として Object Storage のバケット 1 つが console/ pages/ inbox/ の 3 つのプレフィックスに分かれている。スマホはログインして API Gateway を通り、API Gateway は console/ の画面と inbox/ のカードを PAR で直接読み書きする。成果物は期限つき URL で pages/ から直接配信され、PC は inbox/ だけを読み書きする別の PAR を持つ

閲覧面と管理面でホスト名が分かれるため、成果物の HTML からは管理面のセッション Cookie を読めません。

3.3. なぜこの構成にしたか

PAR を使う理由から書きます。OCI で、認証なしに期限つきでオブジェクトを渡せる手段が PAR なので、共有 URL と配信の両方に使いました。S3 にも同じ目的の署名付き URL があり、期限つきの URL でオブジェクトを渡せること自体は両者で同じです8。違いは 2 つで、署名の仕方と期限(S3 は発行者の資格情報で署名し最長 7 日、PAR は Object Storage が発行し期限は任意の日付)と、サーバー側のリソースの有無(S3 は何も残らず、PAR は URL 1 本ごとにリソースが 1 つできる)です。AWS 版が S3 ではなく CloudFront の署名付き URL を選んでいるのは、配信を CloudFront に揃えて鍵と送信元 IP の条件を自分で管理するためと考えられます。OCI 版はそこを PAR に置き換えただけで、優劣ではなく仕組みの違いです。

ホスト名を分けたのは、HTML共有くんのリポジトリにある脅威モデル(docs/threat-model.md)が「悪意ある成果物HTML」の項で、AI が生成した HTML や外部から受け取った HTML には意図しない JavaScript が含まれうると書いているためです。成果物の HTML を管理面のセッションと API から切り離す、という設計です。OCI では Object Storage と API Gateway でホスト名が別になるため、管理面はゲートウェイ経由・閲覧面は Object Storage から直接、と配り方を分けるだけでオリジン(スキーム・ホスト名・ポートの組)が分かれます。

バケットを AWS 版のように 2 つにしなかったのは、OCI では分けてもオリジンが分かれないためです。AWS 版で S3 バケットを 2 つにしているのは、CloudFront ディストリビューションを 2 つ作って管理面と閲覧面のホスト名を分けるためです。OCI の Object Storage は、バケットが違っても URL のホスト名は同じ(objectstorage.ap-tokyo-1.oraclecloud.com)です。つまり、ホスト名が分かれるかどうかはバケットの数とは関係がありません。読み書きの範囲は PAR をプレフィックス単位で発行すれば分けられるので、バケットは 1 つにし、console/・pages/・inbox/ のプレフィックスで分けました。

関数を挟まないのは、要件を「利用者は自分だけ」に絞った結果です。API Gateway から Object Storage を直接読ませるには、バックエンドの URL に PAR を書くことになり、設定を読める権限があれば PAR を取り出せます(6.2 章)。フル構成ではこれを避けるために関数を挟んでいました。

ただし、自分専用で前段にログインがある条件では、増えるリスクは作業用の読み取り専用グループ(以下、作業用グループ)が管理面とインボックスを読めることに限られ、読める範囲はポリシーの条件 1 文で絞れると考えています(内容と文面は 6.2 章。本記事では未実施)。一方で関数を挟むと、コールドスタートへの対処と Container Registry の運用が日常の手間になります(理由は 6.1 章)。この比較で、関数を置かない構成を選びました。


4. 手順

作るものを 4 つに分けて書き、最後に運用の 1 往復をまとめます。問題なく進む部分は一文で済ませ、詰まった箇所のコマンドだけ載せます。

4.1. 配信(Object Storage と PAR)

バケットを 1 つ作り、パブリックアクセスは無効のままにします。配信は PAR だけで行います。成果物と管理面はプレフィックスで分けます。

pages/<slug>/index.html    閲覧面。ページ単位の PAR で直接配る
console/index.html         管理面(一覧。画面名はダッシュボード)。API Gateway 経由で配る
inbox/<id>.json            スマホとのやり取りに使うカード

アップロードでは Content-Type を指定します。

oci os object bulk-upload --bucket-name html-share \
  --src-dir build/pages --object-prefix pages/ \
  --include "*.html" --content-type "text/html" --overwrite

共有 URL には、ページごとに発行した PAR を使います。返ってくる access-uri にホスト名を足すと、そのまま開ける URL になります。

oci os preauth-request create --bucket-name html-share \
  --name "page-sample-report-20260818" \
  --access-type ObjectRead \
  --object-name "pages/sample-report/index.html" \
  --time-expires "2026-08-25T00:00:00Z"

Content-Type を省略すると application/octet-stream になり、ブラウザはダウンロード扱いにします。応答に X-Content-Type-Options: nosniff が付くため、拡張子による判定は行われません。bulk-upload でも自動判定はされません。

プレフィックスを指定して PAR を作った場合、返る access-uri は /o/ で終わり、プレフィックスを含みません。pages/x/index.html を開くには自分でパスを足します。単一オブジェクトの PAR なら、access-uri にオブジェクトのパスまで含まれます。

4.2. ゲートウェイ用の PAR を 2 本

API Gateway のバックエンドに書く PAR を 2 本発行します。管理面の読み取り用と、インボックス(スマホとのやり取りに使うカードの置き場。4.4 章)の読み書き用です。

# 管理面(console/)の読み取り。一覧は不要
oci os preauth-request create --bucket-name html-share \
  --name gw-console --access-type AnyObjectRead \
  --object-name "console/" --time-expires "2026-11-22T00:00:00Z"

# インボックス(inbox/)の読み書き。画面が一覧を出すので一覧を許可する
oci os preauth-request create --bucket-name html-share \
  --name gw-inbox --access-type AnyObjectReadWrite \
  --object-name "inbox/" --bucket-listing-action ListObjects \
  --time-expires "2026-11-22T00:00:00Z"

PC 側が使う inbox/ の PAR(4.4 章)とは別に発行します。片方を失効させても、もう片方が止まらないようにするためです。

4.3. 認証(API Gateway と Identity Domains)

API Gateway は VCN のサブネットに置くため、VCN・インターネットゲートウェイ・パブリックサブネットを作り、TCP 443 だけ開けます。

Identity Domains には OIDC の機密アプリケーションを登録します。付与タイプは認可コードのみ、クライアント種別は機密、「イントロスペクト」を許可します。リダイレクト URL には https://<ゲートウェイのホスト名>/dash/ と https://<ゲートウェイのホスト名>/dash/auth/callback の両方を登録します(/dash はデプロイメントのパス接頭辞で、今回の値です)。今回の環境では IdP(ID プロバイダ。ここでは Identity Domains)からのリダイレクト先が、次のデプロイメント仕様(spec)の fallbackRedirectPath になったためです(詳細は 5.2 章)。ログアウト後の戻り先にも https://<ゲートウェイのホスト名>/dash/ を登録しておきます。

ゲートウェイが何をするかを先に図にします。未認証のリクエストは IdP へ送り、戻ってきたらセッション Cookie を発行し、以後はルートごとに PAR の先へ転送します。

API Gateway のデプロイメントの図。左のブラウザから /dash/ へのリクエストが API Gateway に入る。Cookie が無ければ Identity Domains のサインイン画面へ 302 し、サインイン後は /dash/?code= へ戻ってゲートウェイがコードをトークンに交換し Cookie を発行する(クライアントシークレットは Vault)。ルートは 6 本。/auth/callback と /logout は IdP とのやり取り、/ と /{pathname*} は console の PAR 経由で Object Storage の console/ へ、/api/inbox と /api/inbox/{name} は inbox の PAR 経由で inbox/ へ転送する

クライアントシークレットは Vault に保管し、ゲートウェイの動的グループから読めるようにします。

Allow dynamic-group html-share-apigw-dg to read secret-family in compartment html-share

デプロイメントの仕様は、認証ポリシー・実行ログのレベル・ルート 6 本で構成します。要点だけ抜き出します。

{
  "loggingPolicies": { "executionLog": { "logLevel": "ERROR" } },
  "requestPolicies": {
    "authentication": {
      "type": "TOKEN_AUTHENTICATION",
      "validationPolicy": {
        "type": "REMOTE_DISCOVERY",
        "clientDetails": { "type": "CUSTOM", "clientId": "<クライアント ID>",
                           "clientSecretId": "<Vault シークレットの OCID>", "clientSecretVersionNumber": 1 },
        "sourceUriDetails": { "type": "DISCOVERY_URI",
                              "uri": "https://<ドメイン>/.well-known/openid-configuration" }
      },
      "validationFailurePolicy": {
        "type": "OAUTH2", "scopes": ["openid"], "responseType": "CODE",
        "useCookiesForSession": true, "useCookiesForIntermediateSteps": true, "usePkce": true,
        "maxExpiryDurationInHours": 24,
        "fallbackRedirectPath": "/", "logoutPath": "/logout"
      }
    }
  },
  "routes": [
    { "path": "/auth/callback", "methods": ["GET"], "backend": { "type": "OAUTH2_LOGIN_BACKEND" } },
    { "path": "/logout",        "methods": ["GET"], "backend": { "type": "OAUTH2_LOGOUT_BACKEND" } },
    { "path": "/",              "methods": ["GET"],
      "backend": { "type": "HTTP_BACKEND", "url": "<console の PAR>console/index.html" },
      "responsePolicies": { "headerTransformations": { "setHeaders": { "items": [
        { "name": "Content-Security-Policy", "values": ["default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-src https://objectstorage.ap-tokyo-1.oraclecloud.com; form-action 'self'; base-uri 'none'; frame-ancestors 'none'"], "ifExists": "OVERWRITE" }
      ] } } } },
    { "path": "/api/inbox",     "methods": ["GET"],
      "backend": { "type": "HTTP_BACKEND", "url": "<inbox の PAR>" } },
    { "path": "/api/inbox/{name}", "methods": ["GET", "PUT"],
      "backend": { "type": "HTTP_BACKEND", "url": "<inbox の PAR>inbox/${request.path[name]}" } },
    { "path": "/{pathname*}",   "methods": ["GET"],
      "backend": { "type": "HTTP_BACKEND", "url": "<console の PAR>console/${request.path[pathname]}" } }
  ]
}

図の「未認証なら IdP へ」を受け持つのが validationFailurePolicy です。管理面を返す 2 つのルート(/ と /{pathname*})には、応答ヘッダ変換で Content-Security-Policy(以下 CSP)など 7 種類を spec に書いています(フル構成では関数が付けていたものです。うち 2 種類は後述のとおりゲートウェイ側が付けるため変換は無視されます)。上の例では / の CSP だけを載せていますが、Referrer-Policy: no-referrer、Permissions-Policy、X-Robots-Tag: noindex, nofollow、Cache-Control: no-store なども同じ書き方で items に並べ、/{pathname*} にも同じブロックを付けます。Cache-Control: no-store は後から足したものです(経緯は 5.6 章)。

ログアウトは /logout ルート(OAUTH2_LOGOUT_BACKEND)です。ゲートウェイのセッション Cookie を無効にしたうえで IdP のログアウトへリダイレクトし、IdP 側のサインイン状態も終わります。次に /dash/ を開くとサインイン画面に戻ります。ログアウトを押すのは共有端末で使ったときだけで足り、自分専用の端末では不要です。IdP 側には戻り先の URL を登録しましたが、spec 側の戻り先(allowedPostLogoutUris)は設定していないので、ログアウト後は IdP の画面で止まります。

responseType は "CODE"(大文字)です。公式ドキュメントの JSON 例9は小文字で書かれていますが、そのままでは InvalidParameter になります。SDK のリファレンス10では許容値が CODE の 1 つだけです。

ログイン用のルートには認可ポリシーを付けません。ルート単位の authorization ポリシー(ANONYMOUS)で認証を免除する書き方がありますが9、このバックエンド種別では拒否されます(only AUTHENTICATION_ONLY is allowed)。backend だけを書きます。

spec をテンプレートから envsubst で生成する場合は、${request.path[name]} がゲートウェイのパステンプレート構文であることに注意します。そのまま通すと空文字に置換され、エラーが出ないままルートが使えなくなります。置換する変数名を envsubst '$CONSOLE_PAR $INBOX_PAR' のように限定します。

ヘッダ変換のうち Strict-Transport-Security と X-Content-Type-Options は、実行ログに「Skipping header … as it cannot be transformed」と出て無視されます。応答には両方とも付いていたので、ゲートウェイ側が付けるヘッダと考えられます。spec に書いても問題はありませんが、消してよい 2 項目です。

4.4. インボックス

スマホと PC のやり取りは、カード 1 枚を JSON オブジェクト 1 つとして inbox/ に置く方式です。状態は waiting から answered を経て completed に変わります。カードの項目名は AWS 版のカード(source・title・question・status・approved・responseText など)に揃えました。画面から削除したカードは、PAR では削除ができないので deleted を書いて一覧から外します。

スマホ側は、ログイン後の画面の JavaScript が /api/inbox を GET して一覧を出し、承認や依頼を /api/inbox/<id>.json に PUT します。ゲートウェイがそのまま PAR の先へ転送するので、サーバー側のコードはありません。

PC 側は inbox/ に限定した PAR を 1 本持ち、curl だけで読み書きします。OCI CLI も管理者権限も使いません。

oci os preauth-request create --bucket-name html-share \
  --name pc-inbox --access-type AnyObjectReadWrite \
  --object-name "inbox/" --bucket-listing-action ListObjects \
  --time-expires "2026-11-16T00:00:00Z"

これを ~/.config/html-share-oci/inbox-par.json にパーミッション 600 で置きます。PC 側のスクリプト inbox.sh は push(確認依頼を置く)・pull(回答済みを取得する)・requests(スマホからの依頼を取得する)・watch(回答が置かれるまで待つ)を curl で行います。PC からスマホへ成果物を送るときは、Markdown を HTML に変換して公開し(publish.sh)、確認カードを置くところまでを mobile.sh の 1 本にまとめました(スクリプトの一覧は 4.5 章)。

カードの保持期間は、ライフサイクルポリシーで 90 日にします。

[{ "name": "inbox-retention-90d", "action": "DELETE",
   "timeAmount": 90, "timeUnit": "DAYS", "isEnabled": true,
   "objectNameFilter": { "inclusionPrefixes": ["inbox/"] } }]

ライフサイクルの削除は Object Storage 側で実行されるため、そのサービスプリンシパルへの権限が別に必要です11。付けずに登録すると InsufficientServicePermissions で失敗します。inclusionPrefixes を誤ると成果物や管理面まで削除の対象になるので、登録後に object-lifecycle-policy get で範囲を確認してください。

Allow service objectstorage-ap-tokyo-1 to manage object-family in compartment html-share

4.5. 運用の 1 往復

本人用の一覧に載せる URL はページ(成果物の HTML 1 つ)ごとに発行するので、一覧に成果物が 4 件あれば公開 1 回で PAR が 4 本でき、改稿して公開し直すとさらに 4 本増えます。前回分をそのままにすると、古い版の URL が期限まで開けるまま残り続け、どの版をいつ発行したかが分からなくなります。そこで publish.sh では、同じ用途で前回までに作った PAR を失効させてから新しい PAR を発行しています。

PC 側で使うスクリプトは次の 4 本です。いずれも OCI CLI と curl の呼び出しを並べたシェルスクリプトで、本記事ではコードは載せません。

スクリプト 役割
publish.sh ビルド済みの成果物 HTML を pages/ に、管理面の画面(HTML・JavaScript・画像)を console/ にアップロードし、ページごとの PAR を発行して manifest.json を更新し、前回までのページ用 PAR を失効させる
share.sh 他人に渡す期限つき URL を、ページ 1 つ分だけ別名で発行する
inbox.sh カードの操作(push / pull / requests / watch / complete)。使う資格情報は inbox/ 限定の PAR だけ
mobile.sh Markdown を HTML に変換し、公開対象の一覧 pages.tsv に登録して publish.sh を実行し、確認のカードを inbox.sh push で置く

PC とスマホの 1 往復は次のとおりです。

  1. PC で mobile.sh を実行する。Markdown を HTML に変換して pages/ に置き、公開対象の一覧 pages.tsv にある全ページ(本記事の時点で 4 件)の PAR を新しく発行し、その URL を書いた manifest.json を console/ に置き、前回までの PAR を失効させ、確認のカードを inbox/ に置く
  2. スマホで一覧から開き、インボックスから承認やコメントを返す
  3. PC で inbox.sh が受け取る。直して、もう一度 mobile.sh を実行する(1 に戻る)

一覧は常に最新の URL を指すので、スマホ側の操作は変わりません。前に開いたタブの URL は失効して開けないので、一覧から開き直します。ページ用の有効な PAR は常にページ数と同じ本数です。他人に渡す URL は別の名前で発行して失効の対象にしていないので、再公開してもその URL はそのまま開け、中身は上書きした新しい版になります。

4.6. 管理面の画面は AWS 版の web/ を流用する

公開した時点の管理面は、一覧・ビューア・インボックスの 3 画面を自前の HTML で書いたものでした(合わせて約 400 行)。機能の骨格は同じでも、AWS 版の画面(リポジトリの web/。約 3,600 行)にある日付の区切り、未読の印、スター、検索、表のカード化、ホーム画面への追加といった作り込みはありません。両方を使い比べると見た目と操作感の差が大きく、その差が OCI の部品から来るのか画面の作り込みから来るのかを切り分けたくなりました。そこで web/ をそのまま OCI 版に載せました。web/ は素の JavaScript で外部ライブラリを使っておらず、ライセンスは Apache-2.0 です2。

載せるのに要ったのは 2 つだけです。1 つ目はパスの書き換えで、AWS 版の画面は /app/ や /api/ のようにホスト名直下のパスで書かれているので、ビルド時に API Gateway のデプロイメントの接頭辞(本記事では /dash/)を付けます。2 つ目は API の変換で、画面が fetch で呼ぶ API を、ブラウザ側の JavaScript 1 ファイルが fetch を包んで Object Storage の読み書きに変換します。変換先は次のとおりで、ゲートウェイのルートは 4.3 章のものから増えていません。

画面が呼ぶ API OCI 版での変換先
GET /api/owner/reviews inbox/ を一覧(ListObjects)し、カードを 1 枚ずつ GET する
POST /api/owner/reviews inbox/<id>.json を PUT する(スマホからの依頼)
POST /api/owner/reviews/{id}/answer カードを読み、status を answered にして approved と responseText を書き、PUT する
DELETE /api/owner/reviews/{id} status を deleted にして PUT する
GET / PUT /api/owner/preferences inbox/_preferences.json を GET / PUT する(既読・スター・非表示。端末をまたいで揃えるため)
POST /api/owner/shares、POST /api/owner/pairings 使わない(501 を返す)。共有 URL は PC の share.sh で、PC の登録は inbox/ 限定の PAR で行う

最後の行が、画面を同じにしても違いとして続く点です。共有 URL の画面からの発行は署名付きの API 呼び出し、PC の登録はコードの発行と照合で、どちらもサーバー側の計算が要ります。PAR と API Gateway の転送だけでは作れないので、OCI 版では使わない構成のままにしています。

このほかに、成果物の HTML に AWS 版と同じく表のカード化とカレンダーの畳み込みの JavaScript をビルド時に埋め込み、配色は AWS 版の青系を Oracle の赤系に置き換えました。つまずいたのはパスの書き換え漏れで、<script src="/page-list.js"> のようなホスト名直下の JavaScript と、`/api${path}` のようなテンプレート文字列の 2 か所が規則から漏れ、最初の配置では一覧が空のまま、インボックスは読み込みに失敗しました。API Gateway と同じ応答ヘッダ(CSP)を付けて build/console/ を配る小さなローカルサーバーを用意し、ブラウザで一覧・インボックス・承認まで通してから配置する手順にして直しました。


5. 実行結果

5.1. 往復ができた

スマホでログインすると一覧が出て、インボックスには公開時点から置いてあった 12 枚のカードが新しい項目名に読み替えられて並びました。PC から inbox.sh push で置いた確認カードに、スマホで「承認」を押してコメントを送ると「承認しました」と出て、カードは「PCでの取り込み待ち」に変わりました。PC 側で pull すると同じカードが answered で取得できました。

{
  "id": "card-20260823-100217-aa2c55ad",
  "source": "claude-code",
  "sessionId": "oci-v2-smoke",
  "title": "v2 画面テスト(PC→スマホ)",
  "question": "OCI 版 v2 の画面テスト: この確認カードが新しいインボックス画面に出ていたら「承認」を押し、コメントも一言お願いします",
  "target": "html-share-oci",
  "status": "answered",
  "createdAt": "2026-08-23T01:02:17Z",
  "updatedAt": "2026-08-23T01:38:24.002Z",
  "approved": true,
  "responseText": "OK"
}

逆向きもできました。スマホのインボックスから依頼を置くと、PC 側の requests に source が owner のカードとして届きました。一覧からページを開くと、スマホでは成果物のページがそのまま開き、上に一覧へ戻るバーが付きます。

スマホ実機のインボックス画面。上に「Claudeへの依頼」の入力欄と依頼先の選択欄、下に「Claudeからの確認」のカードが 4 件あり、先頭のカードに承認ボタンとコメント欄が付く

5.2. 初回のログインだけ 401

IdP のサインイン画面を開いたまま 10 分ほど置いてからサインインしたところ、IdP から https://<ゲートウェイのホスト名>/dash/?code=…&state=… へ戻った時点で {"code":401,"message":"Unauthorized"} が返りました。同じ URL を開き直すと、今度は IdP を経由してすぐ戻り、200 で一覧が出ました。

ゲートウェイの実行ログには、最初のリダイレクトが authentication.validationFailurePolicyOAuthStepFailed、メッセージ State value mismatch during OAuth2. として 1 件だけ記録されていました。開き直したときは同じリクエストの中で Token generated successfully. と Call to Identity Provider returned successfully. まで進んでいます。

401 とは別に、リダイレクト先についても確認したことがあります。IdP からのリダイレクト先は /auth/callback ではなく fallbackRedirectPath の / でした。ブラウザのアドレスは /dash/?code=… になり、開き直したときの実行ログでもトークン取得までが /dash/ への 1 リクエストの中で完結しています(実行ログにはパスが記録されないので、これはログの中身とブラウザのアドレスから読んだものです)。SDK リファレンス(2.184.2)10には、IdP のリダイレクト先を OAUTH2_LOGIN_BACKEND のルートに合わせる loginPath という項目があります。ところが今回使った OCI CLI 3.86.0(同梱の Python SDK 2.178.0)のモデルにはこの項目が無く、spec に書いても更新時にエラーにならないまま無視されていました。サーバー側の spec に loginPath は保存されておらず、IdP 側に登録するリダイレクト URL を /auth/callback だけにしていたときは invalid_redirect_uri になりました。4.3 章で両方の URL を登録しているのはこのためです。CLI を更新すれば loginPath が受け付けられ、リダイレクト先が /auth/callback になると考えられますが、未確認です。

5.3. 別オリジンからの書き込みを止めているもの

公式ドキュメントには、セッションを Cookie に保存する設定では CSRF(クロスサイトリクエストフォージェリ)対策の X-CSRF-TOKEN ヘッダが返り、PUT などの変更系リクエストにはそれを付ける必要がある、と書かれています12。本記事の時点(CLI 3.86.0 で作成した spec)で確かめると、応答に X-CSRF-TOKEN は出ず、同一オリジンからの PUT はヘッダを一切付けなくても 200 が返りました(同じ JSON を書き戻す、内容の変わらない操作で確認しています)。

次に、別オリジンからの書き込みが止まるかを確かめました。https://example.com を開いたブラウザの開発者ツールから fetch を実行し、ゲートウェイへ Cookie を付けた GET と PUT を送ってみました。

別オリジンからの操作 結果
GET /dash/api/inbox(Cookie あり・なし) Failed to fetch(ブラウザの CORS、つまり別オリジン間の読み取り制限で遮断)
PUT /dash/api/inbox/<id>.json(Cookie あり、Content-Type と独自ヘッダの有無を両方) Failed to fetch、カードの中身は変わらず
GET /dash/(mode: 'no-cors' で取得) 応答は返るが中身は読めない

PUT は本体の前にプリフライト(OPTIONS)が送られます13。このゲートウェイには OPTIONS に応答するルートも CORS の設定も無いので、本体の PUT は送られません。フォームからの送信は method が GET・POST・dialog に限られるので PUT を出せません14。止めているのは「書き込みを PUT だけにし、そのルートにプリフライトを通す設定を置いていないこと」で、CSRF トークンではありませんでした。セッション Cookie は JavaScript から読めない(HttpOnly)ことも確認しましたが、これは Cookie の持ち出しを防ぐ属性で、別オリジンからのリクエストに Cookie が付くこと自体は防ぎません。Cookie の SameSite 属性は確認していません。同一オリジンの JavaScript が乗っ取られた場合は守れない前提で、成果物の HTML は別ホストに置いています。

5.4. 実行ログに PAR がそのまま記録される

ゲートウェイの実行ログを Logging のサービス・ログとして有効にしていました。ログレベルを指定していなかったのでデフォルトの INFO です。ログインから往復の確認までの 40 分間(9:30〜10:10)で 170 行が記録され、そのうち 31 行が httpBackend.formedBackendUrl で、転送先の URL が PAR のトークンごと書かれていました。

Dynamic request url formed: [https://objectstorage.../p/<PAR>/n/<ns>/b/html-share/o/console/manifest.json]
  from configured url: [https://objectstorage.../p/<PAR>/n/<ns>/b/html-share/o/console/${request.path[pathname]}]

デプロイメント設定を読めなくても、このログを読める権限があれば PAR を取り出せます。そこで spec に loggingPolicies.executionLog.logLevel: "ERROR" を足して更新しました。レベル変更の直前に別に数え直した 50 分間(10:00〜10:50)は 28 行(INFO 25・WARN 2・ERROR 1、うち転送先 URL が 5 行)だったのに対し、変更後にインボックスの画面を開き直した 1 回分(一覧と 12 枚のカードの取得)では 0 行でした。ログの取り込み自体が止まっていないことは、直後に意図的に未認証で 2 回アクセスし、その 2 件だけが ERROR(tokenAuthentication.authenticationFailed)として記録されたことで確かめています。転送先 URL の行は記録されなくなり、認証失敗のような調べたい事象は記録されます。

5.5. エラーメッセージが実態と違う

PAR に関するエラーは、そのまま読むと切り分けを誤ります。

状況 返ってくるもの
期限切れの PAR にアクセス 404 BucketNotFound(Either the bucket named ... does not exist ... or you are not authorized to access it)
範囲外のオブジェクトにアクセス 401 NotAuthenticated(PAR does not exist)
一覧を禁止した PAR で、オブジェクト名を付けない URL を開く 404 BucketNotFound

運用スクリプトで BucketNotFound が出たときは、PAR の期限も確認します。失効の反映は速く、PAR の削除が完了してから約 5 ミリ秒後のアクセスで 401 が返りました。一方でバケットを作った直後は、今回は 30〜60 秒ほどのあいだ BucketNotFound が返りました。

5.6. 使いながら直した点

往復の確認の途中で 2 つ直しました。

1 つ目は、成果物ページの上に付けている「ダッシュボードへ戻る」のリンク(現在の画面では「共有くんの一覧へ」)です。押しても、ビューアの iframe の中では何も起きませんでした。原因は 2 つ重なっていました。1 つは、リンクに target="_top" が無く、iframe の中でゲートウェイへの遷移が起きて、ゲートウェイが返す X-Frame-Options: sameorigin と CSP の frame-ancestors 'none' に止められていたこと、もう 1 つは、target="_top" を足しても、ビューアの iframe に付けた sandbox 属性が上位ウィンドウへの遷移を許していなかったことです。どちらも、ホスト名を分けた構成と iframe の隔離がそのまま働いた結果です。リンクに target="_top" を足し、sandbox にクリック起点の遷移だけを許す allow-top-navigation-by-user-activation を加えて直しました(スクリプトの実行は引き続き許可していません)。

2 つ目は、ログアウトしてやり直したときに PC のブラウザに出た「読み込み中… Failed to fetch」です。ログアウト後にキャッシュされた管理面の HTML が表示され、manifest.json の取得だけがセッション切れで失敗していました。Object Storage の応答には Last-Modified と ETag が付き Cache-Control は付かないため、ブラウザが HTML を再利用していました。管理面の応答に Cache-Control: no-store を足して対処しました(4.3 章)。

直す対象ではない画面も 1 つ出ました。IdP の「アプリケーションにアクセスする権限がありません」で、この用途に作った dashboard ユーザー以外(ドメインの管理者)でサインインしたときに出ます。OIDC アプリのアクセス制御で割り当てたユーザーだけを通す設定が、そのとおりに働いたということです。


6. 考察

6.1. フル構成から削った理由

フル構成では、API Gateway の後ろに Python の関数を置き、リソースプリンシパルで Object Storage を読ませていました。ゲートウェイの設定に PAR を書かないための選択です。やめた理由は 3 つあります。

1 つ目はコールドスタートです。コンテナの起動を伴う呼び出しは、成功したときでも 13.9 秒から 168.2 秒かかりました。調べると import oci が全サービスのクライアントを読み込んでいました。必要なクライアントだけを個別に読み込むように直しましたが、コンテナ自体の起動時間は変わりません。応答時間は 2 秒以下(コンテナが残っていたとき)か 13.9 秒以上(起動を伴うとき)のどちらかで、その間の値はありませんでした。ブラウザからの同期呼び出しなので、起動に時間がかかるときは画面が読み込み中のままになります。

2 つ目は、自分では解消できない失敗でした。ap-tokyo-1 で、コールドスタートがすべて 180 秒ちょうどで 503 FunctionInvokeServiceUnavailable になる時間帯が 8 月 20 日と 21 日に繰り返しありました。ログの cause フィールドは launchRunner: LaunchInstance failed ... 429 TooManyRequests と Status runner not yet specialized の 2 種類です。同じアプリケーション・同じベースイメージ・同じメモリとサブネットに、SDK を含まない 96 MB の hello-world 関数を置いても、本番の 148 MB の関数と同じ原因文字列で同じように失敗しました。イメージの大きさ・コード・依存の問題ではありません。公式のトラブルシューティング15が挙げる他のエラーコード(イメージ取得の失敗・サブネットの IP 枯渇・コンテナ初期化のタイムアウト・テナンシの同時実行数)は、48 時間分・440 件の呼び出しログに 1 件も出ていません(失敗 13 件はすべて ServiceUnavailable)。サービス制限にも余裕がありました。ここまでの切り分けから、コンテナを動かすインスタンスの起動が Compute 側で拒否されている状態と考えられますが、テナンシ側からは観測も制御もできませんでした。

3 つ目は運用の手間です。関数はコンテナイメージなので、Container Registry と認証トークン、Vault のシークレットがもう 1 つ要ります。画面を 1 行直すたびに、イメージのビルド・レジストリへのプッシュ・関数の更新の 3 手順が要りますが、静的 HTML なら bulk-upload の 1 手順です。

関数の役割は、PAR を設定に置かないこと・別オリジンからの書き込みを止めること・セキュリティヘッダを付けることの 3 つでした。1 つ目は方針をやめて、読める権限を絞る対処に変えました(6.2 章)。2 つ目は、変更を PUT だけにしてプリフライトを通す設定を置かないことで止まりました(5.3 章)。3 つ目はゲートウェイの応答ヘッダ変換に移しました。自分専用の管理面に、コードを動かす場所は要りませんでした。

6.2. 設定とログに書き込まれる PAR を、誰が読めるか

バックエンドの URL に PAR を書くと、read api-deployments を持つユーザーはデプロイメント設定から backend.url ごと取り出せます。関数を挟む前の段階で、作業用グループ(AI エージェントにも使わせている読み取り専用のグループ)のユーザーで確認しました。加えて 5.4 章のとおり、実行ログが INFO だと read log-content でも同じ PAR を取り出せます。

今回これを読めるのは、管理者と作業用グループの 2 つです。管理者はもともとバケットを直接読めるので、増えるのは後者が「ログインを通らずに管理面の HTML とインボックスを読み書きでき、一覧の manifest から成果物の URL も分かる」ことだけです。対処はログレベルの変更とポリシーの条件付けの 2 つです。

実行ログのレベルは spec の loggingPolicies.executionLog.logLevel で決まり、未指定なら INFO です16。ERROR にすると INFO の行(転送先 URL を含む)は記録されなくなります(5.4 章)。設定については、作業用グループ(lab-operators)のポリシーに条件を付けて、ゲートウェイとバケットを置いたコンパートメント(html-share)を対象から除きます。

Allow group lab-operators to read all-resources in tenancy
  where target.compartment.name != 'html-share'

本記事の時点で実施したのはログレベルの変更だけで、ポリシーの条件付けは文面だけ示しています。

AWS 版では、CloudFront の Origin Access Control(以下 OAC)が「CloudFront だけが S3 を読める」ことを IAM で保証するので、トークンそのものが存在しません17。設定に秘密を置く構成では、その設定とログを読める権限が、秘密を読める権限と同じ意味になります。

自分以外の、read all-resources のような広い読み取り権限を持つユーザーやグループがいる環境で同じ構成を作る場合は、このポリシーの条件付け(または同等の絞り込み)を先に設定してください。PAR はそれ自体が資格情報なので、設定やログを読める範囲がそのまま管理面とインボックスを読み書きできる範囲になります。

6.3. AWS 版との違い

AWS 版と比べて減ったのは 3 つです。RSA 鍵を生成して SSM に保管し CloudFront に登録する工程、ペアリングコードの発行と引き換えの API、カードを置くためのデータベースが要らなくなりました(鍵の工程が要るのは、3.3 章のとおり AWS 版が CloudFront の署名付き URL を選んでいるためです)。増えたのは、API Gateway の配置に必要な VCN と、クライアントシークレットを置く Vault、動的グループとポリシーが 2 組(ゲートウェイ用・ライフサイクル用)です。

セッションの長さにも差があります。AWS 版は署名 Cookie を 30 日で発行しています。ゲートウェイの maxExpiryDurationInHours に 720 を指定すると must be less than or equal to 24 で拒否されます。ただし公式ドキュメントにこの値の上限は書かれておらず、説明も「認可フローで生成したトークンをキャッシュする時間」です9。24 時間後に実際にログインし直しになるかは観測していないので、本記事で書けるのは「API が 24 を超える値を受け付けない」ことまでです。

発行の単位にも差があります。署名付き URL は鍵 1 つで URL ごとの署名をいくつでも作れ、署名は手元の計算でサーバー側に何も残りません。PAR は 1 本ごとに API を呼び出して作成し、Object Storage 側にリソースが 1 つずつ増えるので、公開のたびに前回分を失効させる運用になります(4.5 章)。保持期間の管理にも同じ違いがあり、DynamoDB の TTL はレコードごとに期限を持てますが、Object Storage のライフサイクルはオブジェクトごとには設定できず、名前の接頭辞やパターンで対象を決めて、対象全体に同じ日数を適用します11。

画面を AWS 版の web/ に揃えてみると(4.6 章)、違いの原因が切り分けられました。一覧とインボックスの見た目と操作は同じファイルなので差がなく、違いとして続くのは、セッションの長さ、画面からの共有 URL の発行、スマホからの PC の登録の 3 つです。後の 2 つは署名付きの API 呼び出しとコードの発行・照合で、どちらもサーバー側の計算が要ります。PAR と API Gateway の転送だけでは作れず、作るなら関数を 1 本戻すことになります。

まとめると、CloudFront と S3 の構成をそのまま OCI で再現することはできませんでした。CloudFront は配信・署名付き URL・署名 Cookie・送信元 IP の条件・オリジンへの限定アクセス(OAC)を 1 つのサービスで担い、ディストリビューションを 2 つ置けばホスト名も分かれます。OCI 版は同じ 3 つの目的を Object Storage(PAR)・API Gateway・Identity Domains・Vault・VCN の組み合わせで満たしましたが、次の 4 点は構成が変わっています。

  • 設定と実行ログに PAR が書き込まれる(6.2 章)
  • セッションの期限に指定できるのは 24 時間まで
  • URL ごとの送信元 IP の条件は付けられない
  • 公開のたびにページ数分の PAR を作り直す

自分専用の用途ではこの差を受け入れました。AWS 版と同じセッション長や IP 条件が要る場合の対処は 6.4 章に書きます。

6.4. 構成が変わった 4 点への対処

6.3 章の 4 点を補う方法として次を挙げます。実施したのは 1 行目の前者だけです。

構成が変わった点 補い方 代償・本記事での状態
設定とログに PAR が書き込まれる 関数を挟み、リソースプリンシパルで Object Storage を読ませる(PAR を置かない)。または PAR を置いたまま、読める範囲をポリシーの条件で絞る(6.2 章) 前者はコールドスタートと運用の手間(6.1 章のフル構成で実施済み)。後者は文面のみ
セッションの期限に指定できるのは 24 時間まで Cloudflare Access を前段に置く(セッションは最長 1 か月) 独自ドメインが要る。未実施
URL ごとの送信元 IP の条件が無い Cloudflare Access の IP 許可リスト(CIDR)。管理面への送信元を絞るだけなら、ゲートウェイを置いたサブネットのセキュリティリストで 443 の送信元 CIDR を限定する 前者は独自ドメインが要る。どちらも未実施
公開のたびにページ数分の PAR を作り直す publish.sh の作りによるもので、変更したページだけ発行し直すようにもできる 未実施

Cloudflare Access のセッションは最長 1 か月18、IP 許可リストは CIDR で指定でき19、Cloudflare Workers には HMAC(ハッシュベースのメッセージ認証コード)検証の公式サンプルもあります20。ただし Access で守れるのは Cloudflare に登録したドメインが前提と理解しているので、独自ドメインの用意が要ります。1 章で触れた Cloudflare@OCI も含めて、Cloudflare とセキュリティリストによる制限は本記事では試していません。


7. まとめ

  • 自分専用なら、Object Storage の PAR と API Gateway のログインだけで、スマホから見る・期限つき URL で渡す・スマホから指示を返すところまで確認できた。関数は要らなかった
  • CloudFront と S3 の構成はそのままは再現できない。同じ 3 つの目的は満たせたが、設定とログに PAR が書き込まれる・セッションの期限は 24 時間までしか指定できない・URL ごとの IP 条件なし・公開のたびにページ数分の PAR を作り直す、という 4 点で構成が変わる。AWS 版と同じ性質が要るときの対処は 6.4 章
  • 管理面の画面は AWS 版の web/ を無改造で流用できた。足したのはパスの書き換えと API 8 本の変換だけで、それでも違うのはセッション長・画面からの共有 URL 発行・PC の登録の 3 つ。いずれも関数が要る
  • 関数をやめた理由は、コールドスタートのばらつき、自分では解消できない 180 秒の失敗、運用の手間の 3 つ
  • 設定と実行ログに PAR が書き込まれる構成なので、それらを読める権限を絞る。実行ログは ERROR に下げ、作業用グループのポリシーには条件を付ける(本記事では文面のみ)
  • つまずいたのは、公式ドキュメントの例がそのままではエラーになる箇所(responseType の大文字小文字、ログイン用ルートの認可ポリシー)、Content-Type の指定漏れ、初回ログインの 401、IdP のリダイレクト先を決める loginPath が今回の CLI では無視されることの 4 点

フル構成を作る途中で直した設計ミス(PC に渡した権限、管理面と閲覧面のオリジン、共有 URL の範囲。直した後の構成が本記事のものです)と権限の粒度の話は、別の記事で扱う予定です。


8. 参考

  1. AIが作ったHTMLをスマホで見たい! 個人ダッシュボード「HTML共有くん」を作った(@minorun365 さんの記事) ↩

  2. minorun365/html-share(HTML共有くんのリポジトリ。ライセンスは Apache-2.0。本記事の管理面の画面は、このリポジトリの web/ をそのまま使っています) ↩ ↩2 ↩3

  3. Introducing Cloudflare@Oracle Cloud Infrastructure: Supercharge Applications and AI Workloads on OCI。Oracle Cloud Infrastructure Blog(2026 年 5 月 27 日)。CDN・WAF・DDoS 対策・API セキュリティを、OCI のコンソールから Cloudflare のサービスとして利用できる ↩

  4. Always Freeリソース。Oracle Cloud Infrastructure ドキュメント。Object Storage 20 GB・Vault シークレット 150 個・VCN 2 つ ↩

  5. Cloud Price List。Oracle。API Gateway は 100 万 API コールあたり 3 ドル(本記事の執筆時点) ↩

  6. Oracle Cloud Infrastructure Free Tier。Oracle Cloud Infrastructure ドキュメント。トライアル終了後、アップグレードしなければ有料リソースは回収される ↩

  7. オブジェクト・ストレージの事前認証済リクエスト。Oracle Cloud Infrastructure ドキュメント。期限は任意の将来日時、削除で即失効 ↩

  8. 署名付き URL を使用したオブジェクトの共有。Amazon S3 ユーザーガイド。発行者の資格情報で署名、有効期限は CLI/SDK で最長 7 日 ↩

  9. JSONファイルの編集でトークン認証および認可リクエスト・ポリシーを追加。OCI API Gateway ドキュメント。validationFailurePolicy(OAUTH2)の JSON 例(responseType は小文字)、ルート単位の authorization ポリシー、maxExpiryDurationInHours の説明 ↩ ↩2 ↩3

  10. OAuth2ResponseValidationFailurePolicy。OCI Python SDK リファレンス(2.184.2)。response_type の許容値 CODE、fallback_redirect_path、login_path ↩ ↩2

  11. オブジェクト・ストレージ・オブジェクト・ライフサイクル管理。Oracle Cloud Infrastructure ドキュメント。サービスへの権限付与と、接頭辞・パターンによる対象指定 ↩ ↩2

  12. トークンの検証によるAPIデプロイメントへの認証および認可の追加。OCI API Gateway ドキュメント。OAuth 2.0 の検証失敗ポリシーの説明(ログイン・リダイレクト・パス、セッション Cookie と CSRF トークンの記述) ↩

  13. オリジン間リソース共有 (CORS)。MDN。プリフライトリクエストの説明 ↩

  14. <form>: フォーム要素。MDN。method 属性に指定できるのは post・get・dialog ↩

  15. OCI Functionsのトラブルシューティング。「ファンクションの呼出し」の節にエラーコードの一覧 ↩

  16. APIデプロイメントへのロギングの追加。OCI API Gateway ドキュメント。実行ログのレベル(デフォルトは「情報」) ↩

  17. Amazon S3 オリジンへのアクセスを制限する。Amazon CloudFront 開発者ガイド(オリジンアクセスコントロール) ↩

  18. Session management。Cloudflare Zero Trust ドキュメント。セッションは 15 分から 1 か月 ↩

  19. Policies。Cloudflare Access のセレクタに IP ranges(CIDR) ↩

  20. Sign requests。Cloudflare Workers の HMAC 署名付き URL の公式例 ↩

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?