@@ -17,13 +17,13 @@ limitations under the License.
17
17
package cert
18
18
19
19
import (
20
+ "bytes"
21
+ "errors"
20
22
"fmt"
21
- "reflect"
22
- "sync"
23
+ "net/url"
23
24
25
+ admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
24
26
"k8s.io/apimachinery/pkg/runtime"
25
- "sigs.k8s.io/controller-runtime/pkg/client"
26
- "sigs.k8s.io/controller-runtime/pkg/client/config"
27
27
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
28
28
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
29
29
)
@@ -32,62 +32,91 @@ import (
32
32
// destination - such as a Secret or local file. Provisioner can update the CA field of
33
33
// certain resources with the CA of the certs.
34
34
type Provisioner struct {
35
- Client client.Client
36
- // CertGenerator generates certificate for a given common name.
37
- CertGenerator generator.CertGenerator
38
35
// CertWriter knows how to persist the certificate.
39
36
CertWriter writer.CertWriter
37
+ }
40
38
41
- once sync.Once
39
+ // Options are options for provisioning the certificate.
40
+ type Options struct {
41
+ // ClientConfig is the WebhookClientCert that contains the information to generate
42
+ // the certificate. The CA Certificate will be updated in the ClientConfig.
43
+ // The updated ClientConfig will be used to inject into other runtime.Objects,
44
+ // e.g. MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
45
+ ClientConfig * admissionregistrationv1beta1.WebhookClientConfig
46
+ // Objects are the objects that will use the ClientConfig above.
47
+ Objects []runtime.Object
48
+ // Dryrun controls if the objects are sent to the API server or output in the io.Writer
49
+ Dryrun bool
42
50
}
43
51
44
- // Sync takes a runtime.Object which is expected to be either a MutatingWebhookConfiguration or
45
- // a ValidatingWebhookConfiguration.
46
- // It provisions certificate for each webhook in the webhookConfiguration, ensures the cert and CA are valid,
47
- // and not expiring. It updates the CABundle in the webhook configuration if necessary.
48
- func (cp * Provisioner ) Sync (webhookConfiguration runtime.Object ) error {
49
- var err error
50
- // Do the initialization for CertInput only once.
51
- cp .once .Do (func () {
52
- if cp .CertGenerator == nil {
53
- cp .CertGenerator = & generator.SelfSignedCertGenerator {}
54
- }
55
- if cp .Client == nil {
56
- cp .Client , err = client .New (config .GetConfigOrDie (), client.Options {})
57
- if err != nil {
58
- return
59
- }
60
- }
61
- if cp .CertWriter == nil {
62
- cp .CertWriter , err = writer .NewCertWriter (
63
- writer.Options {
64
- Client : cp .Client ,
65
- CertGenerator : cp .CertGenerator ,
66
- })
67
- if err != nil {
68
- return
69
- }
70
- }
71
- })
52
+ // Provision provisions certificates for for the WebhookClientConfig.
53
+ // It ensures the cert and CA are valid and not expiring.
54
+ // It updates the CABundle in the webhookClientConfig if necessary.
55
+ // It inject the WebhookClientConfig into options.Objects.
56
+ func (cp * Provisioner ) Provision (options Options ) (bool , error ) {
57
+ if cp .CertWriter == nil {
58
+ return false , errors .New ("CertWriter need to be set" )
59
+ }
60
+
61
+ dnsName , err := dnsNameFromClientConfig (options .ClientConfig )
72
62
if err != nil {
73
- return fmt . Errorf ( "failed to default the CertProvision: %v" , err )
63
+ return false , err
74
64
}
75
65
76
- // Deepcopy the webhook configuration object before invoking EnsureCerts,
77
- // since EnsureCerts will modify the provided object.
78
- cloned := webhookConfiguration .DeepCopyObject ()
79
- err = cp .CertWriter .EnsureCerts (webhookConfiguration )
66
+ certs , changed , err := cp .CertWriter .EnsureCert (dnsName , options .Dryrun )
80
67
if err != nil {
81
- return err
68
+ return false , err
82
69
}
83
70
84
- // If some fields have been changed, we will update the object.
85
- // Mostly this is because of the CABundle field has been updated.
86
- if reflect .DeepEqual (webhookConfiguration , cloned ) {
71
+ caBundle := options .ClientConfig .CABundle
72
+ caCert := certs .CACert
73
+ if ! bytes .Contains (caBundle , caCert ) {
74
+ // Ensure the CA bundle in the webhook configuration has the signing CA.
75
+ options .ClientConfig .CABundle = append (caBundle , caCert ... )
76
+ changed = true
77
+ }
78
+ return changed , cp .inject (options .ClientConfig , options .Objects )
79
+ }
80
+
81
+ // Inject the ClientConfig to the objects.
82
+ // It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
83
+ func (cp * Provisioner ) inject (cc * admissionregistrationv1beta1.WebhookClientConfig , objs []runtime.Object ) error {
84
+ if cc == nil {
87
85
return nil
88
86
}
89
- return nil
90
- // TODO: figure what we want to do with this.
91
- // Disable the auto-updating in this function.
92
- //return cp.Client.Update(context.Background(), cloned)
87
+ for i := range objs {
88
+ switch typed := objs [i ].(type ) {
89
+ case * admissionregistrationv1beta1.MutatingWebhookConfiguration :
90
+ injectForEachWebhook (cc , typed .Webhooks )
91
+ case * admissionregistrationv1beta1.ValidatingWebhookConfiguration :
92
+ injectForEachWebhook (cc , typed .Webhooks )
93
+ }
94
+ }
95
+ return cp .CertWriter .Inject (objs ... )
96
+ }
97
+
98
+ func injectForEachWebhook (
99
+ cc * admissionregistrationv1beta1.WebhookClientConfig ,
100
+ webhooks []admissionregistrationv1beta1.Webhook ) {
101
+ for i := range webhooks {
102
+ // only replacing the CA bundle to preserve the path in the WebhookClientConfig
103
+ webhooks [i ].ClientConfig .CABundle = cc .CABundle
104
+ }
105
+ }
106
+
107
+ func dnsNameFromClientConfig (config * admissionregistrationv1beta1.WebhookClientConfig ) (string , error ) {
108
+ if config .Service != nil && config .URL != nil {
109
+ return "" , fmt .Errorf ("service and URL can't be set at the same time in a webhook: %v" , config )
110
+ }
111
+ if config .Service == nil && config .URL == nil {
112
+ return "" , fmt .Errorf ("one of service and URL need to be set in a webhook: %v" , config )
113
+ }
114
+ if config .Service != nil {
115
+ return generator .ServiceToCommonName (config .Service .Namespace , config .Service .Name ), nil
116
+ }
117
+
118
+ // TODO
119
+ // config.URL != nil
120
+ u , err := url .Parse (* config .URL )
121
+ return u .Host , err
93
122
}
0 commit comments