GKE で Google Cloud Armor を利用してみる
GKE + Cloud Armor
Google Cloud Armor とは、クライアントからの攻撃からwebアプリケーションを保護してくれるというGCPが提供するサービスです。
IP制限を行ったり、事前定義されたWAFを利用して各種攻撃を防いだりできます。
そんなCloud ArmorをGKEで利用するための手順等を簡単にまとめていきます。
前提
- GCP projectの作成
- 各種binのインストールおよび設定
- gcloud
- terraform
- kubectl
やること
以下のような、form送信ができるwebアプリケーションをGKE上で動かしているとします。
ソースコードはこんな感じ
表示したい内容
package main
import (
"fmt"
"net/http"
)
func main() {
server := http.Server{
Addr: ":80",
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "static/index.html")
})
http.HandleFunc("/form", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, r.Form)
})
server.ListenAndServe()
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Form</title>
</head>
<body>
<form action="/form" method="post">
<input type="text" name="post" id="">
<button type="submit">Submit!</button>
</form>
</body>
</html>
-
送信前
-
送信後
form
のレスポンスはContent-Type: text/plain
なのでhtmlタグやscriptをpostしても実行はされないものの、postされたbodyのチェックは行っていないので、
<script>alert('foobar')</script>
(XSS)
や
1 ' or ' 1 ' = ' 1;
(SQLi)
といった内容がpostできてしまいます。
そこで、Google Cloud Armorにより事前構成されたルールのうち以下2つを適用し、攻撃を防げるのか確認していきます。
- xss-stable
- sqli-stable
Google Cloud Armor security policy の作成
ここでは、test-security-policy
という名前で作成していきます。なので、その部分は読み替えていただければと。
ルールの優先度は1000
としていますが、0~2,147,483,646
であれば何でも構いません。
今回適用する2つのルールに優先度は特にないので、まとめて1つのルールとします。
やり方としては以下の3つがありますが、下2つだけ記述します。
- GCPコンソール
- gcloud
- terraform
gcloudで作成
gcloud
から行う場合、
- security policy 作成
- security policy に適用するルールの作成
の2ステップとなります。詳しくは公式ドキュメント参照。
gcloud compute security-policies create test-security-policy \
--description "security policy for test"
gcloud compute security-policies rules create 1000 \
--security-policy test-security-policy
--expression "evaluatePreconfiguredExpr('xss-stable') || evaluatePreconfiguredExpr('sqli-stable')"
--action "deny-403"
terraformで作成
terraformが利用するサービスアカウントには(Update,Deleteも考慮して)以下の権限が必要です。
- compute.securityPolicies.get
- compute.securityPolicies.create
- compute.securityPolicies.update
- compute.securityPolicies.delete
そしたら、以下のような定義ファイルを用意してapplyします。
provider "google" {
project = "PROJECT_ID" # 自身のGCP project id
credentials = "path/to/serviceaccount-key.json" # file指定じゃなくて環境変数利用する場合は不要
}
resource "google_compute_security_policy" "security-policy" {
name = "test-security-policy"
description = "security policy for test"
rule {
action = "deny(403)"
priority = "1000"
match {
expr {
expression = "evaluatePreconfiguredExpr('xss-stable') || evaluatePreconfiguredExpr('sqli-stable')"
}
}
}
rule {
action = "allow"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "default rule"
}
}
1つ注意点として、
コンソールあるいはgcloud
で作成した場合は自動的に優先度2147483647
のデフォルトルールが作成される一方で、
terraform
から作成する場合は自身で優先度2147483647
のデフォルトルールを定義する必要があります。
(コンソールやgcloud
での作成時に自動作成されるすべてのIPアドレスを許可
ルールを定義しています)
GKE 設定
前提として、GKEクラスタは作成済とします。
マニフェスト(Before)
Security Policy適用前のマニフェストは以下の通りです。
apiVersion: v1
kind: Namespace
metadata:
name: cloud-armor-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: cloud-armor-test
name: test-deployment
spec:
selector:
matchLabels:
app: go-simple-form
replicas: 2
template:
metadata:
labels:
app: go-simple-form
spec:
containers:
- name: go-simple-form
image: gcr.io/PROJECT_ID/go-simple-form:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
namespace: cloud-armor-test
name: test-service
spec:
type: NodePort
selector:
app: go-simple-form
ports:
- port: 80
protocol: TCP
targetPort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
namespace: cloud-armor-test
name: test-ingress
spec:
backend:
serviceName: test-service
servicePort: 80
BackendConfig 作成
GKEでGoogle Cloud Armor security policyを利用するには、CRDのBackendConfig
を使用します。
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
namespace: cloud-armor-test
name: test-backendconfig
spec:
securityPolicy:
name: test-security-policy # さっき作成したsecurity policyの名前を指定
Service にアノテーションを追加
上で作成したBackendConfigをServiceリソースのアノテーションで参照することで、securiy policyを適用することができます。
apiVersion: v1
kind: Service
metadata:
namespace: cloud-armor-test
name: test-service
+ annotations:
+ cloud.google.com/backend-config: '{"default": "test-backendconfig"}' # backendconfig name を指定
spec:
type: NodePort
selector:
app: go-simple-form
ports:
- port: 80
protocol: TCP
targetPort: 80
テスト
以上で適用は完了したので、実際のところどうなるのかテストします。
Ingress のマニフェストで静的IPの指定はしていないので、自動でGCLBに振られたIPを確認し、ブラウザから接続します。
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
test-ingress <none> * 34.117.217.233 80 11h
XSS
SQLi
無事どちらもWAFでブロックすることができました!
(post内容が403画面から分からないのでエビデンスとしてちょっと不十分ですが動画撮るほどでもないので...)
Discussion