Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move management of webhook configurations to operator #105

Merged
merged 1 commit into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 1 addition & 5 deletions install/03_rbac.yaml
Expand Up @@ -8,12 +8,8 @@ rules:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
- mutatingwebhookconfigurations
verbs:
- get
- patch
resourceNames:
- autoscaling.openshift.io
- "*"
bison marked this conversation as resolved.
Show resolved Hide resolved
- apiGroups:
- autoscaling.openshift.io
resources:
Expand Down
File renamed without changes.
28 changes: 0 additions & 28 deletions install/07_webhooks.yaml

This file was deleted.

1 change: 0 additions & 1 deletion kustomization.yaml
Expand Up @@ -13,7 +13,6 @@ resources:
- install/04_service.yaml
- install/05_configmap.yaml
- install/06_deployment.yaml
- install/07_webhooks.yaml

secretGenerator:
- name: cluster-autoscaler-operator-cert
Expand Down
11 changes: 6 additions & 5 deletions pkg/operator/operator.go
Expand Up @@ -152,16 +152,17 @@ func (o *Operator) AddControllers() error {
// server to operator's manager instance.
func (o *Operator) AddWebhooks() error {
caPath := filepath.Join(o.config.WebhooksCertDir, "service-ca", "ca-cert.pem")
namespace := o.config.WatchNamespace

// Set up the CA updater and add it to the manager. This will update the
// webhook configurations with the proper CA certificate when and if this
// instance becomes the leader.
caUpdater, err := NewWebhookCAUpdater(o.manager, caPath)
// Set up the webhook config updater and add it to the manager. This will
// update the webhook configurations when and if this instance becomes the
// leader.
webhookUpdater, err := NewWebhookConfigUpdater(o.manager, namespace, caPath)
if err != nil {
return err
}

if err := o.manager.Add(caUpdater); err != nil {
if err := o.manager.Add(webhookUpdater); err != nil {
return err
}

Expand Down
123 changes: 81 additions & 42 deletions pkg/operator/webhookconfig.go
@@ -1,84 +1,123 @@
package operator

import (
"context"
"encoding/base64"
"encoding/json"
"io/ioutil"

"github.com/appscode/jsonpatch"
"k8s.io/apimachinery/pkg/types"
admissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/manager"
)

// WebhookConfigurationName is the name of the webhook configurations to be
// updated with the current CA certificate.
const WebhookConfigurationName = "autoscaling.openshift.io"

// WebhookCAUpdater updates webhook configuratons to inject the CA certficiate
// bundle read from disk at the configured location.
type WebhookCAUpdater struct {
caPath string
client *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
// WebhookConfigUpdater updates webhook configurations to point the Kubernetes
// API server at the operator's validating or mutating webhook server. It would
// be nice if the CVO could apply the configuration as it is mostly static.
// Unfortunately, the CA bundle is not known until runtime.
type WebhookConfigUpdater struct {
caPath string
namespace string
client client.Client
}

// NewWebhookCAUpdater returns a new WebhookCAUpdater.
func NewWebhookCAUpdater(mgr manager.Manager, caPath string) (*WebhookCAUpdater, error) {
var err error

w := &WebhookCAUpdater{caPath: caPath}

w.client, err = admissionregistrationv1beta1.NewForConfig(mgr.GetConfig())
if err != nil {
return nil, err
// NewWebhookConfigUpdater returns a new WebhookConfigUpdater instance.
func NewWebhookConfigUpdater(mgr manager.Manager, namespace, caPath string) (*WebhookConfigUpdater, error) {
w := &WebhookConfigUpdater{
caPath: caPath,
namespace: namespace,
client: mgr.GetClient(),
}

return w, nil
}

// Start fetches the current CA bundle from disk and sets it on the webhook
// configurations, then simply waits for the stop channel to close.
func (w *WebhookCAUpdater) Start(stopCh <-chan struct{}) error {
ca, err := w.GetEncodedCA()
if err != nil {
return err
}

// TODO: This should probably replace the caBundle in all webhook client
// configurations in the object, but unfortuntaely that's not easy to do
// with a JSON patch. For now this only modifies the first entry.
patch := []jsonpatch.Operation{
{
Operation: "replace",
Path: "/webhooks/0/clientConfig/caBundle",
Value: ca,
// Start creates or updates the webhook configurations then waits for the stop
// channel to be closed.
func (w *WebhookConfigUpdater) Start(stopCh <-chan struct{}) error {
vc := &admissionregistrationv1beta1.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: "admissionregistration.k8s.io/v1beta1",
Kind: "ValidatingWebhookConfiguration",
},
ObjectMeta: metav1.ObjectMeta{
Name: WebhookConfigurationName,
Labels: map[string]string{
"k8s-app": OperatorName,
},
},
}

patchBytes, err := json.Marshal(patch)
if err != nil {
op, err := controllerutil.CreateOrUpdate(context.TODO(), w.client, vc, func() error {
var err error
vc.Webhooks, err = w.ValidatingWebhooks()
return err
}

_, err = w.client.ValidatingWebhookConfigurations().
Patch(WebhookConfigurationName, types.JSONPatchType, patchBytes)
})

if err != nil {
return err
}

klog.Info("Updated webhook configuration CA certificates.")
klog.Infof("Webhook configuration status: %s", op)

// Block until the stop channel is closed.
<-stopCh

return nil
}

// ValidatingWebhooks returns the validating webhook configurations.
func (w *WebhookConfigUpdater) ValidatingWebhooks() ([]admissionregistrationv1beta1.Webhook, error) {
caBundle, err := w.GetEncodedCA()
if err != nil {
return nil, err
}

failurePolicy := admissionregistrationv1beta1.Ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this be ignore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the validations are really (or will be when we're not doing allow-all) a nice-to-have. We have OpenAPI validation on the fields where we're able to do that, and other errors will be reported in events or in status conditions after the resource is created. So, we shouldn't completely block the functioning of the operator if the connection to the webhook server is not working -- e.g. if there's some transient networking issue.

sideEffects := admissionregistrationv1beta1.SideEffectClassNone

webhooks := []admissionregistrationv1beta1.Webhook{
{
Name: "clusterautoscalers.autoscaling.openshift.io",
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
Service: &admissionregistrationv1beta1.ServiceReference{
Name: OperatorName,
Namespace: w.namespace,
Path: pointer.StringPtr("/validate-clusterautoscalers"),
},
CABundle: []byte(caBundle),
},
FailurePolicy: &failurePolicy,
SideEffects: &sideEffects,
Rules: []admissionregistrationv1beta1.RuleWithOperations{
{
Rule: admissionregistrationv1beta1.Rule{
APIGroups: []string{"autoscaling.openshift.io"},
APIVersions: []string{"v1"},
Resources: []string{"clusterautoscalers"},
},
Operations: []admissionregistrationv1beta1.OperationType{
admissionregistrationv1beta1.Create,
admissionregistrationv1beta1.Update,
},
},
},
},
}

return webhooks, nil
}

// GetEncodedCA returns the base64 encoded CA certificate used for securing
// admission webhook server connections.
func (w *WebhookCAUpdater) GetEncodedCA() (string, error) {
func (w *WebhookConfigUpdater) GetEncodedCA() (string, error) {
ca, err := ioutil.ReadFile(w.caPath)
if err != nil {
return "", err
Expand Down