title | gep-number | creation-date | status | authors | reviewers | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
18 Shoot CA Rotation |
18 |
2022-02-01 |
implementable |
|
|
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-apiserver
serving certificates and client certificates) kubelet
CA (used for signing client certificates for talking tokubelet
API, e.g.kube-apiserver-kubelet
)etcd
CA (used for signingetcd
serving 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-authentication
ConfigMap and read by extension API servers to verify incomingkube-aggregator
requests) metrics-server
CA (used for signing serving certificates, filled into APIServicecaBundle
field and read bykube-aggregator
to verify the presented serving certificate)ReversedVPN
CA (used for signingvpn-seed-server
serving certificate andvpn-shoot
client certificate)
Out of scope for now:
kubelet
serving CA is self-generated (valid for1y
) and self-signed bykubelet
on startup.kube-apiserver
does not seem to verify the presented serving certificate.kubelet
can 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
ReversedVPN
solution, we don't intend to introduce a dedicated CA for this component. IfReversedVPN
is 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 CertificateSigningRequest
s 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 ServiceAccount
s 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.
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.
- 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.
- 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
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.
- Triggered via
gardener.cloud/operation
annotation 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-apiserver
performs the needful validation: a user can't trigger another rotation if one is already in progress, a user can't triggercomplete-ca-rotation
if first phase has not been completed, etc.
- The annotation triggers a usual shoot reconciliation (just like a kubeconfig or SSH key rotation).
- The
gardenlet
begins 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.
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.kubeconfig
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-apiserver
uses SERVER CERT signed byCA0
and trusts CLIENT CERTS signed byCA0
kube-controller-manager
issues new CLIENT CERTS signed byCA0
- kubeconfig trusts only
CA0
ServiceAccount
secrets 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 bothCA0
andCLIENT_CA1
(--client-ca-file
flag) - Update
kube-controller-manager
to issue new CLIENT CERTS now withCLIENT_CA1
- Update kubeconfig so that its CA bundle contains both
CA0
andCA1
(if kubeconfig still contains a legacy CLIENT CERT then rotate the kubeconfig) - Update
generic-token-kubeconfig
so that its CA bundle contains bothCA0
andCA1
- Update
kube-controller-manager
to populate bothCA0
andCA1
inServiceAccount
secrets - Restart control plane components so that their CA bundle contains both
CA0
andCA1
- 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
Pod
s and propagatesCA0
andCA1
into their mountedServiceAccount
secrets (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
CertificateSigningRequest
s) 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-kubeconfig
so that its CA bundle contains onlyCA1
- Update
kube-controller-manager
to only contain CA1.ServiceAccount
secrets 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
Pod
s since they still containCA0
in 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
)
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 similiar for all the above listed CAs.
t0
:- etcd trusts client certificates signed by
ETCD_CA0
and uses a server certificate signed byETCD_CA0
kube-apiserver
andbackup-restore
use a client certificate signed byETCD_CA0
and trustETCD_CA0
- etcd trusts client certificates signed by
t1
:- Generate
ETCD_CA1
- Update
etcd
to trust CLIENT CERTS signed by bothETCD_CA0
andETCD_CA1
- Update
kube-apiserver
andbackup-restore
:- Adapt CA bundle to trust both
ETCD_CA0
andETCD_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-apiserver
andbackup-restore
so that their CA bundle contains onlyETCD_CA1
- Update
ℹ️ This means we are requiring two restarts of etcd in total.
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-apiserver
uses SERVER CERT signed byCA0
and trusts CLIENT CERTS signed byCA0
kube-controller-manager
issues new CLIENT CERTS withCA0
- kubeconfig contains only
CA0
ServiceAccount
secrets 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-apiserver
deployment - Deploy second
kube-apiserver
deployment trusting only CLIENT CERTS signed byCLIENT_CA1
and using SERVER CERT signed byCA1
- Update
kube-scheduler
, etc., to trust only CLIENT CERTS signed byCLIENT_CA1
(--client-ca-file
flag) - Update
kube-controller-manager
to issue new CLIENT CERTS withCLIENT_CA1
- Update kubeconfig so that it points to the new
DNSRecord
and its CA bundle contains onlyCA1
(if kubeconfig still contains a legacy CLIENT CERT then rotate the kubeconfig) - Update
ServiceAccount
secrets so that their CA bundle contains bothCA0
andCA1
- Restart control plane components so that they point to the second
kube-apiserver
Service
and 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-apiserver
Service
- Adapt
apiserver-proxy-pod-mutator
to pointKUBERNETES_SERVICE_HOST
env variable to secondkube-apiserver
- Trigger node rollout
- This issues new CLIENT CERTS for all kubelets signed by
CLIENT_CA1
and points them to the secondDNSRecord
- This restarts all
Pod
s and propagatesCA0
andCA1
into their mountedServiceAccount
secrets
- This issues new CLIENT CERTS for all kubelets signed by
- Ask the user to exchange all their client credentials (kubeconfig, CLIENT CERTS issued by
CertificateSigningRequest
s)
t2
: User triggers second step of CA rotation process (--> phase two):
- Update
ServiceAccount
secrets so that their CA bundle contains onlyCA1
- Update
apiserver-proxy
to talk to secondkube-apiserver
- Drop first
DNSRecord
,Service
, Istio configuration and firstkube-apiserver
deployment - Drop
CA0
- Ask the user to optionally restart their
Pod
s since they still containCA0
in memory.
- (+) 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
- (+) 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