sigs.k8s.io/controller-runtime@v0.18.2/pkg/internal/testing/certs/tinyca_test.go (about) 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 })