Implemented validation logic for the webhook
- Created a single Validate() function to validate both updating and creating Jenkins CR. - Implemented the Validate function to fetch warnings from the API and do security check if being enabled. - Updated the helm charts and helm-e2e target to run the helm tests.
This commit is contained in:
		
							parent
							
								
									e87c7cac5f
								
							
						
					
					
						commit
						52fe5fe95e
					
				
							
								
								
									
										3
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										3
									
								
								Makefile
								
								
								
								
							|  | @ -96,7 +96,8 @@ e2e: deepcopy-gen manifests ## Runs e2e tests, you can use EXTRA_ARGS | |||
| 
 | ||||
| .PHONY: helm-e2e | ||||
| IMAGE_NAME := $(DOCKER_REGISTRY):$(GITCOMMIT) | ||||
| helm-e2e: helm container-runtime-build ## Runs helm e2e tests, you can use EXTRA_ARGS
 | ||||
| #TODO: install cert-manager before running helm charts
 | ||||
| helm-e2e:  helm install-cert-manager container-runtime-build ## Runs helm e2e tests, you can use EXTRA_ARGS
 | ||||
| 	@echo "+ $@" | ||||
| 	RUNNING_TESTS=1 go test -parallel=1 "./test/helm/" -ginkgo.v -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" -image-name=$(IMAGE_NAME) $(E2E_TEST_ARGS) | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,15 @@ limitations under the License. | |||
| package v1alpha2 | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/plugins" | ||||
| 
 | ||||
| 	"golang.org/x/mod/semver" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	ctrl "sigs.k8s.io/controller-runtime" | ||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||
|  | @ -41,21 +50,142 @@ var _ webhook.Validator = &Jenkins{} | |||
| 
 | ||||
| // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
 | ||||
