forked from kubernetes-sigs/controller-runtime
/
provisioner.go
133 lines (118 loc) · 4.6 KB
/
provisioner.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
/*
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 cert
import (
"bytes"
"errors"
"fmt"
"net"
"net/url"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
)
// Provisioner provisions certificates for webhook configurations and writes them to an output
// destination - such as a Secret or local file. Provisioner can update the CA field of
// certain resources with the CA of the certs.
type Provisioner struct {
// CertWriter knows how to persist the certificate.
CertWriter writer.CertWriter
}
// Options are options for provisioning the certificate.
type Options struct {
// ClientConfig is the WebhookClientCert that contains the information to generate
// the certificate. The CA Certificate will be updated in the ClientConfig.
// The updated ClientConfig will be used to inject into other runtime.Objects,
// e.g. MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
ClientConfig *admissionregistrationv1beta1.WebhookClientConfig
// Objects are the objects that will use the ClientConfig above.
Objects []runtime.Object
}
// Provision provisions certificates for the WebhookClientConfig.
// It ensures the cert and CA are valid and not expiring.
// It updates the CABundle in the webhookClientConfig if necessary.
// It inject the WebhookClientConfig into options.Objects.
func (cp *Provisioner) Provision(options Options) (bool, error) {
if cp.CertWriter == nil {
return false, errors.New("CertWriter need to be set")
}
dnsName, err := dnsNameFromClientConfig(options.ClientConfig)
if err != nil {
return false, err
}
certs, changed, err := cp.CertWriter.EnsureCert(dnsName)
if err != nil {
return false, err
}
caBundle := options.ClientConfig.CABundle
caCert := certs.CACert
// TODO(mengqiy): limit the size of the CABundle by GC the old CA certificate
// this is important since the max record size in etcd is 1MB (latest version is 1.5MB).
if !bytes.Contains(caBundle, caCert) {
// Ensure the CA bundle in the webhook configuration has the signing CA.
options.ClientConfig.CABundle = append(caBundle, caCert...)
changed = true
}
return changed, cp.inject(options.ClientConfig, options.Objects)
}
// Inject the ClientConfig to the objects.
// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
func (cp *Provisioner) inject(cc *admissionregistrationv1beta1.WebhookClientConfig, objs []runtime.Object) error {
if cc == nil {
return nil
}
for i := range objs {
switch typed := objs[i].(type) {
case *admissionregistrationv1beta1.MutatingWebhookConfiguration:
injectForEachWebhook(cc, typed.Webhooks)
case *admissionregistrationv1beta1.ValidatingWebhookConfiguration:
injectForEachWebhook(cc, typed.Webhooks)
default:
return fmt.Errorf("%#v is not supported for injecting a webhookClientConfig",
objs[i].GetObjectKind().GroupVersionKind())
}
}
return cp.CertWriter.Inject(objs...)
}
func injectForEachWebhook(
cc *admissionregistrationv1beta1.WebhookClientConfig,
webhooks []admissionregistrationv1beta1.Webhook) {
for i := range webhooks {
// only replacing the CA bundle to preserve the path in the WebhookClientConfig
webhooks[i].ClientConfig.CABundle = cc.CABundle
}
}
func dnsNameFromClientConfig(config *admissionregistrationv1beta1.WebhookClientConfig) (string, error) {
if config == nil {
return "", errors.New("clientConfig should not be empty")
}
if config.Service != nil && config.URL != nil {
return "", fmt.Errorf("service and URL can't be set at the same time in a webhook: %v", config)
}
if config.Service == nil && config.URL == nil {
return "", fmt.Errorf("one of service and URL need to be set in a webhook: %v", config)
}
if config.Service != nil {
return generator.ServiceToCommonName(config.Service.Namespace, config.Service.Name), nil
}
u, err := url.Parse(*config.URL)
if err != nil {
return "", err
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
return u.Host, nil
}
return host, err
}