Skip to content

Commit fa7fcde

Browse files
authored
Merge pull request kubernetes#125813 from aojea/node_csr_ips
Node Request Certificates require to have IPs
2 parents f820301 + bc63c41 commit fa7fcde

File tree

3 files changed

+183
-15
lines changed

3 files changed

+183
-15
lines changed

pkg/features/kube_features.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ const (
4545
// Enable usage of Provision of PVCs from snapshots in other namespaces
4646
CrossNamespaceVolumeDataSource featuregate.Feature = "CrossNamespaceVolumeDataSource"
4747

48+
// owner: @aojea
49+
// Deprecated: v1.31
50+
//
51+
// Allow kubelet to request a certificate without any Node IP available, only
52+
// with DNS names.
53+
AllowDNSOnlyNodeCSR featuregate.Feature = "AllowDNSOnlyNodeCSR"
54+
4855
// owner: @thockin
4956
// deprecated: v1.29
5057
//
@@ -991,6 +998,8 @@ func init() {
991998
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
992999
CrossNamespaceVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},
9931000

1001+
AllowDNSOnlyNodeCSR: {Default: false, PreRelease: featuregate.Deprecated}, // remove after 1.33
1002+
9941003
AllowServiceLBStatusOnNonLB: {Default: false, PreRelease: featuregate.Deprecated}, // remove after 1.29
9951004

9961005
AnyVolumeDataSource: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.24

pkg/kubelet/certificate/kubelet.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,44 @@ import (
3232
v1 "k8s.io/api/core/v1"
3333
"k8s.io/apimachinery/pkg/types"
3434
"k8s.io/apiserver/pkg/server/dynamiccertificates"
35+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3536
clientset "k8s.io/client-go/kubernetes"
3637
"k8s.io/client-go/util/certificate"
3738
compbasemetrics "k8s.io/component-base/metrics"
3839
"k8s.io/component-base/metrics/legacyregistry"
3940
"k8s.io/klog/v2"
41+
"k8s.io/kubernetes/pkg/features"
4042
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
4143
"k8s.io/kubernetes/pkg/kubelet/metrics"
4244
netutils "k8s.io/utils/net"
4345
)
4446

47+
func newGetTemplateFn(nodeName types.NodeName, getAddresses func() []v1.NodeAddress) func() *x509.CertificateRequest {
48+
return func() *x509.CertificateRequest {
49+
hostnames, ips := addressesToHostnamesAndIPs(getAddresses())
50+
// by default, require at least one IP before requesting a serving certificate
51+
hasRequiredAddresses := len(ips) > 0
52+
53+
// optionally allow requesting a serving certificate with just a DNS name
54+
if utilfeature.DefaultFeatureGate.Enabled(features.AllowDNSOnlyNodeCSR) {
55+
hasRequiredAddresses = hasRequiredAddresses || len(hostnames) > 0
56+
}
57+
58+
// don't return a template if we have no addresses to request for
59+
if !hasRequiredAddresses {
60+
return nil
61+
}
62+
return &x509.CertificateRequest{
63+
Subject: pkix.Name{
64+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
65+
Organization: []string{"system:nodes"},
66+
},
67+
DNSNames: hostnames,
68+
IPAddresses: ips,
69+
}
70+
}
71+
}
72+
4573
// NewKubeletServerCertificateManager creates a certificate manager for the kubelet when retrieving a server certificate
4674
// or returns an error.
4775
func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg *kubeletconfig.KubeletConfiguration, nodeName types.NodeName, getAddresses func() []v1.NodeAddress, certDirectory string) (certificate.Manager, error) {
@@ -92,21 +120,7 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg
92120
)
93121
legacyregistry.MustRegister(certificateRotationAge)
94122

95-
getTemplate := func() *x509.CertificateRequest {
96-
hostnames, ips := addressesToHostnamesAndIPs(getAddresses())
97-
// don't return a template if we have no addresses to request for
98-
if len(hostnames) == 0 && len(ips) == 0 {
99-
return nil
100-
}
101-
return &x509.CertificateRequest{
102-
Subject: pkix.Name{
103-
CommonName: fmt.Sprintf("system:node:%s", nodeName),
104-
Organization: []string{"system:nodes"},
105-
},
106-
DNSNames: hostnames,
107-
IPAddresses: ips,
108-
}
109-
}
123+
getTemplate := newGetTemplateFn(nodeName, getAddresses)
110124