| func (in *Jenkins) ValidateCreate() error { | ||||
| 	jenkinslog.Info("validate create", "name", in.Name) | ||||
| 	if in.Spec.ValidateSecurityWarnings { | ||||
| 		jenkinslog.Info("validate create", "name", in.Name) | ||||
| 		return Validate(*in) | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(user): fill in your validation logic upon object creation.
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
 | ||||
| func (in *Jenkins) ValidateUpdate(old runtime.Object) error { | ||||
| 	jenkinslog.Info("validate update", "name", in.Name) | ||||
| 	if in.Spec.ValidateSecurityWarnings { | ||||
| 		jenkinslog.Info("validate update", "name", in.Name) | ||||
| 		return Validate(*in) | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(user): fill in your validation logic upon object update.
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (in *Jenkins) ValidateDelete() error { | ||||
| 	// TODO(user): fill in your validation logic upon object deletion.
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type Warnings struct { | ||||
| 	Warnings []Warning `json:"securityWarnings"` | ||||
| } | ||||
| 
 | ||||
| type Warning struct { | ||||
| 	Versions []Version `json:"versions"` | ||||
| 	ID       string    `json:"id"` | ||||
| 	Message  string    `json:"message"` | ||||
| 	URL      string    `json:"url"` | ||||
| 	Active   bool      `json:"active"` | ||||
| } | ||||
| type Version struct { | ||||
| 	FirstVersion string `json:"firstVersion"` | ||||
| 	LastVersion  string `json:"lastVersion"` | ||||
| } | ||||
| 
 | ||||
| const APIURL string = "https://plugins.jenkins.io/api/plugin/" | ||||
| 
 | ||||
| func MakeSemanticVersion(version string) string { | ||||
| 	version = "v" + version | ||||
| 	return semver.Canonical(version) | ||||
| } | ||||
| 
 | ||||
| func CompareVersions(firstVersion string, lastVersion string, pluginVersion string) bool { | ||||
| 	firstSemVer := MakeSemanticVersion(firstVersion) | ||||
| 	lastSemVer := MakeSemanticVersion(lastVersion) | ||||
| 	pluginSemVer := MakeSemanticVersion(pluginVersion) | ||||
| 	if semver.Compare(pluginSemVer, firstSemVer) == -1 || semver.Compare(pluginSemVer, lastSemVer) == 1 { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func CheckSecurityWarnings(pluginName string, pluginVersion string) (bool, error) { | ||||
| 	jenkinslog.Info("checking security warnings", "plugin: ", pluginName) | ||||
| 	pluginURL := APIURL + pluginName | ||||
| 	client := &http.Client{ | ||||
| 		Timeout: time.Second * 30, | ||||
| 	} | ||||
| 	request, err := http.NewRequest("GET", pluginURL, nil) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	request.Header.Add("Accept", "application/json") | ||||
| 	request.Header.Add("Content-Type", "application/json") | ||||
| 	response, err := client.Do(request) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
| 	bodyBytes, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	securityWarnings := Warnings{} | ||||
| 
 | ||||
| 	jsonErr := json.Unmarshal(bodyBytes, &securityWarnings) | ||||
| 	if jsonErr != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	jenkinslog.Info("Validate()", "warnings", securityWarnings) | ||||
| 
 | ||||
| 	for _, warning := range securityWarnings.Warnings { | ||||
| 		for _, version := range warning.Versions { | ||||
| 			firstVersion := version.FirstVersion | ||||
| 			lastVersion := version.LastVersion | ||||
| 			if len(firstVersion) == 0 { | ||||
| 				firstVersion = "0" // setting default value in case of empty string
 | ||||
| 			} | ||||
| 			if len(lastVersion) == 0 { | ||||
| 				lastVersion = pluginVersion // setting default value in case of empty string
 | ||||
| 			} | ||||
| 
 | ||||
| 			if CompareVersions(firstVersion, lastVersion, pluginVersion) { | ||||
| 				jenkinslog.Info("security Vulnerabilities detected", "message", warning.Message, "Check security Advisory", warning.URL) | ||||
| 				return true, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
| func Validate(r Jenkins) error { | ||||
| 	basePlugins := plugins.BasePlugins() | ||||
| 	var warnings string = "" | ||||
| 
 | ||||
| 	for _, plugin := range basePlugins { | ||||
| 		name := plugin.Name | ||||
| 		version := plugin.Version | ||||
| 		hasWarnings, err := CheckSecurityWarnings(name, version) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if hasWarnings { | ||||
| 			warnings += "Security Vulnerabilities detected in base plugin:" + name | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, plugin := range r.Spec.Master.Plugins { | ||||
| 		name := plugin.Name | ||||
| 		version := plugin.Version | ||||
| 		hasWarnings, err := CheckSecurityWarnings(name, version) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if hasWarnings { | ||||
| 			warnings += "Security Vulnerabilities detected in the user defined plugin: " + name | ||||
| 		} | ||||
| 	} | ||||
| 	if len(warnings) > 0 { | ||||
| 		return errors.New(warnings) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,26 @@ | |||
| apiVersion: cert-manager.io/v1 | ||||
| kind: Certificate | ||||
| metadata: | ||||
|   name: webhook-certificate | ||||
|   namespace: {{ .Release.Namespace }} | ||||
| spec: | ||||
|   duration: 2160h  | ||||
|   renewBefore: 360h | ||||
|   secretName: webhook-server-cert | ||||
|   dnsNames: | ||||
|   - webhook-service.{{ .Release.Namespace }}.svc  | ||||
|   - webhook-service.{{ .Release.Namespace }}.svc.cluster.local  | ||||
|   issuerRef: | ||||
|     kind: Issuer | ||||
|     name: selfsigned | ||||
| 
 | ||||
| --- | ||||
| apiVersion: cert-manager.io/v1 | ||||
| kind: Issuer | ||||
| metadata: | ||||
|   name: selfsigned | ||||
|   namespace: {{ .Release.Namespace }} | ||||
| spec: | ||||
|   selfSigned: {} | ||||
| 
 | ||||
| --- | ||||
|  | @ -32,6 +32,10 @@ spec: | |||
|           command: | ||||
|             - /manager | ||||
|           args: [] | ||||
|           volumeMounts: | ||||
|           - mountPath: /tmp/k8s-webhook-server/serving-certs | ||||
|             name: cert | ||||
|             readOnly: true | ||||
|           env: | ||||
|             - name: WATCH_NAMESPACE | ||||
|               value: {{ .Values.jenkins.namespace }} | ||||
|  | @ -55,3 +59,9 @@ spec: | |||
|       tolerations: | ||||
|         {{- toYaml . | nindent 8 }} | ||||
|     {{- end }} | ||||
|       volumes: | ||||
|       - name: cert | ||||
|         secret: | ||||
|           defaultMode: 420 | ||||
|           secretName: webhook-server-cert   | ||||
|       terminationGracePeriodSeconds: 10 | ||||
|  | @ -0,0 +1,43 @@ | |||
| apiVersion: admissionregistration.k8s.io/v1 | ||||
| kind: ValidatingWebhookConfiguration | ||||
| metadata: | ||||
|   name: validating-webhook-configuration | ||||
|   annotations: | ||||
|     cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/webhook-certificate  | ||||
| webhooks: | ||||
| - admissionReviewVersions: | ||||
|   - v1 | ||||
|   - v1beta1 | ||||
|   clientConfig: | ||||
|     service: | ||||
|       name: webhook-service | ||||
|       namespace: {{ .Release.Namespace }} | ||||
|       path: /validate-jenkins-io-v1alpha2-jenkins | ||||
|   failurePolicy: Fail | ||||
|   name: vjenkins.kb.io | ||||
|   rules: | ||||
|   - apiGroups: | ||||
|     - jenkins.io | ||||
|     apiVersions: | ||||
|     - v1alpha2 | ||||
|     operations: | ||||
|     - CREATE | ||||
|     - UPDATE | ||||
|     resources: | ||||
|     - jenkins | ||||
|   sideEffects: None | ||||
| 
 | ||||
| --- | ||||
| apiVersion: v1 | ||||
| kind: Service | ||||
| metadata: | ||||
|   name: webhook-service | ||||
|   namespace: {{ .Release.Namespace }} | ||||
| spec: | ||||
|   ports: | ||||
|     - port: 443 | ||||
|       targetPort: 9443 | ||||
|   selector: | ||||
|     app.kubernetes.io/name: {{ include "jenkins-operator.name" . }} | ||||
|     app.kubernetes.io/instance: {{ .Release.Name }} | ||||
| --- | ||||
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -27,4 +27,6 @@ require ( | |||
| 	k8s.io/client-go v0.20.2 | ||||
| 	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 | ||||
| 	sigs.k8s.io/controller-runtime v0.7.0 | ||||
| 	golang.org/x/mod  v0.4.2 | ||||
| 
 | ||||
| ) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -572,6 +572,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB | |||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= | ||||
| golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue