k8s.io/kubernetes@v1.29.3/pkg/apis/certificates/v1beta1/defaults_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1beta1
    18  
    19  import (
    20  	"crypto/ed25519"
    21  	"crypto/rand"
    22  	"crypto/x509"
    23  	"crypto/x509/pkix"
    24  	"encoding/pem"
    25  	"net"
    26  	"net/url"
    27  	"reflect"
    28  	"testing"
    29  
    30  	capi "k8s.io/api/certificates/v1beta1"
    31  )
    32  
    33  func TestIsKubeletServingCSR(t *testing.T) {
    34  	newCSR := func(base pemOptions, overlays ...pemOptions) *x509.CertificateRequest {
    35  		b := csrWithOpts(base, overlays...)
    36  		csr, err := ParseCSR(b)
    37  		if err != nil {
    38  			t.Fatal(err)
    39  		}
    40  		return csr
    41  	}
    42  	tests := map[string]struct {
    43  		req    *x509.CertificateRequest
    44  		usages []capi.KeyUsage
    45  		exp    bool
    46  	}{
    47  		"defaults for kubelet-serving": {
    48  			req:    newCSR(kubeletServerPEMOptions),
    49  			usages: kubeletServerUsages,
    50  			exp:    true,
    51  		},
    52  		"defaults without key encipherment for kubelet-serving": {
    53  			req:    newCSR(kubeletServerPEMOptions),
    54  			usages: kubeletServerUsagesNoRSA,
    55  			exp:    true,
    56  		},
    57  		"does not default to kube-apiserver-client-kubelet if org is not 'system:nodes'": {
    58  			req:    newCSR(kubeletServerPEMOptions, pemOptions{org: "not-system:nodes"}),
    59  			usages: kubeletServerUsages,
    60  			exp:    false,
    61  		},
    62  		"does not default to kubelet-serving if CN does not have system:node: prefix": {
    63  			req:    newCSR(kubeletServerPEMOptions, pemOptions{cn: "notprefixed"}),
    64  			usages: kubeletServerUsages,
    65  			exp:    false,
    66  		},
    67  		"does not default to kubelet-serving if it has an unexpected usage": {
    68  			req:    newCSR(kubeletServerPEMOptions),
    69  			usages: append(kubeletServerUsages, capi.UsageClientAuth),
    70  			exp:    false,
    71  		},
    72  		"does not default to kubelet-serving if it is missing an expected usage": {
    73  			req:    newCSR(kubeletServerPEMOptions),
    74  			usages: kubeletServerUsages[1:],
    75  			exp:    false,
    76  		},
    77  		"does not default to kubelet-serving if it does not specify any dnsNames or ipAddresses": {
    78  			req:    newCSR(kubeletServerPEMOptions, pemOptions{ipAddresses: []net.IP{}, dnsNames: []string{}}),
    79  			usages: kubeletServerUsages[1:],
    80  			exp:    false,
    81  		},
    82  		"does not default to kubelet-serving if it specifies a URI SAN": {
    83  			req:    newCSR(kubeletServerPEMOptions, pemOptions{uris: []string{"http://something"}}),
    84  			usages: kubeletServerUsages,
    85  			exp:    false,
    86  		},
    87  		"does not default to kubelet-serving if it specifies an emailAddress SAN": {
    88  			req:    newCSR(kubeletServerPEMOptions, pemOptions{emailAddresses: []string{"something"}}),
    89  			usages: kubeletServerUsages,
    90  			exp:    false,
    91  		},
    92  	}
    93  	for name, test := range tests {
    94  		t.Run(name, func(t *testing.T) {
    95  			got := IsKubeletServingCSR(test.req, test.usages)
    96  			if test.exp != got {
    97  				t.Errorf("unexpected IsKubeletClientCSR output: exp=%v, got=%v", test.exp, got)
    98  			}
    99  		})
   100  	}
   101  }
   102  
   103  func TestIsKubeletClientCSR(t *testing.T) {
   104  	newCSR := func(base pemOptions, overlays ...pemOptions) *x509.CertificateRequest {
   105  		b := csrWithOpts(base, overlays...)
   106  		csr, err := ParseCSR(b)
   107  		if err != nil {
   108  			t.Fatal(err)
   109  		}
   110  		return csr
   111  	}
   112  	tests := map[string]struct {
   113  		req    *x509.CertificateRequest
   114  		usages []capi.KeyUsage
   115  		exp    bool
   116  	}{
   117  		"defaults for kube-apiserver-client-kubelet": {
   118  			req:    newCSR(kubeletClientPEMOptions),
   119  			usages: kubeletClientUsages,
   120  			exp:    true,
   121  		},
   122  		"does not default to kube-apiserver-client-kubelet if org is not 'system:nodes'": {
   123  			req:    newCSR(kubeletClientPEMOptions, pemOptions{org: "not-system:nodes"}),
   124  			usages: kubeletClientUsages,
   125  			exp:    false,
   126  		},
   127  		"does not default to kube-apiserver-client-kubelet if a dnsName is set": {
   128  			req:    newCSR(kubeletClientPEMOptions, pemOptions{dnsNames: []string{"something"}}),
   129  			usages: kubeletClientUsages,
   130  			exp:    false,
   131  		},
   132  		"does not default to kube-apiserver-client-kubelet if an emailAddress is set": {
   133  			req:    newCSR(kubeletClientPEMOptions, pemOptions{emailAddresses: []string{"something"}}),
   134  			usages: kubeletClientUsages,
   135  			exp:    false,
   136  		},
   137  		"does not default to kube-apiserver-client-kubelet if a uri SAN is set": {
   138  			req:    newCSR(kubeletClientPEMOptions, pemOptions{uris: []string{"http://something"}}),
   139  			usages: kubeletClientUsages,
   140  			exp:    false,
   141  		},
   142  		"does not default to kube-apiserver-client-kubelet if an ipAddress is set": {
   143  			req:    newCSR(kubeletClientPEMOptions, pemOptions{ipAddresses: []net.IP{{0, 0, 0, 0}}}),
   144  			usages: kubeletClientUsages,
   145  			exp:    false,
   146  		},
   147  		"does not default to kube-apiserver-client-kubelet if CN does not have 'system:node:' prefix": {
   148  			req:    newCSR(kubeletClientPEMOptions, pemOptions{cn: "not-prefixed"}),
   149  			usages: kubeletClientUsages,
   150  			exp:    false,
   151  		},
   152  		"does not default to kube-apiserver-client-kubelet if it has an unexpected usage": {
   153  			req:    newCSR(kubeletClientPEMOptions),
   154  			usages: append(kubeletClientUsages, capi.UsageServerAuth),
   155  			exp:    false,
   156  		},
   157  		"does not default to kube-apiserver-client-kubelet if it is missing an expected usage": {
   158  			req:    newCSR(kubeletClientPEMOptions),
   159  			usages: kubeletClientUsages[1:],
   160  			exp:    false,
   161  		},
   162  		"does not default to kube-apiserver-client-kubelet if it is missing an expected usage without key encipherment": {
   163  			req:    newCSR(kubeletClientPEMOptions),
   164  			usages: kubeletClientUsagesNoRSA[1:],
   165  			exp:    false,
   166  		},
   167  		"default to kube-apiserver-client-kubelet without key encipherment": {
   168  			req:    newCSR(kubeletClientPEMOptions),
   169  			usages: kubeletClientUsagesNoRSA,
   170  			exp:    true,
   171  		},
   172  	}
   173  	for name, test := range tests {
   174  		t.Run(name, func(t *testing.T) {
   175  			got := IsKubeletClientCSR(test.req, test.usages)
   176  			if test.exp != got {
   177  				t.Errorf("unexpected IsKubeletClientCSR output: exp=%v, got=%v", test.exp, got)
   178  			}
   179  		})
   180  	}
   181  }
   182  
   183  var (
   184  	kubeletClientUsages = []capi.KeyUsage{
   185  		capi.UsageDigitalSignature,
   186  		capi.UsageKeyEncipherment,
   187  		capi.UsageClientAuth,
   188  	}
   189  	kubeletClientUsagesNoRSA = []capi.KeyUsage{
   190  		capi.UsageDigitalSignature,
   191  		capi.UsageClientAuth,
   192  	}
   193  	kubeletClientPEMOptions = pemOptions{
   194  		cn:  "system:node:nodename",
   195  		org: "system:nodes",
   196  	}
   197  
   198  	kubeletServerUsages = []capi.KeyUsage{
   199  		capi.UsageDigitalSignature,
   200  		capi.UsageKeyEncipherment,
   201  		capi.UsageServerAuth,
   202  	}
   203  	kubeletServerUsagesNoRSA = []capi.KeyUsage{
   204  		capi.UsageDigitalSignature,
   205  		capi.UsageServerAuth,
   206  	}
   207  	kubeletServerPEMOptions = pemOptions{
   208  		cn:          "system:node:requester-name",
   209  		org:         "system:nodes",
   210  		dnsNames:    []string{"node-server-name"},
   211  		ipAddresses: []net.IP{{0, 0, 0, 0}},
   212  	}
   213  )
   214  
   215  func TestSetDefaults_CertificateSigningRequestSpec(t *testing.T) {
   216  	strPtr := func(s string) *string { return &s }
   217  	tests := map[string]struct {
   218  		csr                capi.CertificateSigningRequestSpec
   219  		expectedSignerName string
   220  		expectedUsages     []capi.KeyUsage
   221  	}{
   222  		"defaults to legacy-unknown if request is not a CSR": {
   223  			csr: capi.CertificateSigningRequestSpec{
   224  				Request: []byte("invalid data"),
   225  				Usages:  kubeletServerUsages,
   226  			},
   227  			expectedSignerName: capi.LegacyUnknownSignerName,
   228  		},
   229  		"does not default signerName if signerName is already set": {
   230  			csr: capi.CertificateSigningRequestSpec{
   231  				Request:    csrWithOpts(kubeletServerPEMOptions),
   232  				Usages:     kubeletServerUsages,
   233  				SignerName: strPtr("example.com/not-kubelet-serving"),
   234  			},
   235  			expectedSignerName: "example.com/not-kubelet-serving",
   236  		},
   237  		"defaults usages if not set": {
   238  			csr: capi.CertificateSigningRequestSpec{
   239  				Request:    csrWithOpts(kubeletServerPEMOptions),
   240  				SignerName: strPtr("example.com/test"),
   241  			},
   242  			expectedSignerName: "example.com/test",
   243  			expectedUsages:     []capi.KeyUsage{capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   244  		},
   245  	}
   246  	for name, test := range tests {
   247  		t.Run(name, func(t *testing.T) {
   248  			// create a deepcopy to be sure we don't modify anything in-place
   249  			csrSpec := test.csr.DeepCopy()
   250  			SetDefaults_CertificateSigningRequestSpec(csrSpec)
   251  			if *csrSpec.SignerName != test.expectedSignerName {
   252  				t.Errorf("expected signerName to be defaulted to %q but it is %q", test.expectedSignerName, *csrSpec.SignerName)
   253  			}
   254  
   255  			// only check expectedUsages if it is non-nil
   256  			if test.expectedUsages != nil {
   257  				if !reflect.DeepEqual(test.expectedUsages, csrSpec.Usages) {
   258  					t.Errorf("expected usages to be defaulted to %v but it is %v", test.expectedUsages, csrSpec.Usages)
   259  				}
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func TestSetDefaults_CertificateSigningRequestSpec_KubeletServing(t *testing.T) {
   266  	tests := map[string]struct {
   267  		csr                capi.CertificateSigningRequestSpec
   268  		expectedSignerName string
   269  	}{
   270  		"defaults for kubelet-serving": {
   271  			csr: capi.CertificateSigningRequestSpec{
   272  				Request:  csrWithOpts(kubeletServerPEMOptions),
   273  				Usages:   kubeletServerUsages,
   274  				Username: kubeletServerPEMOptions.cn,
   275  			},
   276  			expectedSignerName: capi.KubeletServingSignerName,
   277  		},
   278  		"does not default to kube-apiserver-client-kubelet if org is not 'system:nodes'": {
   279  			csr: capi.CertificateSigningRequestSpec{
   280  				Request:  csrWithOpts(kubeletServerPEMOptions, pemOptions{org: "not-system:nodes"}),
   281  				Usages:   kubeletServerUsages,
   282  				Username: "system:node:not-requester-name",
   283  			},
   284  			expectedSignerName: capi.LegacyUnknownSignerName,
   285  		},
   286  		"does not default to kubelet-serving if CN does not have system:node: prefix": {
   287  			csr: capi.CertificateSigningRequestSpec{
   288  				Request:  csrWithOpts(kubeletServerPEMOptions, pemOptions{cn: "notprefixed"}),
   289  				Usages:   kubeletServerUsages,
   290  				Username: "notprefixed",
   291  			},
   292  			expectedSignerName: capi.LegacyUnknownSignerName,
   293  		},
   294  		"does not default to kubelet-serving if it has an unexpected usage": {
   295  			csr: capi.CertificateSigningRequestSpec{
   296  				Request:  csrWithOpts(kubeletServerPEMOptions),
   297  				Usages:   append(kubeletServerUsages, capi.UsageClientAuth),
   298  				Username: kubeletServerPEMOptions.cn,
   299  			},
   300  			expectedSignerName: capi.LegacyUnknownSignerName,
   301  		},
   302  		"does not default to kubelet-serving if it is missing an expected usage": {
   303  			csr: capi.CertificateSigningRequestSpec{
   304  				Request: csrWithOpts(kubeletServerPEMOptions),
   305  				// Remove the first usage in 'kubeletServerUsages'
   306  				Usages:   kubeletServerUsages[1:],
   307  				Username: kubeletServerPEMOptions.cn,
   308  			},
   309  			expectedSignerName: capi.LegacyUnknownSignerName,
   310  		},
   311  		"does not default to kubelet-serving if it does not specify any dnsNames or ipAddresses": {
   312  			csr: capi.CertificateSigningRequestSpec{
   313  				Request:  csrWithOpts(kubeletServerPEMOptions, pemOptions{ipAddresses: []net.IP{}, dnsNames: []string{}}),
   314  				Usages:   kubeletServerUsages,
   315  				Username: kubeletServerPEMOptions.cn,
   316  			},
   317  			expectedSignerName: capi.LegacyUnknownSignerName,
   318  		},
   319  		"does not default to kubelet-serving if it specifies a URI SAN": {
   320  			csr: capi.CertificateSigningRequestSpec{
   321  				Request:  csrWithOpts(kubeletServerPEMOptions, pemOptions{uris: []string{"http://something"}}),
   322  				Usages:   kubeletServerUsages,
   323  				Username: kubeletServerPEMOptions.cn,
   324  			},
   325  			expectedSignerName: capi.LegacyUnknownSignerName,
   326  		},
   327  		"does not default to kubelet-serving if it specifies an emailAddress SAN": {
   328  			csr: capi.CertificateSigningRequestSpec{
   329  				Request:  csrWithOpts(kubeletServerPEMOptions, pemOptions{emailAddresses: []string{"something"}}),
   330  				Usages:   kubeletServerUsages,
   331  				Username: kubeletServerPEMOptions.cn,
   332  			},
   333  			expectedSignerName: capi.LegacyUnknownSignerName,
   334  		},
   335  	}
   336  	for name, test := range tests {
   337  		t.Run(name, func(t *testing.T) {
   338  			// create a deepcopy to be sure we don't modify anything in-place
   339  			csrSpec := test.csr.DeepCopy()
   340  			SetDefaults_CertificateSigningRequestSpec(csrSpec)
   341  			if *csrSpec.SignerName != test.expectedSignerName {
   342  				t.Errorf("expected signerName to be defaulted to %q but it is %q", test.expectedSignerName, *csrSpec.SignerName)
   343  			}
   344  		})
   345  	}
   346  }
   347  
   348  func TestSetDefaults_CertificateSigningRequestSpec_KubeletClient(t *testing.T) {
   349  	tests := map[string]struct {
   350  		csr                capi.CertificateSigningRequestSpec
   351  		expectedSignerName string
   352  	}{
   353  		"defaults for kube-apiserver-client-kubelet": {
   354  			csr: capi.CertificateSigningRequestSpec{
   355  				Request: csrWithOpts(kubeletClientPEMOptions),
   356  				Usages:  kubeletClientUsages,
   357  			},
   358  			expectedSignerName: capi.KubeAPIServerClientKubeletSignerName,
   359  		},
   360  		"does not default to kube-apiserver-client-kubelet if org is not 'system:nodes'": {
   361  			csr: capi.CertificateSigningRequestSpec{
   362  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{org: "not-system:nodes"}),
   363  				Usages:  kubeletClientUsages,
   364  			},
   365  			expectedSignerName: capi.LegacyUnknownSignerName,
   366  		},
   367  		"does not default to kube-apiserver-client-kubelet if a dnsName is set": {
   368  			csr: capi.CertificateSigningRequestSpec{
   369  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{dnsNames: []string{"something"}}),
   370  				Usages:  kubeletClientUsages,
   371  			},
   372  			expectedSignerName: capi.LegacyUnknownSignerName,
   373  		},
   374  		"does not default to kube-apiserver-client-kubelet if an emailAddress is set": {
   375  			csr: capi.CertificateSigningRequestSpec{
   376  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{emailAddresses: []string{"something"}}),
   377  				Usages:  kubeletClientUsages,
   378  			},
   379  			expectedSignerName: capi.LegacyUnknownSignerName,
   380  		},
   381  		"does not default to kube-apiserver-client-kubelet if a uri SAN is set": {
   382  			csr: capi.CertificateSigningRequestSpec{
   383  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{uris: []string{"http://something"}}),
   384  				Usages:  kubeletClientUsages,
   385  			},
   386  			expectedSignerName: capi.LegacyUnknownSignerName,
   387  		},
   388  		"does not default to kube-apiserver-client-kubelet if an ipAddress is set": {
   389  			csr: capi.CertificateSigningRequestSpec{
   390  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{ipAddresses: []net.IP{{0, 0, 0, 0}}}),
   391  				Usages:  kubeletClientUsages,
   392  			},
   393  			expectedSignerName: capi.LegacyUnknownSignerName,
   394  		},
   395  		"does not default to kube-apiserver-client-kubelet if CN does not have 'system:node:' prefix": {
   396  			csr: capi.CertificateSigningRequestSpec{
   397  				Request: csrWithOpts(kubeletClientPEMOptions, pemOptions{cn: "not-prefixed"}),
   398  				Usages:  kubeletClientUsages,
   399  			},
   400  			expectedSignerName: capi.LegacyUnknownSignerName,
   401  		},
   402  		"does not default to kube-apiserver-client-kubelet if it has an unexpected usage": {
   403  			csr: capi.CertificateSigningRequestSpec{
   404  				Request: csrWithOpts(kubeletClientPEMOptions),
   405  				Usages:  append(kubeletClientUsages, capi.UsageServerAuth),
   406  			},
   407  			expectedSignerName: capi.LegacyUnknownSignerName,
   408  		},
   409  		"does not default to kube-apiserver-client-kubelet if it is missing an expected usage": {
   410  			csr: capi.CertificateSigningRequestSpec{
   411  				Request: csrWithOpts(kubeletClientPEMOptions),
   412  				// Remove the first usage in 'kubeletClientUsages'
   413  				Usages: kubeletClientUsages[1:],
   414  			},
   415  			expectedSignerName: capi.LegacyUnknownSignerName,
   416  		},
   417  	}
   418  	for name, test := range tests {
   419  		t.Run(name, func(t *testing.T) {
   420  			// create a deepcopy to be sure we don't modify anything in-place
   421  			csrSpec := test.csr.DeepCopy()
   422  			SetDefaults_CertificateSigningRequestSpec(csrSpec)
   423  			if *csrSpec.SignerName != test.expectedSignerName {
   424  				t.Errorf("expected signerName to be defaulted to %q but it is %q", test.expectedSignerName, *csrSpec.SignerName)
   425  			}
   426  		})
   427  	}
   428  }
   429  
   430  type pemOptions struct {
   431  	cn             string
   432  	org            string
   433  	ipAddresses    []net.IP
   434  	dnsNames       []string
   435  	emailAddresses []string
   436  	uris           []string
   437  }
   438  
   439  // overlayPEMOptions overlays one set of pemOptions on top of another to allow
   440  // for easily overriding a single field in the options
   441  func overlayPEMOptions(opts ...pemOptions) pemOptions {
   442  	if len(opts) == 0 {
   443  		return pemOptions{}
   444  	}
   445  	base := opts[0]
   446  	for _, opt := range opts[1:] {
   447  		if opt.cn != "" {
   448  			base.cn = opt.cn
   449  		}
   450  		if opt.org != "" {
   451  			base.org = opt.org
   452  		}
   453  		if opt.ipAddresses != nil {
   454  			base.ipAddresses = opt.ipAddresses
   455  		}
   456  		if opt.dnsNames != nil {
   457  			base.dnsNames = opt.dnsNames
   458  		}
   459  		if opt.emailAddresses != nil {
   460  			base.emailAddresses = opt.emailAddresses
   461  		}
   462  		if opt.uris != nil {
   463  			base.uris = opt.uris
   464  		}
   465  	}
   466  	return base
   467  }
   468  
   469  func csrWithOpts(base pemOptions, overlays ...pemOptions) []byte {
   470  	opts := overlayPEMOptions(append([]pemOptions{base}, overlays...)...)
   471  	uris := make([]*url.URL, len(opts.uris))
   472  	for i, s := range opts.uris {
   473  		u, err := url.ParseRequestURI(s)
   474  		if err != nil {
   475  			panic(err)
   476  		}
   477  		uris[i] = u
   478  	}
   479  	template := &x509.CertificateRequest{
   480  		Subject: pkix.Name{
   481  			CommonName:   opts.cn,
   482  			Organization: []string{opts.org},
   483  		},
   484  		IPAddresses:    opts.ipAddresses,
   485  		DNSNames:       opts.dnsNames,
   486  		EmailAddresses: opts.emailAddresses,
   487  		URIs:           uris,
   488  	}
   489  
   490  	_, key, err := ed25519.GenerateKey(rand.Reader)
   491  	if err != nil {
   492  		panic(err)
   493  	}
   494  
   495  	csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key)
   496  	if err != nil {
   497  		panic(err)
   498  	}
   499  
   500  	csrPemBlock := &pem.Block{
   501  		Type:  "CERTIFICATE REQUEST",
   502  		Bytes: csrDER,
   503  	}
   504  
   505  	p := pem.EncodeToMemory(csrPemBlock)
   506  	if p == nil {
   507  		panic("invalid pem block")
   508  	}
   509  
   510  	return p
   511  }