111125
m, err := certificate.NewManager(&certificate.Config{
112126
ClientsetFn: clientsetFn,

pkg/kubelet/certificate/kubelet_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package certificate
1919
import (
2020
"bytes"
2121
"context"
22+
"crypto/x509"
23+
"crypto/x509/pkix"
2224
"fmt"
2325
"net"
2426
"os"
@@ -28,8 +30,12 @@ import (
2830
"time"
2931

3032
v1 "k8s.io/api/core/v1"
33+
"k8s.io/apimachinery/pkg/types"
3134
"k8s.io/apimachinery/pkg/util/wait"
35+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3236
"k8s.io/client-go/util/cert"
37+
featuregatetesting "k8s.io/component-base/featuregate/testing"
38+
"k8s.io/kubernetes/pkg/features"
3339
netutils "k8s.io/utils/net"
3440
)
3541

@@ -261,3 +267,142 @@ func TestKubeletServerCertificateFromFiles(t *testing.T) {
261267
})
262268
}
263269
}
270+
271+
func TestNewCertificateManagerConfigGetTemplate(t *testing.T) {
272+
nodeName := "fake-node"
273+
nodeIP := netutils.ParseIPSloppy("192.168.1.1")
274+
tests := []struct {
275+
name string
276+
nodeAddresses []v1.NodeAddress
277+
want *x509.CertificateRequest
278+
featuregate bool
279+
}{
280+
{
281+
name: "node addresses or hostnames and gate enabled",
282+
featuregate: true,
283+
},
284+
{
285+
name: "node addresses or hostnames and gate disabled",
286+
featuregate: false,
287+
},
288+
{
289+
name: "only hostnames and gate enabled",
290+
nodeAddresses: []v1.NodeAddress{
291+
{
292+
Type: v1.NodeHostName,
293+
Address: nodeName,
294+
},
295+
},
296+
want: &x509.CertificateRequest{
297+
Subject: pkix.Name{
298+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
299+
Organization: []string{"system:nodes"},
300+
},
301+
DNSNames: []string{nodeName},
302+
},
303+
featuregate: true,
304+
},
305+
{
306+
name: "only hostnames and gate disabled",
307+
nodeAddresses: []v1.NodeAddress{
308+
{
309+
Type: v1.NodeHostName,
310+
Address: nodeName,
311+
},
312+
},
313+
featuregate: false,
314+
},
315+
{
316+
name: "only IP addresses and gate enabled",
317+
nodeAddresses: []v1.NodeAddress{
318+
{
319+
Type: v1.NodeInternalIP,
320+
Address: nodeIP.String(),
321+
},
322+
},
323+
want: &x509.CertificateRequest{
324+
Subject: pkix.Name{
325+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
326+
Organization: []string{"system:nodes"},
327+
},
328+
IPAddresses: []net.IP{nodeIP},
329+
},
330+
featuregate: true,
331+
},
332+
{
333+
name: "only IP addresses and gate disabled",
334+
nodeAddresses: []v1.NodeAddress{
335+
{
336+
Type: v1.NodeInternalIP,
337+
Address: nodeIP.String(),
338+
},
339+
},
340+
want: &x509.CertificateRequest{
341+
Subject: pkix.Name{
342+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
343+
Organization: []string{"system:nodes"},
344+
},
345+
IPAddresses: []net.IP{nodeIP},
346+
},
347+
featuregate: false,
348+
},
349+
{
350+
name: "IP addresses and hostnames and gate enabled",
351+
nodeAddresses: []v1.NodeAddress{
352+
{
353+
Type: v1.NodeHostName,
354+
Address: nodeName,
355+
},
356+
{
357+
Type: v1.NodeInternalIP,
358+
Address: nodeIP.String(),
359+
},
360+
},
361+
want: &x509.CertificateRequest{
362+
Subject: pkix.Name{
363+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
364+
Organization: []string{"system:nodes"},
365+
},
366+
DNSNames: []string{nodeName},
367+
IPAddresses: []net.IP{nodeIP},
368+
},
369+
featuregate: true,
370+
},
371+
{
372+
name: "IP addresses and hostnames and gate disabled",
373+
nodeAddresses: []v1.NodeAddress{
374+
{
375+
Type: v1.NodeHostName,
376+
Address: nodeName,
377+
},
378+
{
379+
Type: v1.NodeInternalIP,
380+
Address: nodeIP.String(),
381+
},
382+
},
383+
want: &x509.CertificateRequest{
384+
Subject: pkix.Name{
385+
CommonName: fmt.Sprintf("system:node:%s", nodeName),
386+
Organization: []string{"system:nodes"},
387+
},
388+
DNSNames: []string{nodeName},
389+
IPAddresses: []net.IP{nodeIP},
390+
},
391+
featuregate: false,
392+
},
393+
}
394+
for _, tt := range tests {
395+
t.Run(tt.name, func(t *testing.T) {
396+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowDNSOnlyNodeCSR, tt.featuregate)
397+
getAddresses := func() []v1.NodeAddress {
398+
return tt.nodeAddresses
399+
}
400+
getTemplate := newGetTemplateFn(types.NodeName(nodeName), getAddresses)
401+
got := getTemplate()
402+
if !reflect.DeepEqual(got, tt.want) {
403+
t.Errorf("Wrong certificate, got %v expected %v", got, tt.want)
404+
return
405+
}
406+
})
407+
}
408+
}

0 commit comments

Comments
 (0)