Skip to content

Commit 365c04b

Browse files
authored
Merge pull request #22 from mengqiy/webhook
cert provisioner
2 parents 2a2c581 + c745b80 commit 365c04b

17 files changed

+1888
-30
lines changed

pkg/admission/certprovisioner/certprovisioner.go renamed to pkg/admission/cert/generator/certgenerator.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package certprovisioner
17+
package generator
1818

19-
// Certs hosts a private key, its corresponding serving certificate and
19+
// Artifacts hosts a private key, its corresponding serving certificate and
2020
// the CA certificate that signs the serving certificate.
21-
type Certs struct {
21+
type Artifacts struct {
2222
Key []byte
2323
Cert []byte
2424
CACert []byte
2525
}
2626

27-
// CertProvisioner is an interface to provision the serving certificate.
28-
type CertProvisioner interface {
29-
// ProvisionServingCert returns a Certs struct.
30-
ProvisionServingCert() (*Certs, error)
27+
// CertGenerator is an interface to provision the serving certificate.
28+
type CertGenerator interface {
29+
// Generate returns a Artifacts struct.
30+
Generate(CommonName string) (*Artifacts, error)
3131
}

pkg/admission/certprovisioner/certprovisioner_test.go renamed to pkg/admission/cert/generator/certgenerator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package certprovisioner
17+
package generator
1818

1919
import "fmt"
2020

pkg/admission/certprovisioner/doc.go renamed to pkg/admission/cert/generator/doc.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,16 @@ limitations under the License.
1515
*/
1616

1717
/*
18-
Package certprovisioner provides an interface and implementation to provision certificates.
18+
Package generator provides an interface and implementation to provision certificates.
1919
20-
Create a implementation instance of certprovisioner.
20+
Create an instance of CertGenerator.
2121
22-
cp := SelfSignedCertProvisioner{
23-
CommonName: "foo.bar.com"
24-
}
22+
cg := SelfSignedCertGenerator{}
2523
26-
Provision the certificates.
27-
certs, err := cp.ProvisionServingCert()
24+
Generate the certificates.
25+
certs, err := cg.Generate("foo.bar.com")
2826
if err != nil {
2927
// handle error
3028
}
3129
*/
32-
package certprovisioner
30+
package generator
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 fake
18+
19+
import (
20+
"fmt"
21+
22+
"sigs.k8s.io/controller-runtime/pkg/admission/cert/generator"
23+
)
24+
25+
// CertGenerator is a CertGenerator for testing.
26+
type CertGenerator struct {
27+
DNSNameToCertArtifacts map[string]*generator.Artifacts
28+
}
29+
30+
var _ generator.CertGenerator = &CertGenerator{}
31+
32+
// Generate generates certificates by matching a common name.
33+
func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) {
34+
certs, found := cp.DNSNameToCertArtifacts[commonName]
35+
if !found {
36+
return nil, fmt.Errorf("failed to find common name %q in the CertGenerator", commonName)
37+
}
38+
return certs, nil
39+
}

pkg/admission/certprovisioner/selfsignedcertprovisioner.go renamed to pkg/admission/cert/generator/selfsigned.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package certprovisioner
17+
package generator
1818

1919
import (
2020
"crypto/x509"
@@ -28,21 +28,18 @@ func ServiceToCommonName(serviceNamespace, serviceName string) string {
2828
return fmt.Sprintf("%s.%s.svc", serviceName, serviceNamespace)
2929
}
3030

31-
// SelfSignedCertProvisioner implements the CertProvisioner interface.
31+
// SelfSignedCertGenerator implements the CertGenerator interface.
3232
// It provisions self-signed certificates.
33-
type SelfSignedCertProvisioner struct {
34-
// Required Common Name
35-
CommonName string
36-
}
33+
type SelfSignedCertGenerator struct{}
3734

38-
var _ CertProvisioner = &SelfSignedCertProvisioner{}
35+
var _ CertGenerator = &SelfSignedCertGenerator{}
3936

40-
// ProvisionServingCert creates and returns a CA certificate, certificate and
37+
// Generate creates and returns a CA certificate, certificate and
4138
// key for the server. serverKey and serverCert are used by the server
4239
// to establish trust for clients, CA certificate is used by the
4340
// client to verify the server authentication chain.
4441
// The cert will be valid for 365 days.
45-
func (cp *SelfSignedCertProvisioner) ProvisionServingCert() (*Certs, error) {
42+
func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, error) {
4643
signingKey, err := cert.NewPrivateKey()
4744
if err != nil {
4845
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
@@ -57,15 +54,15 @@ func (cp *SelfSignedCertProvisioner) ProvisionServingCert() (*Certs, error) {
5754
}
5855
signedCert, err := cert.NewSignedCert(
5956
cert.Config{
60-
CommonName: cp.CommonName,
57+
CommonName: commonName,
6158
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
6259
},
6360
key, signingCert, signingKey,
6461
)
6562
if err != nil {
6663
return nil, fmt.Errorf("failed to create the cert: %v", err)
6764
}
68-
return &Certs{
65+
return &Artifacts{
6966
Key: cert.EncodePrivateKeyPEM(key),
7067
Cert: cert.EncodeCertPEM(signedCert),
7168
CACert: cert.EncodeCertPEM(signingCert),

pkg/admission/certprovisioner/selfsignedcertprovisioner_test.go renamed to pkg/admission/cert/generator/selfsigned_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package certprovisioner
17+
package generator
1818

1919
import (
2020
"crypto/x509"
@@ -24,8 +24,8 @@ import (
2424

2525
func TestProvisionServingCert(t *testing.T) {
2626
cn := "mysvc.myns.svc"
27-
cp := SelfSignedCertProvisioner{CommonName: cn}
28-
certs, _ := cp.ProvisionServingCert()
27+
cp := SelfSignedCertGenerator{}
28+
certs, _ := cp.Generate(cn)
2929

3030
// First, create the set of root certificates. For this example we only
3131
// have one. It's also possible to omit this in order to use the
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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 writer
18+
19+
import (
20+
"bytes"
21+
"crypto/tls"
22+
"errors"
23+
"fmt"
24+
"log"
25+
"net/url"
26+
27+
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
28+
apierrors "k8s.io/apimachinery/pkg/api/errors"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/admission/cert/generator"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
)
33+
34+
const (
35+
// CACertName is the name of the CA certificate
36+
CACertName = "ca-cert.pem"
37+
// ServerKeyName is the name of the server private key
38+
ServerKeyName = "key.pem"
39+
// ServerCertName is the name of the serving certificate
40+
ServerCertName = "cert.pem"
41+
)
42+
43+
// CertWriter provides method to handle webhooks.
44+
type CertWriter interface {
45+
// EnsureCert ensures that the webhooks have proper certificates.
46+
EnsureCerts(runtime.Object) error
47+
}
48+
49+
// Options are options for configuring a CertWriter.
50+
type Options struct {
51+
Client client.Client
52+
CertGenerator generator.CertGenerator
53+
}
54+
55+
// NewCertWriter builds a new CertWriter using the provided options.
56+
// By default, it builds a MultiCertWriter that is composed of a SecretCertWriter and a FSCertWriter.
57+
func NewCertWriter(ops Options) (CertWriter, error) {
58+
if ops.CertGenerator == nil {
59+
ops.CertGenerator = &generator.SelfSignedCertGenerator{}
60+
}
61+
if ops.Client == nil {
62+
// TODO: default the client if possible
63+
return nil, errors.New("Options.Client is required")
64+
}
65+
s := &SecretCertWriter{
66+
Client: ops.Client,
67+
CertGenerator: ops.CertGenerator,
68+
}
69+
//f := &FSCertWriter{
70+
// CertGenerator: ops.CertGenerator,
71+
//}
72+
return &MultiCertWriter{
73+
CertWriters: []CertWriter{
74+
s,
75+
//f,
76+
},
77+
}, nil
78+
}
79+
80+
// handleCommon ensures the given webhook has a proper certificate.
81+
// It uses the given certReadWriter to read and (or) write the certificate.
82+
func handleCommon(webhook *admissionregistrationv1beta1.Webhook, ch certReadWriter) error {
83+
if webhook == nil {
84+
return nil
85+
}
86+
if ch == nil {
87+
return errors.New("certReaderWriter should not be nil")
88+
}
89+
90+
certs, err := createIfNotExists(webhook.Name, ch)
91+
if err != nil {
92+
return err
93+
}
94+
95+
// Recreate the cert if it's invalid.
96+
if !validCert(certs) {
97+
log.Printf("cert is invalid or expiring, regenerating a new one")
98+
certs, err = ch.overwrite(webhook.Name)
99+
if err != nil {
100+
return err
101+
}
102+
}
103+
104+
// Ensure the CA bundle in the webhook configuration has the signing CA.
105+
caBundle := webhook.ClientConfig.CABundle
106+
caCert := certs.CACert
107+
if !bytes.Contains(caBundle, caCert) {
108+
webhook.ClientConfig.CABundle = append(caBundle, caCert...)
109+
}
110+
return nil
111+
}
112+
113+
func createIfNotExists(webhookName string, ch certReadWriter) (*generator.Artifacts, error) {
114+
// Try to read first
115+
certs, err := ch.read(webhookName)
116+
if apierrors.IsNotFound(err) {
117+
// Create if not exists
118+
certs, err = ch.write(webhookName)
119+
switch {
120+
// This may happen if there is another racer.
121+
case apierrors.IsAlreadyExists(err):
122+
certs, err = ch.read(webhookName)
123+
if err != nil {
124+
return certs, err
125+
}
126+
case err != nil:
127+
return certs, err
128+
}
129+
} else if err != nil {
130+
return certs, err
131+
}
132+
return certs, nil
133+
}
134+
135+
// certReadWriter provides methods for reading and writing certificates.
136+
type certReadWriter interface {
137+
// read reads a wehbook name and returns the certs for it.
138+
read(webhookName string) (*generator.Artifacts, error)
139+
// write writes the certs and return the certs it wrote.
140+
write(webhookName string) (*generator.Artifacts, error)
141+
// overwrite overwrites the existing certs and return the certs it wrote.
142+
overwrite(webhookName string) (*generator.Artifacts, error)
143+
}
144+
145+
func validCert(certs *generator.Artifacts) bool {
146+
// TODO:
147+
// 1) validate the key and the cert are valid pair e.g. call crypto/tls.X509KeyPair()
148+
// 2) validate the cert with the CA cert
149+
// 3) validate the cert is for a certain DNSName
150+
// e.g.
151+
// c, err := tls.X509KeyPair(cert, key)
152+
// err := c.Verify(options)
153+
if certs == nil {
154+
return false
155+
}
156+
_, err := tls.X509KeyPair(certs.Cert, certs.Key)
157+
return err == nil
158+
}
159+
160+
func getWebhooksFromObject(obj runtime.Object) ([]admissionregistrationv1beta1.Webhook, error) {
161+
switch typed := obj.(type) {
162+
case *admissionregistrationv1beta1.MutatingWebhookConfiguration:
163+
return typed.Webhooks, nil
164+
case *admissionregistrationv1beta1.ValidatingWebhookConfiguration:
165+
return typed.Webhooks, nil
166+
//case *unstructured.Unstructured:
167+
// TODO: implement this if needed
168+
default:
169+
return nil, fmt.Errorf("unsupported type: %T, only support v1beta1.MutatingWebhookConfiguration and v1beta1.ValidatingWebhookConfiguration", typed)
170+
}
171+
}
172+
173+
func dnsNameForWebhook(config *admissionregistrationv1beta1.WebhookClientConfig) (string, error) {
174+
if config.Service != nil && config.URL != nil {
175+
return "", fmt.Errorf("service and URL can't be set at the same time in a webhook: %v", config)
176+
}
177+
if config.Service == nil && config.URL == nil {
178+
return "", fmt.Errorf("one of service and URL need to be set in a webhook: %v", config)
179+
}
180+
if config.Service != nil {
181+
return generator.ServiceToCommonName(config.Service.Namespace, config.Service.Name), nil
182+
}
183+
// config.URL != nil
184+
u, err := url.Parse(*config.URL)
185+
return u.Host, err
186+
187+
}

0 commit comments

Comments
 (0)