...

Source file src/sigs.k8s.io/controller-runtime/pkg/internal/testing/certs/tinyca_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/internal/testing/certs

     1  /*
     2  Copyright 2021 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 certs_test
    18  
    19  import (
    20  	"crypto/x509"
    21  	"encoding/pem"
    22  	"math/big"
    23  	"net"
    24  	"sort"
    25  	"time"
    26  
    27  	. "github.com/onsi/ginkgo/v2"
    28  	. "github.com/onsi/gomega"
    29  	. "github.com/onsi/gomega/gstruct"
    30  
    31  	"sigs.k8s.io/controller-runtime/pkg/internal/testing/certs"
    32  )
    33  
    34  var _ = Describe("TinyCA", func() {
    35  	var ca *certs.TinyCA
    36  
    37  	BeforeEach(func() {
    38  		var err error
    39  		ca, err = certs.NewTinyCA()
    40  		Expect(err).NotTo(HaveOccurred(), "should be able to initialize the CA")
    41  	})
    42  
    43  	Describe("the CA certs themselves", func() {
    44  		It("should be retrievable as a cert pair", func() {
    45  			Expect(ca.CA.Key).NotTo(BeNil(), "should have a key")
    46  			Expect(ca.CA.Cert).NotTo(BeNil(), "should have a cert")
    47  		})
    48  
    49  		It("should be usable for signing & verifying", func() {
    50  			Expect(ca.CA.Cert.KeyUsage&x509.KeyUsageCertSign).NotTo(BeEquivalentTo(0), "should be usable for cert signing")
    51  			Expect(ca.CA.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
    52  		})
    53  	})
    54  
    55  	It("should produce unique serials among all generated certificates of all types", func() {
    56  		By("generating a few cert pairs for both serving and client auth")
    57  		firstCerts, err := ca.NewServingCert()
    58  		Expect(err).NotTo(HaveOccurred())
    59  		secondCerts, err := ca.NewClientCert(certs.ClientInfo{Name: "user"})
    60  		Expect(err).NotTo(HaveOccurred())
    61  		thirdCerts, err := ca.NewServingCert()
    62  		Expect(err).NotTo(HaveOccurred())
    63  
    64  		By("checking that they have different serials")
    65  		serials := []*big.Int{
    66  			firstCerts.Cert.SerialNumber,
    67  			secondCerts.Cert.SerialNumber,
    68  			thirdCerts.Cert.SerialNumber,
    69  		}
    70  		// quick uniqueness check of numbers: sort, then you only have to compare sequential entries
    71  		sort.Slice(serials, func(i, j int) bool {
    72  			return serials[i].Cmp(serials[j]) == -1
    73  		})
    74  		Expect(serials[1].Cmp(serials[0])).NotTo(Equal(0), "serials shouldn't be equal")
    75  		Expect(serials[2].Cmp(serials[1])).NotTo(Equal(0), "serials shouldn't be equal")
    76  	})
    77  
    78  	Describe("Generated serving certs", func() {
    79  		It("should be valid for short enough to avoid production usage, but long enough for long-running tests", func() {
    80  			cert, err := ca.NewServingCert()
    81  			Expect(err).NotTo(HaveOccurred(), "should be able to generate the serving certs")
    82  
    83  			duration := time.Until(cert.Cert.NotAfter)
    84  			Expect(duration).To(BeNumerically("<=", 168*time.Hour), "not-after should be short-ish (<= 1 week)")
    85  			Expect(duration).To(BeNumerically(">=", 2*time.Hour), "not-after should be enough for long tests (couple of hours)")
    86  		})
    87  
    88  		Context("when encoding names", func() {
    89  			var cert certs.CertPair
    90  			BeforeEach(func() {
    91  				By("generating a serving cert with IPv4 & IPv6 addresses, and DNS names")
    92  				var err error
    93  				// IPs are in the "example & docs" blocks for IPv4 (TEST-NET-1) & IPv6
    94  				cert, err = ca.NewServingCert("192.0.2.1", "localhost", "2001:db8::")
    95  				Expect(err).NotTo(HaveOccurred(), "should be able to create the serving certs")
    96  			})
    97  
    98  			It("should encode all non-IP names as DNS SANs", func() {
    99  				Expect(cert.Cert.DNSNames).To(ConsistOf("localhost"))
   100  			})
   101  
   102  			It("should encode all IP names as IP SANs", func() {
   103  				// NB(directxman12): this is non-exhaustive because we also
   104  				// convert DNS SANs to IPs too (see test below)
   105  				Expect(cert.Cert.IPAddresses).To(ContainElements(
   106  					// normalize the elements with To16 so we can compare them to the output of
   107  					// of ParseIP safely (the alternative is a custom matcher that calls Equal,
   108  					// but this is easier)
   109  					WithTransform(net.IP.To16, Equal(net.ParseIP("192.0.2.1"))),
   110  					WithTransform(net.IP.To16, Equal(net.ParseIP("2001:db8::"))),
   111  				))
   112  			})
   113  
   114  			It("should add the corresponding IP address(es) (as IP SANs) for DNS names", func() {
   115  				// NB(directxman12): we currently fail if the lookup fails.
   116  				// I'm not certain this is the best idea (both the bailing on
   117  				// error and the actual idea), so if this causes issues, you
   118  				// might want to reconsider.
   119  
   120  				localhostAddrs, err := net.LookupHost("localhost")
   121  				Expect(err).NotTo(HaveOccurred(), "should be able to find IPs for localhost")
   122  				localhostIPs := make([]interface{}, len(localhostAddrs))
   123  				for i, addr := range localhostAddrs {
   124  					// normalize the elements with To16 so we can compare them to the output of
   125  					// of ParseIP safely (the alternative is a custom matcher that calls Equal,
   126  					// but this is easier)
   127  					localhostIPs[i] = WithTransform(net.IP.To16, Equal(net.ParseIP(addr)))
   128  				}
   129  				Expect(cert.Cert.IPAddresses).To(ContainElements(localhostIPs...))
   130  			})
   131  		})
   132  
   133  		It("should assume a name of localhost (DNS SAN) if no names are given", func() {
   134  			cert, err := ca.NewServingCert()
   135  			Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert with the default name")
   136  			Expect(cert.Cert.DNSNames).To(ConsistOf("localhost"), "the default DNS name should be localhost")
   137  
   138  		})
   139  
   140  		It("should be usable for server auth, verifying, and enciphering", func() {
   141  			cert, err := ca.NewServingCert()
   142  			Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert")
   143  
   144  			Expect(cert.Cert.KeyUsage&x509.KeyUsageKeyEncipherment).NotTo(BeEquivalentTo(0), "should be usable for key enciphering")
   145  			Expect(cert.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
   146  			Expect(cert.Cert.ExtKeyUsage).To(ContainElement(x509.ExtKeyUsageServerAuth), "should be usable for server auth")
   147  
   148  		})
   149  
   150  		It("should be signed by the CA", func() {
   151  			cert, err := ca.NewServingCert()
   152  			Expect(err).NotTo(HaveOccurred(), "should be able to generate a serving cert")
   153  			Expect(cert.Cert.CheckSignatureFrom(ca.CA.Cert)).To(Succeed())
   154  		})
   155  	})
   156  
   157  	Describe("Generated client certs", func() {
   158  		var cert certs.CertPair
   159  		BeforeEach(func() {
   160  			var err error
   161  			cert, err = ca.NewClientCert(certs.ClientInfo{
   162  				Name:   "user",
   163  				Groups: []string{"group1", "group2"},
   164  			})
   165  			Expect(err).NotTo(HaveOccurred(), "should be able to create a client cert")
   166  		})
   167  
   168  		It("should be valid for short enough to avoid production usage, but long enough for long-running tests", func() {
   169  			duration := time.Until(cert.Cert.NotAfter)
   170  			Expect(duration).To(BeNumerically("<=", 168*time.Hour), "not-after should be short-ish (<= 1 week)")
   171  			Expect(duration).To(BeNumerically(">=", 2*time.Hour), "not-after should be enough for long tests (couple of hours)")
   172  		})
   173  
   174  		It("should be usable for client auth, verifying, and enciphering", func() {
   175  			Expect(cert.Cert.KeyUsage&x509.KeyUsageKeyEncipherment).NotTo(BeEquivalentTo(0), "should be usable for key enciphering")
   176  			Expect(cert.Cert.KeyUsage&x509.KeyUsageDigitalSignature).NotTo(BeEquivalentTo(0), "should be usable for signature verifying")
   177  			Expect(cert.Cert.ExtKeyUsage).To(ContainElement(x509.ExtKeyUsageClientAuth), "should be usable for client auth")
   178  		})
   179  
   180  		It("should encode the user name as the common name", func() {
   181  			Expect(cert.Cert.Subject.CommonName).To(Equal("user"))
   182  		})
   183  
   184  		It("should encode the groups as the organization values", func() {
   185  			Expect(cert.Cert.Subject.Organization).To(ConsistOf("group1", "group2"))
   186  		})
   187  
   188  		It("should be signed by the CA", func() {
   189  			Expect(cert.Cert.CheckSignatureFrom(ca.CA.Cert)).To(Succeed())
   190  		})
   191  	})
   192  })
   193  
   194  var _ = Describe("Certificate Pairs", func() {
   195  	var pair certs.CertPair
   196  	BeforeEach(func() {
   197  		ca, err := certs.NewTinyCA()
   198  		Expect(err).NotTo(HaveOccurred(), "should be able to generate a cert pair")
   199  
   200  		pair = ca.CA
   201  	})
   202  
   203  	Context("when serializing just the public key", func() {
   204  		It("should serialize into a CERTIFICATE PEM block", func() {
   205  			bytes := pair.CertBytes()
   206  			Expect(bytes).NotTo(BeEmpty(), "should produce some cert bytes")
   207  
   208  			block, rest := pem.Decode(bytes)
   209  			Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
   210  
   211  			Expect(block).To(PointTo(MatchAllFields(Fields{
   212  				"Type":    Equal("CERTIFICATE"),
   213  				"Headers": BeEmpty(),
   214  				"Bytes":   Equal(pair.Cert.Raw),
   215  			})))
   216  		})
   217  	})
   218  
   219  	Context("when serializing both parts", func() {
   220  		var certBytes, keyBytes []byte
   221  		BeforeEach(func() {
   222  			var err error
   223  			certBytes, keyBytes, err = pair.AsBytes()
   224  			Expect(err).NotTo(HaveOccurred(), "should be able to serialize the pair")
   225  		})
   226  
   227  		It("should serialize the private key in PKCS8 form in a PRIVATE KEY PEM block", func() {
   228  			Expect(keyBytes).NotTo(BeEmpty(), "should produce some key bytes")
   229  
   230  			By("decoding & checking the PEM block")
   231  			block, rest := pem.Decode(keyBytes)
   232  			Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
   233  
   234  			Expect(block.Type).To(Equal("PRIVATE KEY"))
   235  
   236  			By("decoding & checking the PKCS8 data")
   237  			Expect(x509.ParsePKCS8PrivateKey(block.Bytes)).NotTo(BeNil(), "should be able to parse back the private key")
   238  		})
   239  
   240  		It("should serialize the public key into a CERTIFICATE PEM block", func() {
   241  			Expect(certBytes).NotTo(BeEmpty(), "should produce some cert bytes")
   242  
   243  			block, rest := pem.Decode(certBytes)
   244  			Expect(rest).To(BeEmpty(), "shouldn't have any data besides the PEM block")
   245  
   246  			Expect(block).To(PointTo(MatchAllFields(Fields{
   247  				"Type":    Equal("CERTIFICATE"),
   248  				"Headers": BeEmpty(),
   249  				"Bytes":   Equal(pair.Cert.Raw),
   250  			})))
   251  		})
   252  
   253  	})
   254  })
   255  

View as plain text