k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/certificates/validation/validation_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 validation
    18  
    19  import (
    20  	"crypto/ed25519"
    21  	"crypto/rand"
    22  	"crypto/x509"
    23  	"crypto/x509/pkix"
    24  	"encoding/pem"
    25  	"fmt"
    26  	"math/big"
    27  	mathrand "math/rand"
    28  	"reflect"
    29  	"regexp"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/google/go-cmp/cmp"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/apimachinery/pkg/util/validation/field"
    38  	"k8s.io/client-go/util/certificate/csr"
    39  	capi "k8s.io/kubernetes/pkg/apis/certificates"
    40  	"k8s.io/kubernetes/pkg/apis/core"
    41  	"k8s.io/utils/pointer"
    42  )
    43  
    44  var (
    45  	validObjectMeta = metav1.ObjectMeta{Name: "testcsr"}
    46  	validSignerName = "example.com/valid-name"
    47  	validUsages     = []capi.KeyUsage{capi.UsageKeyEncipherment}
    48  )
    49  
    50  func TestValidateCertificateSigningRequestCreate(t *testing.T) {
    51  	specPath := field.NewPath("spec")
    52  	// maxLengthSignerName is a signerName that is of maximum length, utilising
    53  	// the max length specifications defined in validation.go.
    54  	// It is of the form <fqdn(253)>/<resource-namespace(63)>.<resource-name(253)>
    55  	maxLengthFQDN := fmt.Sprintf("%s.%s.%s.%s", repeatString("a", 63), repeatString("a", 63), repeatString("a", 63), repeatString("a", 61))
    56  	maxLengthSignerName := fmt.Sprintf("%s/%s.%s", maxLengthFQDN, repeatString("a", 63), repeatString("a", 253))
    57  	tests := map[string]struct {
    58  		csr  capi.CertificateSigningRequest
    59  		errs field.ErrorList
    60  	}{
    61  		"CSR with empty request data should fail": {
    62  			csr: capi.CertificateSigningRequest{
    63  				ObjectMeta: validObjectMeta,
    64  				Spec: capi.CertificateSigningRequestSpec{
    65  					Usages:     validUsages,
    66  					SignerName: validSignerName,
    67  				},
    68  			},
    69  			errs: field.ErrorList{
    70  				field.Invalid(specPath.Child("request"), []byte(nil), "PEM block type must be CERTIFICATE REQUEST"),
    71  			},
    72  		},
    73  		"CSR with invalid request data should fail": {
    74  			csr: capi.CertificateSigningRequest{
    75  				ObjectMeta: validObjectMeta,
    76  				Spec: capi.CertificateSigningRequestSpec{
    77  					Usages:     validUsages,
    78  					SignerName: validSignerName,
    79  					Request:    []byte("invalid data"),
    80  				},
    81  			},
    82  			errs: field.ErrorList{
    83  				field.Invalid(specPath.Child("request"), []byte("invalid data"), "PEM block type must be CERTIFICATE REQUEST"),
    84  			},
    85  		},
    86  		"CSR with no usages should fail": {
    87  			csr: capi.CertificateSigningRequest{
    88  				ObjectMeta: validObjectMeta,
    89  				Spec: capi.CertificateSigningRequestSpec{
    90  					SignerName: validSignerName,
    91  					Request:    newCSRPEM(t),
    92  				},
    93  			},
    94  			errs: field.ErrorList{
    95  				field.Required(specPath.Child("usages"), ""),
    96  			},
    97  		},
    98  		"CSR with no signerName set should fail": {
    99  			csr: capi.CertificateSigningRequest{
   100  				ObjectMeta: validObjectMeta,
   101  				Spec: capi.CertificateSigningRequestSpec{
   102  					Usages:  validUsages,
   103  					Request: newCSRPEM(t),
   104  				},
   105  			},
   106  			errs: field.ErrorList{
   107  				field.Required(specPath.Child("signerName"), ""),
   108  			},
   109  		},
   110  		"signerName contains no '/'": {
   111  			csr: capi.CertificateSigningRequest{
   112  				ObjectMeta: validObjectMeta,
   113  				Spec: capi.CertificateSigningRequestSpec{
   114  					Usages:     validUsages,
   115  					Request:    newCSRPEM(t),
   116  					SignerName: "an-invalid-signer-name",
   117  				},
   118  			},
   119  			errs: field.ErrorList{
   120  				field.Invalid(specPath.Child("signerName"), "an-invalid-signer-name", "must be a fully qualified domain and path of the form 'example.com/signer-name'"),
   121  			},
   122  		},
   123  		"signerName contains two '/'": {
   124  			csr: capi.CertificateSigningRequest{
   125  				ObjectMeta: validObjectMeta,
   126  				Spec: capi.CertificateSigningRequestSpec{
   127  					Usages:     validUsages,
   128  					Request:    newCSRPEM(t),
   129  					SignerName: "an-invalid-signer-name.com/something/else",
   130  				},
   131  			},
   132  			errs: field.ErrorList{
   133  				field.Invalid(specPath.Child("signerName"), "an-invalid-signer-name.com/something/else", "must be a fully qualified domain and path of the form 'example.com/signer-name'"),
   134  			},
   135  		},
   136  		"signerName domain component is not fully qualified": {
   137  			csr: capi.CertificateSigningRequest{
   138  				ObjectMeta: validObjectMeta,
   139  				Spec: capi.CertificateSigningRequestSpec{
   140  					Usages:     validUsages,
   141  					Request:    newCSRPEM(t),
   142  					SignerName: "example/some-signer-name",
   143  				},
   144  			},
   145  			errs: field.ErrorList{
   146  				field.Invalid(specPath.Child("signerName"), "example", "should be a domain with at least two segments separated by dots"),
   147  			},
   148  		},
   149  		"signerName path component is empty": {
   150  			csr: capi.CertificateSigningRequest{
   151  				ObjectMeta: validObjectMeta,
   152  				Spec: capi.CertificateSigningRequestSpec{
   153  					Usages:     validUsages,
   154  					Request:    newCSRPEM(t),
   155  					SignerName: "example.com/",
   156  				},
   157  			},
   158  			errs: field.ErrorList{
   159  				field.Invalid(specPath.Child("signerName"), "", `validating label "": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
   160  			},
   161  		},
   162  		"signerName path component ends with a symbol": {
   163  			csr: capi.CertificateSigningRequest{
   164  				ObjectMeta: validObjectMeta,
   165  				Spec: capi.CertificateSigningRequestSpec{
   166  					Usages:     validUsages,
   167  					Request:    newCSRPEM(t),
   168  					SignerName: "example.com/something-",
   169  				},
   170  			},
   171  			errs: field.ErrorList{
   172  				field.Invalid(specPath.Child("signerName"), "something-", `validating label "something-": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
   173  			},
   174  		},
   175  		"signerName path component is a symbol": {
   176  			csr: capi.CertificateSigningRequest{
   177  				ObjectMeta: validObjectMeta,
   178  				Spec: capi.CertificateSigningRequestSpec{
   179  					Usages:     validUsages,
   180  					Request:    newCSRPEM(t),
   181  					SignerName: "example.com/-",
   182  				},
   183  			},
   184  			errs: field.ErrorList{
   185  				field.Invalid(specPath.Child("signerName"), "-", `validating label "-": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
   186  			},
   187  		},
   188  		"signerName path component contains no '.' but is valid": {
   189  			csr: capi.CertificateSigningRequest{
   190  				ObjectMeta: validObjectMeta,
   191  				Spec: capi.CertificateSigningRequestSpec{
   192  					Usages:     validUsages,
   193  					Request:    newCSRPEM(t),
   194  					SignerName: "example.com/some-signer-name",
   195  				},
   196  			},
   197  		},
   198  		"signerName with a total length greater than 571 characters should be rejected": {
   199  			csr: capi.CertificateSigningRequest{
   200  				ObjectMeta: validObjectMeta,
   201  				Spec: capi.CertificateSigningRequestSpec{
   202  					Usages:  validUsages,
   203  					Request: newCSRPEM(t),
   204  					// this string is longer than the max signerName limit (635 chars)
   205  					SignerName: maxLengthSignerName + ".toolong",
   206  				},
   207  			},
   208  			errs: field.ErrorList{
   209  				field.TooLong(specPath.Child("signerName"), maxLengthSignerName+".toolong", len(maxLengthSignerName)),
   210  			},
   211  		},
   212  		"signerName with a fqdn greater than 253 characters should be rejected": {
   213  			csr: capi.CertificateSigningRequest{
   214  				ObjectMeta: validObjectMeta,
   215  				Spec: capi.CertificateSigningRequestSpec{
   216  					Usages:  validUsages,
   217  					Request: newCSRPEM(t),
   218  					// this string is longer than the max signerName limit (635 chars)
   219  					SignerName: fmt.Sprintf("%s.extra/valid-path", maxLengthFQDN),
   220  				},
   221  			},
   222  			errs: field.ErrorList{
   223  				field.TooLong(specPath.Child("signerName"), fmt.Sprintf("%s.extra", maxLengthFQDN), len(maxLengthFQDN)),
   224  			},
   225  		},
   226  		"signerName can have a longer path if the domain component is less than the max length": {
   227  			csr: capi.CertificateSigningRequest{
   228  				ObjectMeta: validObjectMeta,
   229  				Spec: capi.CertificateSigningRequestSpec{
   230  					Usages:     validUsages,
   231  					Request:    newCSRPEM(t),
   232  					SignerName: fmt.Sprintf("abc.io/%s.%s", repeatString("a", 253), repeatString("a", 253)),
   233  				},
   234  			},
   235  		},
   236  		"signerName with a domain label greater than 63 characters will fail": {
   237  			csr: capi.CertificateSigningRequest{
   238  				ObjectMeta: validObjectMeta,
   239  				Spec: capi.CertificateSigningRequestSpec{
   240  					Usages:     validUsages,
   241  					Request:    newCSRPEM(t),
   242  					SignerName: fmt.Sprintf("%s.example.io/valid-path", repeatString("a", 66)),
   243  				},
   244  			},
   245  			errs: field.ErrorList{
   246  				field.Invalid(specPath.Child("signerName"), fmt.Sprintf("%s.example.io", repeatString("a", 66)), fmt.Sprintf(`validating label "%s": must be no more than 63 characters`, repeatString("a", 66))),
   247  			},
   248  		},
   249  		"signerName of max length in format <fully-qualified-domain-name>/<resource-namespace>.<resource-name> is valid": {
   250  			// ensure signerName is of the form domain.com/something and up to 571 characters.
   251  			// This length and format is specified to accommodate signerNames like:
   252  			// <fqdn>/<resource-namespace>.<resource-name>.
   253  			// The max length of a FQDN is 253 characters (DNS1123Subdomain max length)
   254  			// The max length of a namespace name is 63 characters (DNS1123Label max length)
   255  			// The max length of a resource name is 253 characters (DNS1123Subdomain max length)
   256  			// We then add an additional 2 characters to account for the one '.' and one '/'.
   257  			csr: capi.CertificateSigningRequest{
   258  				ObjectMeta: validObjectMeta,
   259  				Spec: capi.CertificateSigningRequestSpec{
   260  					Usages:     validUsages,
   261  					Request:    newCSRPEM(t),
   262  					SignerName: maxLengthSignerName,
   263  				},
   264  			},
   265  		},
   266  		"negative duration": {
   267  			csr: capi.CertificateSigningRequest{
   268  				ObjectMeta: validObjectMeta,
   269  				Spec: capi.CertificateSigningRequestSpec{
   270  					Usages:            validUsages,
   271  					Request:           newCSRPEM(t),
   272  					SignerName:        validSignerName,
   273  					ExpirationSeconds: pointer.Int32(-1),
   274  				},
   275  			},
   276  			errs: field.ErrorList{
   277  				field.Invalid(specPath.Child("expirationSeconds"), int32(-1), "may not specify a duration less than 600 seconds (10 minutes)"),
   278  			},
   279  		},
   280  		"zero duration": {
   281  			csr: capi.CertificateSigningRequest{
   282  				ObjectMeta: validObjectMeta,
   283  				Spec: capi.CertificateSigningRequestSpec{
   284  					Usages:            validUsages,
   285  					Request:           newCSRPEM(t),
   286  					SignerName:        validSignerName,
   287  					ExpirationSeconds: pointer.Int32(0),
   288  				},
   289  			},
   290  			errs: field.ErrorList{
   291  				field.Invalid(specPath.Child("expirationSeconds"), int32(0), "may not specify a duration less than 600 seconds (10 minutes)"),
   292  			},
   293  		},
   294  		"one duration": {
   295  			csr: capi.CertificateSigningRequest{
   296  				ObjectMeta: validObjectMeta,
   297  				Spec: capi.CertificateSigningRequestSpec{
   298  					Usages:            validUsages,
   299  					Request:           newCSRPEM(t),
   300  					SignerName:        validSignerName,
   301  					ExpirationSeconds: pointer.Int32(1),
   302  				},
   303  			},
   304  			errs: field.ErrorList{
   305  				field.Invalid(specPath.Child("expirationSeconds"), int32(1), "may not specify a duration less than 600 seconds (10 minutes)"),
   306  			},
   307  		},
   308  		"too short duration": {
   309  			csr: capi.CertificateSigningRequest{
   310  				ObjectMeta: validObjectMeta,
   311  				Spec: capi.CertificateSigningRequestSpec{
   312  					Usages:            validUsages,
   313  					Request:           newCSRPEM(t),
   314  					SignerName:        validSignerName,
   315  					ExpirationSeconds: csr.DurationToExpirationSeconds(time.Minute),
   316  				},
   317  			},
   318  			errs: field.ErrorList{
   319  				field.Invalid(specPath.Child("expirationSeconds"), *csr.DurationToExpirationSeconds(time.Minute), "may not specify a duration less than 600 seconds (10 minutes)"),
   320  			},
   321  		},
   322  		"valid duration": {
   323  			csr: capi.CertificateSigningRequest{
   324  				ObjectMeta: validObjectMeta,
   325  				Spec: capi.CertificateSigningRequestSpec{
   326  					Usages:            validUsages,
   327  					Request:           newCSRPEM(t),
   328  					SignerName:        validSignerName,
   329  					ExpirationSeconds: csr.DurationToExpirationSeconds(10 * time.Minute),
   330  				},
   331  			},
   332  		},
   333  		"missing usages": {
   334  			csr: capi.CertificateSigningRequest{
   335  				ObjectMeta: validObjectMeta,
   336  				Spec: capi.CertificateSigningRequestSpec{
   337  					Usages:     []capi.KeyUsage{},
   338  					Request:    newCSRPEM(t),
   339  					SignerName: validSignerName,
   340  				},
   341  			},
   342  			errs: field.ErrorList{
   343  				field.Required(specPath.Child("usages"), ""),
   344  			},
   345  		},
   346  		"unknown and duplicate usages": {
   347  			csr: capi.CertificateSigningRequest{
   348  				ObjectMeta: validObjectMeta,
   349  				Spec: capi.CertificateSigningRequestSpec{
   350  					Usages:     []capi.KeyUsage{"unknown", "unknown"},
   351  					Request:    newCSRPEM(t),
   352  					SignerName: validSignerName,
   353  				},
   354  			},
   355  			errs: field.ErrorList{
   356  				field.NotSupported(specPath.Child("usages").Index(0), capi.KeyUsage("unknown"), allValidUsages.List()),
   357  				field.NotSupported(specPath.Child("usages").Index(1), capi.KeyUsage("unknown"), allValidUsages.List()),
   358  				field.Duplicate(specPath.Child("usages").Index(1), capi.KeyUsage("unknown")),
   359  			},
   360  		},
   361  	}
   362  	for name, test := range tests {
   363  		t.Run(name, func(t *testing.T) {
   364  			el := ValidateCertificateSigningRequestCreate(&test.csr)
   365  			if !reflect.DeepEqual(el, test.errs) {
   366  				t.Errorf("returned and expected errors did not match - expected\n%v\nbut got\n%v", test.errs.ToAggregate(), el.ToAggregate())
   367  			}
   368  		})
   369  	}
   370  }
   371  
   372  func repeatString(s string, num int) string {
   373  	l := make([]string, num)
   374  	for i := 0; i < num; i++ {
   375  		l[i] = s
   376  	}
   377  	return strings.Join(l, "")
   378  }
   379  
   380  func newCSRPEM(t *testing.T) []byte {
   381  	template := &x509.CertificateRequest{
   382  		Subject: pkix.Name{
   383  			Organization: []string{"testing-org"},
   384  		},
   385  	}
   386  
   387  	_, key, err := ed25519.GenerateKey(rand.Reader)
   388  	if err != nil {
   389  		t.Fatal(err)
   390  	}
   391  
   392  	csrDER, err := x509.CreateCertificateRequest(rand.Reader, template, key)
   393  	if err != nil {
   394  		t.Fatal(err)
   395  	}
   396  
   397  	csrPemBlock := &pem.Block{
   398  		Type:  "CERTIFICATE REQUEST",
   399  		Bytes: csrDER,
   400  	}
   401  
   402  	p := pem.EncodeToMemory(csrPemBlock)
   403  	if p == nil {
   404  		t.Fatal("invalid pem block")
   405  	}
   406  
   407  	return p
   408  }
   409  
   410  func Test_getValidationOptions(t *testing.T) {
   411  	tests := []struct {
   412  		name   string
   413  		newCSR *capi.CertificateSigningRequest
   414  		oldCSR *capi.CertificateSigningRequest
   415  		want   certificateValidationOptions
   416  	}{{
   417  		name:   "strict create",
   418  		oldCSR: nil,
   419  		want:   certificateValidationOptions{},
   420  	}, {
   421  		name:   "strict update",
   422  		oldCSR: &capi.CertificateSigningRequest{},
   423  		want:   certificateValidationOptions{},
   424  	}, {
   425  		name: "compatible update, approved+denied",
   426  		oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   427  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateDenied}},
   428  		}},
   429  		want: certificateValidationOptions{
   430  			allowBothApprovedAndDenied: true,
   431  		},
   432  	}, {
   433  		name:   "compatible update, legacy signerName",
   434  		oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{SignerName: capi.LegacyUnknownSignerName}},
   435  		want: certificateValidationOptions{
   436  			allowLegacySignerName: true,
   437  		},
   438  	}, {
   439  		name: "compatible update, duplicate condition types",
   440  		oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   441  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved}, {Type: capi.CertificateApproved}},
   442  		}},
   443  		want: certificateValidationOptions{
   444  			allowDuplicateConditionTypes: true,
   445  		},
   446  	}, {
   447  		name: "compatible update, empty condition types",
   448  		oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   449  			Conditions: []capi.CertificateSigningRequestCondition{{}},
   450  		}},
   451  		want: certificateValidationOptions{
   452  			allowEmptyConditionType: true,
   453  		},
   454  	}, {
   455  		name: "compatible update, no diff to certificate",
   456  		newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   457  			Certificate: validCertificate,
   458  		}},
   459  		oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   460  			Certificate: validCertificate,
   461  		}},
   462  		want: certificateValidationOptions{
   463  			allowArbitraryCertificate: true,
   464  		},
   465  	}, {
   466  		name: "compatible update, existing invalid certificate",
   467  		newCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   468  			Certificate: []byte(`new - no PEM blocks`),
   469  		}},
   470  		oldCSR: &capi.CertificateSigningRequest{Status: capi.CertificateSigningRequestStatus{
   471  			Certificate: []byte(`old - no PEM blocks`),
   472  		}},
   473  		want: certificateValidationOptions{
   474  			allowArbitraryCertificate: true,
   475  		},
   476  	}, {
   477  		name:   "compatible update, existing unknown usages",
   478  		oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"unknown"}}},
   479  		want: certificateValidationOptions{
   480  			allowUnknownUsages: true,
   481  		},
   482  	}, {
   483  		name:   "compatible update, existing duplicate usages",
   484  		oldCSR: &capi.CertificateSigningRequest{Spec: capi.CertificateSigningRequestSpec{Usages: []capi.KeyUsage{"any", "any"}}},
   485  		want: certificateValidationOptions{
   486  			allowDuplicateUsages: true,
   487  		},
   488  	}}
   489  	for _, tt := range tests {
   490  		t.Run(tt.name, func(t *testing.T) {
   491  			if got := getValidationOptions(tt.newCSR, tt.oldCSR); !reflect.DeepEqual(got, tt.want) {
   492  				t.Errorf("got  %#v\nwant %#v", got, tt.want)
   493  			}
   494  		})
   495  	}
   496  }
   497  
   498  func TestValidateCertificateSigningRequestUpdate(t *testing.T) {
   499  	validUpdateMeta := validObjectMeta
   500  	validUpdateMeta.ResourceVersion = "1"
   501  
   502  	validUpdateMetaWithFinalizers := validUpdateMeta
   503  	validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
   504  
   505  	validSpec := capi.CertificateSigningRequestSpec{
   506  		Usages:     validUsages,
   507  		Request:    newCSRPEM(t),
   508  		SignerName: "example.com/something",
   509  	}
   510  
   511  	tests := []struct {
   512  		name   string
   513  		newCSR *capi.CertificateSigningRequest
   514  		oldCSR *capi.CertificateSigningRequest
   515  		errs   []string
   516  	}{{
   517  		name:   "no-op",
   518  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   519  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   520  	}, {
   521  		name:   "finalizer change with invalid status",
   522  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   523  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   524  	}, {
   525  		name: "add Approved condition",
   526  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   527  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   528  		}},
   529  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   530  		errs: []string{
   531  			`status.conditions: Forbidden: updates may not add a condition of type "Approved"`,
   532  		},
   533  	}, {
   534  		name:   "remove Approved condition",
   535  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   536  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   537  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   538  		}},
   539  		errs: []string{
   540  			`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`,
   541  		},
   542  	}, {
   543  		name: "add Denied condition",
   544  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   545  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   546  		}},
   547  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   548  		errs: []string{
   549  			`status.conditions: Forbidden: updates may not add a condition of type "Denied"`,
   550  		},
   551  	}, {
   552  		name:   "remove Denied condition",
   553  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   554  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   555  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   556  		}},
   557  		errs: []string{
   558  			`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`,
   559  		},
   560  	}, {
   561  		name: "add Failed condition",
   562  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   563  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   564  		}},
   565  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   566  		errs:   []string{},
   567  	}, {
   568  		name:   "remove Failed condition",
   569  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   570  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   571  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   572  		}},
   573  		errs: []string{
   574  			`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`,
   575  		},
   576  	}, {
   577  		name: "set certificate",
   578  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   579  			Certificate: validCertificate,
   580  		}},
   581  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   582  		errs: []string{
   583  			`status.certificate: Forbidden: updates may not set certificate content`,
   584  		},
   585  	}}
   586  
   587  	for _, tt := range tests {
   588  		t.Run(tt.name, func(t *testing.T) {
   589  			gotErrs := sets.NewString()
   590  			for _, err := range ValidateCertificateSigningRequestUpdate(tt.newCSR, tt.oldCSR) {
   591  				gotErrs.Insert(err.Error())
   592  			}
   593  			wantErrs := sets.NewString(tt.errs...)
   594  			for _, missing := range wantErrs.Difference(gotErrs).List() {
   595  				t.Errorf("missing expected error: %s", missing)
   596  			}
   597  			for _, unexpected := range gotErrs.Difference(wantErrs).List() {
   598  				t.Errorf("unexpected error: %s", unexpected)
   599  			}
   600  		})
   601  	}
   602  }
   603  
   604  func TestValidateCertificateSigningRequestStatusUpdate(t *testing.T) {
   605  	validUpdateMeta := validObjectMeta
   606  	validUpdateMeta.ResourceVersion = "1"
   607  
   608  	validUpdateMetaWithFinalizers := validUpdateMeta
   609  	validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
   610  
   611  	validSpec := capi.CertificateSigningRequestSpec{
   612  		Usages:     validUsages,
   613  		Request:    newCSRPEM(t),
   614  		SignerName: "example.com/something",
   615  	}
   616  
   617  	tests := []struct {
   618  		name   string
   619  		newCSR *capi.CertificateSigningRequest
   620  		oldCSR *capi.CertificateSigningRequest
   621  		errs   []string
   622  	}{{
   623  		name:   "no-op",
   624  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   625  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   626  	}, {
   627  		name:   "finalizer change with invalid status",
   628  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   629  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   630  	}, {
   631  		name: "finalizer change with duplicate and unknown usages",
   632  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: capi.CertificateSigningRequestSpec{
   633  			Usages:     []capi.KeyUsage{"unknown", "unknown"},
   634  			Request:    newCSRPEM(t),
   635  			SignerName: validSignerName,
   636  		}},
   637  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: capi.CertificateSigningRequestSpec{
   638  			Usages:     []capi.KeyUsage{"unknown", "unknown"},
   639  			Request:    newCSRPEM(t),
   640  			SignerName: validSignerName,
   641  		}},
   642  	}, {
   643  		name: "add Approved condition",
   644  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   645  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   646  		}},
   647  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   648  		errs: []string{
   649  			`status.conditions: Forbidden: updates may not add a condition of type "Approved"`,
   650  		},
   651  	}, {
   652  		name:   "remove Approved condition",
   653  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   654  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   655  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   656  		}},
   657  		errs: []string{
   658  			`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`,
   659  		},
   660  	}, {
   661  		name: "add Denied condition",
   662  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   663  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   664  		}},
   665  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   666  		errs: []string{
   667  			`status.conditions: Forbidden: updates may not add a condition of type "Denied"`,
   668  		},
   669  	}, {
   670  		name:   "remove Denied condition",
   671  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   672  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   673  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   674  		}},
   675  		errs: []string{
   676  			`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`,
   677  		},
   678  	}, {
   679  		name: "add Failed condition",
   680  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   681  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   682  		}},
   683  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   684  		errs:   []string{},
   685  	}, {
   686  		name:   "remove Failed condition",
   687  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   688  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   689  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   690  		}},
   691  		errs: []string{
   692  			`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`,
   693  		},
   694  	}, {
   695  		name: "set valid certificate",
   696  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   697  			Certificate: validCertificate,
   698  		}},
   699  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   700  		errs:   []string{},
   701  	}, {
   702  		name: "set invalid certificate",
   703  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   704  			Certificate: invalidCertificateNoPEM,
   705  		}},
   706  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   707  		errs: []string{
   708  			`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`,
   709  		},
   710  	}, {
   711  		name: "reset certificate",
   712  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   713  			Certificate: invalidCertificateNonCertificatePEM,
   714  		}},
   715  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   716  			Certificate: invalidCertificateNoPEM,
   717  		}},
   718  		errs: []string{
   719  			`status.certificate: Forbidden: updates may not modify existing certificate content`,
   720  		},
   721  	}}
   722  
   723  	for _, tt := range tests {
   724  		t.Run(tt.name, func(t *testing.T) {
   725  			gotErrs := sets.NewString()
   726  			for _, err := range ValidateCertificateSigningRequestStatusUpdate(tt.newCSR, tt.oldCSR) {
   727  				gotErrs.Insert(err.Error())
   728  			}
   729  			wantErrs := sets.NewString(tt.errs...)
   730  			for _, missing := range wantErrs.Difference(gotErrs).List() {
   731  				t.Errorf("missing expected error: %s", missing)
   732  			}
   733  			for _, unexpected := range gotErrs.Difference(wantErrs).List() {
   734  				t.Errorf("unexpected error: %s", unexpected)
   735  			}
   736  		})
   737  	}
   738  }
   739  
   740  func TestValidateCertificateSigningRequestApprovalUpdate(t *testing.T) {
   741  	validUpdateMeta := validObjectMeta
   742  	validUpdateMeta.ResourceVersion = "1"
   743  
   744  	validUpdateMetaWithFinalizers := validUpdateMeta
   745  	validUpdateMetaWithFinalizers.Finalizers = []string{"foo"}
   746  
   747  	validSpec := capi.CertificateSigningRequestSpec{
   748  		Usages:     validUsages,
   749  		Request:    newCSRPEM(t),
   750  		SignerName: "example.com/something",
   751  	}
   752  
   753  	tests := []struct {
   754  		name   string
   755  		newCSR *capi.CertificateSigningRequest
   756  		oldCSR *capi.CertificateSigningRequest
   757  		errs   []string
   758  	}{{
   759  		name:   "no-op",
   760  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   761  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   762  	}, {
   763  		name:   "finalizer change with invalid certificate",
   764  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   765  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{Certificate: invalidCertificateNoPEM}},
   766  	}, {
   767  		name: "add Approved condition",
   768  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   769  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   770  		}},
   771  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   772  	}, {
   773  		name:   "remove Approved condition",
   774  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   775  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   776  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   777  		}},
   778  		errs: []string{
   779  			`status.conditions: Forbidden: updates may not remove a condition of type "Approved"`,
   780  		},
   781  	}, {
   782  		name: "add Denied condition",
   783  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   784  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   785  		}},
   786  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   787  	}, {
   788  		name:   "remove Denied condition",
   789  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   790  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   791  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   792  		}},
   793  		errs: []string{
   794  			`status.conditions: Forbidden: updates may not remove a condition of type "Denied"`,
   795  		},
   796  	}, {
   797  		name: "add Failed condition",
   798  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   799  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   800  		}},
   801  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   802  		errs:   []string{},
   803  	}, {
   804  		name:   "remove Failed condition",
   805  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec},
   806  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   807  			Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   808  		}},
   809  		errs: []string{
   810  			`status.conditions: Forbidden: updates may not remove a condition of type "Failed"`,
   811  		},
   812  	}, {
   813  		name: "set certificate",
   814  		newCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMeta, Spec: validSpec, Status: capi.CertificateSigningRequestStatus{
   815  			Certificate: validCertificate,
   816  		}},
   817  		oldCSR: &capi.CertificateSigningRequest{ObjectMeta: validUpdateMetaWithFinalizers, Spec: validSpec},
   818  		errs: []string{
   819  			`status.certificate: Forbidden: updates may not set certificate content`,
   820  		},
   821  	}}
   822  
   823  	for _, tt := range tests {
   824  		t.Run(tt.name, func(t *testing.T) {
   825  			gotErrs := sets.NewString()
   826  			for _, err := range ValidateCertificateSigningRequestApprovalUpdate(tt.newCSR, tt.oldCSR) {
   827  				gotErrs.Insert(err.Error())
   828  			}
   829  			wantErrs := sets.NewString(tt.errs...)
   830  			for _, missing := range wantErrs.Difference(gotErrs).List() {
   831  				t.Errorf("missing expected error: %s", missing)
   832  			}
   833  			for _, unexpected := range gotErrs.Difference(wantErrs).List() {
   834  				t.Errorf("unexpected error: %s", unexpected)
   835  			}
   836  		})
   837  	}
   838  }
   839  
   840  // Test_validateCertificateSigningRequestOptions verifies validation options are effective in tolerating specific aspects of CSRs
   841  func Test_validateCertificateSigningRequestOptions(t *testing.T) {
   842  	validSpec := capi.CertificateSigningRequestSpec{
   843  		Usages:     validUsages,
   844  		Request:    newCSRPEM(t),
   845  		SignerName: "example.com/something",
   846  	}
   847  
   848  	tests := []struct {
   849  		// testcase name
   850  		name string
   851  
   852  		// csr being validated
   853  		csr *capi.CertificateSigningRequest
   854  
   855  		// options that allow the csr to pass validation
   856  		lenientOpts certificateValidationOptions
   857  
   858  		// regexes matching expected errors when validating strictly
   859  		strictRegexes []regexp.Regexp
   860  
   861  		// expected errors (after filtering out errors matched by strictRegexes) when validating strictly
   862  		strictErrs []string
   863  	}{
   864  		// valid strict cases
   865  		{
   866  			name: "no status",
   867  			csr:  &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec},
   868  		}, {
   869  			name: "approved condition",
   870  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   871  				Status: capi.CertificateSigningRequestStatus{
   872  					Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   873  				},
   874  			},
   875  		}, {
   876  			name: "denied condition",
   877  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   878  				Status: capi.CertificateSigningRequestStatus{
   879  					Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   880  				},
   881  			},
   882  		}, {
   883  			name: "failed condition",
   884  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   885  				Status: capi.CertificateSigningRequestStatus{
   886  					Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateFailed, Status: core.ConditionTrue}},
   887  				},
   888  			},
   889  		}, {
   890  			name: "approved+issued",
   891  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   892  				Status: capi.CertificateSigningRequestStatus{
   893  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   894  					Certificate: validCertificate,
   895  				},
   896  			},
   897  		},
   898  
   899  		// legacy signer
   900  		{
   901  			name: "legacy signer",
   902  			csr: &capi.CertificateSigningRequest{
   903  				ObjectMeta: validObjectMeta,
   904  				Spec: func() capi.CertificateSigningRequestSpec {
   905  					specCopy := validSpec
   906  					specCopy.SignerName = capi.LegacyUnknownSignerName
   907  					return specCopy
   908  				}(),
   909  			},
   910  			lenientOpts: certificateValidationOptions{allowLegacySignerName: true},
   911  			strictErrs:  []string{`spec.signerName: Invalid value: "kubernetes.io/legacy-unknown": the legacy signerName is not allowed via this API version`},
   912  		},
   913  
   914  		// invalid condition cases
   915  		{
   916  			name: "empty condition type",
   917  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   918  				Status: capi.CertificateSigningRequestStatus{
   919  					Conditions: []capi.CertificateSigningRequestCondition{{Status: core.ConditionTrue}},
   920  				},
   921  			},
   922  			lenientOpts: certificateValidationOptions{allowEmptyConditionType: true},
   923  			strictErrs:  []string{`status.conditions[0].type: Required value`},
   924  		}, {
   925  			name: "approved and denied",
   926  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   927  				Status: capi.CertificateSigningRequestStatus{
   928  					Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateDenied, Status: core.ConditionTrue}},
   929  				},
   930  			},
   931  			lenientOpts: certificateValidationOptions{allowBothApprovedAndDenied: true},
   932  			strictErrs:  []string{`status.conditions[1].type: Invalid value: "Denied": Approved and Denied conditions are mutually exclusive`},
   933  		}, {
   934  			name: "duplicate condition",
   935  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   936  				Status: capi.CertificateSigningRequestStatus{
   937  					Conditions: []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}, {Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   938  				},
   939  			},
   940  			lenientOpts: certificateValidationOptions{allowDuplicateConditionTypes: true},
   941  			strictErrs:  []string{`status.conditions[1].type: Duplicate value: "Approved"`},
   942  		},
   943  
   944  		// invalid allowArbitraryCertificate cases
   945  		{
   946  			name: "status.certificate, no PEM",
   947  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   948  				Status: capi.CertificateSigningRequestStatus{
   949  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   950  					Certificate: invalidCertificateNoPEM,
   951  				},
   952  			},
   953  			lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
   954  			strictErrs:  []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`},
   955  		}, {
   956  			name: "status.certificate, non-CERTIFICATE PEM",
   957  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   958  				Status: capi.CertificateSigningRequestStatus{
   959  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   960  					Certificate: invalidCertificateNonCertificatePEM,
   961  				},
   962  			},
   963  			lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
   964  			strictErrs:  []string{`status.certificate: Invalid value: "<certificate data>": only CERTIFICATE PEM blocks are allowed, found "CERTIFICATE1"`},
   965  		}, {
   966  			name: "status.certificate, PEM headers",
   967  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   968  				Status: capi.CertificateSigningRequestStatus{
   969  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   970  					Certificate: invalidCertificatePEMHeaders,
   971  				},
   972  			},
   973  			lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
   974  			strictErrs:  []string{`status.certificate: Invalid value: "<certificate data>": no PEM block headers are permitted`},
   975  		}, {
   976  			name: "status.certificate, non-base64 PEM",
   977  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   978  				Status: capi.CertificateSigningRequestStatus{
   979  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   980  					Certificate: invalidCertificateNonBase64PEM,
   981  				},
   982  			},
   983  			lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
   984  			strictErrs:  []string{`status.certificate: Invalid value: "<certificate data>": must contain at least one CERTIFICATE PEM block`},
   985  		}, {
   986  			name: "status.certificate, empty PEM block",
   987  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   988  				Status: capi.CertificateSigningRequestStatus{
   989  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
   990  					Certificate: invalidCertificateEmptyPEM,
   991  				},
   992  			},
   993  			lenientOpts: certificateValidationOptions{allowArbitraryCertificate: true},
   994  			strictErrs:  []string{`status.certificate: Invalid value: "<certificate data>": found CERTIFICATE PEM block containing 0 certificates`},
   995  		}, {
   996  			name: "status.certificate, non-ASN1 data",
   997  			csr: &capi.CertificateSigningRequest{ObjectMeta: validObjectMeta, Spec: validSpec,
   998  				Status: capi.CertificateSigningRequestStatus{
   999  					Conditions:  []capi.CertificateSigningRequestCondition{{Type: capi.CertificateApproved, Status: core.ConditionTrue}},
  1000  					Certificate: invalidCertificateNonASN1Data,
  1001  				},
  1002  			},
  1003  			lenientOpts:   certificateValidationOptions{allowArbitraryCertificate: true},
  1004  			strictRegexes: []regexp.Regexp{*regexp.MustCompile(`status.certificate: Invalid value: "\<certificate data\>": (asn1: structure error: sequence tag mismatch|x509: invalid RDNSequence)`)},
  1005  		},
  1006  	}
  1007  
  1008  	for _, tt := range tests {
  1009  		t.Run(tt.name, func(t *testing.T) {
  1010  			// make sure the lenient options validate with no errors
  1011  			for _, err := range validateCertificateSigningRequest(tt.csr, tt.lenientOpts) {
  1012  				t.Errorf("unexpected error with lenient options: %s", err.Error())
  1013  			}
  1014  
  1015  			// make sure the strict options produce the expected errors
  1016  			gotErrs := sets.NewString()
  1017  			for _, err := range validateCertificateSigningRequest(tt.csr, certificateValidationOptions{}) {
  1018  				gotErrs.Insert(err.Error())
  1019  			}
  1020  
  1021  			// filter errors matching strictRegexes and ensure every strictRegex matches at least one error
  1022  			for _, expectedRegex := range tt.strictRegexes {
  1023  				matched := false
  1024  				for _, err := range gotErrs.List() {
  1025  					if expectedRegex.MatchString(err) {
  1026  						gotErrs.Delete(err)
  1027  						matched = true
  1028  					}
  1029  				}
  1030  				if !matched {
  1031  					t.Errorf("missing expected error matching: %s", expectedRegex.String())
  1032  				}
  1033  			}
  1034  
  1035  			wantErrs := sets.NewString(tt.strictErrs...)
  1036  			for _, missing := range wantErrs.Difference(gotErrs).List() {
  1037  				t.Errorf("missing expected strict error: %s", missing)
  1038  			}
  1039  			for _, unexpected := range gotErrs.Difference(wantErrs).List() {
  1040  				t.Errorf("unexpected errors: %s", unexpected)
  1041  			}
  1042  		})
  1043  	}
  1044  }
  1045  
  1046  func mustMakeCertificate(t *testing.T, template *x509.Certificate) []byte {
  1047  	gen := mathrand.New(mathrand.NewSource(12345))
  1048  
  1049  	pub, priv, err := ed25519.GenerateKey(gen)
  1050  	if err != nil {
  1051  		t.Fatalf("Error while generating key: %v", err)
  1052  	}
  1053  
  1054  	cert, err := x509.CreateCertificate(gen, template, template, pub, priv)
  1055  	if err != nil {
  1056  		t.Fatalf("Error while making certificate: %v", err)
  1057  	}
  1058  
  1059  	return cert
  1060  }
  1061  
  1062  func mustMakePEMBlock(blockType string, headers map[string]string, data []byte) string {
  1063  	return string(pem.EncodeToMemory(&pem.Block{
  1064  		Type:    blockType,
  1065  		Headers: headers,
  1066  		Bytes:   data,
  1067  	}))
  1068  }
  1069  
  1070  func TestValidateClusterTrustBundle(t *testing.T) {
  1071  	goodCert1 := mustMakeCertificate(t, &x509.Certificate{
  1072  		SerialNumber: big.NewInt(0),
  1073  		Subject: pkix.Name{
  1074  			CommonName: "root1",
  1075  		},
  1076  		IsCA:                  true,
  1077  		BasicConstraintsValid: true,
  1078  	})
  1079  
  1080  	goodCert2 := mustMakeCertificate(t, &x509.Certificate{
  1081  		SerialNumber: big.NewInt(0),
  1082  		Subject: pkix.Name{
  1083  			CommonName: "root2",
  1084  		},
  1085  		IsCA:                  true,
  1086  		BasicConstraintsValid: true,
  1087  	})
  1088  
  1089  	badNotCACert := mustMakeCertificate(t, &x509.Certificate{
  1090  		SerialNumber: big.NewInt(0),
  1091  		Subject: pkix.Name{
  1092  			CommonName: "root3",
  1093  		},
  1094  	})
  1095  
  1096  	goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1))
  1097  	goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2))
  1098  
  1099  	goodCert1AlternateBlock := strings.ReplaceAll(goodCert1Block, "\n", "\n\t\n")
  1100  
  1101  	badNotCACertBlock := string(mustMakePEMBlock("CERTIFICATE", nil, badNotCACert))
  1102  
  1103  	badBlockHeadersBlock := string(mustMakePEMBlock("CERTIFICATE", map[string]string{"key": "value"}, goodCert1))
  1104  	badBlockTypeBlock := string(mustMakePEMBlock("NOTACERTIFICATE", nil, goodCert1))
  1105  	badNonParseableBlock := string(mustMakePEMBlock("CERTIFICATE", nil, []byte("this is not a certificate")))
  1106  
  1107  	badTooBigBundle := ""
  1108  	for i := 0; i < (core.MaxSecretSize/len(goodCert1Block))+1; i++ {
  1109  		badTooBigBundle += goodCert1Block + "\n"
  1110  	}
  1111  
  1112  	testCases := []struct {
  1113  		description string
  1114  		bundle      *capi.ClusterTrustBundle
  1115  		opts        ValidateClusterTrustBundleOptions
  1116  		wantErrors  field.ErrorList
  1117  	}{
  1118  		{
  1119  			description: "valid, no signer name",
  1120  			bundle: &capi.ClusterTrustBundle{
  1121  				ObjectMeta: metav1.ObjectMeta{
  1122  					Name: "foo",
  1123  				},
  1124  				Spec: capi.ClusterTrustBundleSpec{
  1125  					TrustBundle: goodCert1Block,
  1126  				},
  1127  			},
  1128  		},
  1129  		{
  1130  			description: "invalid, too big",
  1131  			bundle: &capi.ClusterTrustBundle{
  1132  				ObjectMeta: metav1.ObjectMeta{
  1133  					Name: "foo",
  1134  				},
  1135  				Spec: capi.ClusterTrustBundleSpec{
  1136  					TrustBundle: badTooBigBundle,
  1137  				},
  1138  			},
  1139  			wantErrors: field.ErrorList{
  1140  				field.TooLong(field.NewPath("spec", "trustBundle"), fmt.Sprintf("<value omitted, len %d>", len(badTooBigBundle)), core.MaxSecretSize),
  1141  			},
  1142  		},
  1143  		{
  1144  			description: "invalid, no signer name, invalid name",
  1145  			bundle: &capi.ClusterTrustBundle{
  1146  				ObjectMeta: metav1.ObjectMeta{
  1147  					Name: "k8s.io:bar:foo",
  1148  				},
  1149  				Spec: capi.ClusterTrustBundleSpec{
  1150  					TrustBundle: goodCert1Block,
  1151  				},
  1152  			},
  1153  			wantErrors: field.ErrorList{
  1154  				field.Invalid(field.NewPath("metadata", "name"), "k8s.io:bar:foo", "ClusterTrustBundle without signer name must not have \":\" in its name"),
  1155  			},
  1156  		}, {
  1157  			description: "valid, with signer name",
  1158  			bundle: &capi.ClusterTrustBundle{
  1159  				ObjectMeta: metav1.ObjectMeta{
  1160  					Name: "k8s.io:foo:bar",
  1161  				},
  1162  				Spec: capi.ClusterTrustBundleSpec{
  1163  					SignerName:  "k8s.io/foo",
  1164  					TrustBundle: goodCert1Block,
  1165  				},
  1166  			},
  1167  		}, {
  1168  			description: "invalid, with signer name, missing name prefix",
  1169  			bundle: &capi.ClusterTrustBundle{
  1170  				ObjectMeta: metav1.ObjectMeta{
  1171  					Name: "look-ma-no-prefix",
  1172  				},
  1173  				Spec: capi.ClusterTrustBundleSpec{
  1174  					SignerName:  "k8s.io/foo",
  1175  					TrustBundle: goodCert1Block,
  1176  				},
  1177  			},
  1178  			wantErrors: field.ErrorList{
  1179  				field.Invalid(field.NewPath("metadata", "name"), "look-ma-no-prefix", "ClusterTrustBundle for signerName k8s.io/foo must be named with prefix k8s.io:foo:"),
  1180  			},
  1181  		}, {
  1182  			description: "invalid, with signer name, empty name suffix",
  1183  			bundle: &capi.ClusterTrustBundle{
  1184  				ObjectMeta: metav1.ObjectMeta{
  1185  					Name: "k8s.io:foo:",
  1186  				},
  1187  				Spec: capi.ClusterTrustBundleSpec{
  1188  					SignerName:  "k8s.io/foo",
  1189  					TrustBundle: goodCert1Block,
  1190  				},
  1191  			},
  1192  			wantErrors: field.ErrorList{
  1193  				field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
  1194  			},
  1195  		}, {
  1196  			description: "invalid, with signer name, bad name suffix",
  1197  			bundle: &capi.ClusterTrustBundle{
  1198  				ObjectMeta: metav1.ObjectMeta{
  1199  					Name: "k8s.io:foo:123notvalidDNSSubdomain",
  1200  				},
  1201  				Spec: capi.ClusterTrustBundleSpec{
  1202  					SignerName:  "k8s.io/foo",
  1203  					TrustBundle: goodCert1Block,
  1204  				},
  1205  			},
  1206  			wantErrors: field.ErrorList{
  1207  				field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:123notvalidDNSSubdomain", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`),
  1208  			},
  1209  		}, {
  1210  			description: "valid, with signer name, with inter-block garbage",
  1211  			bundle: &capi.ClusterTrustBundle{
  1212  				ObjectMeta: metav1.ObjectMeta{
  1213  					Name: "k8s.io:foo:abc",
  1214  				},
  1215  				Spec: capi.ClusterTrustBundleSpec{
  1216  					SignerName:  "k8s.io/foo",
  1217  					TrustBundle: "garbage\n" + goodCert1Block + "\ngarbage\n" + goodCert2Block,
  1218  				},
  1219  			},
  1220  		}, {
  1221  			description: "invalid, no signer name, no trust anchors",
  1222  			bundle: &capi.ClusterTrustBundle{
  1223  				ObjectMeta: metav1.ObjectMeta{
  1224  					Name: "foo",
  1225  				},
  1226  				Spec: capi.ClusterTrustBundleSpec{},
  1227  			},
  1228  			wantErrors: field.ErrorList{
  1229  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
  1230  			},
  1231  		}, {
  1232  			description: "invalid, no trust anchors",
  1233  			bundle: &capi.ClusterTrustBundle{
  1234  				ObjectMeta: metav1.ObjectMeta{
  1235  					Name: "k8s.io:foo:abc",
  1236  				},
  1237  				Spec: capi.ClusterTrustBundleSpec{
  1238  					SignerName: "k8s.io/foo",
  1239  				},
  1240  			},
  1241  			wantErrors: field.ErrorList{
  1242  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
  1243  			},
  1244  		}, {
  1245  			description: "invalid, bad signer name",
  1246  			bundle: &capi.ClusterTrustBundle{
  1247  				ObjectMeta: metav1.ObjectMeta{
  1248  					Name: "invalid:foo",
  1249  				},
  1250  				Spec: capi.ClusterTrustBundleSpec{
  1251  					SignerName:  "invalid",
  1252  					TrustBundle: goodCert1Block,
  1253  				},
  1254  			},
  1255  			wantErrors: field.ErrorList{
  1256  				field.Invalid(field.NewPath("spec", "signerName"), "invalid", "must be a fully qualified domain and path of the form 'example.com/signer-name'"),
  1257  			},
  1258  		}, {
  1259  			description: "invalid, no blocks",
  1260  			bundle: &capi.ClusterTrustBundle{
  1261  				ObjectMeta: metav1.ObjectMeta{
  1262  					Name: "foo",
  1263  				},
  1264  				Spec: capi.ClusterTrustBundleSpec{
  1265  					TrustBundle: "non block garbage",
  1266  				},
  1267  			},
  1268  			wantErrors: field.ErrorList{
  1269  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
  1270  			},
  1271  		}, {
  1272  			description: "invalid, bad block type",
  1273  			bundle: &capi.ClusterTrustBundle{
  1274  				ObjectMeta: metav1.ObjectMeta{
  1275  					Name: "foo",
  1276  				},
  1277  				Spec: capi.ClusterTrustBundleSpec{
  1278  					TrustBundle: goodCert1Block + "\n" + badBlockTypeBlock,
  1279  				},
  1280  			},
  1281  			wantErrors: field.ErrorList{
  1282  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has bad block type: NOTACERTIFICATE"),
  1283  			},
  1284  		}, {
  1285  			description: "invalid, block with headers",
  1286  			bundle: &capi.ClusterTrustBundle{
  1287  				ObjectMeta: metav1.ObjectMeta{
  1288  					Name: "foo",
  1289  				},
  1290  				Spec: capi.ClusterTrustBundleSpec{
  1291  					TrustBundle: goodCert1Block + "\n" + badBlockHeadersBlock,
  1292  				},
  1293  			},
  1294  			wantErrors: field.ErrorList{
  1295  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 has PEM block headers"),
  1296  			},
  1297  		}, {
  1298  			description: "invalid, cert is not a CA cert",
  1299  			bundle: &capi.ClusterTrustBundle{
  1300  				ObjectMeta: metav1.ObjectMeta{
  1301  					Name: "foo",
  1302  				},
  1303  				Spec: capi.ClusterTrustBundleSpec{
  1304  					TrustBundle: badNotCACertBlock,
  1305  				},
  1306  			},
  1307  			wantErrors: field.ErrorList{
  1308  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 0 does not have the CA bit set"),
  1309  			},
  1310  		}, {
  1311  			description: "invalid, duplicated blocks",
  1312  			bundle: &capi.ClusterTrustBundle{
  1313  				ObjectMeta: metav1.ObjectMeta{
  1314  					Name: "foo",
  1315  				},
  1316  				Spec: capi.ClusterTrustBundleSpec{
  1317  					TrustBundle: goodCert1Block + "\n" + goodCert1AlternateBlock,
  1318  				},
  1319  			},
  1320  			wantErrors: field.ErrorList{
  1321  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "duplicate trust anchor (indices [0 1])"),
  1322  			},
  1323  		}, {
  1324  			description: "invalid, non-certificate entry",
  1325  			bundle: &capi.ClusterTrustBundle{
  1326  				ObjectMeta: metav1.ObjectMeta{
  1327  					Name: "foo",
  1328  				},
  1329  				Spec: capi.ClusterTrustBundleSpec{
  1330  					TrustBundle: goodCert1Block + "\n" + badNonParseableBlock,
  1331  				},
  1332  			},
  1333  			wantErrors: field.ErrorList{
  1334  				field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "entry 1 does not parse as X.509"),
  1335  			},
  1336  		}, {
  1337  			description: "allow any old garbage in the PEM field if we suppress parsing",
  1338  			bundle: &capi.ClusterTrustBundle{
  1339  				ObjectMeta: metav1.ObjectMeta{
  1340  					Name: "foo",
  1341  				},
  1342  				Spec: capi.ClusterTrustBundleSpec{
  1343  					TrustBundle: "garbage",
  1344  				},
  1345  			},
  1346  			opts: ValidateClusterTrustBundleOptions{
  1347  				SuppressBundleParsing: true,
  1348  			},
  1349  		}}
  1350  	for _, tc := range testCases {
  1351  		t.Run(tc.description, func(t *testing.T) {
  1352  			gotErrors := ValidateClusterTrustBundle(tc.bundle, tc.opts)
  1353  			if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" {
  1354  				t.Fatalf("Unexpected error output from Validate; diff (-got +want)\n%s", diff)
  1355  			}
  1356  
  1357  			// When there are no changes to the object,
  1358  			// ValidateClusterTrustBundleUpdate should not report errors about
  1359  			// the TrustBundle field.
  1360  			tc.bundle.ObjectMeta.ResourceVersion = "1"
  1361  			newBundle := tc.bundle.DeepCopy()
  1362  			newBundle.ObjectMeta.ResourceVersion = "2"
  1363  			gotErrors = ValidateClusterTrustBundleUpdate(newBundle, tc.bundle)
  1364  
  1365  			var filteredWantErrors field.ErrorList
  1366  			for _, err := range tc.wantErrors {
  1367  				if err.Field != "spec.trustBundle" {
  1368  					filteredWantErrors = append(filteredWantErrors, err)
  1369  				}
  1370  			}
  1371  
  1372  			if diff := cmp.Diff(gotErrors, filteredWantErrors); diff != "" {
  1373  				t.Fatalf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff)
  1374  			}
  1375  		})
  1376  	}
  1377  }
  1378  
  1379  func TestValidateClusterTrustBundleUpdate(t *testing.T) {
  1380  	goodCert1 := mustMakeCertificate(t, &x509.Certificate{
  1381  		SerialNumber: big.NewInt(0),
  1382  		Subject: pkix.Name{
  1383  			CommonName: "root1",
  1384  		},
  1385  		IsCA:                  true,
  1386  		BasicConstraintsValid: true,
  1387  	})
  1388  
  1389  	goodCert2 := mustMakeCertificate(t, &x509.Certificate{
  1390  		SerialNumber: big.NewInt(0),
  1391  		Subject: pkix.Name{
  1392  			CommonName: "root2",
  1393  		},
  1394  		IsCA:                  true,
  1395  		BasicConstraintsValid: true,
  1396  	})
  1397  
  1398  	goodCert1Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert1))
  1399  	goodCert2Block := string(mustMakePEMBlock("CERTIFICATE", nil, goodCert2))
  1400  
  1401  	testCases := []struct {
  1402  		description          string
  1403  		oldBundle, newBundle *capi.ClusterTrustBundle
  1404  		wantErrors           field.ErrorList
  1405  	}{{
  1406  		description: "changing signer name disallowed",
  1407  		oldBundle: &capi.ClusterTrustBundle{
  1408  			ObjectMeta: metav1.ObjectMeta{
  1409  				Name: "k8s.io:foo:bar",
  1410  			},
  1411  			Spec: capi.ClusterTrustBundleSpec{
  1412  				SignerName:  "k8s.io/foo",
  1413  				TrustBundle: goodCert1Block,
  1414  			},
  1415  		},
  1416  		newBundle: &capi.ClusterTrustBundle{
  1417  			ObjectMeta: metav1.ObjectMeta{
  1418  				Name: "k8s.io:foo:bar",
  1419  			},
  1420  			Spec: capi.ClusterTrustBundleSpec{
  1421  				SignerName:  "k8s.io/bar",
  1422  				TrustBundle: goodCert1Block,
  1423  			},
  1424  		},
  1425  		wantErrors: field.ErrorList{
  1426  			field.Invalid(field.NewPath("metadata", "name"), "k8s.io:foo:bar", "ClusterTrustBundle for signerName k8s.io/bar must be named with prefix k8s.io:bar:"),
  1427  			field.Invalid(field.NewPath("spec", "signerName"), "k8s.io/bar", "field is immutable"),
  1428  		},
  1429  	}, {
  1430  		description: "adding certificate allowed",
  1431  		oldBundle: &capi.ClusterTrustBundle{
  1432  			ObjectMeta: metav1.ObjectMeta{
  1433  				Name: "k8s.io:foo:bar",
  1434  			},
  1435  			Spec: capi.ClusterTrustBundleSpec{
  1436  				SignerName:  "k8s.io/foo",
  1437  				TrustBundle: goodCert1Block,
  1438  			},
  1439  		},
  1440  		newBundle: &capi.ClusterTrustBundle{
  1441  			ObjectMeta: metav1.ObjectMeta{
  1442  				Name: "k8s.io:foo:bar",
  1443  			},
  1444  			Spec: capi.ClusterTrustBundleSpec{
  1445  				SignerName:  "k8s.io/foo",
  1446  				TrustBundle: goodCert1Block + "\n" + goodCert2Block,
  1447  			},
  1448  		},
  1449  	}, {
  1450  		description: "emptying trustBundle disallowed",
  1451  		oldBundle: &capi.ClusterTrustBundle{
  1452  			ObjectMeta: metav1.ObjectMeta{
  1453  				Name: "k8s.io:foo:bar",
  1454  			},
  1455  			Spec: capi.ClusterTrustBundleSpec{
  1456  				SignerName:  "k8s.io/foo",
  1457  				TrustBundle: goodCert1Block,
  1458  			},
  1459  		},
  1460  		newBundle: &capi.ClusterTrustBundle{
  1461  			ObjectMeta: metav1.ObjectMeta{
  1462  				Name: "k8s.io:foo:bar",
  1463  			},
  1464  			Spec: capi.ClusterTrustBundleSpec{
  1465  				SignerName:  "k8s.io/foo",
  1466  				TrustBundle: "",
  1467  			},
  1468  		},
  1469  		wantErrors: field.ErrorList{
  1470  			field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
  1471  		},
  1472  	}, {
  1473  		description: "emptying trustBundle (replace with non-block garbage) disallowed",
  1474  		oldBundle: &capi.ClusterTrustBundle{
  1475  			ObjectMeta: metav1.ObjectMeta{
  1476  				Name: "k8s.io:foo:bar",
  1477  			},
  1478  			Spec: capi.ClusterTrustBundleSpec{
  1479  				SignerName:  "k8s.io/foo",
  1480  				TrustBundle: goodCert1Block,
  1481  			},
  1482  		},
  1483  		newBundle: &capi.ClusterTrustBundle{
  1484  			ObjectMeta: metav1.ObjectMeta{
  1485  				Name: "k8s.io:foo:bar",
  1486  			},
  1487  			Spec: capi.ClusterTrustBundleSpec{
  1488  				SignerName:  "k8s.io/foo",
  1489  				TrustBundle: "non block garbage",
  1490  			},
  1491  		},
  1492  		wantErrors: field.ErrorList{
  1493  			field.Invalid(field.NewPath("spec", "trustBundle"), "<value omitted>", "at least one trust anchor must be provided"),
  1494  		},
  1495  	}}
  1496  
  1497  	for _, tc := range testCases {
  1498  		t.Run(tc.description, func(t *testing.T) {
  1499  			tc.oldBundle.ObjectMeta.ResourceVersion = "1"
  1500  			tc.newBundle.ObjectMeta.ResourceVersion = "2"
  1501  			gotErrors := ValidateClusterTrustBundleUpdate(tc.newBundle, tc.oldBundle)
  1502  			if diff := cmp.Diff(gotErrors, tc.wantErrors); diff != "" {
  1503  				t.Errorf("Unexpected error output from ValidateUpdate; diff (-got +want)\n%s", diff)
  1504  			}
  1505  		})
  1506  	}
  1507  }
  1508  
  1509  var (
  1510  	validCertificate = []byte(`
  1511  Leading non-PEM content
  1512  -----BEGIN CERTIFICATE-----
  1513  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  1514  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  1515  MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  1516  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  1517  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  1518  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  1519  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  1520  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  1521  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
  1522  -----END CERTIFICATE-----
  1523  Intermediate non-PEM content
  1524  -----BEGIN CERTIFICATE-----
  1525  MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw
  1526  EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1
  1527  MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI
  1528  KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD
  1529  kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm
  1530  MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
  1531  FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b
  1532  dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz
  1533  3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c=
  1534  -----END CERTIFICATE-----
  1535  Trailing non-PEM content
  1536  `)
  1537  
  1538  	invalidCertificateNoPEM = []byte(`no PEM content`)
  1539  
  1540  	invalidCertificateNonCertificatePEM = []byte(`
  1541  Leading non-PEM content
  1542  -----BEGIN CERTIFICATE1-----
  1543  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  1544  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  1545  MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  1546  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  1547  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  1548  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  1549  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  1550  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  1551  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
  1552  -----END CERTIFICATE1-----
  1553  Trailing non-PEM content
  1554  `)
  1555  
  1556  	invalidCertificatePEMHeaders = []byte(`
  1557  Leading non-PEM content
  1558  -----BEGIN CERTIFICATE-----
  1559  Some-Header: Some-Value
  1560  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  1561  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  1562  MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  1563  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  1564  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  1565  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  1566  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  1567  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  1568  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
  1569  -----END CERTIFICATE-----
  1570  Trailing non-PEM content
  1571  `)
  1572  
  1573  	invalidCertificateNonBase64PEM = []byte(`
  1574  Leading non-PEM content
  1575  -----BEGIN CERTIFICATE-----
  1576  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  1577  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  1578  MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  1579  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  1580  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  1581  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  1582  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  1583  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  1584  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1d?????????
  1585  -----END CERTIFICATE-----
  1586  Trailing non-PEM content
  1587  `)
  1588  
  1589  	invalidCertificateEmptyPEM = []byte(`
  1590  Leading non-PEM content
  1591  -----BEGIN CERTIFICATE-----
  1592  -----END CERTIFICATE-----
  1593  Trailing non-PEM content
  1594  `)
  1595  
  1596  	// first character is invalid
  1597  	invalidCertificateNonASN1Data = []byte(`
  1598  Leading non-PEM content
  1599  -----BEGIN CERTIFICATE-----
  1600  MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
  1601  GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
  1602  MTYwOTE3MDUwNjAwWjAUNRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
  1603  BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
  1604  KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
  1605  BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
  1606  K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
  1607  a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
  1608  MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
  1609  -----END CERTIFICATE-----
  1610  Trailing non-PEM content
  1611  `)
  1612  )