LoginSignup
8
2

More than 5 years have passed since last update.

oracle/kubernetes-vault-kms-plugin を試す

Last updated at Posted at 2018-12-10

はじめに

今日は、oracle/kubernetes-vault-kms-plugin という k8s の KMS Plugin を試してみます。この Oracle 社が作って OSS で公開している KMS Plugin は、HashiCorp Vault が持つ KMS(Key Management Service)の機能と連携して、k8s の シークレットの暗号化を実現します。k8s のシークレットを暗号化する方法は、他にもいくつかありますが、KMS を使う方法は、エンベロープ暗号化を導入できるという点で優れています。

エンベロープ暗号化というのは、暗号化の手法の一つです。わたしは、この優れた手法を、KubeCon + CloudNativeCon China 2018『Turtles All the Way Down: Securely Managing Kubernetes Secrets With Secrets - Maya Kaczorowski & Alexandr Tcherniakhovski, Google』PDF資料)というセッションで知りました。すでに世界中で使われているものと思います。

エンベロープ暗号化では、2種類の鍵を使います。一つは、データを暗号化するための鍵です。これを Data Encryption Key(DEK)と呼びます。DEK は、データ毎に異なるものを作り、ローテーションはしません。もう一つは、鍵を暗号化するための鍵です。これを Key Encryption Key(KEK)と呼びます。KEK は、DEK の暗号化に使います。KEK は、KMS などで安全に管理して、定期的にローテーションします。

エンベロープ暗号化を使うと鍵の管理がとても楽になります。簡単に言えば、KEK だけしっかりと管理すれば、それで OK です。DEK は、暗号化してしまうので、データとセットで保存できます。DEK は、ローテーションもしないので、事実上、管理不要です。DEK が変わらないことで、データの再暗号化も不要というメリットも付いてきます。今回、oracle/kubernetes-vault-kms-plugin を試すことで、エンベロープ暗号化も体験できます。

検証環境

image.png

上図は、今回の検証環境です。k8s は、シングル Master 構成です。Vault と etcd は、 Master ノードとは別の VM で動かしました。Master ノード内の kubernetes-vault-kms-plugin プロセスが、kube-apiserver や Vault と連携します。kubectl の実行は、手元の Mac から行いました。

Vault

Vault は、開発モードで動かしました。開発モードの Vault は、アンシールされた状態で起動し、インメモリで稼働します。Vault の操作は、Vault が起動している VM から行いました。Vault を KMS として使うために、Transit Secrets Engine を有効にしました。

# Vault の起動
$ vault server -dev -dev-listen-address="${IP}:8200" -dev-root-token-id="${TOKEN}"

# Transit Secrets Engine の有効化
$ export VAULT_ADDR="http://${IP}:8200"
$ vault secrets enable transit
Success! Enabled the transit secrets engine at: transit/

kubernetes-vault-kms-plugin

kubernetes-vault-kms-plugin は、k8s Master ノード上で、以下の設定で動かしました。

vault-plugin.yaml
keyNames:
  - kube-secret-enc-key
transitPath: /transit
addr: http://"VaultのIPアドレス":8200
token: "Vault のルートトークン"

keyNames:には、kube-secret-enc-key をセットしました。これが KEK になります。KEK は Vault に保存されます。token: には、Vault 起動時に設定したルートトークンをセットしました。これで、Vault にアクセスします。

# kubernetes-vault-kms-plugin の起動
$ kubernetes-vault-kms-plugin \
  -socketFile='/PATH/TO/socketfile.sock' -vaultConfig="$HOME/vault-plugin.yaml"

kubernetes-vault-kms-plugin は、まだリリースバージョンがないため、バイナリには、master ブランチからビルドしたものを使いました。

kube-apiserver は、-socketFile= で指定した UNIX ドメインソケットを使って、kubernetes-vault-kms-plugin と gRPC で通信します。この UNIX ドメインソケット(socketfile.sock)は、kube-apiserver が読めるパスに置く必要があります。kube-apiserver をコンテナで起動している場合は、注意が必要です。

kube-apiserver

kube-apiserver は、KMS Plugin による Secret の暗号化を有効にするために、以下のオプションを追加して起動しました。

--experimental-encryption-provider-config=/PATH/TO/encryption-config.yaml

