Skip to content

Commit f3e97ad

Browse files
author
Mengqi Yu
committed
reuse CA private key
1 parent 0d3ceb8 commit f3e97ad

File tree

14 files changed

+342
-101
lines changed

14 files changed

+342
-101
lines changed

pkg/webhook/internal/cert/generator/certgenerator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type Artifacts struct {
2323
Key []byte
2424
// PEM encoded serving certificate
2525
Cert []byte
26+
// PEM encoded CA private key
27+
CAKey []byte
2628
// PEM encoded CA certificate
2729
CACert []byte
2830
}
@@ -31,4 +33,6 @@ type Artifacts struct {
3133
type CertGenerator interface {
3234
// Generate returns a Artifacts struct.
3335
Generate(CommonName string) (*Artifacts, error)
36+
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
37+
SetCA(caKey, caCert []byte)
3438
}

pkg/webhook/internal/cert/generator/fake/certgenerator.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,37 @@ limitations under the License.
1717
package fake
1818

1919
import (
20+
"bytes"
2021
"fmt"
2122

2223
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
2324
)
2425

2526
// CertGenerator is a certGenerator for testing.
2627
type CertGenerator struct {
28+
CAKey []byte
29+
CACert []byte
2730
DNSNameToCertArtifacts map[string]*generator.Artifacts
2831
}
2932

3033
var _ generator.CertGenerator = &CertGenerator{}
3134

35+
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
36+
func (cp *CertGenerator) SetCA(CAKey, CACert []byte) {
37+
cp.CAKey = CAKey
38+
cp.CACert = CACert
39+
}
40+
3241
// Generate generates certificates by matching a common name.
3342
func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) {
3443
certs, found := cp.DNSNameToCertArtifacts[commonName]
3544
if !found {
3645
return nil, fmt.Errorf("failed to find common name %q in the certGenerator", commonName)
3746
}
47+
if cp.CAKey != nil && cp.CACert != nil &&
48+
!bytes.Contains(cp.CAKey, []byte("invalid")) && !bytes.Contains(cp.CACert, []byte("invalid")) {
49+
certs.CAKey = cp.CAKey
50+
certs.CACert = cp.CACert
51+
}
3852
return certs, nil
3953
}

pkg/webhook/internal/cert/generator/selfsigned.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package generator
1818

1919
import (
20+
"crypto/rsa"
2021
"crypto/x509"
2122
"fmt"
23+
"time"
2224

2325
"k8s.io/client-go/util/cert"
2426
)
@@ -30,24 +32,42 @@ func ServiceToCommonName(serviceNamespace, serviceName string) string {
3032

3133
// SelfSignedCertGenerator implements the certGenerator interface.
3234
// It provisions self-signed certificates.
33-
type SelfSignedCertGenerator struct{}
35+
type SelfSignedCertGenerator struct {
36+
caKey []byte
37+
caCert []byte
38+
}
3439

3540
var _ CertGenerator = &SelfSignedCertGenerator{}
3641

42+
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
43+
func (cp *SelfSignedCertGenerator) SetCA(caKey, caCert []byte) {
44+
cp.caKey = caKey
45+
cp.caCert = caCert
46+
}
47+
3748
// Generate creates and returns a CA certificate, certificate and
3849
// key for the server. serverKey and serverCert are used by the server
3950
// to establish trust for clients, CA certificate is used by the
4051
// client to verify the server authentication chain.
4152
// The cert will be valid for 365 days.
4253
func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, error) {
43-
signingKey, err := cert.NewPrivateKey()
44-
if err != nil {
45-
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
46-
}
47-
signingCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "webhook-cert-ca"}, signingKey)
48-
if err != nil {
49-
return nil, fmt.Errorf("failed to create the CA cert: %v", err)
54+
var signingKey *rsa.PrivateKey
55+
var signingCert *x509.Certificate
56+
var valid bool
57+
var err error
58+
59+
valid, signingKey, signingCert = cp.validCACert()
60+
if !valid {
61+
signingKey, err = cert.NewPrivateKey()
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
64+
}
65+
signingCert, err = cert.NewSelfSignedCACert(cert.Config{CommonName: "webhook-cert-ca"}, signingKey)
66+
if err != nil {
67+
return nil, fmt.Errorf("failed to create the CA cert: %v", err)
68+
}
5069
}
70+
5171
key, err := cert.NewPrivateKey()
5272
if err != nil {
5373
return nil, fmt.Errorf("failed to create the private key: %v", err)
@@ -65,6 +85,33 @@ func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, erro
6585
return &Artifacts{
6686
Key: cert.EncodePrivateKeyPEM(key),
6787
Cert: cert.EncodeCertPEM(signedCert),
88+
CAKey: cert.EncodePrivateKeyPEM(signingKey),
6889
CACert: cert.EncodeCertPEM(signingCert),
6990
}, nil
7091
}
92+
93+
func (cp *SelfSignedCertGenerator) validCACert() (bool, *rsa.PrivateKey, *x509.Certificate) {
94+
if !ValidCACert(cp.caKey, cp.caCert, cp.caCert, "",
95+
time.Now().AddDate(1, 0, 0)) {
96+
return false, nil, nil
97+
}
98+
99+
var ok bool
100+
key, err := cert.ParsePrivateKeyPEM(cp.caKey)
101+
if err != nil {
102+
return false, nil, nil
103+
}
104+
privateKey, ok := key.(*rsa.PrivateKey)
105+
if !ok {
106+
return false, nil, nil
107+
}
108+
109+
certs, err := cert.ParseCertsPEM(cp.caCert)
110+
if err != nil {
111+
return false, nil, nil
112+
}
113+
if len(certs) != 1 {
114+
return false, nil, nil
115+
}
116+
return true, privateKey, certs[0]
117+
}

