GCP上建置 firewall 防火牆

程式寫好部署完畢、執行環境準備完成、資料庫就緒,服務準備上線了! 等等!在 Google Cloud 上開放服務之前,你有沒有確認過服務是不是能夠被正常存取呢?是不是有內部使用的服務能被外人存取呢?在這裡我們帶你看看 GCE firewall ,讓您瞭解透過使用 GCE firewall 能夠為您的服務做到何種程度的存取控制,在維持可以正常提供服務的同時減少讓人有機可乘之處。

準備測試環境

建立測試 GCE firewall 用的 network 。
gcloud compute --project "demo-project" \    networks create "demo" --mode "auto"
然後建立兩個放在 demo network 中的 GCE instance 。
gcloud compute --project "demo-project" \    instances create "demo-1" --zone "asia-east1-a" \    --machine-type "f1-micro" \    --subnet "demo" \    --image-project "debian-cloud" \    --image-family "debian-8" \    --boot-disk-size "10" \    --boot-disk-type "pd-standard" \    --boot-disk-device-name "demo-1" gcloud compute --project "demo-project" \    instances create "demo-2" --zone "asia-east1-a" \    --machine-type "f1-micro" \    --subnet "demo" \    --metadata "startup-script=apt-get install nginx-light -y && echo \"Hello \$HOSTNAME\" | tee /var/www/html/index.html > /dev/null" \    --image-project "debian-cloud" \    --image-family "debian-8" \    --boot-disk-size "10" \    --boot-disk-type "pd-standard" \    --boot-disk-device-name "demo-2"

以 source IP 控制存取

我們在本機先試著用 SSH 連上 demo-1
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'echo "Hello World"'
等了一段時間之後,我們得到了以下訊息 (這裡假設 demo-1 的 external IP 是 222.111.111.111) 。
ssh: connect to host 222.111.111.111 port 22: Operation timed out ERROR: (gcloud.compute.ssh) [/usr/bin/ssh] exited with return code [255]. See https://cloud.google.com/compute/docs/troubleshooting#ssherrors for troubleshooting hints.
大概是說明無法連線,請我們去找某某文件解決這個問題。不過沒關係,我們先執行下面這個指令,列出 demo 這個 network 中所有的 firewall rule 。
gcloud compute --project "demo-project" \    firewall-rules list | awk '{ if ( $2 ~ /(NETWORK|demo)/ ) print }'
執行完後我們會發現目前沒有任何 firewall rule 。在這裡我們就來說明一下 GCE firewall 是怎麼運作的,基本上 GCE firewall 對於流入的流量就是「你沒說可以的就是不行!」,所以沒有 firewall rule 等於沒有任何流量可以送達 GCE instance ,我們必須要新增 firewall rule 去決定哪裡來的流量 (source) 可以透過什麼管道 (protocol & port number) 送達哪些 GCE instance (target) 。 所以我們在本機執行以下指令查詢自己對外使用的 IP 。
curl https://ipinfo.io/ip
這裡假設我們查到自己的 IP 是 111.111.111.111 ,接著執行以下指令加入 firewall rule 讓我們能以這個 IP 作為 source 、透過 SSH (使用 TCP port 22 ) 連上 GCE instance 。
gcloud compute --project "demo-project" \    firewall-rules create "demo-allow-ssh" \    --network "demo" --source-ranges "111.111.111.111" --allow tcp:22
之後我們再次試著連上 demo-1
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'echo "Hello World"'
因為我們已經新增了 firewall rule ,所以這次順利的以 SSH 連上了 demo-1 ,看到指令執行完出現的 Hello World 了。

以 target instance 上的 tag 控制存取

上面我們已經介紹了如何以 source IP 來控制存取 GCE instance ,但有時你不想、或是不需要讓所有的 GCE instance 都能被存取,這時我們能夠使用 tag 來進一步控制 firewall rule 適用的範圍。 我們可以執行以下指令修改稍早我們建立的 firewall rule ,限制做為 target 的 GCE instance 要有 ssh 這個 tag 。
gcloud compute --project "demo-project" firewall-rules update "demo-allow-ssh" --target-tags "ssh"
之後我們試著連上 demo-1
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'echo "Hello World"'
因為 demo-1 上並沒有 ssh 這個 tag ,所以 firewall rule 不讓流量通過。 所以我們來幫 demo-1 加上 ssh 這個 tag 。
gcloud compute --project "demo-project" instances add-tags "demo-1" --tags "ssh"
之後再來試驗一次。
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'echo "Hello World"'
因為 demo-1 這個 target GCE instance 上已經有 ssh tag ,加上 source IP 是 111.111.111.111 ,所以我們就能看到 Hello World 這個結果了。

