Skip to content

Node Request Certificates require to have IPs#125813

Merged
k8s-ci-robot merged 1 commit into
kubernetes:masterfrom
aojea:node_csr_ips
Jul 18, 2024
Merged

Node Request Certificates require to have IPs#125813
k8s-ci-robot merged 1 commit into
kubernetes:masterfrom
aojea:node_csr_ips

Conversation

@aojea

@aojea aojea commented Jun 30, 2024

Copy link
Copy Markdown
Member

/kind bug

What this PR does / why we need it:

Before #121028 the hostname was always added to the Node object by the kubelet as address type v1.NodeHostName.

Post #121028 the kubelet waits for the external cloud provider to set all the Node addresses, v1.NodeHostName included.

#121028 caused an issue with some scenarios where the users want to override the hostname, and was fixed by #124516.

Post #124516 the kubelet initialize the Node object with the obtained hostname during the bootstrap, it can be overridden later by the cloud provider (unit test asserting this behavior in #125809)

Since now the Node object already contains at least one IP or DNSName, depending of the value of the hostname, the kubelet, when using the certificate manager, always requests a certificate at bootstrap with incomplete information, as it still may have to wait for the external cloud provider to populate the node addresses.

Make the condition to request the kubelet certificate to have at least one IP addresses, instead of having one IP or DNSName, solve the problem of kubelet requesting certificates too early.

An alternative is to wait for the external cloud provider taint to be removed

kubelet now requests serving certificates only once it has at least one IP address in the `.status.addresses` of its associated Node object. This avoids requesting DNS-only serving certificates before externally set addresses are in place. Until 1.33, the previous behavior can be opted back into by setting the deprecated AllowDNSOnlyNodeCSR feature gate to true in the kubelet.

/sig node
/sig auth

/assign @danwinship @liggitt

