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?

CDKとAPI Gatewayと複数設定されたCORSのOriginについて

Last updated at Posted at 2024-08-25

依頼内容

CORSのoriginsを下記の通り受け付けるようにして!

https://somedomain.com
https://*.somedomain.com 、たとえば https://sub.somedomain.com こんな感じのオリジン全部。

どうしよう?

プリフライト時の(OPTIONS) メソッドでリクエストきたときの access-control-allow-origin ヘッダのことですね・・
えーとまずRFC的にどう書くんだっけか。

Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: <origin>
Access-Control-Allow-Origin: null

では・・originって何?

RFC 6454より。

   origin              = "Origin:" OWS origin-list-or-null OWS
   origin-list-or-null = %x6E %x75 %x6C %x6C / origin-list
   origin-list         = serialized-origin *( SP serialized-origin )
   serialized-origin   = scheme "://" host [ ":" port ]
                       ; <scheme>, <host>, <port> from RFC 3986

BNF表記めっちゃ久しぶりだけど・・origin-listはスペースで区切って好きなだけ並べるがいいって感じよね。

では、hostって何? RFC 3986見ろって書いてあるので・・

  host        = IP-literal / IPv4address / reg-name
  ...
  reg-name    = *( unreserved / pct-encoded / sub-delims )

hostの前者2つがIPアドレスで、このreg-nameというのがホスト名の書式。

   pct-encoded   = "%" HEXDIG HEXDIG
   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
                 / "*" / "+" / "," / ";" / "="

* ありますねーーーー!

じゃあ設定してもいいんだ。

と思って設定しようとするとAPI Gatewayが変な感じ。

CDKはAPI Gatewayで何をしようとしているのか?

まずはoriginを1個だけ設定した状態から、複数設定したときのdiffを御覧いただきたい。

[~] AWS::ApiGateway::Method Resource-watch/OPTIONS ResourcewatchOPTIONS3348F02A 
 └─ [~] Integration
     └─ [~] .IntegrationResponses:
         └─ @@ -1,13 +1,16 @@
            [ ] [
            [ ]   {
            [ ]     "ResponseParameters": {
            [ ]       "method.response.header.Access-Control-Allow-Origin": "'https://somedomain.com'",
            [ ]       "method.response.header.Vary": "'Origin'",
            [ ]       "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'",
            [ ]       "method.response.header.Access-Control-Max-Age": "'7200'",
            [ ]       "method.response.header.Access-Control-Expose-Headers": "''"
            [ ]     },
            [+]     "ResponseTemplates": {
            [+]       "application/json": "#set($origin = $input.params().header.get(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params().header.get(\"origin\")) #end\n#if($origin == \"https://*.somedomain\")\n  #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin)\n#end"
            [+]     },
            [ ]     "StatusCode": "204"
            [ ]   }
            [ ] ]

パターンその1

...
> Origin: https://somedomain.com
> 
< HTTP/2 204 
< date: ***
< access-control-allow-origin: https://somedomain.com
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
< vary: Origin
< access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
< access-control-expose-headers: 
< access-control-max-age: 7200

オッケー

パターンその2

...
> Origin: https://sub.somedomain.com
> 
< HTTP/2 204 
< date: ***
< x-amzn-requestid: ***
< access-control-allow-origin: https://somedomain.com
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
< vary: Origin
< access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
< access-control-expose-headers: 
< access-control-max-age: 7200

ダメ。
access-control-allow-origin が設定されない。

でもdiffをよく見ると・・・なんでレスポンステンプレートを設定しようとする?

            [+]     "ResponseTemplates": {
            [+]       "application/json": "#set($origin = $input.params().header.get(\"Origin\"))\n#if($origin == \"\") #set($origin = $input.params().header.get(\"origin\")) #end\n#if($origin == \"https://*.somedomain\")\n  #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin)\n#end"
            [+]     },

このレスポンステンプレートでやろうとしていること。

「リクエストの Origin ヘッダの値を読んで判定しレスポンスの Access-Control-Allow-Origin ヘッダの内容を決めよう、このヘッダには一つしか 値 を設定しないからね。」

そしてその判定条件はOriginと設定したOriginsの要素の完全一致
アスタリスクで Origin 飛ばしてくるブラウザなんてないから・・・・このままではダメ。

証拠にリクエストの Originヘッダにアスタリスクぶっこんで完全一致するように仕向けると。

> Origin: https://*.somedomain.com
> 
< HTTP/2 204 
< date: Sun, 25 Aug 2024 *** GMT
< access-control-allow-origin: https://*.somedomain.com
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
< vary: Origin
< access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
< access-control-expose-headers: 
< access-control-max-age: 7200

わろた。

まーブラウザがアスタリスクで書いてある access-control-allow-origin ヘッダの値受け取っても正しく解釈するとは限らんしな、丁寧に1個だけ返してあげようという日本人的な配慮が感じられる。

どうしましょうかね

現状のコードは下記。

        const apiWatch = new Resource(this, "Resource-xyz", {
            parent: api,
            pathPart: "xyz",
            defaultCorsPreflightOptions: {
                allowOrigins: ["https://somedomain.com", "https://*.somedomain.com"],
                allowHeaders: Cors.DEFAULT_HEADERS,
                exposeHeaders: [],
                maxAge: Duration.seconds(7200)
            },
        });

CORS関連の部分は用意してある設定を用いると手が出せない。
自前でOPTIONSヘッダ実装すればなんとかなると思う。

仕様としては、リクエストの Origin がパターンにマッチするんだったらそのまま Origin の値を レスポンスの Access-Control-Allow-Origin ヘッダに転写して返す、だ。

CDKのテンプレートに変換されたコードをそのまま真似してOPTIONSメソッド実装すればいいと思うんだけどそれだけではアスタリスクの判定がうまくいかない・・はず。

レスポンステンプレートの中でなんとかできる、か?

レスポンステンプレート(VTL)のリファレンス
https://velocity.apache.org/engine/2.0/vtl-reference.html

image.png

正規表現とかだめですねこれはw
ページ内探しても正規表現に関する言及がない。

さーどうしようかなっと・・

参照

この辺で議論されている厄ネタ。
https://github.com/aws/aws-cdk/issues/30453

で多分バグではなくって仕様だけど、RFC上複数のoriginをスペースで並べて返していいってなってるからResponseTemplateで変なことしなくていいんじゃない?って思うんだけどなんか理由でもあるのかな。

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?