AWS WAF で AppSync デフォルトドメインへのアクセスを制限できるか試してみた

2022.06.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

先日、AppSyncでカスタムドメインを設定しました。

この記事のさいごにも記載しましたが、デフォルトドメインも引き続き使えるようです。
API Gatewayの場合はデフォルトドメインを無効化できますが、セキュリティ上mTLSを構成した時などに無効化することが推奨されていることなどもお伝えしました。

もしAppSyncでデフォルトドメインを無効化したい場合はどうしたらいいのかを調べてみました。

標準で無効化する方法はなさそう

「無効化」という機能自体は用意されていなさそうです。
AppSyncで完全にカスタムできそうなセキュリティ周りの機能としては以下が利用可能です。

AWS_LAMBDA 認証

AWS Lambdaを使って、独自のAPI認証ロジックを実装することが出来ます。
Lambda関数は次の形式のイベントを処理することが出来るようです。

{
    "authorizationToken": "ExampleAUTHtoken123123123",
    "requestContext": {
        "apiId": "aaaaaa123123123example123",
        "accountId": "111122223333",
        "requestId": "f4081827-1111-4444-5555-5cf4695f339f",
        "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n",
        "operationName": "MyQuery",
        "variables": {}
    }
}

一度実装して検証してみる価値はありそうですが、今回の要件を処理するための情報として不足していそうな気もします。

AWS WAFを使用

前回の記事のさいごにも登場しましたが、AppSyncではAWS WAF統合機能があります。

Web ACLであればかなり柔軟なルール設定が出来るので、アクセス元のドメインなどで簡易的な制御は出来そうですね。
今回はこちらを試してみたいと思います。

AWS WAF で試してみる

前回の記事から推察するに、カスタムドメインはCloudFrontディストリビューションを経由してアクセスされるので、デフォルトドメインの場合とアクセス元情報などが結構異なっているはず、と思っていたのですが、どうやらAppSyncのエンドポイントはデフォルトでCloudFrontを経由するようになっているみたいです。(デフォルト作成したAppSyncの名前解決先がCloudFrontエッジになっていました)

カスタムドメインとデフォルトドメインでのリクエストの違いを確認する

まずはWAFを適用し、リクエストを確認してみたいと思います。
AppSyncコンソールの設定メニューからWeb application firewallを有効化します。

有効化後に、特にブロックルールは設定しないWeb ACLを関連付けします。

カスタムドメイン

POST /graphql
accept: */*
cloudfront-forwarded-proto: https
cloudfront-is-desktop-viewer: true
cloudfront-is-mobile-viewer: false
cloudfront-is-smarttv-viewer: false
cloudfront-is-tablet-viewer: false
cloudfront-viewer-country: JP
content-length: 66
content-type: application/graphql
host: graphhoge.tak1wa.com
user-agent: curl/7.64.1
via: 2.0 823ea75be36f9495c1eb23cb55639cd2.cloudfront.net (CloudFront)
x-amz-cf-id: 9yU1mx0gHhDD4q8ROnen_r7fIme3NtNFgsDz3eyDUe2ZEjAkjgwI2Q==
x-amzn-trace-id: Root=1-629beb91-23c5a2313c23ee126383410d
x-api-key: <hogehogeapikey>
x-appsync-dnsname: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com
x-forwarded-for: 111.222.333.444, 130.176.135.164
x-forwarded-port: 443
x-forwarded-proto: https

デフォルトドメイン

POST /graphql
accept: */*
cloudfront-forwarded-proto: https
cloudfront-is-desktop-viewer: true
cloudfront-is-mobile-viewer: false
cloudfront-is-smarttv-viewer: false
cloudfront-is-tablet-viewer: false
cloudfront-viewer-country: JP
content-length: 66
content-type: application/graphql
host: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com
user-agent: curl/7.64.1
via: 2.0 70e24e789a7f5c3f75693b4d637a2d22.cloudfront.net (CloudFront)
x-amz-cf-id: wFv8V3yj4HUM5Ujlu2eZXbKuEzj0v72wNTUY1LTmnTf4t0e-IlgASw==
x-amzn-trace-id: Root=1-629beb7f-2befd8b90d44221215030c91
x-api-key: <hogehogeapikey>
x-forwarded-for: 111.222.333.444, 52.46.48.81
x-forwarded-port: 443
x-forwarded-proto: https

