【GKE/Terraform】KMSを使ってGKEのSecretをよりセキュアに管理する
こちらの記事に使われてるコードの以下のリポジトリにあります。
こんな人向け
- GKEのシークレットが本当にセキュアか不安
- GKEのシークレットをもっとセキュアに管理したい
- アプリケーションレイヤでのシークレットの暗号化ってなに?
- アプリケーションレイヤでのシークレットの暗号化を有効にしたけど、本当にKMSで暗号化されてんの?
そもそもGKEのシークレットってどこに保存されてるの?
- 以下の図がkubernetesの簡易的な構成図です
- secretのデータは
etcd
に格納されています - Userが
kubectl get secret
のようなコマンドを実行すると、kube-api-server
がetcdからsecretのデータを取得し、復号化してユーザーに返却します - GKE はデフォルトで、シークレットなどの保存されている顧客コンテンツを暗号化してくれています
-
etcd
内のデータをよりセキュアに管理するためには、 アプリケーションレイヤでのシークレットの暗号化 を行います
アプリケーションレイヤでのシークレットの暗号化とは?
- アプリケーションレイヤでsecretを暗号化を有効にすると、etcd に格納されているデータを自分で用意したKMSの鍵を使って暗号化します
- たとえば、攻撃者がetcdのオフラインコピーにアクセスした場合でもデータの内容を保護できるようになります
- 自分でKMSで鍵を用意するので、任意のタイミングでローテーションを行うこともできます
- 公式ドキュメント
アプリケーションレイヤでのシークレットの暗号化を有効にしたGKEクラスタを作る
※この記事ではterraformでの作成方法の例を載せます。GCPコンソールやgcloudコマンドで行う例をみたい場合は、公式ドキュメントを参考にしてください。
KMSの鍵を作成する
KMSの鍵を使って、暗号化、復号化するので、KMSの鍵を作成します。
resource "google_kms_crypto_key" "sample_crypto_key" {
name = "sample_crypto_key"
key_ring = google_kms_key_ring.sample_key_ring.id
}
resource "google_kms_key_ring" "sample_key_ring" {
name = "sample_key_ring"
# GKEクラスタと同じリージョンに作成する必要がある
location = "asia-northeast1"
}
KMSへのアクセス権限を付与する
クラスタプロジェクトのGKEサービスアカウントにKMSへのアクセス権限を付与しないと、KMSの鍵を使えません。なので、権限を付与してあげます。
resource "google_kms_key_ring_iam_member" "key_ring" {
key_ring_id = google_kms_key_ring.sample_key_ring_v2.id
role = google_project_iam_custom_role.gke_cluster_defualt_to_access_kms.id
# クラスタプロジェクトのGKEサービスアカウントはGoogleによって勝手に作成されています
member = "serviceAccount:service-${"あなたのGoogleプロジェクト番号"}@container-engine-robot.iam.gserviceaccount.com"
}
resource "google_project_iam_custom_role" "gke_cluster_defualt_to_access_kms" {
role_id = "GKEClusterDefaulttoAccessKMS"
title = "GKE Cluster Default to AccessKMS"
permissions = [
# KMSを使った暗号化、復号化に必要なロール
"cloudkms.cryptoKeyVersions.useToDecrypt",
"cloudkms.cryptoKeyVersions.useToEncrypt"
]
}
GKEクラスタを作成する
アプリケーションレイヤでのシークレットの暗号化を有効化したGKEクラスタを作成します。
resource "google_service_account" "my_cluster" {
account_id = "my-cluster"
display_name = "My Service Account For My Cluster"
}
resource "google_container_cluster" "my_cluster" {
name = "my-cluster"
# KMSと同じリージョンにする必要があります”
location = "asia-northeast1-a"
project = "あなたのGoogleプロジェクトID"
# デフォルトのノードプールがないクラスターを作成することはできないので、
# クラスター作成直後にデフォルトのノードプールを削除する
remove_default_node_pool = true
initial_node_count = 1
# アプリケーションレイヤでのシークレットの暗号化を有効にする
database_encryption {
state = "ENCRYPTED"
key_name = google_kms_crypto_key.sample_crypto_key.self_link
}
}
resource "google_container_node_pool" "my_cluster_nodes" {
name = "my-node-pool"
# KMSと同じリージョンにする必要があります”
location = "asia-northeast1-a"
cluster = google_container_cluster.my_cluster.name
node_count = 1
node_config {
preemptible = true
machine_type = "n1-standard-1"
service_account = google_service_account.my_cluster.email
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
}
}
本当に暗号化されてる?
gcloudコマンドにGoogleプロジェクトIDをセットしてから
$ gcloud config set project "あなたのGoogleプロジェクトID"
とりあえずGKEクラスタを作って、secretを作成する
$ gcloud container clusters get-credentials my-cluster --zone asia-northeast1-a
$ echo "apiVersion: v1
kind: Secret
metadata:
name: sample
type: Opaque
data:
extra: YmFyCg==" | kubectl apply -f -
ちゃんと作成できたか確認
$ kubectl get secret sample -o yaml
OK
このsecretの内容が etcd
に格納され、KMSのキーを使って暗号化されている想定です
まずは手元でKMSのキーを暗号化・復号化できるか試した
てきとうに暗号化してみました
$ echo -n "some text here" | gcloud kms encrypt \
--plaintext-file=- \
--ciphertext-file=- \
--location=asia-northeast1 \
--keyring=sample_key_ring \
--key=sample_crypto_key | base64
ちゃんと暗号化できました
CiQAhVZUErgoc6zddWjqx3lkpuI6+Ju5g2rEqcZMv194x9zMfDoSNwCtz90Eh31c+KAFl9hqucVASJge0T7UoqVgDyejqnEKl9UGRJLZ4pg+ogpn406cjBiDo25LIqk=
復号化もしてみた
$ echo -n "CiQAhVZUErgoc6zddWjqx3lkpuI6+Ju5g2rEqcZMv194x9zMfDoSNwCtz90Eh31c+KAFl9hqucVASJge0T7UoqVgDyejqnEKl9UGRJLZ4pg+ogpn406cjBiDo25LIqk=" | base64 -D | gcloud kms decrypt \
--plaintext-file=- \
--ciphertext-file=- \
--location=asia-northeast1 \
--keyring=sample_key_ring \
--key=sample_crypto_key
ちゃんと復号可できた
some text here
KMSのキーを消してみた
GCPコンソールからKMSのキーを削除してみた
消えるまで24時間かかるらしい(○ Will be destroyed on 2/15/21, 1:07 PM)
ので、24時間待ちました
KMSのキーが消えたので、手元で暗号化・復号化できないことを確認する
手元でもう一度暗号化するしてみます
$ echo -n "some text here" | gcloud kms encrypt \
--plaintext-file=- \
--ciphertext-file=- \
--location=asia-northeast1 \
--keyring=sample_key_ring \
--key=sample_crypto_key | base64
KMSのキーが消えているので、エラーします
KMSのキーが消えてるから、kubernetesのsecret見れなくなってるよね?!
$ kubectl get secret sample -o yaml
ばっちり確認できちゃいました。。。。
なぜ確認できるのか、公式ドキュメントを読み漁りましたがわかりませんでした。
おそらくkubernetesのapi-serverがキャッシュしてるのではないだろうかと思います
しかし、新しいsecretを作成しようとすると以下のようなエラーになりました!
Error from server (InternalError): error when creating "secret.yaml": Internal error occurred: rpc error: code = NotFound desc =
CloudKMS key version has been destroyed: projects/GoogleプロジェクトID/locations/asia-northeast1/keyRings/sample_key_ring/cryptoKeys/sample_crypto_key.
You will not be able to restore the key because it has been more than 24 hours since the destruction of the key.
相変わらずsecretをみることはできてしまいましたが、新しくsecretを作成できなかったということは、secretの作成時にKMSが使われていると言うことですね!
また、スクショ撮り忘れてしまったのですが、GCPコンソールでGKEクラスタのページを開くと、「KMSのキーがないよ〜」って警告も出ていました!
まとめ
- GKEのsecretをよりセキュアにしたい人は、KMSによるsecretの暗号化は有効にすべき
- クラスタプロジェクトのGKEサービスアカウントにKMSへのアクセス権限を付与するのを忘れないで
- ちゃんとKMSを使ってsecretの暗号化はしてくれているようです
- KSMのキーを消しちゃうとクラスタがぶっ壊れるので、絶対に消さないでね!
- キーのローテーションもできるよ!
個人的宿題
- KMSのキーを消してもsecretを取得できたしまった原因について、GCPのサポートで聞く(今働いてる現場がサポート契約してくれているので、機会があったら聞きます)
Discussion