inthisfucking.world

💩🌎

Cloud Armorを使って、サーバーレスなサービスに対して特定の国からのアクセスをブロックする

GCPのExternal HTTP(S) Load Balancerは、特定の国や地域、IPアドレスからのアクセスを許可/拒否したりする機能を持っていません。Cloud Armorを使うとそれらが可能になります。ここでは、GCPのサーバーレスなサービスを対象に、Cloud Armorを使って特定の国からのアクセスを拒否する方法を記載します。

2021年4月29日更新
GAE (standard) に対して、ロードバランサー及びVPCからのトラフィックのみを受け付けるようにする設定が追加されました。詳しくはこの記事のGAEの項目を参照ください。

External HTTP(S) Load BalancerとCloud Armorの関係

External HTTP(S) Load Balancerは、Googleのpoints of presence (PoPs) において実装されています。Googleのpopとは、Googleのネットワークへの入り口のようなもので、世界中に沢山存在しています。Googleのサービスへのアクセスは、これらのpopを通り、Googleのデータセンターに到達します (厳密には、使用しているGCPのVPCがPremium Tierかどうかが関係しています)。

gclbとpop、データセンターのネットワークの位置関係の図

Google Cloud Armor security policy overview“より引用

Cloud Armorは、External HTTP(S) Load Balancerに対するアクセスを許可/拒否することを可能にします。つまり、Cloud Armorを使って特定のアクセスを拒否する場合、External HTTP(S) Load Balancerが実装されているGoogleのpopにおいてアクセスが拒否されるので、Googleのネットワークやデータセンターにリクエストは到達しません。

例えば、External HTTP(S) Load Balancerの後ろにApp Engine (以下GAE) のappを置きます。そのGAEのappは、asia-northeast1 (東京) にあるとします。そして、米国からのアクセスを拒否するためにCloud Armorを使います。米国からそのGAEのappに対してリクエストが送られた場合、External HTTP(S) Load Balancerが実装されているGoogleのpopで拒否されるので、米国から日本にリクエストが到達することはありません。当然、当該のGAEのappにも到達しません。

Serverless NEGによって、サーバーレスなサービスに対してExternal HTTP(S) Load Balancerが使える

最近、External HTTP(S) Load Balancerのバックエンドとしてserverless NEGというものが登場しました。従来、External HTTP(S) Load Balancerのバックエンドとしてはinstance groupのみサポートされていました。つまり、最終的にトラフィックを受けるものとしてはGCEのインスタンスのみでした。

serverless NEGは、Cloud Run、Cloud Functions、GAEをバックエンドして扱うことが出来ます。これによって、External HTTP(S) Load Balancerのバックエンドとして、Cloud Run、Cloud Functions、GAEを対象とすることが出来るようになりました。つまり、サーバーレスのサービスにおいても、Cloud Armorを使ったアクセスの制御の恩恵を受けることが出来ます。

Cloud Armorを使って特定の国、例えば米国からのアクセスを拒否してみましょう。Cloud Armorを使ってアクセスの制御を行う一連の手順は以下のようになります。

security policyを作成する

$ gcloud compute security-policies create block-us-policy \
    --description "policy for blocking access from the US"

この例では、”block-us-policy”という名前のsecurity policyを作成しています。このsecurity policyに対して、デフォルトのルールを設定します。このデフォルトのルールは、この後に設定する他のルールのどれにも一致しない場合に適応されるcatch-allなルールとなります。

作成したsecurity policyに対して、デフォルトのルールを設定する

$ gcloud compute security-policies rules update 2147483647 \
    --security-policy block-us-policy \
    --action "deny-404"

先程作成した”block-us-policy”に対して、”deny-404”というアクションを持つルールを作成します。読んでその通り、404を返してアクセスを拒否するアクションです。actionパラメータに指定できる値はそんなに多くなく、allow/deny-403/deny-404/deny-502のいずれかを指定することになります (gcloudコマンドのドキュメントを参照ください)。

“2147483647”は、このルールに対する優先順位です。優先順位は、0がもっとも優先順位が高く、2147483647が最も優先順位が低い値となります。最も優先順位が低いルール、つまりデフォルトのルールということになります。

デフォルトのルール以外のルールを設定する