オプションに指定する設定ファイルは、以下の内容で作成しました。このファイルは、kube-apiserver が起動時に読み込むため、kube-apiserver が読めるパスに置きます。kube-apiserver をコンテナで起動する場合は、注意が必要です。

encryption-config.yaml
kind: EncryptionConfig
apiVersion: v1
resources:
 - resources:
   - secrets
   providers:
   - kms:
       name: myVault
       endpoint: unix:///PATH/TO/socketfile.sock
       cachesize: 0
   - identity: {}  

endpoint:には、kubernetes-vault-kms-plugin の UNIX ドメインソケット のパスをセットしました。kms:name:は、myVault としました。

cachesize:には、0 をセットしました。kube-apiserver は、KMS Plugin を使ってデコードした、平文の DEKを、cachesize:の個数分キャッシュします。キャッシュは動作を分かりにくくするので、無効にしようと思い、0 をセットしました。しかし、ソースコードを見る限り、cachesize: 0だと Default の1000 が適用されるようです。残念。まあ、リスタートをかませればいいか。

検証

KMS Plugin の有効化の前後(暗号化あり・暗号化なし)で、それぞれ、Secret を作って検証します。最初に kubectl で Secret を参照できるか確認します。それから、etcdctl で etcd のデータを確認します。暗号化ありの場合は、etcd のデータの Secret 部分は、暗号化されているはずです。

暗号化 Secret Key Value
なし secret0 password Merry Christmas!
あり secret3 password Merry Christmas!

secret1 と secret2 がないのは、作業上の都合です。気にしないで下さい。

暗号化なし

# secret0 を作成
$ kubectl create secret generic secret0 --from-literal=password='Merry Christmas!'
secret/secret0 created

# kubectl で secret0 を参照
$ kubectl get secret secret0 -o yaml |grep password: |cut -d":" -f2 |base64 --decode; echo
Merry Christmas!

# etcdctl で secret0 を参照
$ etcdctl get /registry/secrets/default/secret0 |hexdump -C
00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 73 65 63 72 65 74  |s/default/secret|
00000020  30 0a 6b 38 73 00 0a 0c  0a 02 76 31 12 06 53 65  |0.k8s.....v1..Se|
00000030  63 72 65 74 12 74 0a 4c  0a 07 73 65 63 72 65 74  |cret.t.L..secret|
00000040  30 12 00 1a 07 64 65 66  61 75 6c 74 22 00 2a 24  |0....default".*$|
00000050  32 30 62 39 37 36 31 37  2d 66 39 31 37 2d 31 31  |20b97617-f917-11|
00000060  65 38 2d 62 38 61 32 2d  66 61 31 36 33 65 34 38  |e8-b8a2-fa163e48|
00000070  62 63 35 33 32 00 38 00  42 08 08 ec e4 a2 e0 05  |bc532.8.B.......|
00000080  10 00 7a 00 12 1c 0a 08  70 61 73 73 77 6f 72 64  |..z.....password|
00000090  12 10 4d 65 72 72 79 20  43 68 72 69 73 74 6d 61  |..Merry Christma|
000000a0  73 21 1a 06 4f 70 61 71  75 65 1a 00 22 00 0a     |s!..Opaque.."..|
000000af

暗号化なしの場合は、etcd の中に、平文でしっかりと、Merry Christmas! の文字があります。

暗号化あり

# secret3 を作成
$ kubectl create secret generic secret3 --from-literal=password='Merry Christmas!'
secret/secret3 created

# kubectl で secret3 を参照
$ kubectl get secret secret3 -o yaml |grep password: |cut -d":" -f2 |base64 --decode; echo
Merry Christmas!