以 source instance 上的 tag 控制存取

tag 能夠用在限制 target GCE instance ,也能用在限制 source GCE instance ,藉此控制 GCE instance 間的存取。 我們先來試著從 demo-1 存取 demo-2 上的 HTTP 服務 (使用 TCP port 80 ) 。
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'curl http://demo-2/ 2>/dev/null'
因為沒有適用的 firewall rule 所以流量無法從 demo-1 送達 demo-2 所以我們來加 firewall rule ,這個 firewall rule 准許有著 src tag 的 GCE instance 可以存取有著 tgt tag GCE instance 的 TCP port 80 。
gcloud compute --project "demo-project" \    firewall-rules create "demo-allow-src2tgt" \    --network "demo" --source-tags "src" --target-tags "tgt" --allow tcp:80
然後幫 demo-2 加上 tag tgt
gcloud compute --project "demo-project" instances add-tags "demo-2" --tags "tgt"
再次嘗試連線。
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'curl http://demo-2/ 2>/dev/null'
還是失敗,因為 demo-1 沒有 src 這個 tag ,現在我們幫他加上去。
gcloud compute --project "demo-project" instances add-tags "demo-1" --tags "src"
再度嘗試連線。
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'curl http://demo-2/ 2>/dev/null'
這次我們看到了 Hello demo-2 ,代表我們順利的用 source tag & target tag 控制了網路的存取。 接著我們做另外一個實驗,這裡假設 demo-2 的 external IP 是 222.111.111.222 ,我們改執行以下指令。
gcloud compute --project "demo-project" ssh "demo-1" --zone "asia-east1-a" \    --command 'curl http://222.111.111.222/ 2>/dev/null'
這個實驗的連線沒有建立成功。這裡要說明一下,雖然意圖一樣是要從 demo-1 存取 demo-2 ,但之前我們都是以 GCE instance name 來存取, GCE 會透過內部的 DNS 幫我們把 instance name 轉換成 internal IP ,所以實際上 demo-1 是用 internal IP 在存取 demo-2 的。而 source tag 只適用在 GCE instance 間以 internal IP 互相存取的場合,所以當我們嘗試存取 demo-2 的 external IP 時,實際上 GCE firewall 看到的 source 是 demo-1 的 external IP ,這時我們就要改用 source IP 的方式來控制存取了。

總結

這裡我們帶各位走過了最常見的、以 source IP 作為 GCE firewall 控管方式的設定,之後我們也透過 target tag 讓 GCE firewall 限制可以被存取的 GCE instance 的範圍,最後也展示了以 source tag 來限制 GCE instance 間的存取。如此你可以實現限制某些服務只有來自特定的 IP (如辦公室) 可以存取、終端用戶只能存取帶有特定 tag 的 GCE instance 上的 HTTP 服務、或是只有 API server 可以存取 DB server 等控管流入 GCE instance 流量的方式。至於 GCE instance 對外的流量,基本上除了往經常被 SMTP 服務使用的 TCP port 25, 465, 587 送的流量外,其他對外的流量是不會被阻擋,也無法透過 GCE firewall 的任何設定阻擋,這部份必須自己在 GCE instance 上面做設定控管。 另外,有一種 firewall 被稱作 web application firewall (WAF) ,與 GCE firewall 不同的是 WAF 是針對 HTTP(S) 流量進行過濾,在偵測到有 SQL injection, Cross-Site Scripting 等攻擊流量經過 (不論流入或流出) 時阻擋該流量通過。如此你可以讓 GCE firewall 開放 HTTP(S) (TCP port 80, 443) 的流量通過 WAF , 在 WAF 過濾完這些流量以後再放行到真正處理 HTTP(S) request 的 GCE instance 上,然後 HTTP response 再經過 WAF 的過濾,最終回到使用者的瀏覽器上。在這邊我們只先說明 WAF 與 GCE firewall 不同的地方以及適用的情況,並不實際操作給各位看。

Reference

Using Firewalls | Compute Engine Documentation Advanced networking details Sending Email from an Instance