$ gcloud compute security-policies rules update 1000 \
    --security-policy block-us-policy \
    --expression "origin.region_code == 'US'" \
    --action "deny-404" \
    --description "Block US"

expressionパラメータで、リクエストのアクセス元の国として米国を指定し、actionパラメータで”deny-404”を設定しています。アクセスが米国からのものであった場合、404を返す形でアクセスを拒否します。これで、サーバーレスのサービスに対して、特定の国からのアクセスを拒否するExternal HTTP(S) Load Balancerを作ることが出来ます。

一点気をつけなければならないのは、サーバーレスのサービスは、deployしたappにアクセスするためのエンドポイント (url) を作成する点です。External HTTP(S) Load Balancerの後ろにサーバーレスのサービスを置いた場合でも、External HTTP(S) Load Balancerを介さずに直接サーバーレスのサービスにdeployされたappにアクセス出来ることを忘れないようにしましょう。尚、この直接のアクセスは、External HTTP(S) Load Balancerを経由していないので、Cloud Armorも適応されません。

つまり、結局はサーバーレスのサービスの方でも、直接アクセスされた場合に対する対応は行わなければなりません。以下、External HTTP(S) Load Balancerを使用してサーバーレスのサービスに負荷分散する場合において、サーバーレスのサービスの方で出来るアクセスの制御を紹介します。

GAE

GAEは、GAEのappにアクセスがあった場合に色々なHTTPヘッダーを追加します。x-appengine-をprefixとしたものが良い例です (一覧はこちら)。これらのヘッダーの中に、x-appengine-countryというものがあり、その値としてアクセス元の国の値がISO 3166-1 alpha-2のフォーマットで入っています (例: 日本であれば”JP”)。

GAEのappにおいて特定の国からのアクセスを制御する場合は、x-appengine-countryの値を利用するのが楽です。これらのGAEが足すHTTPヘッダーは、特定の値が指定された形でリクエストされても上書きされるので、GAEが追加した値が入っていることが保証されています。

External HTTP(S) Load Balancerの後ろにGAEのappを置いた場合、External HTTP(S) Load Balancerは“via”というヘッダーを追加します。このヘッダーは、External HTTP(S) Load Balancerを経由した場合は上書きされますが、そうではない場合には指定された値がそのまま到達します。クライアントが、意図的に”via: 1.1 google”というHTTPヘッダーを含んだリクエストを直接GAEのappに送った場合は、”via: 1.1 google”というヘッダーがGAEのappにそのまま到達します。つまり、このヘッダーを利用して、リクエストがExternal HTTP(S) Load Balancerを経由したのかどうかを正しく判断することは出来ません。

この記事を掲載した後、GAEがロードバランサー及びVPCからのみのトラフィックを受け付ける設定をサポートしました。`gcloud app services update`コマンドの`--ingress`オプションに、`internal-only`若しくは`internal-and-cloud-load-balancing.`を指定することで、VPCからのみ、若しくはVPCから及びロードバランサーからのみのトラフィックを受け付けるように変更できます。 これを利用することで、GAE appに直接アクセスされることを防ぐことができます。

Cloud Functions

Cloud Functionsも、基本的にはGAEと同じで、x-appengine-countryの値を利用するのが楽です。Cloud Functionsのappにも、GAE同様、x-appengine-をprefixとしたHTTPヘッダーが追加されます (さてなぜでしょう🤐️)。

GAEと大きく違う点もあります。Cloud Functionsは、ingressのトラフィックをVPCかロードバランサーからのみに制限することが出来ます。つまり、公のインターネットからはアクセス出来ない、けれどもExternal HTTP(S) Load Balancerからのアクセスであれば受け付ける様な設定が可能です。

これによって、Cloud FunctionsのappへのアクセスをExternal HTTP(S) Load Balancerからに絞ることが可能です (厳密にはVPCからのアクセスは可能です)。Cloud Armorを使ってExternal HTTP(S) Load Balancerへのアクセスを制御すれば、その制御を通ったリクエストだけを受けるCloud Functionsのappの作成が実現出来ます。 

結論

serverless NEGによって、サーバーレスのサービスに対してもCloud Armorが使えるようになりました。Cloud Armorによって特定の国からのアクセスを制御する場合は、現時点ではGAEのappよりもCloud FunctionsのappをExternal HTTP(S) Load Balancerに置いた方が綺麗に実装出来るでしょう。