カスタムドメインでアクセスした時のみ、x-appsync-dnsnameヘッダーが追加されています。
上記ヘッダーの技術情報を見つけることが出来ませんでしたが、CloudFrontのどこかの段階で追加しているのでしょう。
なお、AppSyncのアクセスログではx-appsync-customdnsというヘッダーも存在していたのですが、WAFで制御する時点では存在してないヘッダーでした。もう少しあとの経路で追加されているヘッダーのようですね。

HTTPヘッダーで制御する

今回はカスタムドメインのみ許可して、デフォルトドメインをブロックしたいので、x-appsync-dnsnameヘッダーで判定をしてみます。
ここではx-appsync-dnsnameを使っていますが、hostでも良いと思います。
どちらにせよ、このレベルでの制御はクライアントが明示的にヘッダーを追加した場合は通過出来るので、「一応設定しておくか」くらいの気持ちが大事かなと思います。

さて、先程関連付けしたACLにブロックルールを追加します。

これだけです。JSON定義だと以下のようになります。

{
  "Name": "header-graphhoge",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "header-graphhoge"
  },
  "Statement": {
    "NotStatement": {
      "Statement": {
        "ByteMatchStatement": {
          "FieldToMatch": {
            "SingleHeader": {
              "Name": "x-appsync-dnsname"
            }
          },
          "PositionalConstraint": "EXACTLY",
          "SearchString": "5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com",
          "TextTransformations": [
            {
              "Type": "NONE",
              "Priority": 0
            }
          ]
        }
      }
    }
  }
}

リクエスト送ってみる

では確認してみましょう。

デフォルトドメイン

$ curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \
-H "Content-Type:application/graphql" \
-H "x-api-key:<hogehogeapikey>" \
-d '{"query": "query ListEvents { listEvents { items { id name } } }"}'
{
  "errors" : [ {
    "errorType" : "WAFForbiddenException",
    "message" : "Forbidden"
  } ]
}

デフォルトドメインでリクエストを送信したところ、ブロックが出来ました。
次にカスタムドメインでリクエストを送信してみます。

カスタムドメイン

$ curl -X POST "https://graphhoge.tak1wa.com/graphql" \
-H "Content-Type:application/graphql" \
-H "x-api-key:<hogehogeapikey>" \
-d '{"query": "query ListEvents { listEvents { items { id name } } }"}'
{"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}}

こちらは通過しましたね。

ヘッダーを明示的に付与してデフォルトドメインでリクエストを送ってみる

最後に、デフォルトドメインへリクエストを送信するが、ヘッダーを明示的に指定した場合です。
WAFでブロックするヘッダーをx-appsync-dnsnamehostでそれぞれ試しましたが、どちらも通過出来ました。

curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \
-H "Content-Type:application/graphql" \
-H "x-api-key:<hogehogeapikey>" \
-H "x-appsync-dnsname: 5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com" \
-d '{"query": "query ListEvents { listEvents { items { id name } } }"}'
{"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}}

$ curl -X POST "https://5hh6qatoyfcgjoxy2vangzkgle.appsync-api.ap-northeast-1.amazonaws.com/graphql" \
-H "Content-Type:application/graphql" \
-H "x-api-key:<hogehogeapikey>" \
-H "host: graphhoge.tak1wa.com" \
-d '{"query": "query ListEvents { listEvents { items { id name } } }"}'
{"data":{"listEvents":{"items":[{"id":"8b69181d-47ca-45a1-837d-e9a0dc6386d4","name":"My First Event"},{"id":"b00b5674-35bf-4ba0-b233-724378ac85ed","name":"My First Event"}]}}}

さいごに

本日は前回AppSyncでカスタムドメインを構成したので、その際にデフォルトドメインを無効化出来るのか、どういう方法があるのかを検証して試してみました。
簡易的ですが AWS WAFでヘッダーを判定してのブロックは出来ました。
カスタムドメインもデフォルトドメインも名前解決以外の経路は同じようなので、もう一段何か追加してIPアドレスなどでブロックする方法は出来るかもしれないなぁと思って、そこでやめました。