【GKE/Terraform】KMSを使ってGKEのSecretをよりセキュアに管理する

6 min読了の目安(約6100字TECH技術記事

こちらの記事に使われてるコードの以下のリポジトリにあります。

https://github.com/nekoshita/gke-application-layer-secret-encryption-example

こんな人向け

  • 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コマンドで行う例をみたい場合は、公式ドキュメントを参考にしてください。

https://cloud.google.com/kubernetes-engine/docs/how-to/encrypting-secrets

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のサポートで聞く(今働いてる現場がサポート契約してくれているので、機会があったら聞きます)