Edited at

KubernetesのSecretは本当に安全か

More than 1 year has passed since last update.

この記事はKubernetes Advent Calendar 12日目の記事になります。


Overview

KubernetesのPodにパスワードやトークンを指定する際にはSecretを使います。(ってゆうか使ってください)

コンテナ内に秘密情報を入れたままDockerHub等にアップロードしてしまい大変な事態に・・・ってことには注意しましょう。

さて、この記事の本題ですが"KubernetesのSecretは本当に安全か"です。

確かにKubernetes上に機能として存在しますが、本当に安全性は確保されているのでしょうか?

また、もしノードが攻撃された場合、どの程度までSecretのデータを守れるのでしょうか?

この記事では公式ドキュメントを参考に現在のSecretについて紹介できればと思います。

※著者はまだまだ未熟者ゆえ間違い等もあるかと思いますが、温かい目&お手柔らかなコメントでお願いします:slight_smile:


前段(Secretとは)

参考サイトのほうが詳しいので詳細はそちらにお譲りします。

概要だけ述べますと、次のような感じになります。


  • 作成したsecretはKubernetesのetcd上にplain-text形式で保存される

  • Nodeではpodのtmpfs上に保存される

  • Node Authorization機能(v1.7〜)により、Node は割り当てられた Pod が参照する Secret 以外にはアクセスできない。

まとめると、生データはetcd上にある。Masterにはない。NodeはSecretを参照するPodがいるNodeのtmpfs上にのみ存在する(ディスク上にデータは保存されない)。通信はすべてhttps(etcd/Master/Node間)。

※いずれもちゃんと設定した場合

・・・なかなか安全そうです。


参考:

