Merge pull request #125813 from aojea/node_csr_ips

Node Request Certificates require to have IPs
This commit is contained in:
Kubernetes Prow Robot 2024-07-18 14:50:48 -07:00 committed by GitHub
commit fa7fcde5a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 183 additions and 15 deletions

View File

@ -45,6 +45,13 @@ const (
// Enable usage of Provision of PVCs from snapshots in other namespaces
CrossNamespaceVolumeDataSource featuregate.Feature = "CrossNamespaceVolumeDataSource"
// owner: @aojea
// Deprecated: v1.31
//
// Allow kubelet to request a certificate without any Node IP available, only
// with DNS names.
AllowDNSOnlyNodeCSR featuregate.Feature = "AllowDNSOnlyNodeCSR"
// owner: @thockin
// deprecated: v1.29
//
@ -991,6 +998,8 @@ func init() {
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
CrossNamespaceVolumeDataSource: {Default: false, PreRelease: featuregate.Alpha},
AllowDNSOnlyNodeCSR: {Default: false, PreRelease: featuregate.Deprecated}, // remove after 1.33
AllowServiceLBStatusOnNonLB: {Default: false, PreRelease: featuregate.Deprecated}, // remove after 1.29
AnyVolumeDataSource: {Default: true, PreRelease: featuregate.Beta}, // on by default in 1.24

View File

@ -32,16 +32,44 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/certificate"
compbasemetrics "k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
"k8s.io/kubernetes/pkg/kubelet/metrics"
netutils "k8s.io/utils/net"
)
func newGetTemplateFn(nodeName types.NodeName, getAddresses func() []v1.NodeAddress) func() *x509.CertificateRequest {
return func() *x509.CertificateRequest {
hostnames, ips := addressesToHostnamesAndIPs(getAddresses())
// 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
}
return &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: hostnames,
IPAddresses: ips,
}
}
}
// NewKubeletServerCertificateManager creates a certificate manager for the kubelet when retrieving a server certificate
// or returns an error.
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
)
legacyregistry.MustRegister(certificateRotationAge)
getTemplate := func() *x509.CertificateRequest {
hostnames, ips := addressesToHostnamesAndIPs(getAddresses())
// don't return a template if we have no addresses to request for
if len(hostnames) == 0 && len(ips) == 0 {
return nil
}
return &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: hostnames,
IPAddresses: ips,
}
}
getTemplate := newGetTemplateFn(nodeName, getAddresses)
m, err := certificate.NewManager(&certificate.Config{
ClientsetFn: clientsetFn,

View File

@ -19,6 +19,8 @@ package certificate
import (
"bytes"
"context"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net"
"os"
@ -28,8 +30,12 @@ import (
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/util/cert"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/features"
netutils "k8s.io/utils/net"
)
@ -261,3 +267,142 @@ func TestKubeletServerCertificateFromFiles(t *testing.T) {
})
}
}
func TestNewCertificateManagerConfigGetTemplate(t *testing.T) {
nodeName := "fake-node"
nodeIP := netutils.ParseIPSloppy("192.168.1.1")
tests := []struct {
name string
nodeAddresses []v1.NodeAddress
want *x509.CertificateRequest
featuregate bool
}{
{
name: "node addresses or hostnames and gate enabled",
featuregate: true,
},
{
name: "node addresses or hostnames and gate disabled",
featuregate: false,
},
{
name: "only hostnames and gate enabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeHostName,
Address: nodeName,
},
},
want: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: []string{nodeName},
},
featuregate: true,
},
{
name: "only hostnames and gate disabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeHostName,
Address: nodeName,
},
},
featuregate: false,
},
{
name: "only IP addresses and gate enabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeInternalIP,
Address: nodeIP.String(),
},
},
want: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
IPAddresses: []net.IP{nodeIP},
},
featuregate: true,
},
{
name: "only IP addresses and gate disabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeInternalIP,
Address: nodeIP.String(),
},
},
want: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
IPAddresses: []net.IP{nodeIP},
},
featuregate: false,
},
{
name: "IP addresses and hostnames and gate enabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeHostName,
Address: nodeName,
},
{
Type: v1.NodeInternalIP,
Address: nodeIP.String(),
},
},
want: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: []string{nodeName},
IPAddresses: []net.IP{nodeIP},
},
featuregate: true,
},
{
name: "IP addresses and hostnames and gate disabled",
nodeAddresses: []v1.NodeAddress{
{
Type: v1.NodeHostName,
Address: nodeName,
},
{
Type: v1.NodeInternalIP,
Address: nodeIP.String(),
},
},
want: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: fmt.Sprintf("system:node:%s", nodeName),
Organization: []string{"system:nodes"},
},
DNSNames: []string{nodeName},
IPAddresses: []net.IP{nodeIP},
},
featuregate: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AllowDNSOnlyNodeCSR, tt.featuregate)
getAddresses := func() []v1.NodeAddress {
return tt.nodeAddresses
}
getTemplate := newGetTemplateFn(types.NodeName(nodeName), getAddresses)
got := getTemplate()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Wrong certificate, got %v expected %v", got, tt.want)
return
}
})
}
}