...

Source file src/k8s.io/kubernetes/pkg/kubelet/clustertrustbundle/clustertrustbundle_manager_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/clustertrustbundle

     1  /*
     2  Copyright 2023 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 clustertrustbundle
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/ed25519"
    23  	"crypto/rand"
    24  	"crypto/x509"
    25  	"crypto/x509/pkix"
    26  	"encoding/pem"
    27  	"fmt"
    28  	"math/big"
    29  	"sort"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/google/go-cmp/cmp"
    35  	certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1"
    36  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/client-go/informers"
    38  	"k8s.io/client-go/kubernetes/fake"
    39  	"k8s.io/client-go/tools/cache"
    40  )
    41  
    42  func TestBeforeSynced(t *testing.T) {
    43  	kc := fake.NewSimpleClientset()
    44  
    45  	informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
    46  
    47  	ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
    48  	ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
    49  
    50  	_, err := ctbManager.GetTrustAnchorsByName("foo", false)
    51  	if err == nil {
    52  		t.Fatalf("Got nil error, wanted non-nil")
    53  	}
    54  }
    55  
    56  func TestGetTrustAnchorsByName(t *testing.T) {
    57  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    58  	defer cancel()
    59  
    60  	ctb1 := &certificatesv1alpha1.ClusterTrustBundle{
    61  		ObjectMeta: metav1.ObjectMeta{
    62  			Name: "ctb1",
    63  		},
    64  		Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
    65  			TrustBundle: mustMakeRoot(t, "root1"),
    66  		},
    67  	}
    68  
    69  	ctb2 := &certificatesv1alpha1.ClusterTrustBundle{
    70  		ObjectMeta: metav1.ObjectMeta{
    71  			Name: "ctb2",
    72  		},
    73  		Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
    74  			TrustBundle: mustMakeRoot(t, "root2"),
    75  		},
    76  	}
    77  
    78  	kc := fake.NewSimpleClientset(ctb1, ctb2)
    79  
    80  	informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
    81  
    82  	ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
    83  	ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
    84  
    85  	informerFactory.Start(ctx.Done())
    86  	if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
    87  		t.Fatalf("Timed out waiting for informer to sync")
    88  	}
    89  
    90  	gotBundle, err := ctbManager.GetTrustAnchorsByName("ctb1", false)
    91  	if err != nil {
    92  		t.Fatalf("Error while calling GetTrustAnchorsByName: %v", err)
    93  	}
    94  
    95  	if diff := diffBundles(gotBundle, []byte(ctb1.Spec.TrustBundle)); diff != "" {
    96  		t.Fatalf("Got bad bundle; diff (-got +want)\n%s", diff)
    97  	}
    98  
    99  	gotBundle, err = ctbManager.GetTrustAnchorsByName("ctb2", false)
   100  	if err != nil {
   101  		t.Fatalf("Error while calling GetTrustAnchorsByName: %v", err)
   102  	}
   103  
   104  	if diff := diffBundles(gotBundle, []byte(ctb2.Spec.TrustBundle)); diff != "" {
   105  		t.Fatalf("Got bad bundle; diff (-got +want)\n%s", diff)
   106  	}
   107  
   108  	_, err = ctbManager.GetTrustAnchorsByName("not-found", false)
   109  	if err == nil { // EQUALS nil
   110  		t.Fatalf("While looking up nonexisting ClusterTrustBundle, got nil error, wanted non-nil")
   111  	}
   112  
   113  	_, err = ctbManager.GetTrustAnchorsByName("not-found", true)
   114  	if err != nil {
   115  		t.Fatalf("Unexpected error while calling GetTrustAnchorsByName for nonexistent CTB with allowMissing: %v", err)
   116  	}
   117  }
   118  
   119  func TestGetTrustAnchorsByNameCaching(t *testing.T) {
   120  	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
   121  	defer cancel()
   122  
   123  	ctb1 := &certificatesv1alpha1.ClusterTrustBundle{
   124  		ObjectMeta: metav1.ObjectMeta{
   125  			Name: "foo",
   126  		},
   127  		Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
   128  			TrustBundle: mustMakeRoot(t, "root1"),
   129  		},
   130  	}
   131  
   132  	ctb2 := &certificatesv1alpha1.ClusterTrustBundle{
   133  		ObjectMeta: metav1.ObjectMeta{
   134  			Name: "foo",
   135  		},
   136  		Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
   137  			TrustBundle: mustMakeRoot(t, "root2"),
   138  		},
   139  	}
   140  
   141  	kc := fake.NewSimpleClientset(ctb1)
   142  
   143  	informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
   144  
   145  	ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
   146  	ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
   147  
   148  	informerFactory.Start(ctx.Done())
   149  	if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
   150  		t.Fatalf("Timed out waiting for informer to sync")
   151  	}
   152  
   153  	t.Run("foo should yield the first certificate", func(t *testing.T) {
   154  		gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
   155  		if err != nil {
   156  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   157  		}
   158  
   159  		wantBundle := ctb1.Spec.TrustBundle
   160  
   161  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   162  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   163  		}
   164  	})
   165  
   166  	t.Run("foo should still yield the first certificate", func(t *testing.T) {
   167  		gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
   168  		if err != nil {
   169  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   170  		}
   171  
   172  		wantBundle := ctb1.Spec.TrustBundle
   173  
   174  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   175  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   176  		}
   177  	})
   178  
   179  	if err := kc.CertificatesV1alpha1().ClusterTrustBundles().Delete(ctx, ctb1.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
   180  		t.Fatalf("Error while deleting the old CTB: %v", err)
   181  	}
   182  	if _, err := kc.CertificatesV1alpha1().ClusterTrustBundles().Create(ctx, ctb2, metav1.CreateOptions{}); err != nil {
   183  		t.Fatalf("Error while adding new CTB: %v", err)
   184  	}
   185  
   186  	// We need to sleep long enough for the informer to notice the new
   187  	// ClusterTrustBundle, but much less than the 5 minutes of the cache TTL.
   188  	// This shows us that the informer is properly clearing the cache.
   189  	time.Sleep(5 * time.Second)
   190  
   191  	t.Run("foo should yield the new certificate", func(t *testing.T) {
   192  		gotBundle, err := ctbManager.GetTrustAnchorsByName("foo", false)
   193  		if err != nil {
   194  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   195  		}
   196  
   197  		wantBundle := ctb2.Spec.TrustBundle
   198  
   199  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   200  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   201  		}
   202  	})
   203  }
   204  
   205  func TestGetTrustAnchorsBySignerName(t *testing.T) {
   206  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   207  	defer cancel()
   208  
   209  	ctb1 := mustMakeCTB("signer-a-label-a-1", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "0"))
   210  	ctb2 := mustMakeCTB("signer-a-label-a-2", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "1"))
   211  	ctb2dup := mustMakeCTB("signer-a-label-2-dup", "foo.bar/a", map[string]string{"label": "a"}, ctb2.Spec.TrustBundle)
   212  	ctb3 := mustMakeCTB("signer-a-label-b-1", "foo.bar/a", map[string]string{"label": "b"}, mustMakeRoot(t, "2"))
   213  	ctb4 := mustMakeCTB("signer-b-label-a-1", "foo.bar/b", map[string]string{"label": "a"}, mustMakeRoot(t, "3"))
   214  
   215  	kc := fake.NewSimpleClientset(ctb1, ctb2, ctb2dup, ctb3, ctb4)
   216  
   217  	informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
   218  
   219  	ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
   220  	ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
   221  
   222  	informerFactory.Start(ctx.Done())
   223  	if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
   224  		t.Fatalf("Timed out waiting for informer to sync")
   225  	}
   226  
   227  	t.Run("big labelselector should cause error", func(t *testing.T) {
   228  		longString := strings.Builder{}
   229  		for i := 0; i < 63; i++ {
   230  			longString.WriteString("v")
   231  		}
   232  		matchLabels := map[string]string{}
   233  		for i := 0; i < 100*1024/63+1; i++ {
   234  			matchLabels[fmt.Sprintf("key-%d", i)] = longString.String()
   235  		}
   236  
   237  		_, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: matchLabels}, false)
   238  		if err == nil || !strings.Contains(err.Error(), "label selector length") {
   239  			t.Fatalf("Bad error, got %v, wanted it to contain \"label selector length\"", err)
   240  		}
   241  	})
   242  
   243  	t.Run("signer-a label-a should yield two sorted certificates", func(t *testing.T) {
   244  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
   245  		if err != nil {
   246  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   247  		}
   248  
   249  		wantBundle := ctb1.Spec.TrustBundle + ctb2.Spec.TrustBundle
   250  
   251  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   252  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   253  		}
   254  	})
   255  
   256  	t.Run("signer-a with nil selector should yield zero certificates", func(t *testing.T) {
   257  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", nil, true)
   258  		if err != nil {
   259  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   260  		}
   261  
   262  		wantBundle := ""
   263  
   264  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   265  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   266  		}
   267  	})
   268  
   269  	t.Run("signer-b with empty selector should yield one certificates", func(t *testing.T) {
   270  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{}, false)
   271  		if err != nil {
   272  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   273  		}
   274  
   275  		if diff := diffBundles(gotBundle, []byte(ctb4.Spec.TrustBundle)); diff != "" {
   276  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   277  		}
   278  	})
   279  
   280  	t.Run("signer-a label-b should yield one certificate", func(t *testing.T) {
   281  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, false)
   282  		if err != nil {
   283  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   284  		}
   285  
   286  		if diff := diffBundles(gotBundle, []byte(ctb3.Spec.TrustBundle)); diff != "" {
   287  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   288  		}
   289  	})
   290  
   291  	t.Run("signer-b label-a should yield one certificate", func(t *testing.T) {
   292  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
   293  		if err != nil {
   294  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   295  		}
   296  
   297  		if diff := diffBundles(gotBundle, []byte(ctb4.Spec.TrustBundle)); diff != "" {
   298  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   299  		}
   300  	})
   301  
   302  	t.Run("signer-b label-b allowMissing=true should yield zero certificates", func(t *testing.T) {
   303  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, true)
   304  		if err != nil {
   305  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   306  		}
   307  
   308  		if diff := diffBundles(gotBundle, []byte{}); diff != "" {
   309  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   310  		}
   311  	})
   312  
   313  	t.Run("signer-b label-b allowMissing=false should yield zero certificates (error)", func(t *testing.T) {
   314  		_, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/b", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "b"}}, false)
   315  		if err == nil { // EQUALS nil
   316  			t.Fatalf("Got nil error while calling GetTrustAnchorsBySigner, wanted non-nil")
   317  		}
   318  	})
   319  }
   320  
   321  func TestGetTrustAnchorsBySignerNameCaching(t *testing.T) {
   322  	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
   323  	defer cancel()
   324  
   325  	ctb1 := mustMakeCTB("signer-a-label-a-1", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "0"))
   326  	ctb2 := mustMakeCTB("signer-a-label-a-2", "foo.bar/a", map[string]string{"label": "a"}, mustMakeRoot(t, "1"))
   327  
   328  	kc := fake.NewSimpleClientset(ctb1)
   329  
   330  	informerFactory := informers.NewSharedInformerFactoryWithOptions(kc, 0)
   331  
   332  	ctbInformer := informerFactory.Certificates().V1alpha1().ClusterTrustBundles()
   333  	ctbManager, _ := NewInformerManager(ctbInformer, 256, 5*time.Minute)
   334  
   335  	informerFactory.Start(ctx.Done())
   336  	if !cache.WaitForCacheSync(ctx.Done(), ctbInformer.Informer().HasSynced) {
   337  		t.Fatalf("Timed out waiting for informer to sync")
   338  	}
   339  
   340  	t.Run("signer-a label-a should yield one certificate", func(t *testing.T) {
   341  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
   342  		if err != nil {
   343  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   344  		}
   345  
   346  		wantBundle := ctb1.Spec.TrustBundle
   347  
   348  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   349  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   350  		}
   351  	})
   352  
   353  	t.Run("signer-a label-a should yield the same result when called again", func(t *testing.T) {
   354  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
   355  		if err != nil {
   356  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   357  		}
   358  
   359  		wantBundle := ctb1.Spec.TrustBundle
   360  
   361  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   362  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   363  		}
   364  	})
   365  
   366  	if err := kc.CertificatesV1alpha1().ClusterTrustBundles().Delete(ctx, ctb1.ObjectMeta.Name, metav1.DeleteOptions{}); err != nil {
   367  		t.Fatalf("Error while deleting the old CTB: %v", err)
   368  	}
   369  	if _, err := kc.CertificatesV1alpha1().ClusterTrustBundles().Create(ctx, ctb2, metav1.CreateOptions{}); err != nil {
   370  		t.Fatalf("Error while adding new CTB: %v", err)
   371  	}
   372  
   373  	// We need to sleep long enough for the informer to notice the new
   374  	// ClusterTrustBundle, but much less than the 5 minutes of the cache TTL.
   375  	// This shows us that the informer is properly clearing the cache.
   376  	time.Sleep(5 * time.Second)
   377  
   378  	t.Run("signer-a label-a should return the new certificate", func(t *testing.T) {
   379  		gotBundle, err := ctbManager.GetTrustAnchorsBySigner("foo.bar/a", &metav1.LabelSelector{MatchLabels: map[string]string{"label": "a"}}, false)
   380  		if err != nil {
   381  			t.Fatalf("Got error while calling GetTrustAnchorsBySigner: %v", err)
   382  		}
   383  
   384  		wantBundle := ctb2.Spec.TrustBundle
   385  
   386  		if diff := diffBundles(gotBundle, []byte(wantBundle)); diff != "" {
   387  			t.Fatalf("Bad bundle; diff (-got +want)\n%s", diff)
   388  		}
   389  	})
   390  }
   391  
   392  func mustMakeRoot(t *testing.T, cn string) string {
   393  	pub, priv, err := ed25519.GenerateKey(rand.Reader)
   394  	if err != nil {
   395  		t.Fatalf("Error while generating key: %v", err)
   396  	}
   397  
   398  	template := &x509.Certificate{
   399  		SerialNumber: big.NewInt(0),
   400  		Subject: pkix.Name{
   401  			CommonName: cn,
   402  		},
   403  		IsCA:                  true,
   404  		BasicConstraintsValid: true,
   405  	}
   406  
   407  	cert, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv)
   408  	if err != nil {
   409  		t.Fatalf("Error while making certificate: %v", err)
   410  	}
   411  
   412  	return string(pem.EncodeToMemory(&pem.Block{
   413  		Type:    "CERTIFICATE",
   414  		Headers: nil,
   415  		Bytes:   cert,
   416  	}))
   417  }
   418  
   419  func mustMakeCTB(name, signerName string, labels map[string]string, bundle string) *certificatesv1alpha1.ClusterTrustBundle {
   420  	return &certificatesv1alpha1.ClusterTrustBundle{
   421  		ObjectMeta: metav1.ObjectMeta{
   422  			Name:   name,
   423  			Labels: labels,
   424  		},
   425  		Spec: certificatesv1alpha1.ClusterTrustBundleSpec{
   426  			SignerName:  signerName,
   427  			TrustBundle: bundle,
   428  		},
   429  	}
   430  }
   431  
   432  func diffBundles(a, b []byte) string {
   433  	var block *pem.Block
   434  
   435  	aBlocks := []*pem.Block{}
   436  	for {
   437  		block, a = pem.Decode(a)
   438  		if block == nil {
   439  			break
   440  		}
   441  		aBlocks = append(aBlocks, block)
   442  	}
   443  	sort.Slice(aBlocks, func(i, j int) bool {
   444  		if aBlocks[i].Type < aBlocks[j].Type {
   445  			return true
   446  		} else if aBlocks[i].Type == aBlocks[j].Type {
   447  			comp := bytes.Compare(aBlocks[i].Bytes, aBlocks[j].Bytes)
   448  			return comp <= 0
   449  		} else {
   450  			return false
   451  		}
   452  	})
   453  
   454  	bBlocks := []*pem.Block{}
   455  	for {
   456  		block, b = pem.Decode(b)
   457  		if block == nil {
   458  			break
   459  		}
   460  		bBlocks = append(bBlocks, block)
   461  	}
   462  	sort.Slice(bBlocks, func(i, j int) bool {
   463  		if bBlocks[i].Type < bBlocks[j].Type {
   464  			return true
   465  		} else if bBlocks[i].Type == bBlocks[j].Type {
   466  			comp := bytes.Compare(bBlocks[i].Bytes, bBlocks[j].Bytes)
   467  			return comp <= 0
   468  		} else {
   469  			return false
   470  		}
   471  	})
   472  
   473  	return cmp.Diff(aBlocks, bBlocks)
   474  }
   475  

View as plain text