公式docs(https://kubernetes.io/docs/concepts/configuration/secret/)

Secretとは(https://ubiteku.oinker.me/2017/03/01/kubernetes-secrets/)

Node Authorization(https://qiita.com/tkusumi/items/f6a4f9150aa77d8f9822)


Secretの安全性

しかし、Secretがetcd上にplain-text形式で保存されるのは気になりますね。

公式docs上にも安全性のためにはetcdのアクセス制御をきちんと設定しろとありますが、もう少し安全にできないのでしょうか?

Kubernetes 1.7よりetcdを暗号化する機能が開発されました。

この機能を使うと、より安全にSecretを運用できそうです。

※2017年12月(v1.8)現在、まだalpha機能です。

公式docs(https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)


etcdの中身の確認方法

ではここから実機検証です。

まず、etcdの暗号化機能を使用する前に、何もしないとパスワード等が生データのまま保存されているのか確認しておきましょう。


確認用Kubernetesクラスタの作成

vagrantとkubeadmでサクッと確認用のKubernetesクラスタを作りましょう。

※本当に動作確認のためだけですので読者さまの環境で動作しなくてもごめんなさい(てか多分VMを再起動させると動作しない←)

vagrant box add centos7 https://github.com/holms/vagrant-centos7-box/releases/download/7.1.1503.001/CentOS-7.1.1503-x86_64-netboot.box

vagrant up (※Vagrantfileは下記参照)


Vagrantfile.

Vagrant.configure("2") do |config|

config.vm.box = "centos7"
config.vm.network "private_network", ip: "192.168.33.10"
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
end
end

※sshでVMにアクセスしてからの手順

[vagrant@localhost ~]# sudo su -

[root@localhost ~]# yum update -y && yum install -y docker etcd
[root@localhost ~]# swapoff -a
[root@localhost ~]# systemctl stop firewalld && systemctl disable firewalld
[root@localhost ~]# sysctl -w net.bridge.bridge-nf-call-iptables=1
[root@localhost ~]# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://yum.kubernetes.io/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

[root@localhost ~]# yum install -y kubelet kubeadm kubectl

[root@localhost ~]# systemctl enable docker && systemctl start docker
[root@localhost ~]# systemctl enable kubelet

[root@localhost ~]# systemctl enable etcd && systemctl start etcd

[root@localhost ~]# cat <<EOF > etcd-conf.yaml
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
advertiseAddress: 192.168.33.10
etcd:
endpoints:
- http://127.0.0.1:2379
networking:
podSubnet: 10.244.0.0/16
EOF

[root@localhost ~]# kubeadm init --config=etcd-conf.yaml

[root@localhost ~]# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml --kubeconfig=/etc/kubernetes/admin.conf
[root@localhost ~]# kubectl taint node localhost.localdomain node-role.kubernetes.io/master --kubeconfig=/etc/kubernetes/admin.conf


Secretデータの作成

続いてクラスタにサンプルのSecretを作成します。

秘密文章は何でもいいのですが、今回はpassword: "ilovejapan"にします。

echo "ilovejapan" | base64

vi secret.yaml
※yamlファイルは以下を参照
kubectl create -f secret.yaml


secret.yaml

apiVersion: v1

kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: aWxvdmVqYXBhbgo=


etcd上のデータの確認

etcd上のデータを確認する方法として2つの方法があります。

(1) バイナリを直接見る

hexdump -C /var/lib/etcd/default.etcd/member/snap/db | grep "ilovejapan"

000dc2b0  6f 72 64 12 0b 69 6c 6f  76 65 6a 61 70 61 6e 0a  |ord..ilovejapan.|

(2) etcdctlコマンドで見る

ETCDCTL_API=3 etcdctl get --prefix /registry/secrets/default/mysecret | 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 6d 79 73 65 63 72 |s/default/mysecr|
00000020 65 74 0a 6b 38 73 00 0a 0c 0a 02 76 31 12 06 53 |et.k8s.....v1..S|
00000030 65 63 72 65 74 12 70 0a 4d 0a 08 6d 79 73 65 63 |ecret.p.M..mysec|
00000040 72 65 74 12 00 1a 07 64 65 66 61 75 6c 74 22 00 |ret....default".|
00000050 2a 24 32 32 39 35 65 31 39 63 2d 64 34 32 38 2d |*$2295e19c-d428-|
00000060 31 31 65 37 2d 61 33 32 35 2d 30 38 30 30 32 37 |11e7-a325-080027|
00000070 36 62 35 37 38 38 32 00 38 00 42 08 08 ea f8 f4 |6b57882.8.B.....|
00000080 d0 05 10 00 7a 00 12 17 0a 08 70 61 73 73 77 6f |....z.....passwo|
00000090 72 64 12 0b 69 6c 6f 76 65 6a 61 70 61 6e 0a 1a |rd..ilovejapan..|
000000a0 06 4f 70 61 71 75 65 1a 00 22 00 0a |.Opaque.."..|
000000ac


etcdの暗号化

etcd上の任意のディレクトリ(※)を暗号化する機能を使ってみましょう。

この機能は大雑把に言うとyaml形式で暗号化方式と共通かぎ暗号方式の秘密鍵を書いて、apiserverに読み込ませる感じです。

※: etcd v3からディレクトリという概念はなくなったので厳密にいうとディレクトリではない

etcdの暗号化機能を使用するためにはkube-apiserverに--experimental-encryption-provider-config="configファイル"を指定して起動します。

kubeadmの場合はconfigファイルをapiserverに渡すためにHostPathもマウントしておきましょう。

今回パスワードは公式サイトに倣って以下のコマンドで作成しています。

head -c 32 /dev/urandom | base64

yamlはこんな感じです。


/etc/kubernetes/manifests/kube-apiserver.yaml

apiVersion: v1

kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --experimental-encryption-provider-config=/etc/kubernetes/enc-conf.yaml
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
- --secure-port=6443
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --admission-control=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota
- --requestheader-allowed-names=front-proxy-client
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --allow-privileged=true
- --advertise-address=192.168.33.10
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --enable-bootstrap-token-auth=true
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --requestheader-username-headers=X-Remote-User
- --requestheader-group-headers=X-Remote-Group
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --insecure-port=0
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --authorization-mode=Node,RBAC
- --etcd-servers=http://127.0.0.1:2379
image: gcr.io/google_containers/kube-apiserver-amd64:v1.8.4
livenessProbe:
failureThreshold: 8
httpGet:
host: 127.0.0.1
path: /healthz
port: 6443
scheme: HTTPS
initialDelaySeconds: 15
timeoutSeconds: 15
name: kube-apiserver
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/kubernetes
name: k8s-conf
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/pki
name: ca-certs-etc-pki
readOnly: true
hostNetwork: true
volumes:
- hostPath:
path: /etc/kubernetes
type: DirectoryOrCreate
name: k8s-conf
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/pki
type: DirectoryOrCreate
name: ca-certs-etc-pki
status: {}


/etc/kubernetes/enc-conf.yaml

kind: EncryptionConfig

apiVersion: v1
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: cSNmGdfrr/IZeYN9npkl3F7/ScQcer2We9VxmM4X5ww=
- identity: {}

その後kube-apiserverを再起動させます。

systemctl restart kubelet

先ほどと同様にetcdの中身を確認してみると・・・

ETCDCTL_API=3 etcdctl get --prefix /registry/secrets/default/mysecret | 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 6d 79 73 65 63 72 |s/default/mysecr|
00000020 65 74 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 |et.k8s:enc:aescb|
00000030 63 3a 76 31 3a 6b 65 79 31 3a 55 55 5d 34 1e 5d |c:v1:key1:UU]4.]|
00000040 72 67 0e be ef 16 5b d3 0c 08 36 42 2f 7c 92 1b |rg....[...6B/|..|
00000050 70 29 1f 67 0b 79 9c 29 2d 4c 1c ef 8a 7b 66 2b |p).g.y.)-L...{f+|
00000060 0a a8 8a 1e 5e 72 e5 6a 50 52 62 1e 8a 80 58 6d |....^r.jPRb...Xm|
00000070 99 5c d3 21 50 a1 08 14 b5 be 7d a1 1f 73 78 22 |.\.!P.....}..sx"|
00000080 7d c4 33 b5 eb 8a f1 dd 32 e2 c3 cc 1f 64 e0 f1 |}.3.....2....d..|
00000090 6d aa 49 56 31 93 79 19 cf 53 dc 0b db a7 60 8b |m.IV1.y..S....`.|
000000a0 a7 ad ad 33 2a 5d 3a 1b e2 15 91 9d d5 e3 c5 ea |...3*]:.........|
000000b0 fc 6b d4 21 33 a0 2e 80 3f 1d a8 0b f5 b4 98 98 |.k.!3...?.......|
000000c0 1c 2a 00 1d d7 e6 55 f9 88 35 c2 c6 a3 b3 20 63 |.*....U..5.... c|
000000d0 e8 2e eb af 1f bc 6b 92 d8 72 0a |......k..r.|
000000db

無事パスワードは見えなくなりました。


総評

KubernetesのSecretは機能が豊富でちゃんと設定すれば安全そうです。

ただKubernetesはSecretだけにとどまらずちゃんと設定するのが大変なのですが・・・

もうちょっと簡単に安全なクラスタを設計できれば万々歳ですね