diff --git a/cmd/manager/server.go b/cmd/manager/server.go index c12279b0c..ed8a8382b 100644 --- a/cmd/manager/server.go +++ b/cmd/manager/server.go @@ -18,11 +18,11 @@ import ( "fmt" "os" + "sigs.k8s.io/controller-runtime/pkg/webhook" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - _ "k8s.io/client-go/plugin/pkg/client/auth" - drm "github.com/openshift/cluster-network-operator/pkg/util/k8s" "github.com/spf13/cobra" + _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" @@ -30,6 +30,7 @@ import ( "istio.io/operator/pkg/apis" "istio.io/operator/pkg/controller" "istio.io/operator/pkg/controller/istiocontrolplane" + iscpwebhook "istio.io/operator/pkg/webhook/istiocontrolplane" "istio.io/pkg/ctrlz" "istio.io/pkg/log" ) @@ -132,6 +133,14 @@ func run() { log.Fatalf("Could not add all controllers to operator manager: %v", err) } + // setup webhooks + log.Info("setting up webhook server") + crv := mgr.GetWebhookServer() + crv.CertDir = "/tmp/k8s-webhook-server/serving-certs" + crv.Port = 8443 + crv.Register("/validate-install-istio-io-v1alpha2-istiocontrolplane", + &webhook.Admission{Handler: &iscpwebhook.IscpValidator{}}) + log.Info("Starting the Cmd.") // Start the Cmd diff --git a/deploy/cert.yaml b/deploy/cert.yaml new file mode 100644 index 000000000..aa02e039b --- /dev/null +++ b/deploy/cert.yaml @@ -0,0 +1,26 @@ +apiVersion: certmanager.k8s.io/v1alpha1 +kind: Certificate +metadata: + labels: + app: istio-operator + name: webhook-server-cert + namespace: istio-operator +spec: + commonName: istio-operator.istio-operator.svc + dnsNames: + - istio-operator.istio-operator.svc.cluster.local + issuerRef: + kind: Issuer + name: webhook-selfsigned-issuer + secretName: webhook-server-cert +--- + +apiVersion: certmanager.k8s.io/v1alpha1 +kind: Issuer +metadata: + labels: + app: istio-operator + name: webhook-selfsigned-issuer + namespace: istio-operator +spec: + selfSigned: {} \ No newline at end of file diff --git a/deploy/clusterrole.yaml b/deploy/clusterrole.yaml index 6374301fe..ded1c823f 100644 --- a/deploy/clusterrole.yaml +++ b/deploy/clusterrole.yaml @@ -111,4 +111,17 @@ rules: - serviceaccounts verbs: - '*' +- apiGroups: + - batch + resources: + - jobs + verbs: + - '*' +- apiGroups: + - certmanager.k8s.io + resources: + - certificate + - issuer + verbs: + - '*' ... diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml index ac845b705..ee5150d2e 100644 --- a/deploy/kustomization.yaml +++ b/deploy/kustomization.yaml @@ -8,4 +8,6 @@ resources: - service_account.yaml - operator.yaml - service.yaml +- cert.yaml +- webhook.yaml ... diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 3e6ff598e..d91704a56 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -17,7 +17,11 @@ spec: serviceAccountName: istio-operator containers: - name: istio-operator - image: gcr.io/istio-testing/operator:1.5-dev + image: richardwxn/operator:test + ports: + - containerPort: 443 + name: webhook-server + protocol: TCP command: - istio-operator - server @@ -42,4 +46,13 @@ spec: fieldPath: metadata.name - name: OPERATOR_NAME value: "istio-operator" -... + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert +... \ No newline at end of file diff --git a/deploy/service.yaml b/deploy/service.yaml index b5b47d190..d395ba44b 100644 --- a/deploy/service.yaml +++ b/deploy/service.yaml @@ -5,9 +5,12 @@ metadata: namespace: istio-operator labels: name: istio-operator - name: istio-operator-metrics + name: istio-operator spec: ports: + - port: 443 + name: webhook + targetPort: 8443 - name: http-metrics port: 8383 targetPort: 8383 diff --git a/deploy/webhook.yaml b/deploy/webhook.yaml new file mode 100644 index 000000000..2c6c27f7b --- /dev/null +++ b/deploy/webhook.yaml @@ -0,0 +1,26 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration + annotations: + certmanager.k8s.io/inject-ca-from: istio-operator/webhook-server-cert +webhooks: + - clientConfig: + caBundle: Cg== + service: + name: istio-operator + namespace: istio-operator + path: /validate-install-istio-io-v1alpha2-istiocontrolplane + failurePolicy: Fail + name: vistiocontrolplane.kb.io + rules: + - apiGroups: + - install.istio.io + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - istiocontrolplanes diff --git a/pkg/validate/validate.go b/pkg/validate/validate.go index bbf26b565..06b0684cb 100644 --- a/pkg/validate/validate.go +++ b/pkg/validate/validate.go @@ -45,6 +45,11 @@ func CheckIstioControlPlaneSpec(is *v1alpha2.IstioControlPlaneSpec, checkRequire return util.AppendErrs(errs, validate(defaultValidations, is, nil, checkRequired)) } +// CheckIstioControlPlaneSpecExcludeValues validates the IstioControlPlane spec schema only, excluding the values.yaml pass through part. +func CheckIstioControlPlaneSpecExcludeValues(is *v1alpha2.IstioControlPlaneSpec, checkRequired bool) (errs util.Errors) { + return util.AppendErrs(errs, validate(defaultValidations, is, nil, checkRequired)) +} + func validate(validations map[string]ValidatorFunc, structPtr interface{}, path util.Path, checkRequired bool) (errs util.Errors) { scope.Debugf("validate with path %s, %v (%T)", path, structPtr, structPtr) if structPtr == nil { diff --git a/pkg/webhook/istiocontrolplane/iscpwebhook.go b/pkg/webhook/istiocontrolplane/iscpwebhook.go new file mode 100644 index 000000000..17fca11ab --- /dev/null +++ b/pkg/webhook/istiocontrolplane/iscpwebhook.go @@ -0,0 +1,63 @@ +// Copyright 2019 Istio 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 istiocontrolplane + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "istio.io/operator/pkg/apis/istio/v1alpha2" + "istio.io/operator/pkg/validate" +) + +// +kubebuilder:webhook:path=/validate-v1alpha2-istiocontrolplane,mutating=true,failurePolicy=fail, +// groups="install.istio.io",resources=IstioControlPlane,verbs=create;update,versions=v1alpha2,name=mistiocontrolplane.kb.io + +// podAnnotator annotates Pods +type IscpValidator struct { + client client.Client + decoder *admission.Decoder +} + +// iscpValidator validates created IstioControlPlane CR. +func (a *IscpValidator) Handle(ctx context.Context, req admission.Request) admission.Response { + icp := &v1alpha2.IstioControlPlane{} + err := a.decoder.Decode(req, icp) + if err != nil { + return admission.Denied(err.Error()) + } + //TODO: update to full validation including values part after values schema formalized. + if errs := validate.CheckIstioControlPlaneSpecExcludeValues(icp.Spec, false); len(errs) != 0 { + fmt.Printf("proceed with validation err: %v", errs.Error()) + // TODO: allow the request now until we fully done the validation logic. + return admission.Allowed("IstioControlPlane schema validated with err") + } + return admission.Allowed("IstioControlPlane schema validated") +} + +// InjectClient injects the client. +func (a *IscpValidator) InjectClient(c client.Client) error { + a.client = c + return nil +} + +// InjectDecoder injects the decoder. +func (a *IscpValidator) InjectDecoder(d *admission.Decoder) error { + a.decoder = d + return nil +}