BoundServiceAccountTokenVolumeとは
BoundServiceAccountTokenVolume
はv1.13からalphaの機能として提供されており、FeatureGateで有効にすることができます。これにより、これまでのSecretに書き込まれたトークンをマウントする方式に変わり、宛先や有効期限が指定されたトークンがProjectedVolumeによってPodに渡されるようになります。似たような機能としてTokenRequestProjection
がありますが、BoundServiceAccountTokenVolume
はTokenRequestProjection
で明示的に指定する必要のあったパラメータを動的に設定してくれます。
BoundServiceAccountTokenVolume
はTokenRequest
、TokenRequestProjection
やRootCAConfigMap
の機能に依存していますが、これらはv1.20時点ではBetaまたはGAになっています。
kube-apiserver(k8s v1.20)
$ kube-apiserver
- --feature-gates=BoundServiceAccountTokenVolume=true
- --service-account-issuer=https://kubernetes.default.svc.cluster.local
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
...
kube-controller-manager(k8s v1.20)
$ kube-controller-manager
- --feature-gates=BoundServiceAccountTokenVolume=true
...
しかしトークンが期限付きに切り替わるといっても、一度にすべてを切り替える必要は無さそうです。
v1.20時点においては、BoundServiceAccountTokenVolume
を有効にしても、SecretベースのレガシーなService Account トークンは引き続き新規でも発行される状態のようですし、kube-apiserverにて--service-account-extend-token-expiration
をtrue
に設定すると、期限付きトークンの期限を1年間に延長することができます。
さらに、k8sではserviceaccount_legacy_tokens_total
というメトリックが提供されており、これを利用してレガシーなトークン(期限や宛先が設定されていない)を利用しているクライアントが存在していないか確認することができます。また、トークンの有効期限を延長している場合も同様にserviceaccount_stale_tokens_total
によってローテーションが正しく行われていないクライアントの存在を確認することができます。
これらを活用することで、確認しながら確実に切り替えを進めていくことができそうです。
動作確認
K8s v1.20にてFeatureGateでBoundServiceAccountTokenVolume
を有効にした状態でトークンの状況を確認してみます。
$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
EOF
Podのマニフェストを確認してみます。
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2020-12-17T05:55:31Z"
generateName: nginx-deployment-66b6c48dd5-
labels:
app: nginx
pod-template-hash: 66b6c48dd5
managedFields:
# 省略
name: nginx-deployment-66b6c48dd5-79vdm
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: nginx-deployment-66b6c48dd5
uid: f9e5f1ef-5b48-4469-8165-a864422af47c
resourceVersion: "3518"
uid: b94c6894-1b49-40de-a3f7-6feabd58ccfa
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-jz2lz
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: bound-token-test-worker
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: kube-api-access-jz2lz
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
上記のように何も指定していなくてもK8s APIサーバの証明書を検証するためのCA証明書やServiceAccount トークンがProjected Volumeを使ってPodに渡されるように設定されています。(.spec.volumes[0].projected
あたり参照)
レガシートークンの場合は以下のようになります。
volumes:
- name: default-token-2dq9k
secret:
defaultMode: 420
secretName: default-token-2dq9k
Podに渡されたトークンの中身を見てみると以下のような値が設定されています。k8s v1.20ではデフォルトで--service-account-extend-token-expiration=true
に設定されているため、有効期限が1年後になっており、さらにkubernetes.io.warnafter
というkubernetes固有のclaimが設定されています。これはおおよそ1時間後の本来の有効期限が設定されています。この値を超えてもトークンを使っているクライアントが存在する場合には先ほど説明したserviceaccount_stale_tokens_total
がカウントされます。
Token自体の有効期限(exp)が1年後になっていても、Podにマウントされているトークンは本来の期限内(1時間)でローテーションされるようです。また、多くのクライアントライブラリでは短時間で定期的にローテーションしているように見えました。
$ export TOKEN=$(kubectl exec nginx-deployment-66b6c48dd5-79vdm -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ irb
irb(main):002:0> t = ENV['TOKEN'].split('.')
irb(main):003:0> puts Base64.urlsafe_decode64(t[1])
{"aud":["https://kubernetes.default.svc.cluster.local"],"exp":1639720531,"iat":1608184531,"iss":"https://kubernetes.default.svc.cluster.local","kubernetes.io":{"namespace":"default","pod":{"name":"nginx-deployment-66b6c48dd5-79vdm","uid":"b94c6894-1b49-40de-a3f7-6feabd58ccfa"},"serviceaccount":{"name":"default","uid":"7cd6f2df-e503-4509-be21-53a2e9edd40a"},"warnafter":1608188138},"nbf":1608184531,"sub":"system:serviceaccount:default:default"}
{
"aud": ["https://kubernetes.default.svc.cluster.local"],
"exp": 1639720531,
"iat": 1608184531,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "nginx-deployment-66b6c48dd5-79vdm",
"uid": "b94c6894-1b49-40de-a3f7-6feabd58ccfa"
},
"serviceaccount": {
"name": "default",
"uid": "7cd6f2df-e503-4509-be21-53a2e9edd40a"
},
"warnafter": 1608188138
},
"nbf": 1608184531,
"sub": "system:serviceaccount:default:default"
}
Subject | system:serviceaccount:default:default |
---|---|
Audience | https://kubernetes.default.svc.cluster.local |
IssuedAt | 1608184531 = 2020-12-17 14:55:31 |
Expiration Time | 1639720531 = 2021-12-17 14:55:31 |
kubernetes.io.warnafter | 1608188138 = 2020-12-17 15:55:38 |
APIサーバの/metrics
を参照してみると以下のようになっていました。
# TYPE serviceaccount_legacy_tokens_total counter
serviceaccount_legacy_tokens_total 0
# HELP serviceaccount_stale_tokens_total [ALPHA] Cumulative stale projected service account tokens used
# TYPE serviceaccount_stale_tokens_total counter
serviceaccount_stale_tokens_total 0
# HELP serviceaccount_valid_tokens_total [ALPHA] Cumulative valid projected service account tokens used
# TYPE serviceaccount_valid_tokens_total counter
serviceaccount_valid_tokens_total 2264
このとき、Authorizationヘッダにレガシーなトークン(Secretに書き込まれているもの)をセットしてみると、 serviceaccount_legacy_tokens_total
が増えることが確認できます。
$ curl https://kubernetes.default.svc/metrics -H "Authorization: Bearer ${LEGACY_TOKEN}" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt |grep "serviceaccount_"
# TYPE serviceaccount_legacy_tokens_total counter
serviceaccount_legacy_tokens_total 1
# HELP serviceaccount_stale_tokens_total [ALPHA] Cumulative stale projected service account tokens used
# TYPE serviceaccount_stale_tokens_total counter
serviceaccount_stale_tokens_total 0
# HELP serviceaccount_valid_tokens_total [ALPHA] Cumulative valid projected service account tokens used
# TYPE serviceaccount_valid_tokens_total counter
serviceaccount_valid_tokens_total 2309
ちなみにservice-account-extend-token-expiration=false
の状態では以下のようにトークンの有効期限は1時間に設定されます。
irb(main):002:0> t = ENV['TOKEN'].split('.')
=> ["eyJhbGciOiJSUzI1NiIsImtpZCI6Ik54Tm55TjlWeXhqWXJzRi04c05KNjktMDJRWDdaaHptOHRHX2NnUW9LeVEifQ", "eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjA4MTkyNzE5LCJpYXQiOjE2MDgxODkxMTIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lueC1kZXBsb3ltZW50LTY2YjZjNDhkZDUtanFuOWciLCJ1aWQiOiJmMDcyNWI1Ni1iNDg5LTQ1NTItOGYxZi00NjI2OTJmOGFlYWYifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiJlZGRiNTNmZi1iZGVkLTRiOWUtOGUzZS1jYWIzNTYyMWQ3NjYifX0sIm5iZiI6MTYwODE4OTExMiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9", "PD0zRGev0XThGfd2kQqinxHpTKareo0BU5qXBQztK4J2J-zXyXqU3hzEnCpeVeTpVtAzFrisYnbBnF1cm29OwJpefvjbb1Y3q1zPboMz-5G3RYnLvkd8hi6SPtNQ5B8QRAhXGNrKlb6fChUnJtsfdt_9bOjtPDdJKi5UP6Yf2ho23bqvHd95epvGQKb8iRaGSGPyyeboCSFpzRaKEJBuJ_BGyH_-VsbILAJjs5q8QY-aZQelGX4YZAFcUSa7COerDBWfSDiiv1vs9SuwYmviJzgpGoEBWRZFf2OWCOAKc5GIaOKwK7I26np4SyN8Zpa71GNowvsQRTXvfPYx_hkUrg"]
irb(main):003:0> puts Base64.urlsafe_decode64(t[1])
{"aud":["https://kubernetes.default.svc.cluster.local"],"exp":1608192719,"iat":1608189112,"iss":"https://kubernetes.default.svc.cluster.local","kubernetes.io":{"namespace":"default","pod":{"name":"nginx-deployment-66b6c48dd5-jqn9g","uid":"f0725b56-b489-4552-8f1f-462692f8aeaf"},"serviceaccount":{"name":"default","uid":"eddb53ff-bded-4b9e-8e3e-cab35621d766"}},"nbf":1608189112,"sub":"system:serviceaccount:default:default"}
=> nil
Subject | system:serviceaccount:default:default |
---|---|
Audience | https://kubernetes.default.svc.cluster.local |
IssuedAt | 1608189112 = 2020-12-17 16:11:52 |
Expiration Time | 1608192719 = 2020-12-17 17:11:59 |
kubernetes.io.warnafter | N/A |
まとめ
BoundServiceAccountTokenVolume
は現在ベータリリースに向けた準備が行われており、v1.21でベータ、v1.22でGAのスケジュールで進められているようです。
https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/1205-bound-service-account-tokens/README.md#graduation-criteria
トークンの切り替えには各種ライブラリの対応を含めて、多くのコードで変更が必要になることが予想されるため、早い段階からやるべきことを認識して備えておくのが良さそうです。