依頼内容
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
正規表現とかだめですねこれはw
ページ内探しても正規表現に関する言及がない。
さーどうしようかなっと・・
参照
この辺で議論されている厄ネタ。
https://github.com/aws/aws-cdk/issues/30453
で多分バグではなくって仕様だけど、RFC上複数のoriginをスペースで並べて返していいってなってるからResponseTemplateで変なことしなくていいんじゃない?って思うんだけどなんか理由でもあるのかな。