-
Notifications
You must be signed in to change notification settings - Fork 39.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Certificate rotation for kubelet server certs. #45059
Certificate rotation for kubelet server certs. #45059
Conversation
@ericchiang Initial bootstrapping of the server cert by requesting a signed cert from the certificate request signing API on the API server is in here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the Go 1.8.1 is getting rolled back so we might have to wait a bit for the CI :( #38228 (comment)
@@ -72,7 +72,7 @@ func (cc *groupApprover) AutoApprove(csr *certificates.CertificateSigningRequest | |||
if len(x509cr.DNSNames)+len(x509cr.EmailAddresses)+len(x509cr.IPAddresses) != 0 { | |||
return csr, nil | |||
} | |||
if !hasExactUsages(csr, kubeletClientUsages) { | |||
if !hasExactUsages(csr, kubeletClientUsages) && !hasExactUsages(csr, kubeletServerUsages) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the server cert, I'd expect some matching of the user that requested it (system:node:(nodename)
) and the common name requested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What check are you thinking on the common name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During bootstrapping, the client cert contains the common name system:node:( nodename )
. When it requests a serving cert from the certificates API, it'll be authenticated as the username system:node:( nodename )
. When approving these requests, I'd expect that a node would only be able to request serving certs for it's nodename, and not others.
Actually, after the groupapprover is used to grant the client cert, the kubelet can use that cert and wont be part of the bootstrapping group after. This was brought up by @deads2k in the original client cert bootstrapping PR: #30094 (comment)
cc @kubernetes/sig-auth-pr-reviews
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. There are three distinct autoapproval cases we want to detect (see #45030)
- CSR shaped like a kubelet client cert, requested by the kubelet itself
- CSR shaped like a kubelet serving cert, requested by the kubelet itself
- CSR shaped like a kubelet client cert, requested by a different user (like a shared bootstrap user)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems beyond the scope of the group approver. Maybe a different PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is my hope to put some bare minimum into this PR and move on to #45030 next, after getting some feedback on that issue. @mikedanese says he thinks it would be a relatively small change, but it will depend on feedback.
If it seems reasonable to proceed here with some bare minimum, would addressing @ericchiang's comment be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the checks this function does wouldn't match the shape of a node's serving cert... it would have IP and DNS SANs, at least. I'd expect to start to see the refactor into isNodeClientCSR(CSR) and isNodeServingCSR(CSR) here.
the way we eventually determine which IP and DNS names a node can ask for a serving cert for are will be interesting... should a kubelet have to register its Node API object before it can ask for a serving cert?
fc62cd1
to
8c462c2
Compare
8c462c2
to
f5c6484
Compare
f5c6484
to
7dc7cf3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi @liggitt the controller manager approver changes have been removed from this PR.
@@ -46,6 +46,9 @@ const ( | |||
// manager. In the background it communicates with the API server to get new | |||
// certificates for certificates about to expire. | |||
type Manager interface { | |||
// CertificateSigningRequestClient sets the client interface that is used for | |||
// signing new certificates generated as part of rotation. | |||
CertificateSigningRequestClient(certificatesclient.CertificateSigningRequestInterface) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expect this to be part of the initializer. Any reason this was added to the interface? It doesn't actually look like it's being used outside of tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not used in this PR, but it's used in #41912. It's specifically to work around the one case where the API client needs the certificate from the certificate manager to make a connection, and the certificate manager needs the API client to request certificate changes. The sequence of steps is:
- Initialize certificate manager with local certs (either bootstrap or previously requested and stored cert). No client is passed in yet, so the certificate manager can't rotate/update yet.
- Initialize the API client with the certificate manager
- .Start() the certificate manager
- certificate manager uses the client to request an updated certificate (if it needs too, either because of bootstrapping or expiration)
/assign @dchen1107 |
/assign @timstclair |
/unassign @dchen1107 |
7dc7cf3
to
b3530ac
Compare
b3530ac
to
f0bbe84
Compare
@k8s-bot gce etcd3 e2e test this |
f0bbe84
to
9a9d3c1
Compare
pkg/kubelet/kubelet.go
Outdated
func initializeServerCertificateManager(kubeClient clientset.Interface, kubeCfg *componentconfig.KubeletConfiguration, nodeName types.NodeName, cloudIPs []net.IP, hostnames []string) (certificate.Manager, error) { | ||
var certSigningRequestClient clientcertificates.CertificateSigningRequestInterface | ||
if kubeClient != nil && kubeClient.Certificates() != nil { | ||
certSigningRequestClient = kubeClient.Certificates().CertificateSigningRequests() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are the implications if we don't have a client here? is it even possible for the cert manager to do anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The certificate manager can serve whatever local certificates it has, either received through the command line parameters, config file, or existing files on disk.
DNSNames: hostnames, | ||
IPAddresses: ips, | ||
}, | ||
Usages: []certificates.KeyUsage{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: might be worth having a helper in the certificates api package that returns usages for a TLS client cert, and one that returns usages for a TLS serving cert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will follow on with this change.
pkg/kubelet/kubelet.go
Outdated
if err != nil { | ||
return nil, fmt.Errorf("failed to initialize certificate store: %v", err) | ||
} | ||
ips, err := allLocalIPsWithoutLoopback() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hoist IP detection out of this function, this function should just take the lists of dns and ip SANs we want
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}) | ||
} | ||
|
||
func allLocalIPsWithoutLoopback() ([]net.IP, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the kubelet takes an --address
option that lets you bind to a specific interface. if that is set to a specific IP, we should just use that (we should only iterate over all interfaces if it is binding to 0.0.0.0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
looking good, just a couple questions and a couple structural changes around where/how we determine local ips. (needs a release note in the description as well) |
dfca6bc
to
2b4b578
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for adding a more detailed release note.
pkg/kubelet/kubelet.go
Outdated
if err != nil { | ||
return nil, err | ||
} | ||
ips = append(ips, cloudIPs...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for allLocalIPsWithoutLoopback
to include the external IPs? If so, I think you should probably de-dupe this list?
// Gets a server certificate for the kubelet from the Certificate Signing | ||
// Request API instead of generating one self signed and auto rotates the | ||
// certificate as expiration approaches. | ||
RotateKubeletServerCertificate utilfeature.Feature = "RotateKubeletServerCertificate" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think feature gates should be a long-term option. If we want something to toggle this after it goes to GA, it should be a separate flag.
d2febc8
to
92a9d07
Compare
Added a release note. |
Replaces the current kubelet server side self signed certs with certs signed by the Certificate Request Signing API on the API server. Also renews expiring kubelet server certs as expiration approaches.
92a9d07
to
4c22e6b
Compare
/lgtm |
/assign @smarterclayton |
/approve as per reviewers |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: jcbsmpsn, smarterclayton, timstclair
Needs approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
Automatic merge from submit-queue (batch tested with PRs 46635, 45619, 46637, 45059, 46415) |
This broke hack/local-up-cluster - while this will be disabled by default in hack local up cluster, we need a fix to it that allows someone to locally test this change. |
PR to disable alpha gates in local up cluster is already open. Would be easy to create a CA for the controller manager to enable signing. |
@@ -1037,6 +1094,62 @@ type Kubelet struct { | |||
dockerLegacyService dockershim.DockerLegacyService | |||
} | |||
|
|||
func initializeServerCertificateManager(kubeClient clientset.Interface, kubeCfg *componentconfig.KubeletConfiguration, nodeName types.NodeName, ips []net.IP, hostnames []string) (certificate.Manager, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to move this into its own package (since it's not dependent on anything in the kubelet) and import it, primarily to separate it from this mega package, but also to make it easier to consume from other contexts (like tools that want to provide their own certificate management in a similar form).
Objections to a small refactor? I also want to do that for the client code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaces the current kubelet server side self signed certs with certs signed by
the Certificate Request Signing API on the API server. Also renews expiring
kubelet server certs as expiration approaches.
Two Points:
--feature-gates=RotateKubeletServerCertificate=true
set, the kubelet willrequest a certificate during the boot cycle and pause waiting for the request to
be satisfied.
--insecure-experimental-approve-all-kubelet-csrs-for-group=
must be set onthe cluster controller manager. There is an improved mechanism for auto
approval proposed.
Release note: