GEP-0018: Automated Shoot CA Rotation
Table of Contents
Summary
This proposal outlines an on-demand, multi-step approach to rotate all certificate authorities (CA) used in a Shoot cluster. This process includes creating new CAs, invalidating the old ones and recreating all certificates signed by the CAs.
We propose to bundle the rotation of all CAs in the Shoot together as one triggerable action. This includes the recreation and invalidation of the following CAs and all certificates signed by them:
- Cluster CA (currently used for signing
kube-apiserverserving certificates and client certificates) kubeletCA (used for signing client certificates for talking tokubeletAPI, e.g.kube-apiserver-kubelet)etcdCA (used for signingetcdserving certificates and client certificates)- front-proxy CA (used for signing client certificates that
kube-aggregator(part ofkube-apiserver) uses to talk to extension API servers, filled intoextension-apiserver-authenticationConfigMap and read by extension API servers to verify incomingkube-aggregatorrequests) metrics-serverCA (used for signing serving certificates, filled into APIServicecaBundlefield and read bykube-aggregatorto verify the presented serving certificate)ReversedVPNCA (used for signingvpn-seed-serverserving certificate andvpn-shootclient certificate)
Out of scope for now:
kubeletserving CA is self-generated (valid for1y) and self-signed bykubeleton startup.kube-apiserverdoes not seem to verify the presented serving certificate.kubeletcan be configured to request serving certificate via CSR that can be verified bykube-apiserver, though, we consider this as a separate improvement outside of this GEP.
- Legacy VPN solution uses the cluster CA for both serving and client certificates. As the solution is soon to be dropped in favor of the new
ReversedVPNsolution, we don't intend to introduce a dedicated CA for this component. IfReversedVPNis disabled and the CA rotation is triggered, we make sure to propagate the cluster CA to the relevant places in the legacy VPN solution.
Naturally, not all certificates used for communication with the kube-apiserver are under control of Gardener. An example for a Gardener-controlled certificate is the kubelet client certificate used to communicate with the api server. An example for credentials not controlled by Gardener are kubeconfigs or client certificates requested via CertificateSigningRequests by the shoot owner.
We propose to use a two step approach to rotate CAs. The start of each phase is triggered by the shoot owner. In summary, the first phase is used to create new CAs (for example, the new api server and client CA). Then we make sure that all servers and clients under Gardener's control trust both old and new CA. Next, we renew all client certificates that are under Gardener's control so they are now signed by the new CAs. This includes a node rollout in order to propagate the certificates to kubelets and restart all pods. Afterwards, the user needs to change their client credentials to trust both old and new cluster CA. In the second phase, we remove all trust to the old CA for servers and clients under Gardener's control. This does not include a node rollout but all still running pods using ServiceAccounts will continue to trust the old CA until they restart. Also, the user needs to retrieve the new CA bundle to no longer trust the old CA.
A detailed overview of all steps required for each phase is given in the proposal section of this GEP.
Introducing a new client CA
Currently, client certificates and the kube-apiserver certificate are signed by the same CA. We propose to create a separate client CA when triggering the rotation. The client CA is used to sign certificates of clients talking to the API Server.
Motivation
There are a few reasons for rotating shoot cluster CAs:
- If we have to invalidate client certificates for the kube-apiserver or any other component we are forced to rotate the CA. The only way to invalidate them is to stop trusting all client certificates that are signed by the respective CA, as Kubernetes does not support revoking certificates.
- If the CA itself got leaked.
- If the CA is about to expire.
- If a company policy requires to rotate a CA after a certain point in time.
In each of those cases we currently need to basically manually recreate and replace all CAs and certificates. The process of rotating by hand is cumbersome and could lead to errors due to the many steps needing to be performed in the right order. By automating the process we want to create a way to securely and easily rotate shoot CAs.
Goals
- Offer an automated and safe solution to rotate all CAs in a shoot cluster.
- Offer a process that is easily understandable for developers and users.
- Rotate the different CAs in the shoot with a similar process to reduce complexity.
- Add visibility for Shoot owners when the last CA rotation happened.
Non-Goals
- Offer an automated solution for rotating other static credentials (like static token).
- Later on, a similar two-phase approach could be implemented for the kubeconfig rotation. However, this is out of scope for this enhancement.
- Creating a process that runs fully automated without shoot owner interaction. As the shoot owner controls some secrets, that would probably not even be possible.
- Forcing the shoot owner to rotate after a certain time period. Our goal, rather, is to issue long-running certificates and let the user decide depending on their requirements to rotate as needed.
- Configurable default CA lifetime
Proposal
We will add a new feature gate CARotation for gardener-apiserver and gardenlet, which allows to enable or disable the possibility to trigger the rotation.
Triggering the CA Rotation
- Triggered via
gardener.cloud/operationannotation in symmetry with other operations like reconciliation, kubeconfig rotation, etc.- Annotation increases the generation
- Value for triggering first phase:
start-ca-rotation - Value for triggering the second phase:
complete-ca-rotation gardener-apiserverperforms the needful validation: a user can't trigger another rotation if one is already in progress, a user can't triggercomplete-ca-rotationif first phase has not been completed, etc.
- The annotation triggers a usual shoot reconciliation (just like a kubeconfig or SSH key rotation).
- The
gardenletbegins the CA rotation sequence by setting the new status section.status.credentials.caRotation(probably inupdateShootStatusOperationStart) and removes the annotation afterwards.- Shoot reconciliation needs to be idempotent to CA rotation phase, i.e., if a usual reconciliation or maintenance operation is triggered in between, no new CAs are generated or similar things that would interfere with the CA rotation sequence.
Changing the Shoot Status
A new section in the Shoot status is added when the first rotation is triggered:
status:
credentials:
rotation:
certificateAuthorities:
phase: Prepare # Prepare|Finalize|Completed
lastCompletion: 2022-02-07T14:23:44Z
# kubeconfig:
# phase:
# lastCompletion:Later on, this section could be augmented with other information, like the names of the credentials secrets (e.g. gardener/gardener#1749)
status:
credentials:
resources:
- type: kubeconfig
kind: Secret
name: shoot-foo.kubeconfigRotation Sequence for Cluster and Client CA
The proposal section includes a detailed description of all steps involved for rotating from a given CA0 to the target CA1.
t0: Today's situation:
kube-apiserveruses SERVER CERT signed byCA0and trusts CLIENT CERTS signed byCA0kube-controller-managerissues new CLIENT CERTS signed byCA0- kubeconfig trusts only
CA0 ServiceAccountsecrets trust onlyCA0- kubelet uses CLIENT CERT signed by
CA0
t1: Shoot owner triggers first step of CA rotation process (--> phase one is started):
- Generate
CA1 - Generate
CLIENT_CA1 - Update
kube-apiserver,kube-scheduler, etc., to trust CLIENT CERTS signed by bothCA0andCLIENT_CA1(--client-ca-fileflag) - Update
kube-controller-managerto issue new CLIENT CERTS now withCLIENT_CA1 - Update kubeconfig so that its CA bundle contains both
CA0andCA1(if kubeconfig still contains a legacy CLIENT CERT then rotate the kubeconfig) - Update
generic-token-kubeconfigso that its CA bundle contains bothCA0andCA1 - Update
kube-controller-managerto populate bothCA0andCA1inServiceAccountsecrets - Restart control plane components so that their CA bundle contains both
CA0andCA1 - Renew CLIENT CERTS (sign them with
CLIENT_CA1) for the following control plane components: Prometheus, DWD, legacy VPN), if not dropped already in the context of gardener/gardener#4661 - Trigger node rollout
- This issues new CLIENT CERTS for all kubelets signed by
CLIENT_CA1 - This restarts all
Pods and propagatesCA0andCA1into their mountedServiceAccountsecrets (note CAs can not be reloaded by go client, therefore we need a restart of pods)
- This issues new CLIENT CERTS for all kubelets signed by
- Ask the user to exchange all their client credentials (kubeconfig, CLIENT CERTS issued by
CertificateSigningRequests) to trust both CA0 and CA1
t2: Shoot owner triggers second step of CA rotation process (--> phase two is started):
Prerequisite: All Gardener-controlled actions listed in t1 were executed successfully (for example node rollout). The shoot owner has guaranteed that they exchanged their client credentials and triggered step 2 via an annotation.
- Renew SERVER CERTS (sign them with
CA1) forkube-apiserver,kube-controller-manager,cloud-controller-manager, etc. - Update
kube-apiserver,kube-scheduler, etc., to trust only CLIENT CERTS signed byCLIENT_CA1 - Update kubeconfig so that its CA bundle contains only
CA1 - Update
generic-token-kubeconfigso that its CA bundle contains onlyCA1 - Update
kube-controller-managerto only contain CA1.ServiceAccountsecrets created after this point will get secrets that include onlyCA1 - Restart control plane components so that their CA bundle contains only
CA1 - Restart kubelets so that the CA bundle in their kubeconfigs contain only
CA1 - Delete
CA0 - Ask the user to optionally restart their
Pods since they still containCA0in memory in order to eliminate trust to the old cluster CA. - Ask the user to exchange all their client credentials (download kubeconfig containing only
CA1; when using CLIENT CERTS trust onlyCA1)
Rotation Sequence of Other CAs
Apart from the kube-apiserver CA (and the client CA), we also use 5 other CAs as mentioned above in the Gardener codebase. We propose to rotate those CAs together with the kube-apiserver CA following the same trigger.
ℹ️ Note for the front-proxy CA: users need to make sure, extension API servers have reloaded the extension-apiserver-authentication ConfigMap, before triggering the second phase.
You can find Gardener managed CAs listed in wanted_secrets.go.
Regarding the rotation steps, we want to follow a similar approach to the one we defined for the kube-apiserver CA. Exemplary, we are going to show the timeline for ETCD_CA but the logic should be similar for all the above listed CAs.
t0:- etcd trusts client certificates signed by
ETCD_CA0and uses a server certificate signed byETCD_CA0 kube-apiserverandbackup-restoreuse a client certificate signed byETCD_CA0and trustETCD_CA0
- etcd trusts client certificates signed by
t1:- Generate
ETCD_CA1 - Update
etcdto trust CLIENT CERTS signed by bothETCD_CA0andETCD_CA1 - Update
kube-apiserverandbackup-restore:- Adapt CA bundle to trust both
ETCD_CA0andETCD_CA1 - Renew CLIENT CERTS (sign them with
ETCD_CA1)
- Adapt CA bundle to trust both
- Generate
t2:- Update
etcd:- Trust only CLIENT CERTS signed by
ETCD_CA1 - Renew SERVER CERT (sign it with
ETCD_CA1)
- Trust only CLIENT CERTS signed by
- Update
kube-apiserverandbackup-restoreso that their CA bundle contains onlyETCD_CA1
- Update
ℹ️ This means we are requiring two restarts of etcd in total.
Alternatives
This section presents a different approach to rotate the CAs, which is to temporarily create a second set of api-servers utilizing the new CA. After presenting the approach, advantages and disadvantages of both approaches are listed.
t0: Today's situation:
kube-apiserveruses SERVER CERT signed byCA0and trusts CLIENT CERTS signed byCA0kube-controller-managerissues new CLIENT CERTS withCA0- kubeconfig contains only
CA0 ServiceAccountsecrets contain onlyCA0- kubelet uses CLIENT CERT signed by
CA0
t1: User triggers first step of CA rotation process (--> phase one):
- Generate
CA1 - Generate
CLIENT_CA1 - Create new
DNSRecord,Service, Istio configuration, etc., for secondkube-apiserverdeployment - Deploy second
kube-apiserverdeployment trusting only CLIENT CERTS signed byCLIENT_CA1and using SERVER CERT signed byCA1 - Update
kube-scheduler, etc., to trust only CLIENT CERTS signed byCLIENT_CA1(--client-ca-fileflag) - Update
kube-controller-managerto issue new CLIENT CERTS withCLIENT_CA1 - Update kubeconfig so that it points to the new
DNSRecordand its CA bundle contains onlyCA1(if kubeconfig still contains a legacy CLIENT CERT then rotate the kubeconfig) - Update
ServiceAccountsecrets so that their CA bundle contains bothCA0andCA1 - Restart control plane components so that they point to the second
kube-apiserverServiceand so that their CA bundle contains onlyCA1 - Renew CLIENT CERTS (sign them with
CLIENT_CA1) for control plane components (Prometheus, DWD, legacy VPN) and point them to the secondkube-apiserverService - Adapt
apiserver-proxy-pod-mutatorto pointKUBERNETES_SERVICE_HOSTenv variable to secondkube-apiserver - Trigger node rollout
- This issues new CLIENT CERTS for all kubelets signed by
CLIENT_CA1and points them to the secondDNSRecord - This restarts all
Pods and propagatesCA0andCA1into their mountedServiceAccountsecrets
- This issues new CLIENT CERTS for all kubelets signed by
- Ask the user to exchange all their client credentials (kubeconfig, CLIENT CERTS issued by
CertificateSigningRequests)
t2: User triggers second step of CA rotation process (--> phase two):
- Update
ServiceAccountsecrets so that their CA bundle contains onlyCA1 - Update
apiserver-proxyto talk to secondkube-apiserver - Drop first
DNSRecord,Service, Istio configuration and firstkube-apiserverdeployment - Drop
CA0 - Ask the user to optionally restart their
Pods since they still containCA0in memory.
Advantages/Disadvantages Approach Two API Servers
- (+) User needs to adapt client credentials only once
- (/) Unstable API server domain
- (-) Probably more implementation effort
- (-) More complex
- (-) CA rotation process does not work similar for all CAs in our system
Advantages/Disadvantages of Currently Preferred Approach (See Proposal)
- (+) Implementation effort seems "straight-forward"
- (+) CA rotation process works similar for all CAs in our system
- (/) Stable API server domain
- (-) User needs to adapt client credentials twice