webhook 配置校验

initConfigValidation

func (s *Server) initConfigValidation(args *PilotArgs) error {
    ...
    log.Info("initializing config validator")
    // always start the validation server
    params := server.Options{
        Schemas:      collections.Istio,
        DomainSuffix: args.RegistryOptions.KubeOptions.DomainSuffix,
        Mux:          s.httpsMux,
    }
    // 根据参数初始化Server
    whServer, err := server.New(params)
    if err != nil {
        return err
    }

    s.addStartFunc(func(stop <-chan struct{}) error {
        whServer.Run(stop)
        return nil
    })

    // 是否校验webhook
    if webhookConfigName := validationWebhookConfigName.Get(); webhookConfigName != "" && s.kubeClient != nil {
        if webhookConfigName == validationWebhookConfigNameTemplate {
            webhookConfigName = strings.ReplaceAll(validationWebhookConfigNameTemplate, validationWebhookConfigNameTemplateVar, args.Namespace)
        }

        caBundlePath := s.caBundlePath
        if hasCustomTLSCerts(args.ServerOptions.TLSOptions) {
            caBundlePath = args.ServerOptions.TLSOptions.CaCertFile
        }
        o := controller.Options{
            WatchedNamespace:  args.Namespace,
            CAPath:            caBundlePath,
            WebhookConfigName: webhookConfigName,
            ServiceName:       "istiod",
        }
        // 初始化webhook controller
        whController, err := controller.New(o, s.kubeClient)
        if err != nil {
            log.Errorf("failed to start validation controller: %v", err)
            return err
        }
        s.addTerminatingStartFunc(func(stop <-chan struct{}) error {
            le := leaderelection.NewLeaderElection(args.Namespace, args.PodName, leaderelection.ValidationController, s.kubeClient)
            le.AddRunFunction(func(leaderStop <-chan struct{}) {
                log.Infof("Starting validation controller")
                // 启动controller
                whController.Start(leaderStop)
            })
            le.Run(stop)
            return nil
        })
    }
    return nil
}
// New creates a new instance of the admission webhook server.
func New(p Options) (*Webhook, error) {
    if p.Mux == nil {
        scope.Error("mux not set correctly")
        return nil, errors.New("expected mux to be passed, but was not passed")
    }
    wh := &Webhook{
        schemas: p.Schemas,
    }

    p.Mux.HandleFunc("/validate", wh.serveValidate)
    // old handlers retained backwards compatibility during upgrades
    p.Mux.HandleFunc("/admitpilot", wh.serveAdmitPilot)

    return wh, nil
}

校验规则

func (wh *Webhook) admitPilot(request *kube.AdmissionRequest) *kube.AdmissionResponse {
    switch request.Operation {
    case kube.Create, kube.Update:
    default:
        scope.Warnf("Unsupported webhook operation %v", request.Operation)
        reportValidationFailed(request, reasonUnsupportedOperation)
        return &kube.AdmissionResponse{Allowed: true}
    }

    var obj crd.IstioKind
    // 是否是istio类型,符合istio的spec规范
    if err := json.Unmarshal(request.Object.Raw, &obj); err != nil {
        scope.Infof("cannot decode configuration: %v", err)
        reportValidationFailed(request, reasonYamlDecodeError)
        return toAdmissionResponse(fmt.Errorf("cannot decode configuration: %v", err))
    }

    gvk := obj.GroupVersionKind()

    // 1️v1beta1转化为v1alpha3.
    if gvk.Group == "networking.istio.io" && gvk.Version == "v1beta1" {
        gvk.Version = "v1alpha3"
    }
    // 查找对应类型的scheme
    s, exists := wh.schemas.FindByGroupVersionKind(resource.FromKubernetesGVK(&gvk))
    if !exists {
        scope.Infof("unrecognized type %v", obj.Kind)
        reportValidationFailed(request, reasonUnknownType)
        return toAdmissionResponse(fmt.Errorf("unrecognized type %v", obj.Kind))
    }
    // 查看是否能转换为对应的CRD
    out, err := crd.ConvertObject(s, &obj, wh.domainSuffix)
    if err != nil {
        scope.Infof("error decoding configuration: %v", err)
        reportValidationFailed(request, reasonCRDConversionError)
        return toAdmissionResponse(fmt.Errorf("error decoding configuration: %v", err))
    }
    // 查看是否满足配置要求
    warnings, err := s.Resource().ValidateConfig(*out)
    if err != nil {
        scope.Infof("configuration is invalid: %v", err)
        reportValidationFailed(request, reasonInvalidConfig)
        return toAdmissionResponse(fmt.Errorf("configuration is invalid: %v", err))
    }

    // 查看是否包含未知字段
    if reason, err := checkFields(request.Object.Raw, request.Kind.Kind, request.Namespace, obj.Name); err != nil {
        reportValidationFailed(request, reason)
        return toAdmissionResponse(err)
    }
    // 写入指标
    reportValidationPass(request)
    // 返回AdmissionResponse
    return &kube.AdmissionResponse{Allowed: true, Warnings: toKubeWarnings(warnings)}
}

如果启用了webhook配置校验则将通过

检查mutatingwebhookc 配置是否有效填充CABundle和FailurePolicy

func (c *Controller) updateValidatingWebhookConfiguration(caBundle []byte, failurePolicy kubeApiAdmission.FailurePolicyType) error {
    ...

    updated := current.DeepCopyObject().(*kubeApiAdmission.ValidatingWebhookConfiguration)

    for i := range updated.Webhooks {
        updated.Webhooks[i].ClientConfig.CABundle = caBundle
        updated.Webhooks[i].FailurePolicy = &failurePolicy
    }

    if !reflect.DeepEqual(updated, current) {
        latest, err := c.client.AdmissionregistrationV1beta1().
            ValidatingWebhookConfigurations().Update(context.TODO(), updated, kubeApiMeta.UpdateOptions{})
...
}

Last updated