# etcdctl で secret3 を参照
$ etcdctl get /registry/secrets/default/secret3 |hexdump -C
00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 73 65 63 72 65 74  |s/default/secret|
00000020  33 0a 6b 38 73 3a 65 6e  63 3a 6b 6d 73 3a 76 31  |3.k8s:enc:kms:v1|
00000030  3a 6d 79 56 61 75 6c 74  3a 00 67 6b 75 62 65 2d  |:myVault:.gkube-|
00000040  73 65 63 72 65 74 2d 65  6e 63 2d 6b 65 79 3a 76  |secret-enc-key:v|
00000050  31 3a 33 6e 69 76 73 61  6f 65 61 43 35 6e 54 69  |1:3nivsaoeaC5nTi|
00000060  46 39 70 79 43 74 4a 32  44 50 51 37 6c 6b 42 5a  |F9pyCtJ2DPQ7lkBZ|
00000070  63 4c 56 38 69 44 67 79  4c 36 42 4c 35 6b 41 41  |cLV8iDgyL6BL5kAA|
00000080  56 62 76 46 69 6f 41 30  65 75 79 56 53 41 52 2f  |VbvFioA0euyVSAR/|
00000090  74 75 47 39 7a 63 57 71  43 38 50 61 62 69 45 72  |tuG9zcWqC8PabiEr|
000000a0  49 68 c2 10 69 6b e8 7b  69 8a b9 10 c9 77 29 bd  |Ih..ik.{i....w).|
000000b0  0c f2 61 a9 e1 12 e9 9d  d1 47 0b 05 e5 03 d0 be  |..a......G......|
000000c0  18 85 35 f9 34 62 0e dc  00 01 a6 db 97 ee 60 ab  |..5.4b........`.|
000000d0  59 64 4c a6 99 60 37 62  5e 6a ac 28 8f 0d 79 1e  |YdL..`7b^j.(..y.|
000000e0  12 1b b9 5b 62 78 5e c2  c0 b7 51 4c 78 1b b8 e3  |...[bx^...QLx...|
000000f0  b7 9b 90 6b a1 39 7a b0  f5 a1 e0 1a 90 c2 78 f8  |...k.9z.......x.|
00000100  34 c8 74 f5 90 05 b3 5c  e0 3b fe 29 d6 04 6d ed  |4.t....\.;.)..m.|
00000110  12 2f 73 3a 55 1f 8c 5d  d7 5c f8 11 45 02 2a 77  |./s:U..].\..E.*w|
00000120  24 61 fc 9a 9f 44 62 08  15 ce 82 c8 1e 8c 5e ce  |$a...Db.......^.|
00000130  0c 31 f5 f5 3e e8 46 81  9e fe bd 1c 83 c0 1b af  |.1..>.F.........|
00000140  66 dd 0a                                          |f..|
00000143

暗号化ありの場合は、etcd の中に、Merry Christmas!password といった文字列は見当たりません。Key と Value の両方が、しっかりと暗号化されています。やったね。

解析

せっかくなので、暗号化ありの場合の etcd のデータを解析してみます。

00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 64 65 66 61 75 6c  74 2f 73 65 63 72 65 74  |s/default/secret|
00000020  33 0a 6b 38 73 3a 65 6e  63 3a 6b 6d 73 3a 76 31  |3.k8s:enc:kms:v1|
00000030  3a 6d 79 56 61 75 6c 74  3a 00 67 6b 75 62 65 2d  |:myVault:.gkube-|
00000040  73 65 63 72 65 74 2d 65  6e 63 2d 6b 65 79 3a 76  |secret-enc-key:v|
00000050  31 3a 33 6e 69 76 73 61  6f 65 61 43 35 6e 54 69  |1:3nivsaoeaC5nTi|
~
00000130  0c 31 f5 f5 3e e8 46 81  9e fe bd 1c 83 c0 1b af  |.1..>.F.........|
00000140  66 dd 0a                                          |f..|

3行目2バイト目の 0a(\n) を挟んで、etcd の Key と Value になっています。Value は、3a(:) 区切りになっていて、0a(\n)で終わっています。

  • Key: /registry/secrets/default/secret3
  • Value: k8s:enc:kms:v1:myVault:.gkube-secret-enc-key:3niv〜..f.
カラム
1 k8s
2 enc
3 kms
4 v1
5 myVault
6 .gkube-secret-enc-key
7 v1
8 3niv〜..f.

1-5番目のカラムには、encryption-config.yaml の内容が反映されています。6番目のカラムには、KEK 名が入っています。.g(00 67)という奇妙なデータがくっ付いていますが、何だかわかりません。7番目のカラムは、kube-secret-enc-key のバージョンでしょう。最後の8番目のカラムは、暗号化後のデータのようです。

