-
Notifications
You must be signed in to change notification settings - Fork 211
/
certwriter.go
153 lines (135 loc) · 4.42 KB
/
certwriter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package writer
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/generator"
)
const (
// CAKeyName is the name of the CA private key
CAKeyName = "ca-key.pem"
// CACertName is the name of the CA certificate
CACertName = "ca-cert.pem"
// ServerKeyName is the name of the server private key
ServerKeyName = "key.pem"
// ServerCertName is the name of the serving certificate
ServerCertName = "cert.pem"
)
// CertWriter provides method to handle webhooks.
type CertWriter interface {
// EnsureCert provisions the cert for the webhookClientConfig.
EnsureCert(dnsName string) (*generator.Artifacts, bool, error)
// Inject injects the necessary information given the objects.
// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
Inject(objs ...client.Object) error
}
// handleCommon ensures the given webhook has a proper certificate.
// It uses the given certReadWriter to read and (or) write the certificate.
func handleCommon(dnsName string, ch certReadWriter) (*generator.Artifacts, bool, error) {
if len(dnsName) == 0 {
return nil, false, errors.New("dnsName should not be empty")
}
if ch == nil {
return nil, false, errors.New("certReaderWriter should not be nil")
}
certs, changed, err := createIfNotExists(ch)
if err != nil {
return nil, changed, err
}
// Recreate the cert if it's invalid.
valid := validCert(certs, dnsName)
if !valid {
log.Info("cert is invalid or expiring, regenerating a new one")
certs, err = ch.overwrite()
if err != nil {
return nil, false, err
}
changed = true
}
return certs, changed, nil
}
func createIfNotExists(ch certReadWriter) (*generator.Artifacts, bool, error) {
// Try to read first
certs, err := ch.read()
if isNotFound(err) {
// Create if not exists
certs, err = ch.write()
switch {
// This may happen if there is another racer.
case isAlreadyExists(err):
certs, err = ch.read()
return certs, true, err
default:
return certs, true, err
}
}
return certs, false, err
}
// certReadWriter provides methods for reading and writing certificates.
type certReadWriter interface {
// read reads a webhook name and returns the certs for it.
read() (*generator.Artifacts, error)
// write writes the certs and return the certs it wrote.
write() (*generator.Artifacts, error)
// overwrite overwrites the existing certs and return the certs it wrote.
overwrite() (*generator.Artifacts, error)
}
// validCert verifies if the certificate is valid, including
// additional verifications to ensure compatibility with Kubernetes
// and it's default HTTP client.
func validCert(certs *generator.Artifacts, dnsName string) bool {
if certs == nil {
return false
}
// Verify key and cert are valid pair
_, err := tls.X509KeyPair(certs.Cert, certs.Key)
if err != nil {
return false
}
// Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time.
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(certs.CACert) {
return false
}
block, _ := pem.Decode([]byte(certs.Cert))
if block == nil {
return false
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return false
}
ops := x509.VerifyOptions{
DNSName: dnsName,
Roots: pool,
CurrentTime: time.Now().AddDate(0, 6, 0),
}
_, err = cert.Verify(ops)
if err != nil {
return false
}
return DoesCertificateWorkWithK8sAPIClient(cert)
}
// DoesCertificateWorkWithK8sAPIClient returns false if the certificate
// is not compatible with Kubernetes HTTP clients.
func DoesCertificateWorkWithK8sAPIClient(cert *x509.Certificate) bool {
// check to see if the cert has a DNSName. Certificates that
// do not will not be considered valid for Kubernetes distributions built
// with go 1.15 or higher.
return len(cert.DNSNames) > 0
}