...

Source file src/k8s.io/kubernetes/pkg/controller/certificates/signer/signer_test.go

Documentation: k8s.io/kubernetes/pkg/controller/certificates/signer

     1  /*
     2  Copyright 2019 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 signer
    18  
    19  import (
    20  	"context"
    21  	"crypto/ecdsa"
    22  	"crypto/elliptic"
    23  	"crypto/x509"
    24  	"crypto/x509/pkix"
    25  	"encoding/pem"
    26  	"io/ioutil"
    27  	"math/rand"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/google/go-cmp/cmp"
    32  
    33  	capi "k8s.io/api/certificates/v1"
    34  	"k8s.io/apimachinery/pkg/util/diff"
    35  	"k8s.io/client-go/kubernetes/fake"
    36  	testclient "k8s.io/client-go/testing"
    37  	"k8s.io/client-go/util/cert"
    38  	"k8s.io/client-go/util/certificate/csr"
    39  	capihelper "k8s.io/kubernetes/pkg/apis/certificates/v1"
    40  	"k8s.io/kubernetes/pkg/controller/certificates"
    41  	testingclock "k8s.io/utils/clock/testing"
    42  )
    43  
    44  func TestSigner(t *testing.T) {
    45  	fakeClock := testingclock.FakeClock{}
    46  
    47  	s, err := newSigner("kubernetes.io/legacy-unknown", "./testdata/ca.crt", "./testdata/ca.key", nil, 1*time.Hour)
    48  	if err != nil {
    49  		t.Fatalf("failed to create signer: %v", err)
    50  	}
    51  
    52  	csrb, err := ioutil.ReadFile("./testdata/kubelet.csr")
    53  	if err != nil {
    54  		t.Fatalf("failed to read CSR: %v", err)
    55  	}
    56  	x509cr, err := capihelper.ParseCSR(csrb)
    57  	if err != nil {
    58  		t.Fatalf("failed to parse CSR: %v", err)
    59  	}
    60  
    61  	certData, err := s.sign(x509cr, []capi.KeyUsage{
    62  		capi.UsageSigning,
    63  		capi.UsageKeyEncipherment,
    64  		capi.UsageServerAuth,
    65  		capi.UsageClientAuth,
    66  	},
    67  		// requesting a duration that is greater than TTL is ignored
    68  		csr.DurationToExpirationSeconds(3*time.Hour),
    69  		fakeClock.Now,
    70  	)
    71  	if err != nil {
    72  		t.Fatalf("failed to sign CSR: %v", err)
    73  	}
    74  	if len(certData) == 0 {
    75  		t.Fatalf("expected a certificate after signing")
    76  	}
    77  
    78  	certs, err := cert.ParseCertsPEM(certData)
    79  	if err != nil {
    80  		t.Fatalf("failed to parse certificate: %v", err)
    81  	}
    82  	if len(certs) != 1 {
    83  		t.Fatalf("expected one certificate")
    84  	}
    85  
    86  	want := x509.Certificate{
    87  		Version: 3,
    88  		Subject: pkix.Name{
    89  			CommonName:   "system:node:k-a-node-s36b",
    90  			Organization: []string{"system:nodes"},
    91  		},
    92  		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
    93  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
    94  		BasicConstraintsValid: true,
    95  		NotBefore:             fakeClock.Now().Add(-5 * time.Minute),
    96  		NotAfter:              fakeClock.Now().Add(1 * time.Hour),
    97  		PublicKeyAlgorithm:    x509.ECDSA,
    98  		SignatureAlgorithm:    x509.SHA256WithRSA,
    99  		MaxPathLen:            -1,
   100  	}
   101  
   102  	if !cmp.Equal(*certs[0], want, diff.IgnoreUnset()) {
   103  		t.Errorf("unexpected diff: %v", cmp.Diff(certs[0], want, diff.IgnoreUnset()))
   104  	}
   105  }
   106  
   107  func TestHandle(t *testing.T) {
   108  	cases := []struct {
   109  		name string
   110  		// parameters to be set on the generated CSR
   111  		commonName string
   112  		dnsNames   []string
   113  		org        []string
   114  		usages     []capi.KeyUsage
   115  		// whether the generated CSR should be marked as approved
   116  		approved bool
   117  		// whether the generated CSR should be marked as failed
   118  		failed bool
   119  		// the signerName to be set on the generated CSR
   120  		signerName string
   121  		// if true, expect an error to be returned
   122  		err bool
   123  		// if true, expect an error to be returned during construction
   124  		constructionErr bool
   125  		// additional verification function
   126  		verify func(*testing.T, []testclient.Action)
   127  	}{
   128  		{
   129  			name:       "should sign if signerName is kubernetes.io/kube-apiserver-client",
   130  			signerName: "kubernetes.io/kube-apiserver-client",
   131  			commonName: "hello-world",
   132  			org:        []string{"some-org"},
   133  			usages:     []capi.KeyUsage{capi.UsageClientAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   134  			approved:   true,
   135  			verify: func(t *testing.T, as []testclient.Action) {
   136  				if len(as) != 1 {
   137  					t.Errorf("expected one Update action but got %d", len(as))
   138  					return
   139  				}
   140  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   141  				if len(csr.Status.Certificate) == 0 {
   142  					t.Errorf("expected certificate to be issued but it was not")
   143  				}
   144  			},
   145  		},
   146  		{
   147  			name:       "should sign without key encipherment if signerName is kubernetes.io/kube-apiserver-client",
   148  			signerName: "kubernetes.io/kube-apiserver-client",
   149  			commonName: "hello-world",
   150  			org:        []string{"some-org"},
   151  			usages:     []capi.KeyUsage{capi.UsageClientAuth, capi.UsageDigitalSignature},
   152  			approved:   true,
   153  			verify: func(t *testing.T, as []testclient.Action) {
   154  				if len(as) != 1 {
   155  					t.Errorf("expected one Update action but got %d", len(as))
   156  					return
   157  				}
   158  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   159  				if len(csr.Status.Certificate) == 0 {
   160  					t.Errorf("expected certificate to be issued but it was not")
   161  				}
   162  			},
   163  		},
   164  		{
   165  			name:       "should refuse to sign if signerName is kubernetes.io/kube-apiserver-client and contains an unexpected usage",
   166  			signerName: "kubernetes.io/kube-apiserver-client",
   167  			commonName: "hello-world",
   168  			org:        []string{"some-org"},
   169  			usages:     []capi.KeyUsage{capi.UsageServerAuth, capi.UsageClientAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   170  			approved:   true,
   171  			verify: func(t *testing.T, as []testclient.Action) {
   172  				if len(as) != 1 {
   173  					t.Errorf("expected one Update action but got %d", len(as))
   174  					return
   175  				}
   176  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   177  				if len(csr.Status.Certificate) != 0 {
   178  					t.Errorf("expected no certificate to be issued")
   179  				}
   180  				if !certificates.HasTrueCondition(csr, capi.CertificateFailed) {
   181  					t.Errorf("expected Failed condition")
   182  				}
   183  			},
   184  		},
   185  		{
   186  			name:       "should sign if signerName is kubernetes.io/kube-apiserver-client-kubelet",
   187  			signerName: "kubernetes.io/kube-apiserver-client-kubelet",
   188  			commonName: "system:node:hello-world",
   189  			org:        []string{"system:nodes"},
   190  			usages:     []capi.KeyUsage{capi.UsageClientAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   191  			approved:   true,
   192  			verify: func(t *testing.T, as []testclient.Action) {
   193  				if len(as) != 1 {
   194  					t.Errorf("expected one Update action but got %d", len(as))
   195  					return
   196  				}
   197  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   198  				if len(csr.Status.Certificate) == 0 {
   199  					t.Errorf("expected certificate to be issued but it was not")
   200  				}
   201  			},
   202  		},
   203  		{
   204  			name:       "should sign without usage key encipherment if signerName is kubernetes.io/kube-apiserver-client-kubelet",
   205  			signerName: "kubernetes.io/kube-apiserver-client-kubelet",
   206  			commonName: "system:node:hello-world",
   207  			org:        []string{"system:nodes"},
   208  			usages:     []capi.KeyUsage{capi.UsageClientAuth, capi.UsageDigitalSignature},
   209  			approved:   true,
   210  			verify: func(t *testing.T, as []testclient.Action) {
   211  				if len(as) != 1 {
   212  					t.Errorf("expected one Update action but got %d", len(as))
   213  					return
   214  				}
   215  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   216  				if len(csr.Status.Certificate) == 0 {
   217  					t.Errorf("expected certificate to be issued but it was not")
   218  				}
   219  			},
   220  		},
   221  		{
   222  			name:       "should sign if signerName is kubernetes.io/legacy-unknown",
   223  			signerName: "kubernetes.io/legacy-unknown",
   224  			approved:   true,
   225  			verify: func(t *testing.T, as []testclient.Action) {
   226  				if len(as) != 1 {
   227  					t.Errorf("expected one Update action but got %d", len(as))
   228  					return
   229  				}
   230  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   231  				if len(csr.Status.Certificate) == 0 {
   232  					t.Errorf("expected certificate to be issued but it was not")
   233  				}
   234  			},
   235  		},
   236  		{
   237  			name:       "should sign if signerName is kubernetes.io/kubelet-serving",
   238  			signerName: "kubernetes.io/kubelet-serving",
   239  			commonName: "system:node:testnode",
   240  			org:        []string{"system:nodes"},
   241  			usages:     []capi.KeyUsage{capi.UsageServerAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   242  			dnsNames:   []string{"example.com"},
   243  			approved:   true,
   244  			verify: func(t *testing.T, as []testclient.Action) {
   245  				if len(as) != 1 {
   246  					t.Errorf("expected one Update action but got %d", len(as))
   247  					return
   248  				}
   249  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   250  				if len(csr.Status.Certificate) == 0 {
   251  					t.Errorf("expected certificate to be issued but it was not")
   252  				}
   253  			},
   254  		},
   255  		{
   256  			name:       "should sign without usage key encipherment if signerName is kubernetes.io/kubelet-serving",
   257  			signerName: "kubernetes.io/kubelet-serving",
   258  			commonName: "system:node:testnode",
   259  			org:        []string{"system:nodes"},
   260  			usages:     []capi.KeyUsage{capi.UsageServerAuth, capi.UsageDigitalSignature},
   261  			dnsNames:   []string{"example.com"},
   262  			approved:   true,
   263  			verify: func(t *testing.T, as []testclient.Action) {
   264  				if len(as) != 1 {
   265  					t.Errorf("expected one Update action but got %d", len(as))
   266  					return
   267  				}
   268  				csr := as[0].(testclient.UpdateAction).GetObject().(*capi.CertificateSigningRequest)
   269  				if len(csr.Status.Certificate) == 0 {
   270  					t.Errorf("expected certificate to be issued but it was not")
   271  				}
   272  			},
   273  		},
   274  		{
   275  			name:       "should do nothing if failed",
   276  			signerName: "kubernetes.io/kubelet-serving",
   277  			commonName: "system:node:testnode",
   278  			org:        []string{"system:nodes"},
   279  			usages:     []capi.KeyUsage{capi.UsageServerAuth, capi.UsageDigitalSignature, capi.UsageKeyEncipherment},
   280  			dnsNames:   []string{"example.com"},
   281  			approved:   true,
   282  			failed:     true,
   283  			verify: func(t *testing.T, as []testclient.Action) {
   284  				if len(as) != 0 {
   285  					t.Errorf("expected no action to be taken")
   286  				}
   287  			},
   288  		},
   289  		{
   290  			name:            "should do nothing if an unrecognised signerName is used",
   291  			signerName:      "kubernetes.io/not-recognised",
   292  			constructionErr: true,
   293  			approved:        true,
   294  			verify: func(t *testing.T, as []testclient.Action) {
   295  				if len(as) != 0 {
   296  					t.Errorf("expected no action to be taken")
   297  				}
   298  			},
   299  		},
   300  		{
   301  			name:       "should do nothing if not approved",
   302  			signerName: "kubernetes.io/kubelet-serving",
   303  			verify: func(t *testing.T, as []testclient.Action) {
   304  				if len(as) != 0 {
   305  					t.Errorf("expected no action to be taken")
   306  				}
   307  			},
   308  		},
   309  		{
   310  			name:            "should do nothing if signerName does not start with kubernetes.io",
   311  			signerName:      "example.com/sample-name",
   312  			constructionErr: true,
   313  			approved:        true,
   314  			verify: func(t *testing.T, as []testclient.Action) {
   315  				if len(as) != 0 {
   316  					t.Errorf("expected no action to be taken")
   317  				}
   318  			},
   319  		},
   320  		{
   321  			name:            "should do nothing if signerName starts with kubernetes.io but is unrecognised",
   322  			signerName:      "kubernetes.io/not-a-real-signer",
   323  			constructionErr: true,
   324  			approved:        true,
   325  			verify: func(t *testing.T, as []testclient.Action) {
   326  				if len(as) != 0 {
   327  					t.Errorf("expected no action to be taken")
   328  				}
   329  			},
   330  		},
   331  	}
   332  
   333  	for _, c := range cases {
   334  		t.Run(c.name, func(t *testing.T) {
   335  			client := &fake.Clientset{}
   336  			s, err := newSigner(c.signerName, "./testdata/ca.crt", "./testdata/ca.key", client, 1*time.Hour)
   337  			switch {
   338  			case c.constructionErr && err != nil:
   339  				return
   340  			case c.constructionErr && err == nil:
   341  				t.Fatalf("expected failure during construction of controller")
   342  			case !c.constructionErr && err != nil:
   343  				t.Fatalf("failed to create signer: %v", err)
   344  
   345  			case !c.constructionErr && err == nil:
   346  				// continue with rest of test
   347  			}
   348  
   349  			csr := makeTestCSR(csrBuilder{cn: c.commonName, signerName: c.signerName, approved: c.approved, failed: c.failed, usages: c.usages, org: c.org, dnsNames: c.dnsNames})
   350  			ctx := context.TODO()
   351  			if err := s.handle(ctx, csr); err != nil && !c.err {
   352  				t.Errorf("unexpected err: %v", err)
   353  			}
   354  			c.verify(t, client.Actions())
   355  		})
   356  	}
   357  }
   358  
   359  // noncryptographic for faster testing
   360  // DO NOT COPY THIS CODE
   361  var insecureRand = rand.New(rand.NewSource(0))
   362  
   363  type csrBuilder struct {
   364  	cn         string
   365  	dnsNames   []string
   366  	org        []string
   367  	signerName string
   368  	approved   bool
   369  	failed     bool
   370  	usages     []capi.KeyUsage
   371  }
   372  
   373  func makeTestCSR(b csrBuilder) *capi.CertificateSigningRequest {
   374  	pk, err := ecdsa.GenerateKey(elliptic.P256(), insecureRand)
   375  	if err != nil {
   376  		panic(err)
   377  	}
   378  	csrb, err := x509.CreateCertificateRequest(insecureRand, &x509.CertificateRequest{
   379  		Subject: pkix.Name{
   380  			CommonName:   b.cn,
   381  			Organization: b.org,
   382  		},
   383  		DNSNames: b.dnsNames,
   384  	}, pk)
   385  	if err != nil {
   386  		panic(err)
   387  	}
   388  	csr := &capi.CertificateSigningRequest{
   389  		Spec: capi.CertificateSigningRequestSpec{
   390  			Request: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrb}),
   391  			Usages:  b.usages,
   392  		},
   393  	}
   394  	if b.signerName != "" {
   395  		csr.Spec.SignerName = b.signerName
   396  	}
   397  	if b.approved {
   398  		csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
   399  			Type: capi.CertificateApproved,
   400  		})
   401  	}
   402  	if b.failed {
   403  		csr.Status.Conditions = append(csr.Status.Conditions, capi.CertificateSigningRequestCondition{
   404  			Type: capi.CertificateFailed,
   405  		})
   406  	}
   407  	return csr
   408  }
   409  
   410  func Test_signer_duration(t *testing.T) {
   411  	t.Parallel()
   412  
   413  	tests := []struct {
   414  		name              string
   415  		certTTL           time.Duration
   416  		expirationSeconds *int32
   417  		want              time.Duration
   418  	}{
   419  		{
   420  			name:              "can request shorter duration than TTL",
   421  			certTTL:           time.Hour,
   422  			expirationSeconds: csr.DurationToExpirationSeconds(30 * time.Minute),
   423  			want:              30 * time.Minute,
   424  		},
   425  		{
   426  			name:              "cannot request longer duration than TTL",
   427  			certTTL:           time.Hour,
   428  			expirationSeconds: csr.DurationToExpirationSeconds(3 * time.Hour),
   429  			want:              time.Hour,
   430  		},
   431  		{
   432  			name:              "cannot request negative duration",
   433  			certTTL:           time.Hour,
   434  			expirationSeconds: csr.DurationToExpirationSeconds(-time.Minute),
   435  			want:              10 * time.Minute,
   436  		},
   437  		{
   438  			name:              "cannot request duration less than 10 mins",
   439  			certTTL:           time.Hour,
   440  			expirationSeconds: csr.DurationToExpirationSeconds(10*time.Minute - time.Second),
   441  			want:              10 * time.Minute,
   442  		},
   443  		{
   444  			name:              "can request duration of exactly 10 mins",
   445  			certTTL:           time.Hour,
   446  			expirationSeconds: csr.DurationToExpirationSeconds(10 * time.Minute),
   447  			want:              10 * time.Minute,
   448  		},
   449  		{
   450  			name:              "can request duration equal to the default",
   451  			certTTL:           time.Hour,
   452  			expirationSeconds: csr.DurationToExpirationSeconds(time.Hour),
   453  			want:              time.Hour,
   454  		},
   455  		{
   456  			name:              "can choose not to request a duration to get the default",
   457  			certTTL:           time.Hour,
   458  			expirationSeconds: nil,
   459  			want:              time.Hour,
   460  		},
   461  	}
   462  	for _, tt := range tests {
   463  		tt := tt
   464  
   465  		t.Run(tt.name, func(t *testing.T) {
   466  			t.Parallel()
   467  
   468  			s := &signer{
   469  				certTTL: tt.certTTL,
   470  			}
   471  			if got := s.duration(tt.expirationSeconds); got != tt.want {
   472  				t.Errorf("duration() = %v, want %v", got, tt.want)
   473  			}
   474  		})
   475  	}
   476  }
   477  

View as plain text