Skip to content

Commit 2b2a4ee

Browse files
author
Mengqi Yu
committed
restructure certwriter and certprovisioner packages
1 parent ce2b12a commit 2b2a4ee

File tree

14 files changed

+559
-672
lines changed

14 files changed

+559
-672
lines changed

pkg/webhook/bootstrap.go

Lines changed: 125 additions & 149 deletions
Large diffs are not rendered by default.

pkg/webhook/doc.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ Start the server by starting the manager
6464
if err != nil {
6565
// handle error
6666
}
67-
6867
*/
6968
package webhook
7069

pkg/webhook/internal/cert/doc.go

Lines changed: 10 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,88 +15,22 @@ limitations under the License.
1515
*/
1616

1717
/*
18-
Package cert provides functions to manage webhooks certificates.
18+
Package cert provides functions to manage certificates for webhookClientConfiguration.
1919
20-
There are 3 typical ways to use this library:
20+
Create a Provisioner with a CertWriter.
2121
22-
* Using the Sync function as a Reconciler function.
23-
24-
* Invoking it directly from the webhook server at startup.
25-
26-
* Deploying it as an init container along with the webhook server.
27-
28-
Webhook Configuration
29-
30-
The following is an example MutatingWebhookConfiguration in yaml.
31-
32-
apiVersion: admissionregistration.k8s.io/v1beta1
33-
kind: MutatingWebhookConfiguration
34-
metadata:
35-
name: myMutatingWebhookConfiguration
36-
annotations:
37-
secret.certprovisioner.kubernetes.io/webhook-1: namespace-bar/secret-foo
38-
secret.certprovisioner.kubernetes.io/webhook-2: default/secret-baz
39-
webhooks:
40-
- name: webhook-1
41-
rules:
42-
- apiGroups:
43-
- ""
44-
apiVersions:
45-
- v1
46-
operations:
47-
- "*"
48-
resources:
49-
- pods
50-
clientConfig:
51-
service:
52-
namespace: service-ns-1
53-
name: service-foo
54-
path: "/mutating-pods"
55-
caBundle: [] # CA bundle here
56-
- name: webhook-2
57-
rules:
58-
- apiGroups:
59-
- apps
60-
apiVersions:
61-
- v1
62-
operations:
63-
- "*"
64-
resources:
65-
- deployments
66-
clientConfig:
67-
service:
68-
namespace: service-ns-2
69-
name: service-bar
70-
path: "/mutating-deployment"
71-
caBundle: [] # CA bundle here
72-
73-
Build the Provisioner
74-
75-
You can choose to provide your own CertGenerator and CertWriter.
76-
An easier way is to use an empty Options the package will default it with reasonable values.
77-
The package will write self-signed certificates to secrets.
78-
79-
// Build a client. You can also create a client with your own config.Config.
80-
cl, err := client.New(config.GetConfigOrDie(), client.Options)
81-
if err != nil {
82-
// handle error
22+
provisioner := Provisioner{
23+
CertWriter: admission.NewSecretCertWriter(admission.SecretCertWriterOptions{...}),
8324
}
8425
85-
// Build a Provisioner with unspecified CertGenerator and CertWriter.
86-
cp := &Provisioner{client: cl}
87-
88-
Provision certificates
26+
Provision the certificates for the webhookClientConfig
8927
90-
Provision certificates for webhook configuration objects' by calling Sync method.
91-
92-
err = cp.Sync(mwc)
28+
err := provisioner.Provision(Options{
29+
ClientConfig: webhookClientConfig,
30+
Objects: []runtime.Object{mutatingWebhookConfiguration, validatingWebhookConfiguration}
31+
})
9332
if err != nil {
94-
// handler error
33+
// handle error
9534
}
96-
97-
When the above MutatingWebhookConfiguration is processed, the cert provisioner will create
98-
the certificate and create a secret named "secret-foo" in namespace "namespace-bar" for webhook "webhook-1".
99-
Similarly, it will create an secret named "secret-baz" in namespace "default" for webhook "webhook-2".
100-
And it will also write the CA back to the WebhookConfiguration.
10135
*/
10236
package cert

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
/*
1818
Package generator provides an interface and implementation to provision certificates.
1919
20-
Create an instance of CertGenerator.
20+
Create an instance of certGenerator.
2121
2222
cg := SelfSignedCertGenerator{}
2323

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
2323
)
2424

25-
// CertGenerator is a CertGenerator for testing.
25+
// CertGenerator is a certGenerator for testing.
2626
type CertGenerator struct {
2727
DNSNameToCertArtifacts map[string]*generator.Artifacts
2828
}
@@ -33,7 +33,7 @@ var _ generator.CertGenerator = &CertGenerator{}
3333
func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) {
3434
certs, found := cp.DNSNameToCertArtifacts[commonName]
3535
if !found {
36-
return nil, fmt.Errorf("failed to find common name %q in the CertGenerator", commonName)
36+
return nil, fmt.Errorf("failed to find common name %q in the certGenerator", commonName)
3737
}
3838
return certs, nil
3939
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func ServiceToCommonName(serviceNamespace, serviceName string) string {
2828
return fmt.Sprintf("%s.%s.svc", serviceName, serviceNamespace)
2929
}
3030

31-
// SelfSignedCertGenerator implements the CertGenerator interface.
31+
// SelfSignedCertGenerator implements the certGenerator interface.
3232
// It provisions self-signed certificates.
3333
type SelfSignedCertGenerator struct{}
3434

pkg/webhook/internal/cert/provisioner.go

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ limitations under the License.
1717
package cert
1818

1919
import (
20+
"bytes"
21+
"errors"
2022
"fmt"
21-
"reflect"
22-
"sync"
23+
"net/url"
2324

25+
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
2426
"k8s.io/apimachinery/pkg/runtime"
25-
"sigs.k8s.io/controller-runtime/pkg/client"
26-
"sigs.k8s.io/controller-runtime/pkg/client/config"
2727
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/generator"
2828
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/cert/writer"
2929
)
@@ -32,62 +32,91 @@ import (
3232
// destination - such as a Secret or local file. Provisioner can update the CA field of
3333
// certain resources with the CA of the certs.
3434
type Provisioner struct {
35-
Client client.Client
36-
// CertGenerator generates certificate for a given common name.
37-
CertGenerator generator.CertGenerator
3835
// CertWriter knows how to persist the certificate.
3936
CertWriter writer.CertWriter
37+
}
4038

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
4250
}
4351

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)
7262
if err != nil {
73-
return fmt.Errorf("failed to default the CertProvision: %v", err)
63+
return false, err
7464
}
7565

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)
8067
if err != nil {
81-
return err
68+
return false, err
8269
}
8370

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 {
8785
return nil
8886
}
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
93122
}

pkg/webhook/internal/cert/writer/atomic/atomic_writer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import (
2828
"strings"
2929
"testing"
3030

31+
log "github.com/go-logr/logr/testing"
3132
"k8s.io/apimachinery/pkg/util/sets"
3233
utiltesting "k8s.io/client-go/util/testing"
33-
log "github.com/go-logr/logr/testing"
3434
)
3535

3636
func TestNewAtomicWriter(t *testing.T) {

0 commit comments

Comments
 (0)