mirror of https://github.com/knative/caching.git
247 lines
7.8 KiB
Go
247 lines
7.8 KiB
Go
/*
|
|
Copyright 2018 The Knative 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 webhook
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/markbates/inflect"
|
|
"go.uber.org/zap"
|
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"knative.dev/pkg/configmap"
|
|
"knative.dev/pkg/logging"
|
|
"knative.dev/pkg/webhook"
|
|
)
|
|
|
|
var (
|
|
// GroupVersionKind for deployment to be used to set the webhook's owner reference.
|
|
deploymentKind = extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment")
|
|
)
|
|
|
|
// APICoverageWebhook encapsulates necessary configuration details for the api-coverage webhook.
|
|
type APICoverageWebhook struct {
|
|
// WebhookName is the name of the validation webhook we create to intercept API calls.
|
|
WebhookName string
|
|
|
|
// ServiceName is the name of K8 service under which the webhook runs.
|
|
ServiceName string
|
|
|
|
// DeploymentName is the deployment name for the webhook.
|
|
DeploymentName string
|
|
|
|
// Namespace is the namespace in which everything above lives.
|
|
Namespace string
|
|
|
|
// Port where the webhook is served.
|
|
Port int
|
|
|
|
// RegistrationDelay controls how long validation requests
|
|
// occurs after the webhook is started. This is used to avoid
|
|
// potential races where registration completes and k8s apiserver
|
|
// invokes the webhook before the HTTP server is started.
|
|
RegistrationDelay time.Duration
|
|
|
|
// ClientAuthType declares the policy the webhook server will follow for TLS Client Authentication.
|
|
ClientAuth tls.ClientAuthType
|
|
|
|
// CaCert is the CA Cert for the webhook server.
|
|
CaCert []byte
|
|
|
|
// FailurePolicy policy governs the webhook validation decisions.
|
|
FailurePolicy admissionregistrationv1beta1.FailurePolicyType
|
|
|
|
// Logger is the configured logger for the webhook.
|
|
Logger *zap.SugaredLogger
|
|
|
|
// KubeClient is the K8 client to the target cluster.
|
|
KubeClient kubernetes.Interface
|
|
}
|
|
|
|
func (acw *APICoverageWebhook) generateServerConfig() (*tls.Config, error) {
|
|
serverKey, serverCert, caCert, err := webhook.CreateCerts(context.Background(), acw.ServiceName, acw.Namespace)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error creating webhook certificates: %v", err)
|
|
}
|
|
|
|
cert, err := tls.X509KeyPair(serverCert, serverKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error creating X509 Key pair for webhook server: %v", err)
|
|
}
|
|
|
|
acw.CaCert = caCert
|
|
return &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
ClientAuth: acw.ClientAuth,
|
|
}, nil
|
|
}
|
|
|
|
func (acw *APICoverageWebhook) getWebhookServer(handler http.Handler) (*http.Server, error) {
|
|
tlsConfig, err := acw.generateServerConfig()
|
|
if err != nil {
|
|
// generateServerConfig() is expected to provided explanatory error message.
|
|
return nil, err
|
|
}
|
|
|
|
return &http.Server{
|
|
Handler: handler,
|
|
Addr: fmt.Sprintf(":%d", acw.Port),
|
|
TLSConfig: tlsConfig,
|
|
}, nil
|
|
}
|
|
|
|
func (acw *APICoverageWebhook) registerWebhook(rules []admissionregistrationv1beta1.RuleWithOperations, namespace string) error {
|
|
webhook := &admissionregistrationv1beta1.ValidatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: acw.WebhookName,
|
|
Namespace: namespace,
|
|
},
|
|
Webhooks: []admissionregistrationv1beta1.Webhook{{
|
|
Name: acw.WebhookName,
|
|
Rules: rules,
|
|
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
|
|
Service: &admissionregistrationv1beta1.ServiceReference{
|
|
Namespace: namespace,
|
|
Name: acw.ServiceName,
|
|
},
|
|
CABundle: acw.CaCert,
|
|
},
|
|
FailurePolicy: &acw.FailurePolicy,
|
|
},
|
|
},
|
|
}
|
|
|
|
deployment, err := acw.KubeClient.ExtensionsV1beta1().Deployments(namespace).Get(acw.DeploymentName, metav1.GetOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("Error retrieving Deployment Extension object: %v", err)
|
|
}
|
|
deploymentRef := metav1.NewControllerRef(deployment, deploymentKind)
|
|
webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef)
|
|
|
|
_, err = acw.KubeClient.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(webhook)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating ValidatingWebhookConfigurations object: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (acw *APICoverageWebhook) getValidationRules(resources map[schema.GroupVersionKind]webhook.GenericCRD) []admissionregistrationv1beta1.RuleWithOperations {
|
|
var rules []admissionregistrationv1beta1.RuleWithOperations
|
|
for gvk := range resources {
|
|
plural := strings.ToLower(inflect.Pluralize(gvk.Kind))
|
|
|
|
rules = append(rules, admissionregistrationv1beta1.RuleWithOperations{
|
|
Operations: []admissionregistrationv1beta1.OperationType{
|
|
admissionregistrationv1beta1.Create,
|
|
admissionregistrationv1beta1.Update,
|
|
},
|
|
Rule: admissionregistrationv1beta1.Rule{
|
|
APIGroups: []string{gvk.Group},
|
|
APIVersions: []string{gvk.Version},
|
|
Resources: []string{plural},
|
|
},
|
|
})
|
|
}
|
|
return rules
|
|
}
|
|
|
|
// SetupWebhook sets up the webhook with the provided http.handler, resourcegroup Map, namespace and stop channel.
|
|
func (acw *APICoverageWebhook) SetupWebhook(handler http.Handler, resources map[schema.GroupVersionKind]webhook.GenericCRD, namespace string, stop <-chan struct{}) error {
|
|
server, err := acw.getWebhookServer(handler)
|
|
rules := acw.getValidationRules(resources)
|
|
if err != nil {
|
|
return fmt.Errorf("Webhook server object creation failed: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-time.After(acw.RegistrationDelay):
|
|
err = acw.registerWebhook(rules, namespace)
|
|
if err != nil {
|
|
return fmt.Errorf("Webhook registration failed: %v", err)
|
|
}
|
|
acw.Logger.Info("Successfully registered webhook")
|
|
case <-stop:
|
|
return nil
|
|
}
|
|
|
|
serverBootstrapErrCh := make(chan struct{})
|
|
go func() {
|
|
if err := server.ListenAndServeTLS("", ""); err != nil {
|
|
acw.Logger.Error("ListenAndServeTLS for admission webhook returned error", zap.Error(err))
|
|
close(serverBootstrapErrCh)
|
|
return
|
|
}
|
|
acw.Logger.Info("Successfully started webhook server")
|
|
}()
|
|
|
|
select {
|
|
case <-stop:
|
|
return server.Close()
|
|
case <-serverBootstrapErrCh:
|
|
return errors.New("webhook server bootstrap failed")
|
|
}
|
|
}
|
|
|
|
// BuildWebhookConfiguration builds the APICoverageWebhook object using the provided names.
|
|
func BuildWebhookConfiguration(componentCommonName string, webhookName string, namespace string) *APICoverageWebhook {
|
|
cm, err := configmap.Load("/etc/config-logging")
|
|
if err != nil {
|
|
log.Fatalf("Error loading logging configuration: %v", err)
|
|
}
|
|
|
|
config, err := logging.NewConfigFromMap(cm)
|
|
if err != nil {
|
|
log.Fatalf("Error parsing logging configuration: %v", err)
|
|
}
|
|
logger, _ := logging.NewLoggerFromConfig(config, "webhook")
|
|
|
|
clusterConfig, err := rest.InClusterConfig()
|
|
if err != nil {
|
|
log.Fatalf("Failed to get in cluster config: %v", err)
|
|
}
|
|
|
|
kubeClient, err := kubernetes.NewForConfig(clusterConfig)
|
|
if err != nil {
|
|
log.Fatalf("Failed to get client set: %v", err)
|
|
}
|
|
|
|
return &APICoverageWebhook{
|
|
Logger: logger,
|
|
KubeClient: kubeClient,
|
|
FailurePolicy: admissionregistrationv1beta1.Fail,
|
|
ClientAuth: tls.NoClientCert,
|
|
RegistrationDelay: time.Second * 2,
|
|
Port: 8443, // Using port greater than 1024 as webhook runs as non-root user.
|
|
Namespace: namespace,
|
|
DeploymentName: componentCommonName,
|
|
ServiceName: componentCommonName,
|
|
WebhookName: webhookName,
|
|
}
|
|
}
|