pkg/webhook/internal/cert/generator/selfsigned_test.go

Lines changed: 110 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,116 @@ package generator
1919
import (
2020
"crypto/x509"
2121
"encoding/pem"
22-
"testing"
22+
23+
. "github.com/onsi/ginkgo"
24+
. "github.com/onsi/gomega"
2325
)
2426

25-
func TestProvisionServingCert(t *testing.T) {
27+
var _ = Describe("Cert Generator", func() {
2628
cn := "mysvc.myns.svc"
27-
cp := SelfSignedCertGenerator{}
28-
certs, _ := cp.Generate(cn)
29-
30-
// First, create the set of root certificates. For this example we only
31-
// have one. It's also possible to omit this in order to use the
32-
// default root set of the current operating system.
33-
roots := x509.NewCertPool()
34-
ok := roots.AppendCertsFromPEM(certs.CACert)
35-
if !ok {
36-
t.Fatalf("failed to parse root certificate: %s", certs.CACert)
37-
}
38-
39-
block, _ := pem.Decode(certs.Cert)
40-
if block == nil {
41-
t.Fatalf("failed to parse certificate PEM: %s", certs.Cert)
42-
}
43-
cert, err := x509.ParseCertificate(block.Bytes)
44-
if err != nil {
45-
t.Fatalf("failed to parse certificate: %v", err)
46-
}
47-
48-
opts := x509.VerifyOptions{
49-
DNSName: cn,
50-
Roots: roots,
51-
}
52-
53-
if _, err := cert.Verify(opts); err != nil {
54-
t.Fatalf("failed to verify certificate: %v", err)
55-
}
56-
}
29+
Describe("CA doesn't exist", func() {
30+
It("should generate CA", func() {
31+
cp := SelfSignedCertGenerator{}
32+
certs, err := cp.Generate(cn)
33+
Expect(err).NotTo(HaveOccurred())
34+
35+
// First, create the set of root certificates. For this example we only
36+
// have one. It's also possible to omit this in order to use the
37+
// default root set of the current operating system.
38+
roots := x509.NewCertPool()
39+
ok := roots.AppendCertsFromPEM(certs.CACert)
40+
Expect(ok).To(BeTrue())
41+
42+
block, _ := pem.Decode(certs.Cert)
43+
Expect(block).NotTo(BeNil())
44+
45+
cert, err := x509.ParseCertificate(block.Bytes)
46+
Expect(err).NotTo(HaveOccurred())
47+
48+
opts := x509.VerifyOptions{
49+
DNSName: cn,
50+
Roots: roots,
51+
}
52+
53+
_, err = cert.Verify(opts)
54+
Expect(err).NotTo(HaveOccurred())
55+
})
56+
})
57+
58+
Describe("CA doesn't exist", func() {
59+
Context("CA is valid", func() {
60+
It("should reuse existing CA", func() {
61+
cp := SelfSignedCertGenerator{}
62+
certs, err := cp.Generate("foo.example.com")
63+
Expect(err).NotTo(HaveOccurred())
64+
65+
cp = SelfSignedCertGenerator{}
66+
cp.SetCA(certs.CAKey, certs.CACert)
67+
certs, err = cp.Generate(cn)
68+
Expect(err).NotTo(HaveOccurred())
69+
70+
Expect(certs.CAKey).To(Equal(cp.caKey))
71+
Expect(certs.CACert).To(Equal(cp.caCert))
72+
73+
// First, create the set of root certificates. For this example we only
74+
// have one. It's also possible to omit this in order to use the
75+
// default root set of the current operating system.
76+
roots := x509.NewCertPool()
77+
ok := roots.AppendCertsFromPEM(certs.CACert)
78+
Expect(ok).To(BeTrue())
79+
80+
block, _ := pem.Decode(certs.Cert)
81+
Expect(block).NotTo(BeNil())
82+
83+
cert, err := x509.ParseCertificate(block.Bytes)
84+
Expect(err).NotTo(HaveOccurred())
85+
86+
opts := x509.VerifyOptions{
87+
DNSName: cn,
88+
Roots: roots,
89+
}
90+
91+
_, err = cert.Verify(opts)
92+
Expect(err).NotTo(HaveOccurred())
93+
})
94+
})
95+
96+
Context("CA is invalid", func() {
97+
It("should reuse existing CA", func() {
98+
cp := SelfSignedCertGenerator{}
99+
certs, err := cp.Generate("foo.example.com")
100+
Expect(err).NotTo(HaveOccurred())
101+
102+
cp = SelfSignedCertGenerator{}
103+
cp.SetCA([]byte("invalidCAKey"), []byte("invalidCACert"))
104+
105+
certs, err = cp.Generate(cn)
106+
Expect(err).NotTo(HaveOccurred())
107+
108+
Expect(certs.CAKey).NotTo(Equal(cp.caKey))
109+
Expect(certs.CACert).NotTo(Equal(cp.caCert))
110+
111+
// First, create the set of root certificates. For this example we only
112+
// have one. It's also possible to omit this in order to use the
113+
// default root set of the current operating system.
114+
roots := x509.NewCertPool()
115+
ok := roots.AppendCertsFromPEM(certs.CACert)
116+
Expect(ok).To(BeTrue())
117+
118+
block, _ := pem.Decode(certs.Cert)
119+
Expect(block).NotTo(BeNil())
120+
121+
cert, err := x509.ParseCertificate(block.Bytes)
122+
Expect(err).NotTo(HaveOccurred())
123+
124+
opts := x509.VerifyOptions{
125+
DNSName: cn,
126+
Roots: roots,
127+
}
128+
129+
_, err = cert.Verify(opts)
130+
Expect(err).NotTo(HaveOccurred())
131+
})
132+
})
133+
})
134+
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2018 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 generator
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
25+
"sigs.k8s.io/controller-runtime/pkg/envtest"
26+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
27+
)
28+
29+
func TestSource(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecsWithDefaultAndCustomReporters(t, "Cert Generator Test Suite", []Reporter{envtest.NewlineReporter{}})
32+
}
33+
34+
var _ = BeforeSuite(func(done Done) {
35+
logf.SetLogger(logf.ZapLoggerTo(GinkgoWriter, true))
36+
close(done)
37+
}, 60)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2018 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 generator
18+
19+
import (
20+
"crypto/tls"
21+
"crypto/x509"
22+
"encoding/pem"
23+
"time"
24+
)
25+
26+
// ValidCACert think cert and key are valid if they meet the following requirements:
27+
// - key and cert are valid pair
28+
// - caCert is the root ca of cert
29+
// - cert is for dnsName
30+
// - cert won't expire before time
31+
func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool {
32+
if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 {
33+
return false
34+
}
35+
// Verify key and cert are valid pair
36+
_, err := tls.X509KeyPair(cert, key)
37+
if err != nil {
38+
return false
39+
}
40+
41+
// Verify cert is valid for at least 1 year.
42+
pool := x509.NewCertPool()
43+
if !pool.AppendCertsFromPEM(caCert) {
44+
return false
45+
}
46+
block, _ := pem.Decode([]byte(cert))
47+
if block == nil {
48+
return false
49+
}
50+
c, err := x509.ParseCertificate(block.Bytes)
51+
if err != nil {
52+
return false
53+
}
54+
ops := x509.VerifyOptions{
55+
DNSName: dnsName,
56+
Roots: pool,
57+
CurrentTime: time,
58+
}
59+
_, err = c.Verify(ops)
60+
return err == nil
61+
}

0 commit comments

Comments
 (0)