ちょっと話題の記事

Amazon API Gateway プライベート API の「プライベート」を誤解してると、とても危ないという話。

プライベートって紛らわしい言葉ですね
2020.07.09

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

API Gateway にはパブリック API と、プライベート API が作成できますが、「プライベート」という言葉を正しく理解していますでしょうか?

「自分の AWS アカウント内からのみアクセスできる API でしょ?」

と考えたあたなに質問です。図にすると、このようなイメージでしょうか?

あなたのアカウント内にプライベートな API Gateway が存在し、他の AWS アカウントから接続するには承認しないと接続できない。それが「プライベート」だと。

このようにお考えの場合、API Gateway にはリソースポリシーでアクセス元を制限したり、API Gateway で認証なども実装できますが「今回はプライベートなので、特に制限は必要ありません。」と考え、以下のようなリソースポリシーを設定していたりするでしょうか?

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:111222333444:n2pu2qztr8/*"
        }
    ]
}

もし、このように認識されていたら大きな間違いです。いますぐプライベート API Gateway に何らかの制限をかけてください。

「プライベート」とは

私の理解ではプライベート API Gateway の「プライベート」とは、「パブリックから直接アクセスできない」ただそれだけです。あなただけがアクセスできるプライベートネットワーク内の API という意味ではありません。(だって、それって VPC ですよね。API Gateway は VPC に属しません)

なので、AWS のプライベートネットワーク内においては特に制限をしない限り、誰でもアクセスできる API Gateway です。「プライベート」といっても、ネットワーク的に分断されていないというイメージでは、以下のように考えるとしっくり来るかと思います。(公式にこのように表現された図はみてないので、あくまでイメージとして)。

API Gateway という共通基盤があり、あなたが作成したプライベート API のオーナーはあなたです。ただし、ネットワーク的には VPC エンドポイントさえ経由すれば誰でもアクセスできます。アクセスするための承認を必要としない、ということです。

厳密にいえば、"Principal": "*"Allow していることが承認行為をしてしまっていることになりますが、ここで言う承認とは VPC ピアリングのようなネットワーク的な接続の承認はありません、という意味です。

「え、怖いやん」と思うかもしれませんが、考え方としては S3 のバケットポリシーと同じです。バケットポリシーを "Principal": "*""Action": "s3:GetObject" を許可すれば誰からもアクセスできる公開バケットになりますよね?ネットワーク的な接続承認はないはずです。プライベート API という名称によって、「自分のプライベートなネットワーク内にある API」と誤解しているに過ぎません。

もう一度、先程のリソースポリシーを見てください。"Principal": "*""Action": "execute-api:Invoke" を許可する。"s3:GetObject""execute-api:Invoke" に変わっただけですね。プライベート API は単にパブリックエンドポイントを持たないというだけですので、このポリシーは AWS のプライベートネットワーク内においては、パブリック公開(誰からもつつけるという意味でのパブリック)している、ということになります。

パブリックの API Gateway の場合、リソースポリシーでユーザーや、ロール、通信元による制限をされていたと思います。プライベート API Gateway も同じです。「プライベート」という言葉に踊らされないでください。

試してみる

それでは実際にアクセスできるところを確認します。2 つのアカウントを使って以下のように構成しています。

プライベート API Gateway はサンプルをインポートしています。認証は使用しておらず、API Gateway のリソースポリシーは下記のとおりです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:111222333444:n2pu2qztr8/*"
        }
    ]
}

この状態で 666777888999 の AWS アカウント内の EC2 から curl で API コールしてみると・・・

[ec2-user@ip-172-31-0-157 ~]$ curl https://n2pu2qztr8.execute-api.ap-northeast-1.amazonaws.com/prod/pets/
[
  {
    "id": 1,
    "type": "dog",
    "price": 249.99
  },
  {
    "id": 2,
    "type": "cat",
    "price": 124.99
  },
  {
    "id": 3,
    "type": "fish",
    "price": 0.99
  }
]

API コール出来てしまいますね。

リソースポリシーで "Principal": "*" であったり、特に Condition 設定されていない場合、各々の VPC エンドポイントを経由するだけで誰でもプライベート API Gateway にアクセスできることがお解りいただけたでしょうか。

VPC エンドポイントで制限する

では、どうすれば良いかといえば簡単なことです。プライベート API Gateway でもアクセス元を制限しましょう。または認証を入れましょう。

通信元の VPC エンドポイント ID で許可するネットワークを絞る場合は、以下のとおりです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:111222333444:n2pu2qztr8/*",
            "Condition": {
                "StringEquals": {
                    "aws:sourceVpce": "vpce-111222333444"
                }
            }
        }
    ]
}

このように設定し、API を再デプロイし反映します。そうすると、以下のように vpce-111222333444 を経由する EC2 からはアクセス出来ますが、

[ec2-user@ip-192-168-0-59 ~]$ curl https://n2pu2qztr8.execute-api.ap-northeast-1.amazonaws.com/prod/pets/
[
  {
    "id": 1,
    "type": "dog",
    "price": 249.99
  },
  {
    "id": 2,
    "type": "cat",
    "price": 124.99
  },
  {
    "id": 3,
    "type": "fish",
    "price": 0.99
  }
]

許可されていない VPC エンドポイントからはアクセス出来なくなります。

[ec2-user@ip-172-31-0-157 ~]$ curl https://n2pu2qztr8.execute-api.ap-northeast-1.amazonaws.com/prod/pets/
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:********3444:n2pu2qztr8/prod/GET/pets/"}

送信元 IP 制限は意味がない

プライベート API Gateway の場合、送信元 IP アドレスでの制限はオススメしません。同じ IP 範囲であれば、他人の AWS アカウントからでもアクセス出来ることに変わりないからです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:641908663583:n2pu2qztr8/*",
            "Condition": {
                "IpAddress": {
                    "aws:VpcSourceIp": [
                        "192.168.0.0/20",
                        "172.31.0.0/16"
                    ]
                }
            }
        }
    ]
}

別アカウントですが、許可された CIDR 範囲の EC2 からアクセスしてみましょう。

[ec2-user@ip-172-31-0-157 ~]$ curl https://n2pu2qztr8.execute-api.ap-northeast-1.amazonaws.com/prod/pets/
[
  {
    "id": 1,
    "type": "dog",
    "price": 249.99
  },
  {
    "id": 2,
    "type": "cat",
    "price": 124.99
  },
  {
    "id": 3,
    "type": "fish",
    "price": 0.99
  }
]

ご覧のとおり、別アカウントであろうと VPC エンドポイントが該当の IP 範囲だとアクセス出来てしまいますね。これでは意味がありません。必ず環境固有となる ID 等を使って制限しましょう。

さいごに

ついつい「プライベート」という名称で、「自分にとってのプライベート」と勘違いしそうになりますよね。。プライベートだと信じて使っていたら、実はプライベートネットワーク内においては公開状態だった、ということになりかねません。

プライベート API Gateway でも基本的にはパブリック API Gateway 同樣に、送信元による許可であったり、今回は特に触れていませんが認証を実装して安全に利用しましょう。

以上!大阪オフィスの丸毛(@marumo1981)でした!