00000080  56 62 76 46 69 6f 41 30  65 75 79 56 53 41 52 2f  |VbvFioA0euyVSAR/|
00000090  74 75 47 39 7a 63 57 71  43 38 50 61 62 69 45 72  |tuG9zcWqC8PabiEr|
000000a0  49 68 c2 10 69 6b e8 7b  69 8a b9 10 c9 77 29 bd  |Ih..ik.{i....w).|
000000b0  0c f2 61 a9 e1 12 e9 9d  d1 47 0b 05 e5 03 d0 be  |..a......G......|

8カラム目のデータをよーく見ると、途中から、ASCII 文字コードが激減しています。このことから、8カラム目のデータは、異なるアルゴリズムで暗号化した2種類のデータが結合されたものだと予想できます。どっちがどっちだか分かりませんが、DEK と Secret がくっ付いているのだと思います。

KEK のローテーション

エンベロープ暗号化の醍醐味は、KEK のローテーションにあります。せっかくなので、体験しておきます。今回の KEK である、kube-secret-enc-key は、Vault に保存されているはずです。

# Transit Keys を確認
$ vault list transit/keys
Keys
----
kube-secret-enc-key

# kube-secret-enc-key を確認
$ vault kv get transit/keys/kube-secret-enc-key
============= Data =============
Key                       Value
---                       -----
allow_plaintext_backup    false
deletion_allowed          false
derived                   false
exportable                false
keys                      map[1:1544086570]
latest_version            1
min_available_version     0
min_decryption_version    1
min_encryption_version    0
name                      kube-secret-enc-key
supports_decryption       true
supports_derivation       true
supports_encryption       true
supports_signing          false
type                      aes256-gcm96

やはり、Vault にありました。暗号化アルゴリズムは、aes256-gcm96 とあります。

ローテートして、その後も、secret3 を参照できるか確認してみます。secret3 のデコード後の DEK が kube-apiserver のキャッシュに残っているかもしれないので、念のため、参照前に kube-apiserver をリスタートしておきます。

# kube-secret-enc-key をローテート
$ vault write -force transit/keys/kube-secret-enc-key/rotate
Success! Data written to: transit/keys/kube-secret-enc-key/rotate

# kube-secret-enc-key を確認
$ vault kv get transit/keys/kube-secret-enc-key |grep version
latest_version            2
min_available_version     0
min_decryption_version    1
min_encryption_version    0

# kube-apiserver をリスタート
$ sudo systemctl restart kube-apiserver

# kubectl で secret3 を参照
$ kubectl get secret secret3 -o yaml |grep password: |cut -d":" -f2 |base64 --decode; echo
Merry Christmas!

ローテートしても、secret3 を参照できました。kube-secret-enc-key の latest_version は 2 に上がりました。min_decryption_version という項目は 1 のままです。

もう一回ローテートします。

# kube-secret-enc-key をローテート
$ vault write -force transit/keys/kube-secret-enc-key/rotate
Success! Data written to: transit/keys/kube-secret-enc-key/rotate

# kube-secret-enc-key を確認
$ vault kv get transit/keys/kube-secret-enc-key |grep version
latest_version            3
min_available_version     0
min_decryption_version    1
min_encryption_version    0

# kube-apiserver をリスタート
$ sudo systemctl restart kube-apiserver

# kubectl で secret3 を参照
$ kubectl get secret secret3 -o yaml |grep password: |cut -d":" -f2 |base64 --decode; echo
Merry Christmas!

再度ローテートしても、secret3 を参照できました。kube-secret-enc-key の latest_version は 3 に上がりましたが、min_decryption_version は 1 のままです。

secret3 の DEK は、バージョン1の kube-secret-enc-key で暗号化しました。kube-apiserver をリスタートしているので、DEK のキャッシュはないはずです。それでも secret3 を読めているということは、Vault が、バージョン1 の kube-secret-enc-key を、消さずに、まだ持っているということだと思います。

Vault の Transit Secrets Engine には、Update Key ConfigurationTrim Key といった API が用意されています。これらの API を使うと、min_decryption_version を操作したり、古い Transit Key を削除したりできるようです。

まとめ

今日は、oracle/kubernetes-vault-kms-plugin を試しました。k8s の Secret が、しっかりと暗号化されていることを確認できたと同時に、エンベロープ暗号化という、優れた暗号化の手法を体験することができました。

参考


このエントリは、弊社 Z Lab のメンバーによる Z Lab Advent Calendar 2018 の11日目として業務時間中に書きました。12日目は @TakanariKo の担当です。

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2