@k8s-ci-robot k8s-ci-robot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Jun 30, 2024
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. kind/bug Categorizes issue or PR as related to a bug. sig/node Categorizes an issue or PR as relevant to SIG Node. sig/auth Categorizes an issue or PR as relevant to SIG Auth. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jun 30, 2024
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the triage/accepted label and provide further guidance.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Jun 30, 2024
@aojea aojea changed the title Node csr ips Node Request Certificates require to have IPs Jun 30, 2024
@k8s-ci-robot k8s-ci-robot added area/cloudprovider area/kubelet sig/cloud-provider Categorizes an issue or PR as relevant to SIG Cloud Provider. labels Jun 30, 2024
@k8s-ci-robot k8s-ci-robot requested review from dims and wojtek-t June 30, 2024 15:25
Comment thread pkg/kubelet/certificate/kubelet.go Outdated
// when pods uses hostNetwork: true, as PodIPs.
// Nodes that use IP addresses as Hostname are interpreted as an IP
// address, so it is possible that are nodes that don't have any DNS name.
if len(ips) == 0 {

@liggitt liggitt Jun 30, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this bit. DNS-only entries could be valid if that's what the external provider sets

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be valid, but Kubernetes has a strong requirement on have at least one IP, as Pods depend on them, so my thinking is that a Node will need always at least one IP ... hence, any state without IPs on the node.status.addresses is a transitional state

@aojea aojea Jun 30, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the point of discussion here is when is really ok to request a certificate, do we want to request a certificate for a node that does not have any IP address reported?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe, because reporting ≠ having. Maybe the node is somehow multihomed but any connection based on a resolved IP address will stay up and working through magic technology.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a specific problem with external cloud providers that when the initialize the node object the set only the hostname, after that the cloud provider process the node and updates with the IP addresses.
The problem is that , if the node apporver does not consider valid a node with only DNS names and not with IP, there is a impact on node startup because nodes generate a first certificate request that is incomplete

Comment thread pkg/kubelet/certificate/kubelet.go Outdated
if len(hostnames) == 0 && len(ips) == 0 {
// don't return a template if we have no addresses to request for.
// A Kubernetes Node requires to have at minimum one IP address
// because those are used on the Pods field HostIPs and in some cases,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it uses the IP from node.status.addresses as pod hostIP?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api docs

// NodeHostName identifies a name of the node. Although every node can be assumed
// to have a NodeAddress of this type, its exact syntax and semantics are not
// defined, and are not consistent between different clusters.
NodeHostName NodeAddressType = "Hostname"
// NodeInternalIP identifies an IP address which is assigned to one of the node's
// network interfaces. Every node should have at least one address of this type.
//
// An internal IP is normally expected to be reachable from every other node, but
// may not be visible to hosts outside the cluster. By default it is assumed that
// kube-apiserver can reach node internal IPs, though it is possible to configure
// clusters where this is not the case.
//
// NodeInternalIP is the default type of node IP, and does not necessarily imply
// that the IP is ONLY reachable internally. If a node has multiple internal IPs,
// no specific semantics are assigned to the additional IPs.
NodeInternalIP NodeAddressType = "InternalIP"

@sftim

sftim commented Jun 30, 2024

Copy link
Copy Markdown
Contributor

Changelog suggestion

-kubelet, when configured to rotate certificates, will only request certificates when there is at least one IP address on the node.status.addresses
+Changed the way the kubelet requests certificates during rotation.
+The kubelet now only requests signing for a new certificate when there is at least one IP address in the
+`.status.addresses` of its associated Node object.

@danwinship

Copy link
Copy Markdown
Contributor

An alternative is to wait for the external cloud provider to be removed

I assume the word "taint" is missing there? (Or are we removing the external cloud providers now that we've gotten rid of the legacy ones 😂)

PR makes sense to me.

@aojea

aojea commented Jul 2, 2024

Copy link
Copy Markdown
Member Author

An alternative is to wait for the external cloud provider to be removed

I assume the word "taint" is missing there? (Or are we removing the external cloud providers now that we've gotten rid of the legacy ones 😂)

PR makes sense to me.

lol, yes, taint is indeed missing

@aojea

aojea commented Jul 15, 2024

Copy link
Copy Markdown
Member Author

This change is also consistent with the API documentation for node addresses, that require at least one internalIP address, but does not require a hostname address

// NodeHostName identifies a name of the node. Although every node can be assumed
// to have a NodeAddress of this type, its exact syntax and semantics are not
// defined, and are not consistent between different clusters.
NodeHostName NodeAddressType = "Hostname"
// NodeInternalIP identifies an IP address which is assigned to one of the node's
// network interfaces. Every node should have at least one address of this type.
//
// An internal IP is normally expected to be reachable from every other node, but
// may not be visible to hosts outside the cluster. By default it is assumed that
// kube-apiserver can reach node internal IPs, though it is possible to configure
// clusters where this is not the case.
//
// NodeInternalIP is the default type of node IP, and does not necessarily imply
// that the IP is ONLY reachable internally. If a node has multiple internal IPs,
// no specific semantics are assigned to the additional IPs.
NodeInternalIP NodeAddressType = "InternalIP"

@liggitt

liggitt commented Jul 16, 2024

Copy link
Copy Markdown
Member

I'm tentatively ok with this, given the way kubelet uses / requires an address of type IP for pod hostIP to work properly.

My read of kubelet address self-reporting is that in non-cloud envs, the kubelet always reports an IP (either detected or overridden with --node-ip), so this is scoped to external cloud providers.

A couple thoughts on reducing risk of this interacting weirdly with someone's setup:

  1. check with folks that run kubelets in various envs or setup tools (e.g. @deads2k, @danwinship (acked above, but just double-checking), @enj, @dims, @neolit123, @BenTheElder) that they don't know of weird setups this would cause issues with, for example:
    • kubelet serving expected to be working and secured using only the node DNS name in the critical path before the node IP address is assigned
    • the external cloud provider doesn't assign a node address of type IP at all (I think this would break pod hostIP functionality, so it seems really unlikely)
  2. should we add a temporary gate (e.g. RequireIPKubeletServerCertificate, defaulting true) to allow opting out of this as a mitigation for a couple releases in case someone has a weird setup we didn't consider?

@neolit123 neolit123 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the change seems ok to me, but is there a case where the cloud provider would only set an IP address in the status? if so, there should be a test case for that too.

seems like NewKubeletServerCertificateManager is not unit tested in terms of this hostname and IP validation.

@liggitt

liggitt commented Jul 16, 2024

Copy link
Copy Markdown
Member

is there a case where the cloud provider would only set an IP address in the status

only an IP is fine... that would have worked before, that would work with this PR as well (but it's good to have a unit test for it)

@neolit123

neolit123 commented Jul 16, 2024

Copy link
Copy Markdown
Member

kubelet serving expected to be working and secured using only the node DNS name in the critical path before the node IP address is assigned

i can imagine scenarios like that - i.e. someone using the hostname to access the kubelet server and never using the IP.
(IIUC this part)

the external cloud provider doesn't assign a node address of type IP at all (I think this would break pod hostIP functionality, so it seems really unlikely)

agreed that this seems unlikely.

should we add a temporary gate (e.g. RequireIPKubeletServerCertificate, defaulting true) to allow opting out of this as a mitigation for a couple releases in case someone has a weird setup we didn't consider?

seems better to have the opt-out if immediate breakages happen then users need to opt-out until they can adapt their setups.

@liggitt

liggitt commented Jul 16, 2024

Copy link
Copy Markdown
Member

i can imagine scenarios like that - i.e. someone using the hostname to access the kubelet server and never using the IP. (IIUC this part)

If they report a DNS name, the kubelet will still include it in cert requests, and they can still use it as before. What would not work is if they only told the kubelet its DNS name, told it it had 0 IP addresses, and still expected kubelet to request serving certificates with just the DNS names and no IPs.

@k8s-ci-robot k8s-ci-robot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jul 17, 2024
@aojea

aojea commented Jul 17, 2024

Copy link
Copy Markdown
Member Author

added a feature gate to be able to opt-in for the old behavior, I also had to refactor a bit the code to be able to unit test it

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Jul 17, 2024
@aojea

aojea commented Jul 17, 2024

Copy link
Copy Markdown
Member Author

Volumes [Driver: csi-hostpath] [Testpattern: Dynamic PV (block volmode)] volumes should store data expand_more

/test pull-kubernetes-e2e-gce

Comment thread pkg/kubelet/certificate/kubelet.go Outdated
},
)
legacyregistry.MustRegister(certificateRotationAge)
registerMetricsOnce.Do(func() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, if newCertificateManagerConfig is called more than once, the returned configs will have metrics that aren't actually registered or reporting anywhere

before, MustRegister would panic on this... now, we just silently return not-working configs

if we're going to let this be called multiple times, I think the certificateRenewFailure / certificateRotationAge have to become shared package vars that lazily init / register once on first call

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the returned configs will have metrics that aren't actually registered or reporting anywhere

metrics are global and you can only register once, so if is called multiple times the metrics will still apply

before, MustRegister would panic on this... now, we just silently return not-working configs

why will be not-working?

if we're going to let this be called multiple times, I think the certificateRenewFailure / certificateRotationAge have to become shared package vars that lazily init / register once on first call

I started with that but the diff was larger( thinking on backports)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I didn't realize there is another certficiate.Config below 🤔

Comment thread pkg/kubelet/certificate/kubelet.go Outdated
// don't return a template if we have no addresses to request for
if len(hostnames) == 0 && len(ips) == 0 {
return nil
// don't return a template if we have no IP addresses or DNS names to request for

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why, but I'm finding the combination of the nesting and the feature gate name really hard to follow. Would something like this be clearer?

// by default, require at least one IP before requesting a serving certificate
hasRequiredAddresses := len(ips) > 0

// optionally allow requesting a serving certificate with just a DNS name
if utilfeature.DefaultFeatureGate.Enabled(features.AllowDNSOnlyNodeCSR) {
  hasRequiredAddresses = hasRequiredAddresses || len(hostnames) > 0
}

// don't return a template if we have no addresses to request for
if !hasRequiredAddresses {
  return nil
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is hard to follow but cleaner when we remove the feature gate, the diff is simpler

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is hard to follow but it is cleaner, so when we remove the feature gate, the diff is simpler

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update with your suggestion

A Kubernetes Node requires to have at minimum one IP address
because those are used on the Pods field HostIPs and in some cases,
when pods uses hostNetwork: true, as PodIPs.
Nodes that use IP addresses as Hostname are interpreted as an IP
address, so it is possible that are nodes that don't hane any DNSname.

The feature gate AllowDNSOnlyNodeCSR will allow user to opt-in for
the old behavior.

Change-Id: I094531d87246f1e7a5ef4fe57bd5d9840cb1375d
@aojea

aojea commented Jul 18, 2024

Copy link
Copy Markdown
Member Author

@liggitt the config generator was the wrong abstraction as it left the code worse than is today.
I took another approach and just decoupled the getTemplate() function to make it unit testable and code is more compatible with previous state

@aojea

aojea commented Jul 18, 2024

Copy link
Copy Markdown
Member Author

TestCustomResourceDefaultingWithoutWatchCache

does not look related

/test pull-kubernetes-integration

@neolit123

neolit123 commented Jul 18, 2024

Copy link
Copy Markdown
Member

TestCustomResourceDefaultingWithoutWatchCache

does not look related

/test pull-kubernetes-integration

@neolit123 neolit123 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/kind feature
/lgtm

@k8s-ci-robot k8s-ci-robot added the kind/feature Categorizes issue or PR as related to a new feature. label Jul 18, 2024
@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Jul 18, 2024
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

LGTM label has been added.

DetailsGit tree hash: 32ef1270c3d13c841b81c2e237249aa3d0cf6df9

@liggitt

liggitt commented Jul 18, 2024

Copy link
Copy Markdown
Member

/lgtm
/approve

@k8s-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: aojea, liggitt

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/cloudprovider area/kubelet cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. kind/feature Categorizes issue or PR as related to a new feature. lgtm "Looks good to me", indicates that a PR is ready to be merged. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/auth Categorizes an issue or PR as relevant to SIG Auth. sig/cloud-provider Categorizes an issue or PR as relevant to SIG Cloud Provider. sig/node Categorizes an issue or PR as relevant to SIG Node. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

8 participants