Skip to content

Commit 848bc88

Browse files
committed
Tests for TinyCA
This adds a full test suite for TinyCA, which was missing before, and is especially needed with the new envtest client cert generation.
1 parent b0ed0a9 commit 848bc88

File tree

2 files changed

+255
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)