Implemented validation logic for the webhook (#593)
* Fix workflow for autogenerating docs (#592) * Use grep -c flag in check for changes step to fix case when more than 1 website file was modified * 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. * Configure bot for labelling new issues as needing triage (#597) * Configure bot for managing stale issues (#598) * Docs: explanation what is backed up and why (#599) * Explanation what's backed up and why * Auto-updated docs (#600) Co-authored-by: prryb <prryb@users.noreply.github.com> * Docs: clarification of description of get latest command in backup (#601) * Auto-updated docs (#602) Co-authored-by: Sig00rd <Sig00rd@users.noreply.github.com> * Bump seedjobs agent image version to 4.9-1 (#604) * Add GitLFS pull after checkout behaviour to SeedJob GroovyScript Template (#483) Add GitLFS pull after checkout behaviour to support also repositories which are relying on Git LFS Close #482 * Docs: minor fixes (#608) * Link to project's DockerHub in README's section on nightly builds, add paragraph about nightly builds in installation docs * Fix repositoryURL in sample seedJob configuration with SSH auth * Slightly expand on #348 * Fix formatting in docs on Jenkins' customization, update plugin versions * Add notes on Jenkins home Volume in Helm chart values.yaml and docs (#589) * Auto-updated docs (#610) Co-authored-by: Sig00rd <Sig00rd@users.noreply.github.com> * Reimplemented the validation logic with caching the security warnings - Reimplemented the validator interface - Updated manifests to allocate more resources * Add an issue template for documentation (#613) * Docs: add info on restricted volumeMounts other than jenkins-home(#612) * Update note in installation docs * Update Helm chart default values.yaml * Update schema * Auto-updated docs (#616) Co-authored-by: Sig00rd <Sig00rd@users.noreply.github.com> * Auto-updated docs (#617) Co-authored-by: Sig00rd <Sig00rd@users.noreply.github.com> * Updated Validation logic - Defined a security manager struct to cache all the plugin data - Added flag to make validating security warnings optional while deploying the operator * Helm Chart: Remove empty priorityClassName from Jenkins template (#618) Also bump Helm Chart version to v0.5.2 * Added unit test cases for webhook * Updated Helm Charts - Optimized the charts - Made the webhook optional - Added cert manager as dependency to be installed while running webhook * Updated unit tests, helm charts and validation logic * Completed helm e2e tests and updated helm charts - Completed helm tests for various scenarios - Disabled startupapi check for cert manager webhook, defined a secret and updated templates - Made the webhook completely optional * Code optimization and cleanup * Modified helm tests * code cleanup and optimization
This commit is contained in:
		
							parent
							
								
									3e5d80269d
								
							
						
					
					
						commit
						34c9ee3cd5
					
				
							
								
								
									
										5
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										5
									
								
								Makefile
								
								
								
								
							|  | @ -96,6 +96,7 @@ e2e: deepcopy-gen manifests ## Runs e2e tests, you can use EXTRA_ARGS | ||||||
| 
 | 
 | ||||||
| .PHONY: helm-e2e | .PHONY: helm-e2e | ||||||
| IMAGE_NAME := $(DOCKER_REGISTRY):$(GITCOMMIT) | IMAGE_NAME := $(DOCKER_REGISTRY):$(GITCOMMIT) | ||||||
|  | #TODO: install cert-manager before running helm charts
 | ||||||
| helm-e2e:  helm container-runtime-build ## Runs helm e2e tests, you can use EXTRA_ARGS
 | helm-e2e:  helm container-runtime-build ## Runs helm e2e tests, you can use EXTRA_ARGS
 | ||||||
| 	@echo "+ $@" | 	@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) | 	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) | ||||||
|  | @ -537,8 +538,10 @@ all-in-one-build-webhook: ## Re-generate all-in-one yaml | ||||||
| 	 | 	 | ||||||
| # start the cluster locally and set it to use the docker daemon from minikube
 | # start the cluster locally and set it to use the docker daemon from minikube
 | ||||||
| install-cert-manager: minikube-start | install-cert-manager: minikube-start | ||||||
| 	kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml  | 	kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.5.1/cert-manager.yaml  | ||||||
| 
 | 
 | ||||||
|  | uninstall-cert-manager: minikube-start | ||||||
|  | 	kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.5.1/cert-manager.yaml  | ||||||
| 	 | 	 | ||||||
| #Launch cert-manager and deploy the operator locally along with webhook
 | #Launch cert-manager and deploy the operator locally along with webhook
 | ||||||
| deploy-webhook: install-cert-manager install-crds container-runtime-build all-in-one-build-webhook  | deploy-webhook: install-cert-manager install-crds container-runtime-build all-in-one-build-webhook  | ||||||
|  |  | ||||||
|  | @ -17,14 +17,32 @@ limitations under the License. | ||||||
| package v1alpha2 | package v1alpha2 | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"compress/gzip" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/plugins" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/mod/semver" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/webhook" | 	"sigs.k8s.io/controller-runtime/pkg/webhook" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // log is for logging in this package.
 | var ( | ||||||
| var jenkinslog = logf.Log.WithName("jenkins-resource") | 	jenkinslog                   = logf.Log.WithName("jenkins-resource") // log is for logging in this package.
 | ||||||
|  | 	PluginsMgr PluginDataManager = *NewPluginsDataManager("/tmp/plugins.json.gzip", "/tmp/plugins.json", false, time.Duration(1000)*time.Second) | ||||||
|  | 	_          webhook.Validator = &Jenkins{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const Hosturl = "https://ci.jenkins.io/job/Infra/job/plugin-site-api/job/generate-data/lastSuccessfulBuild/artifact/plugins.json.gzip" | ||||||
| 
 | 
 | ||||||
| func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||||||
| 	return ctrl.NewWebhookManagedBy(mgr). | 	return ctrl.NewWebhookManagedBy(mgr). | ||||||
|  | @ -37,25 +55,287 @@ func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||||||
| // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
 | ||||||
| // +kubebuilder:webhook:path=/validate-jenkins-io-jenkins-io-v1alpha2-jenkins,mutating=false,failurePolicy=fail,sideEffects=None,groups=jenkins.io.jenkins.io,resources=jenkins,verbs=create;update,versions=v1alpha2,name=vjenkins.kb.io,admissionReviewVersions={v1,v1beta1}
 | // +kubebuilder:webhook:path=/validate-jenkins-io-jenkins-io-v1alpha2-jenkins,mutating=false,failurePolicy=fail,sideEffects=None,groups=jenkins.io.jenkins.io,resources=jenkins,verbs=create;update,versions=v1alpha2,name=vjenkins.kb.io,admissionReviewVersions={v1,v1beta1}
 | ||||||
| 
 | 
 | ||||||
| var _ webhook.Validator = &Jenkins{} |  | ||||||
| 
 |  | ||||||
| // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
 | ||||||
| func (in *Jenkins) ValidateCreate() error { | func (in *Jenkins) ValidateCreate() error { | ||||||
|  | 	if in.Spec.ValidateSecurityWarnings { | ||||||
| 		jenkinslog.Info("validate create", "name", in.Name) | 		jenkinslog.Info("validate create", "name", in.Name) | ||||||
|  | 		return Validate(*in) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO(user): fill in your validation logic upon object creation.
 |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
 | ||||||
| func (in *Jenkins) ValidateUpdate(old runtime.Object) error { | func (in *Jenkins) ValidateUpdate(old runtime.Object) error { | ||||||
|  | 	if in.Spec.ValidateSecurityWarnings { | ||||||
| 		jenkinslog.Info("validate update", "name", in.Name) | 		jenkinslog.Info("validate update", "name", in.Name) | ||||||
|  | 		return Validate(*in) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// TODO(user): fill in your validation logic upon object update.
 |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (in *Jenkins) ValidateDelete() error { | func (in *Jenkins) ValidateDelete() error { | ||||||
| 	// TODO(user): fill in your validation logic upon object deletion.
 |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type PluginDataManager struct { | ||||||
|  | 	PluginDataCache    PluginsInfo | ||||||
|  | 	Timeout            time.Duration | ||||||
|  | 	CompressedFilePath string | ||||||
|  | 	PluginDataFile     string | ||||||
|  | 	IsCached           bool | ||||||
|  | 	Attempts           int | ||||||
|  | 	SleepTime          time.Duration | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PluginsInfo struct { | ||||||
|  | 	Plugins []PluginInfo `json:"plugins"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PluginInfo struct { | ||||||
|  | 	Name             string    `json:"name"` | ||||||
|  | 	SecurityWarnings []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"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type PluginData struct { | ||||||
|  | 	Version string | ||||||
|  | 	Kind    string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Validates security warnings for both updating and creating a Jenkins CR
 | ||||||
|  | func Validate(r Jenkins) error { | ||||||
|  | 	if !PluginsMgr.IsCached { | ||||||
|  | 		return errors.New("plugins data has not been fetched") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pluginSet := make(map[string]PluginData) | ||||||
|  | 	var faultyBasePlugins string | ||||||
|  | 	var faultyUserPlugins string | ||||||
|  | 	basePlugins := plugins.BasePlugins() | ||||||
|  | 
 | ||||||
|  | 	for _, plugin := range basePlugins { | ||||||
|  | 		// Only Update the map if the plugin is not present or a lower version is being used
 | ||||||
|  | 		if pluginData, ispresent := pluginSet[plugin.Name]; !ispresent || semver.Compare(makeSemanticVersion(plugin.Version), pluginData.Version) == 1 { | ||||||
|  | 			pluginSet[plugin.Name] = PluginData{Version: plugin.Version, Kind: "base"} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, plugin := range r.Spec.Master.Plugins { | ||||||
|  | 		if pluginData, ispresent := pluginSet[plugin.Name]; !ispresent || semver.Compare(makeSemanticVersion(plugin.Version), pluginData.Version) == 1 { | ||||||
|  | 			pluginSet[plugin.Name] = PluginData{Version: plugin.Version, Kind: "user-defined"} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, plugin := range PluginsMgr.PluginDataCache.Plugins { | ||||||
|  | 		if pluginData, ispresent := pluginSet[plugin.Name]; ispresent { | ||||||
|  | 			var hasVulnerabilities bool | ||||||
|  | 			for _, warning := range plugin.SecurityWarnings { | ||||||
|  | 				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 = pluginData.Version // setting default value in case of empty string
 | ||||||
|  | 					} | ||||||
|  | 					// checking if this warning applies to our version as well
 | ||||||
|  | 					if compareVersions(firstVersion, lastVersion, pluginData.Version) { | ||||||
|  | 						jenkinslog.Info("Security Vulnerability detected in "+pluginData.Kind+" "+plugin.Name+":"+pluginData.Version, "Warning message", warning.Message, "For more details,check security advisory", warning.URL) | ||||||
|  | 						hasVulnerabilities = true | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if hasVulnerabilities { | ||||||
|  | 				if pluginData.Kind == "base" { | ||||||
|  | 					faultyBasePlugins += "\n" + plugin.Name + ":" + pluginData.Version | ||||||
|  | 				} else { | ||||||
|  | 					faultyUserPlugins += "\n" + plugin.Name + ":" + pluginData.Version | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(faultyBasePlugins) > 0 || len(faultyUserPlugins) > 0 { | ||||||
|  | 		var errormsg string | ||||||
|  | 		if len(faultyBasePlugins) > 0 { | ||||||
|  | 			errormsg += "security vulnerabilities detected in the following base plugins: " + faultyBasePlugins | ||||||
|  | 		} | ||||||
|  | 		if len(faultyUserPlugins) > 0 { | ||||||
|  | 			errormsg += "security vulnerabilities detected in the following user-defined plugins: " + faultyUserPlugins | ||||||
|  | 		} | ||||||
|  | 		return errors.New(errormsg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewPluginsDataManager(compressedFilePath string, pluginDataFile string, isCached bool, timeout time.Duration) *PluginDataManager { | ||||||
|  | 	return &PluginDataManager{ | ||||||
|  | 		CompressedFilePath: compressedFilePath, | ||||||
|  | 		PluginDataFile:     pluginDataFile, | ||||||
|  | 		IsCached:           isCached, | ||||||
|  | 		Timeout:            timeout, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (in *PluginDataManager) ManagePluginData(sig chan bool) { | ||||||
|  | 	var isInit bool | ||||||
|  | 	var retryInterval time.Duration | ||||||
|  | 	for { | ||||||
|  | 		var isCached bool | ||||||
|  | 		err := in.fetchPluginData() | ||||||
|  | 		if err == nil { | ||||||
|  | 			isCached = true | ||||||
|  | 		} else { | ||||||
|  | 			jenkinslog.Info("Cache plugin data", "failed to fetch plugin data", err) | ||||||
|  | 		} | ||||||
|  | 		// should only be executed once when the operator starts
 | ||||||
|  | 		if !isInit { | ||||||
|  | 			sig <- isCached // sending signal to main to continue
 | ||||||
|  | 			isInit = true | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		in.IsCached = in.IsCached || isCached | ||||||
|  | 		if !isCached { | ||||||
|  | 			retryInterval = time.Duration(1) * time.Hour | ||||||
|  | 		} else { | ||||||
|  | 			retryInterval = time.Duration(12) * time.Hour | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(retryInterval) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Downloads extracts and reads the JSON data in every 12 hours
 | ||||||
|  | func (in *PluginDataManager) fetchPluginData() error { | ||||||
|  | 	jenkinslog.Info("Initializing/Updating the plugin data cache") | ||||||
|  | 	var err error | ||||||
|  | 	for in.Attempts = 0; in.Attempts < 5; in.Attempts++ { | ||||||
|  | 		err = in.download() | ||||||
|  | 		if err != nil { | ||||||
|  | 			jenkinslog.V(log.VDebug).Info("Cache Plugin Data", "failed to download file", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for in.Attempts = 0; in.Attempts < 5; in.Attempts++ { | ||||||
|  | 		err = in.extract() | ||||||
|  | 		if err != nil { | ||||||
|  | 			jenkinslog.V(log.VDebug).Info("Cache Plugin Data", "failed to extract file", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for in.Attempts = 0; in.Attempts < 5; in.Attempts++ { | ||||||
|  | 		err = in.cache() | ||||||
|  | 		if err != nil { | ||||||
|  | 			jenkinslog.V(log.VDebug).Info("Cache Plugin Data", "failed to read plugin data file", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (in *PluginDataManager) download() error { | ||||||
|  | 	out, err := os.Create(in.CompressedFilePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer out.Close() | ||||||
|  | 
 | ||||||
|  | 	client := http.Client{ | ||||||
|  | 		Timeout: in.Timeout, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := client.Get(Hosturl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	_, err = io.Copy(out, resp.Body) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (in *PluginDataManager) extract() error { | ||||||
|  | 	reader, err := os.Open(in.CompressedFilePath) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer reader.Close() | ||||||
|  | 	archive, err := gzip.NewReader(reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer archive.Close() | ||||||
|  | 	writer, err := os.Create(in.PluginDataFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer writer.Close() | ||||||
|  | 
 | ||||||
|  | 	_, err = io.Copy(writer, archive) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Loads the JSON data into memory and stores it
 | ||||||
|  | func (in *PluginDataManager) cache() error { | ||||||
|  | 	jsonFile, err := os.Open(in.PluginDataFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer jsonFile.Close() | ||||||
|  | 	byteValue, err := ioutil.ReadAll(jsonFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = json.Unmarshal(byteValue, &in.PluginDataCache) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // returns a semantic version that can be used for comparison, allowed versioning format vMAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH
 | ||||||
|  | func makeSemanticVersion(version string) string { | ||||||
|  | 	if version[0] != 'v' { | ||||||
|  | 		version = "v" + version | ||||||
|  | 	} | ||||||
|  | 	return semver.Canonical(version) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Compare if the current version lies between first version and last 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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,177 @@ | ||||||
|  | package v1alpha2 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMakeSemanticVersion(t *testing.T) { | ||||||
|  | 	t.Run("only major version specified", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("1") | ||||||
|  | 		assert.Equal(t, got, "v1.0.0") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("major and minor version specified", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("1.2") | ||||||
|  | 		assert.Equal(t, got, "v1.2.0") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("major,minor and patch version specified", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("1.2.3") | ||||||
|  | 		assert.Equal(t, got, "v1.2.3") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("semantic versions begin with a leading v and no patch version", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("v2.5") | ||||||
|  | 		assert.Equal(t, got, "v2.5.0") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("semantic versions with prerelease versions", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("2.1.2-alpha.1") | ||||||
|  | 		assert.Equal(t, got, "v2.1.2-alpha.1") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("semantic versions with prerelease versions", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("0.11.2-9.c8b45b8bb173") | ||||||
|  | 		assert.Equal(t, got, "v0.11.2-9.c8b45b8bb173") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("semantic versions with build suffix", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("1.7.9+meta") | ||||||
|  | 		assert.Equal(t, got, "v1.7.9") | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("invalid semantic version", func(t *testing.T) { | ||||||
|  | 		got := makeSemanticVersion("google-login-1.2") | ||||||
|  | 		assert.Equal(t, got, "") | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestCompareVersions(t *testing.T) { | ||||||
|  | 	t.Run("Plugin Version lies between first and last version", func(t *testing.T) { | ||||||
|  | 		got := compareVersions("1.2", "1.6", "1.4") | ||||||
|  | 		assert.Equal(t, got, true) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Plugin Version is greater than the last version", func(t *testing.T) { | ||||||
|  | 		got := compareVersions("1", "2", "3") | ||||||
|  | 		assert.Equal(t, got, false) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Plugin Version is less than the first version", func(t *testing.T) { | ||||||
|  | 		got := compareVersions("1.4", "2.5", "1.1") | ||||||
|  | 		assert.Equal(t, got, false) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("Plugins Versions have prerelease version and it lies between first and last version", func(t *testing.T) { | ||||||
|  | 		got := compareVersions("1.2.1-alpha", "1.2.1", "1.2.1-beta") | ||||||
|  | 		assert.Equal(t, got, true) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("Plugins Versions have prerelease version and it is greater than the last version", func(t *testing.T) { | ||||||
|  | 		got := compareVersions("v2.2.1-alpha", "v2.5.1-beta.1", "v2.5.1-beta.2") | ||||||
|  | 		assert.Equal(t, got, false) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidate(t *testing.T) { | ||||||
|  | 	t.Run("Validating when plugins data file is not fetched", func(t *testing.T) { | ||||||
|  | 		userplugins := []Plugin{{Name: "script-security", Version: "1.77"}, {Name: "git-client", Version: "3.9"}, {Name: "git", Version: "4.8.1"}, {Name: "plain-credentials", Version: "1.7"}} | ||||||
|  | 		jenkinscr := *createJenkinsCR(userplugins, true) | ||||||
|  | 		got := jenkinscr.ValidateCreate() | ||||||
|  | 		assert.Equal(t, got, errors.New("plugins data has not been fetched")) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	PluginsMgr.IsCached = true | ||||||
|  | 	t.Run("Validating a Jenkins CR with plugins not having security warnings and validation is turned on", func(t *testing.T) { | ||||||
|  | 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
|  | 			{Name: "security-script"}, | ||||||
|  | 			{Name: "git-client"}, | ||||||
|  | 			{Name: "git"}, | ||||||
|  | 			{Name: "google-login", SecurityWarnings: createSecurityWarnings("", "1.2")}, | ||||||
|  | 			{Name: "sample-plugin", SecurityWarnings: createSecurityWarnings("", "0.8")}, | ||||||
|  | 			{Name: "mailer"}, | ||||||
|  | 			{Name: "plain-credentials"}}} | ||||||
|  | 		userplugins := []Plugin{{Name: "script-security", Version: "1.77"}, {Name: "git-client", Version: "3.9"}, {Name: "git", Version: "4.8.1"}, {Name: "plain-credentials", Version: "1.7"}} | ||||||
|  | 		jenkinscr := *createJenkinsCR(userplugins, true) | ||||||
|  | 		got := jenkinscr.ValidateCreate() | ||||||
|  | 		assert.Nil(t, got) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("Validating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | ||||||
|  | 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
|  | 			{Name: "security-script", SecurityWarnings: createSecurityWarnings("1.2", "2.2")}, | ||||||
|  | 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | ||||||
|  | 			{Name: "git-client"}, | ||||||
|  | 			{Name: "git"}, | ||||||
|  | 			{Name: "sample-plugin", SecurityWarnings: createSecurityWarnings("0.8", "")}, | ||||||
|  | 			{Name: "command-launcher", SecurityWarnings: createSecurityWarnings("1.2", "1.4")}, | ||||||
|  | 			{Name: "plain-credentials"}, | ||||||
|  | 			{Name: "google-login", SecurityWarnings: createSecurityWarnings("1.1", "1.3")}, | ||||||
|  | 			{Name: "mailer", SecurityWarnings: createSecurityWarnings("1.0.3", "1.1.4")}, | ||||||
|  | 		}} | ||||||
|  | 		userplugins := []Plugin{{Name: "google-login", Version: "1.2"}, {Name: "mailer", Version: "1.1"}, {Name: "git", Version: "4.8.1"}, {Name: "command-launcher", Version: "1.6"}, {Name: "workflow-cps", Version: "2.59"}} | ||||||
|  | 		jenkinscr := *createJenkinsCR(userplugins, true) | ||||||
|  | 		got := jenkinscr.ValidateCreate() | ||||||
|  | 		assert.Equal(t, got, errors.New("security vulnerabilities detected in the following user-defined plugins: \nworkflow-cps:2.59\ngoogle-login:1.2\nmailer:1.1")) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("Updating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | ||||||
|  | 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
|  | 			{Name: "handy-uri-templates-2-api", SecurityWarnings: createSecurityWarnings("2.1.8-1.0", "2.2.8-1.0")}, | ||||||
|  | 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | ||||||
|  | 			{Name: "resource-disposer", SecurityWarnings: createSecurityWarnings("0.7", "1.2")}, | ||||||
|  | 			{Name: "git"}, | ||||||
|  | 			{Name: "jjwt-api"}, | ||||||
|  | 			{Name: "blueocean-github-pipeline", SecurityWarnings: createSecurityWarnings("1.2.0-alpha-2", "1.2.0-beta-5")}, | ||||||
|  | 			{Name: "command-launcher", SecurityWarnings: createSecurityWarnings("1.2", "1.4")}, | ||||||
|  | 			{Name: "plain-credentials"}, | ||||||
|  | 			{Name: "ghprb", SecurityWarnings: createSecurityWarnings("1.1", "1.43")}, | ||||||
|  | 			{Name: "mailer", SecurityWarnings: createSecurityWarnings("1.0.3", "1.1.4")}, | ||||||
|  | 		}} | ||||||
|  | 
 | ||||||
|  | 		userplugins := []Plugin{{Name: "google-login", Version: "1.2"}, {Name: "mailer", Version: "1.1"}, {Name: "git", Version: "4.8.1"}, {Name: "command-launcher", Version: "1.6"}, {Name: "workflow-cps", Version: "2.59"}} | ||||||
|  | 		oldjenkinscr := *createJenkinsCR(userplugins, true) | ||||||
|  | 
 | ||||||
|  | 		userplugins = []Plugin{{Name: "handy-uri-templates-2-api", Version: "2.1.8-1.0"}, {Name: "resource-disposer", Version: "0.8"}, {Name: "jjwt-api", Version: "0.11.2-9.c8b45b8bb173"}, {Name: "blueocean-github-pipeline", Version: "1.2.0-beta-3"}, {Name: "ghprb", Version: "1.39"}} | ||||||
|  | 		newjenkinscr := *createJenkinsCR(userplugins, true) | ||||||
|  | 		got := newjenkinscr.ValidateUpdate(&oldjenkinscr) | ||||||
|  | 		assert.Equal(t, got, errors.New("security vulnerabilities detected in the following user-defined plugins: \nhandy-uri-templates-2-api:2.1.8-1.0\nresource-disposer:0.8\nblueocean-github-pipeline:1.2.0-beta-3\nghprb:1.39")) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("Validation is turned off", func(t *testing.T) { | ||||||
|  | 		userplugins := []Plugin{{Name: "google-login", Version: "1.2"}, {Name: "mailer", Version: "1.1"}, {Name: "git", Version: "4.8.1"}, {Name: "command-launcher", Version: "1.6"}, {Name: "workflow-cps", Version: "2.59"}} | ||||||
|  | 		jenkinscr := *createJenkinsCR(userplugins, false) | ||||||
|  | 		got := jenkinscr.ValidateCreate() | ||||||
|  | 		assert.Nil(t, got) | ||||||
|  | 
 | ||||||
|  | 		userplugins = []Plugin{{Name: "google-login", Version: "1.2"}, {Name: "mailer", Version: "1.1"}, {Name: "git", Version: "4.8.1"}, {Name: "command-launcher", Version: "1.6"}, {Name: "workflow-cps", Version: "2.59"}} | ||||||
|  | 		newjenkinscr := *createJenkinsCR(userplugins, false) | ||||||
|  | 		got = newjenkinscr.ValidateUpdate(&jenkinscr) | ||||||
|  | 		assert.Nil(t, got) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createJenkinsCR(userPlugins []Plugin, validateSecurityWarnings bool) *Jenkins { | ||||||
|  | 	jenkins := &Jenkins{ | ||||||
|  | 		TypeMeta: JenkinsTypeMeta(), | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      "jenkins", | ||||||
|  | 			Namespace: "test", | ||||||
|  | 		}, | ||||||
|  | 		Spec: JenkinsSpec{ | ||||||
|  | 			Master: JenkinsMaster{ | ||||||
|  | 				Plugins:               userPlugins, | ||||||
|  | 				DisableCSRFProtection: false, | ||||||
|  | 			}, | ||||||
|  | 			ValidateSecurityWarnings: validateSecurityWarnings, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return jenkins | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createSecurityWarnings(firstVersion string, lastVersion string) []Warning { | ||||||
|  | 	return []Warning{{Versions: []Version{{FirstVersion: firstVersion, LastVersion: lastVersion}}, ID: "null", Message: "unit testing", URL: "null", Active: false}} | ||||||
|  | } | ||||||
|  | @ -11,6 +11,16 @@ entries: | ||||||
|     urls: |     urls: | ||||||
|     - https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart/jenkins-operator/jenkins-operator-0.5.2.tgz |     - https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart/jenkins-operator/jenkins-operator-0.5.2.tgz | ||||||
|     version: 0.5.2 |     version: 0.5.2 | ||||||
|  |   - apiVersion: v2 | ||||||
|  |     appVersion: 0.6.0 | ||||||
|  |     created: "2021-06-11T13:50:32.677639006+02:00" | ||||||
|  |     description: Kubernetes native operator which fully manages Jenkins on Kubernetes | ||||||
|  |     digest: 48fbf15c3ffff7003623edcde0bec39dc37d0a62303f08066960d5fac799af90 | ||||||
|  |     icon: https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/assets/jenkins-operator-icon.png | ||||||
|  |     name: jenkins-operator | ||||||
|  |     urls: | ||||||
|  |     - https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart/jenkins-operator/jenkins-operator-0.5.2.tgz | ||||||
|  |     version: 0.5.2 | ||||||
|   - apiVersion: v2 |   - apiVersion: v2 | ||||||
|     appVersion: 0.6.0 |     appVersion: 0.6.0 | ||||||
|     created: "2021-06-11T13:50:32.677639006+02:00" |     created: "2021-06-11T13:50:32.677639006+02:00" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | dependencies: | ||||||
|  | - name: cert-manager | ||||||
|  |   repository: https://charts.jetstack.io | ||||||
|  |   version: v1.5.1 | ||||||
|  | digest: sha256:3220f5584bd04a8c8d4b2a076d49cc046211a463bb9a12ebbbae752be9b70bb1 | ||||||
|  | generated: "2021-08-18T01:07:49.505353718+05:30" | ||||||
|  | @ -4,3 +4,8 @@ description: Kubernetes native operator which fully manages Jenkins on Kubernete | ||||||
| name: jenkins-operator | name: jenkins-operator | ||||||
| version: 0.5.2 | version: 0.5.2 | ||||||
| icon: https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/assets/jenkins-operator-icon.png | icon: https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/assets/jenkins-operator-icon.png | ||||||
|  | dependencies: | ||||||
|  | - name: cert-manager | ||||||
|  |   version: "1.5.1" | ||||||
|  |   condition: webhook.enabled | ||||||
|  |   repository: https://charts.jetstack.io | ||||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -5,6 +5,7 @@ kind: CustomResourceDefinition | ||||||
| metadata: | metadata: | ||||||
|   annotations: |   annotations: | ||||||
|     controller-gen.kubebuilder.io/version: v0.4.1 |     controller-gen.kubebuilder.io/version: v0.4.1 | ||||||
|  |   creationTimestamp: null | ||||||
|   name: jenkins.jenkins.io |   name: jenkins.jenkins.io | ||||||
| spec: | spec: | ||||||
|   group: jenkins.io |   group: jenkins.io | ||||||
|  | @ -35,6 +36,10 @@ spec: | ||||||
|           spec: |           spec: | ||||||
|             description: Spec defines the desired state of the Jenkins |             description: Spec defines the desired state of the Jenkins | ||||||
|             properties: |             properties: | ||||||
|  |               ValidateSecurityWarnings: | ||||||
|  |                 description: ValidateSecurityWarnings enables or disables validating | ||||||
|  |                   potential security warnings in Jenkins plugins via admission webhooks. | ||||||
|  |                 type: boolean | ||||||
|               backup: |               backup: | ||||||
|                 description: 'Backup defines configuration of Jenkins backup More |                 description: 'Backup defines configuration of Jenkins backup More | ||||||
|                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' |                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' | ||||||
|  |  | ||||||
|  | @ -145,8 +145,8 @@ spec: | ||||||
|     securityContext: |     securityContext: | ||||||
|     {{- toYaml . | nindent 6 }} |     {{- toYaml . | nindent 6 }} | ||||||
|     {{- end }} |     {{- end }} | ||||||
|   {{- with .Values.jenkins.seedJobs }} |  | ||||||
|   ValidateSecurityWarnings: {{ .Values.jenkins.ValidateSecurityWarnings }} |   ValidateSecurityWarnings: {{ .Values.jenkins.ValidateSecurityWarnings }} | ||||||
|  |   {{- with .Values.jenkins.seedJobs }} | ||||||
|   seedJobs: {{- toYaml . | nindent 4 }} |   seedJobs: {{- toYaml . | nindent 4 }} | ||||||
|   {{- end }} |   {{- end }} | ||||||
| {{- end }} | {{- end }} | ||||||
|  |  | ||||||
|  | @ -31,7 +31,16 @@ spec: | ||||||
|               protocol: TCP |               protocol: TCP | ||||||
|           command: |           command: | ||||||
|             - /manager |             - /manager | ||||||
|           args: [] |           args:  | ||||||
|  |           {{- if .Values.webhook.enabled }} | ||||||
|  |           - --validate-security-warnings | ||||||
|  |           {{- end }} | ||||||
|  |           {{- if .Values.webhook.enabled }} | ||||||
|  |           volumeMounts: | ||||||
|  |           - mountPath: /tmp/k8s-webhook-server/serving-certs | ||||||
|  |             name: webhook-certs | ||||||
|  |             readOnly: true | ||||||
|  |           {{- end }} | ||||||
|           env: |           env: | ||||||
|             - name: WATCH_NAMESPACE |             - name: WATCH_NAMESPACE | ||||||
|               value: {{ .Values.jenkins.namespace }} |               value: {{ .Values.jenkins.namespace }} | ||||||
|  | @ -55,3 +64,11 @@ spec: | ||||||
|       tolerations: |       tolerations: | ||||||
|         {{- toYaml . | nindent 8 }} |         {{- toYaml . | nindent 8 }} | ||||||
|     {{- end }} |     {{- end }} | ||||||
|  |     {{- if .Values.webhook.enabled }} | ||||||
|  |       volumes: | ||||||
|  |       - name: webhook-certs | ||||||
|  |         secret: | ||||||
|  |           defaultMode: 420 | ||||||
|  |           secretName: jenkins-{{ .Values.webhook.certificate.name }} | ||||||
|  |       terminationGracePeriodSeconds: 10 | ||||||
|  |     {{- end }} | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | {{- if .Values.webhook.enabled }} | ||||||
|  | apiVersion: cert-manager.io/v1 | ||||||
|  | kind: Certificate | ||||||
|  | metadata: | ||||||
|  |   name: jenkins-{{ .Values.webhook.certificate.name }} | ||||||
|  |   namespace: {{ .Release.Namespace }} | ||||||
|  | spec: | ||||||
|  |   duration: {{ .Values.webhook.certificate.duration }} | ||||||
|  |   renewBefore: {{ .Values.webhook.certificate.renewbefore }} | ||||||
|  |   secretName: jenkins-{{ .Values.webhook.certificate.name }} | ||||||
|  |   dnsNames: | ||||||
|  |   - jenkins-webhook-service.{{ .Release.Namespace }}.svc  | ||||||
|  |   - jenkins-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: {} | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: Secret | ||||||
|  | metadata: | ||||||
|  |   name: jenkins-{{ .Values.webhook.certificate.name }} | ||||||
|  | type: opaque | ||||||
|  | 
 | ||||||
|  | {{- end }} | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | {{- if .Values.webhook.enabled }} | ||||||
|  | apiVersion: admissionregistration.k8s.io/v1 | ||||||
|  | kind: ValidatingWebhookConfiguration | ||||||
|  | metadata: | ||||||
|  |   name: {{ .Release.Name }}-webhook | ||||||
|  |   annotations: | ||||||
|  |     cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/jenkins-{{ .Values.webhook.certificate.name }} | ||||||
|  | webhooks: | ||||||
|  | - admissionReviewVersions: | ||||||
|  |   - v1 | ||||||
|  |   - v1beta1 | ||||||
|  |   clientConfig: | ||||||
|  |     service: | ||||||
|  |       name: jenkins-webhook-service | ||||||
|  |       namespace: {{ .Release.Namespace }} | ||||||
|  |       path: /validate-jenkins-io-v1alpha2-jenkins | ||||||
|  |   failurePolicy: Fail | ||||||
|  |   name: vjenkins.kb.io | ||||||
|  |   timeoutSeconds: 30 | ||||||
|  |   rules: | ||||||
|  |   - apiGroups: | ||||||
|  |     - jenkins.io | ||||||
|  |     apiVersions: | ||||||
|  |     - v1alpha2 | ||||||
|  |     operations: | ||||||
|  |     - CREATE | ||||||
|  |     - UPDATE | ||||||
|  |     resources: | ||||||
|  |     - jenkins | ||||||
|  |     scope: "Namespaced" | ||||||
|  |   sideEffects: None | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: Service | ||||||
|  | metadata: | ||||||
|  |   name: jenkins-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 }} | ||||||
|  | --- | ||||||
|  | {{- end }} | ||||||
|  | @ -280,3 +280,22 @@ operator: | ||||||
|   nodeSelector: {} |   nodeSelector: {} | ||||||
|   tolerations: [] |   tolerations: [] | ||||||
|   affinity: {} |   affinity: {} | ||||||
|  | 
 | ||||||
|  | webhook: | ||||||
|  | # TLS certificates for webhook | ||||||
|  |   certificate: | ||||||
|  |     name: webhook-certificate | ||||||
|  | 
 | ||||||
|  |     # validity of the certificate | ||||||
|  |     duration: 2160h | ||||||
|  |    | ||||||
|  |     # time after which the certificate will be automatically renewed  | ||||||
|  |     renewbefore: 360h | ||||||
|  |   # enable or disable the validation webhook | ||||||
|  |   enabled: false | ||||||
|  | 
 | ||||||
|  | # This startupapicheck is a Helm post-install hook that waits for the webhook | ||||||
|  | # endpoints to become available. | ||||||
|  | cert-manager: | ||||||
|  |   startupapicheck: | ||||||
|  |     enabled: false | ||||||
|  | @ -5,6 +5,7 @@ kind: CustomResourceDefinition | ||||||
| metadata: | metadata: | ||||||
|   annotations: |   annotations: | ||||||
|     controller-gen.kubebuilder.io/version: v0.4.1 |     controller-gen.kubebuilder.io/version: v0.4.1 | ||||||
|  |   creationTimestamp: null | ||||||
|   name: jenkins.jenkins.io |   name: jenkins.jenkins.io | ||||||
| spec: | spec: | ||||||
|   group: jenkins.io |   group: jenkins.io | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | 
 | ||||||
| apiVersion: jenkins.io/v1alpha2 | apiVersion: jenkins.io/v1alpha2 | ||||||
| kind: Jenkins | kind: Jenkins | ||||||
| metadata: | metadata: | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -27,4 +27,6 @@ require ( | ||||||
| 	k8s.io/client-go v0.20.2 | 	k8s.io/client-go v0.20.2 | ||||||
| 	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 | 	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 | ||||||
| 	sigs.k8s.io/controller-runtime v0.7.0 | 	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.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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= | ||||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								main.go
								
								
								
								
							
							
						
						
									
										13
									
								
								main.go
								
								
								
								
							|  | @ -78,6 +78,7 @@ func main() { | ||||||
| 	var metricsAddr string | 	var metricsAddr string | ||||||
| 	var enableLeaderElection bool | 	var enableLeaderElection bool | ||||||
| 	var probeAddr string | 	var probeAddr string | ||||||
|  | 	var ValidateSecurityWarnings bool | ||||||
| 
 | 
 | ||||||
| 	isRunningInCluster, err := resources.IsRunningInCluster() | 	isRunningInCluster, err := resources.IsRunningInCluster() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -88,6 +89,7 @@ func main() { | ||||||
| 	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") | 	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") | ||||||
| 	flag.BoolVar(&enableLeaderElection, "leader-elect", isRunningInCluster, "Enable leader election for controller manager. "+ | 	flag.BoolVar(&enableLeaderElection, "leader-elect", isRunningInCluster, "Enable leader election for controller manager. "+ | ||||||
| 		"Enabling this will ensure there is only one active controller manager.") | 		"Enabling this will ensure there is only one active controller manager.") | ||||||
|  | 	flag.BoolVar(&ValidateSecurityWarnings, "validate-security-warnings", false, "Enable validation for potential security warnings in jenkins custom resource plugins") | ||||||
| 	hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") | 	hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") | ||||||
| 	port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") | 	port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") | ||||||
| 	useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") | 	useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") | ||||||
|  | @ -109,6 +111,15 @@ func main() { | ||||||
| 	} | 	} | ||||||
| 	logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) | 	logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) | ||||||
| 
 | 
 | ||||||
|  | 	if ValidateSecurityWarnings { | ||||||
|  | 		isInitialized := make(chan bool) | ||||||
|  | 		go v1alpha2.PluginsMgr.ManagePluginData(isInitialized) | ||||||
|  | 
 | ||||||
|  | 		if !<-isInitialized { | ||||||
|  | 			logger.Info("Unable to get the plugins data") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// get a config to talk to the API server
 | 	// get a config to talk to the API server
 | ||||||
| 	cfg, err := config.GetConfig() | 	cfg, err := config.GetConfig() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -169,9 +180,11 @@ func main() { | ||||||
| 		fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) | 		fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if ValidateSecurityWarnings { | ||||||
| 		if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil { | 		if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil { | ||||||
| 			fatal(errors.Wrap(err, "unable to create Webhook"), *debug) | 			fatal(errors.Wrap(err, "unable to create Webhook"), *debug) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	// +kubebuilder:scaffold:builder
 | 	// +kubebuilder:scaffold:builder
 | ||||||
| 
 | 
 | ||||||
| 	if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { | 	if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { | ||||||
|  |  | ||||||
|  | @ -1,20 +1,26 @@ | ||||||
| package helm | package helm | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/constants" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/test/e2e" | 	"github.com/jenkinsci/kubernetes-operator/test/e2e" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"k8s.io/apimachinery/pkg/util/intstr" | ||||||
| 
 | 
 | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	// +kubebuilder:scaffold:imports
 | 	// +kubebuilder:scaffold:imports
 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var _ = Describe("Jenkins controller", func() { | var _ = Describe("Jenkins Controller with webhook", func() { | ||||||
|  | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		namespace *corev1.Namespace | 		namespace *corev1.Namespace | ||||||
| 	) | 	) | ||||||
|  | @ -23,12 +29,16 @@ var _ = Describe("Jenkins controller", func() { | ||||||
| 		namespace = e2e.CreateNamespace() | 		namespace = e2e.CreateNamespace() | ||||||
| 	}) | 	}) | ||||||
| 	AfterEach(func() { | 	AfterEach(func() { | ||||||
|  | 		cmd := exec.Command("../../bin/helm", "delete", "jenkins", "--namespace", namespace.Name) | ||||||
|  | 		output, err := cmd.CombinedOutput() | ||||||
|  | 		Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
|  | 
 | ||||||
| 		e2e.ShowLogsIfTestHasFailed(CurrentGinkgoTestDescription().Failed, namespace.Name) | 		e2e.ShowLogsIfTestHasFailed(CurrentGinkgoTestDescription().Failed, namespace.Name) | ||||||
| 		e2e.DestroyNamespace(namespace) | 		e2e.DestroyNamespace(namespace) | ||||||
| 	}) | 	}) | ||||||
| 	Context("when deploying Helm Chart to cluster", func() { |  | ||||||
| 		It("creates Jenkins instance and configures it", func() { |  | ||||||
| 
 | 
 | ||||||
|  | 	Context("Deploys jenkins operator with helm charts with default values", func() { | ||||||
|  | 		It("Deploys Jenkins operator and configures default Jenkins instance", func() { | ||||||
| 			jenkins := &v1alpha2.Jenkins{ | 			jenkins := &v1alpha2.Jenkins{ | ||||||
| 				TypeMeta: v1alpha2.JenkinsTypeMeta(), | 				TypeMeta: v1alpha2.JenkinsTypeMeta(), | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | @ -39,22 +49,186 @@ var _ = Describe("Jenkins controller", func() { | ||||||
| 
 | 
 | ||||||
| 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
| 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | ||||||
| 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install") | 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install", "--wait") | ||||||
| 			output, err := cmd.CombinedOutput() | 			output, err := cmd.CombinedOutput() | ||||||
| 			Expect(err).NotTo(HaveOccurred(), string(output)) | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
| 
 | 
 | ||||||
| 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | ||||||
| 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | ||||||
| 
 | 
 | ||||||
| 			cmd = exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | 		}) | ||||||
| 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | 	}) | ||||||
| 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install") |  | ||||||
| 			output, err = cmd.CombinedOutput() |  | ||||||
| 
 | 
 | ||||||
|  | 	Context("Deploys jenkins operator with helm charts with validating webhook and jenkins instance disabled", func() { | ||||||
|  | 		It("Deploys operator,denies creating a jenkins cr and creates jenkins cr with validation turned off", func() { | ||||||
|  | 
 | ||||||
|  | 			By("Deploying the operator along with webhook and cert-manager") | ||||||
|  | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
|  | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), "--set-string", fmt.Sprintf("operator.image=%s", *imageName), | ||||||
|  | 				"--set", fmt.Sprintf("webhook.enabled=%t", true), "--set", fmt.Sprintf("jenkins.enabled=%t", false), "--install", "--wait") | ||||||
|  | 			output, err := cmd.CombinedOutput() | ||||||
| 			Expect(err).NotTo(HaveOccurred(), string(output)) | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
| 
 | 
 | ||||||
|  | 			By("Waiting for the operator to fetch the plugin data ") | ||||||
|  | 			time.Sleep(time.Duration(200) * time.Second) | ||||||
|  | 
 | ||||||
|  | 			By("Denying a create request for a Jenkins custom resource with some plugins having security warnings and validation is turned on") | ||||||
|  | 			userplugins := []v1alpha2.Plugin{ | ||||||
|  | 				{Name: "simple-theme-plugin", Version: "0.6"}, | ||||||
|  | 				{Name: "audit-trail", Version: "3.5"}, | ||||||
|  | 				{Name: "github", Version: "1.29.0"}, | ||||||
|  | 			} | ||||||
|  | 			jenkins := CreateJenkinsCR("jenkins", namespace.Name, userplugins, true) | ||||||
|  | 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(MatchError("admission webhook \"vjenkins.kb.io\" denied the request: security vulnerabilities detected in the following user-defined plugins: \naudit-trail:3.5\ngithub:1.29.0")) | ||||||
|  | 
 | ||||||
|  | 			By("Creating the Jenkins resource with plugins not having any security warnings and validation is turned on") | ||||||
|  | 			userplugins = []v1alpha2.Plugin{ | ||||||
|  | 				{Name: "simple-theme-plugin", Version: "0.6"}, | ||||||
|  | 				{Name: "audit-trail", Version: "3.8"}, | ||||||
|  | 				{Name: "github", Version: "1.31.0"}, | ||||||
|  | 			} | ||||||
|  | 			jenkins = CreateJenkinsCR("jenkins", namespace.Name, userplugins, true) | ||||||
|  | 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | ||||||
| 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | ||||||
|  | 
 | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		It("Deploys operator, creates a jenkins cr and denies update request for another one", func() { | ||||||
|  | 			By("Deploying the operator along with webhook and cert-manager") | ||||||
|  | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
|  | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), "--set-string", fmt.Sprintf("operator.image=%s", *imageName), | ||||||
|  | 				"--set", fmt.Sprintf("webhook.enabled=%t", true), "--set", fmt.Sprintf("jenkins.enabled=%t", false), "--install", "--wait") | ||||||
|  | 			output, err := cmd.CombinedOutput() | ||||||
|  | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
|  | 
 | ||||||
|  | 			By("Waiting for the operator to fetch the plugin data ") | ||||||
|  | 			time.Sleep(time.Duration(200) * time.Second) | ||||||
|  | 
 | ||||||
|  | 			By("Creating a Jenkins custom resource with some plugins having security warnings but validation is turned off") | ||||||
|  | 			userplugins := []v1alpha2.Plugin{ | ||||||
|  | 				{Name: "simple-theme-plugin", Version: "0.6"}, | ||||||
|  | 				{Name: "audit-trail", Version: "3.5"}, | ||||||
|  | 				{Name: "github", Version: "1.29.0"}, | ||||||
|  | 			} | ||||||
|  | 			jenkins := CreateJenkinsCR("jenkins", namespace.Name, userplugins, false) | ||||||
|  | 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
|  | 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | ||||||
|  | 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | ||||||
|  | 
 | ||||||
|  | 			By("Failing to update the Jenkins custom resource because some plugins have security warnings and validation is turned on") | ||||||
|  | 			userplugins = []v1alpha2.Plugin{ | ||||||
|  | 				{Name: "vncviewer", Version: "1.7"}, | ||||||
|  | 				{Name: "build-timestamp", Version: "1.0.3"}, | ||||||
|  | 				{Name: "deployit-plugin", Version: "7.5.5"}, | ||||||
|  | 				{Name: "github-branch-source", Version: "2.0.7"}, | ||||||
|  | 				{Name: "aws-lambda-cloud", Version: "0.4"}, | ||||||
|  | 				{Name: "groovy", Version: "1.31"}, | ||||||
|  | 				{Name: "google-login", Version: "1.2"}, | ||||||
|  | 			} | ||||||
|  | 			jenkins.Spec.Master.Plugins = userplugins | ||||||
|  | 			jenkins.Spec.ValidateSecurityWarnings = true | ||||||
|  | 			Expect(e2e.K8sClient.Update(context.TODO(), jenkins)).Should(MatchError("admission webhook \"vjenkins.kb.io\" denied the request: security vulnerabilities detected in the following user-defined plugins: \nvncviewer:1.7\ndeployit-plugin:7.5.5\ngithub-branch-source:2.0.7\ngroovy:1.31\ngoogle-login:1.2")) | ||||||
|  | 
 | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  | 
 | ||||||
| }) | }) | ||||||
|  | 
 | ||||||
|  | func CreateJenkinsCR(name string, namespace string, userPlugins []v1alpha2.Plugin, validateSecurityWarnings bool) *v1alpha2.Jenkins { | ||||||
|  | 	jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 		TypeMeta: v1alpha2.JenkinsTypeMeta(), | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      name, | ||||||
|  | 			Namespace: namespace, | ||||||
|  | 		}, | ||||||
|  | 		Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 			GroovyScripts: v1alpha2.GroovyScripts{ | ||||||
|  | 				Customization: v1alpha2.Customization{ | ||||||
|  | 					Configurations: []v1alpha2.ConfigMapRef{}, | ||||||
|  | 					Secret: v1alpha2.SecretRef{ | ||||||
|  | 						Name: "", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			ConfigurationAsCode: v1alpha2.ConfigurationAsCode{ | ||||||
|  | 				Customization: v1alpha2.Customization{ | ||||||
|  | 					Configurations: []v1alpha2.ConfigMapRef{}, | ||||||
|  | 					Secret: v1alpha2.SecretRef{ | ||||||
|  | 						Name: "", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 				Containers: []v1alpha2.Container{ | ||||||
|  | 					{ | ||||||
|  | 						Name: resources.JenkinsMasterContainerName, | ||||||
|  | 						Env: []corev1.EnvVar{ | ||||||
|  | 							{ | ||||||
|  | 								Name:  "TEST_ENV", | ||||||
|  | 								Value: "test_env_value", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						ReadinessProbe: &corev1.Probe{ | ||||||
|  | 							Handler: corev1.Handler{ | ||||||
|  | 								HTTPGet: &corev1.HTTPGetAction{ | ||||||
|  | 									Path:   "/login", | ||||||
|  | 									Port:   intstr.FromString("http"), | ||||||
|  | 									Scheme: corev1.URISchemeHTTP, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							InitialDelaySeconds: int32(100), | ||||||
|  | 							TimeoutSeconds:      int32(4), | ||||||
|  | 							FailureThreshold:    int32(40), | ||||||
|  | 							SuccessThreshold:    int32(1), | ||||||
|  | 							PeriodSeconds:       int32(10), | ||||||
|  | 						}, | ||||||
|  | 						LivenessProbe: &corev1.Probe{ | ||||||
|  | 							Handler: corev1.Handler{ | ||||||
|  | 								HTTPGet: &corev1.HTTPGetAction{ | ||||||
|  | 									Path:   "/login", | ||||||
|  | 									Port:   intstr.FromString("http"), | ||||||
|  | 									Scheme: corev1.URISchemeHTTP, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							InitialDelaySeconds: int32(80), | ||||||
|  | 							TimeoutSeconds:      int32(4), | ||||||
|  | 							FailureThreshold:    int32(30), | ||||||
|  | 							SuccessThreshold:    int32(1), | ||||||
|  | 							PeriodSeconds:       int32(5), | ||||||
|  | 						}, | ||||||
|  | 						VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 							{ | ||||||
|  | 								Name:      "plugins-cache", | ||||||
|  | 								MountPath: "/usr/share/jenkins/ref/plugins", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Name:  "envoyproxy", | ||||||
|  | 						Image: "envoyproxy/envoy-alpine:v1.14.1", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Plugins:               userPlugins, | ||||||
|  | 				DisableCSRFProtection: false, | ||||||
|  | 				NodeSelector:          map[string]string{"kubernetes.io/os": "linux"}, | ||||||
|  | 				Volumes: []corev1.Volume{ | ||||||
|  | 					{ | ||||||
|  | 						Name: "plugins-cache", | ||||||
|  | 						VolumeSource: corev1.VolumeSource{ | ||||||
|  | 							EmptyDir: &corev1.EmptyDirVolumeSource{}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			ValidateSecurityWarnings: validateSecurityWarnings, | ||||||
|  | 			Service: v1alpha2.Service{ | ||||||
|  | 				Type: corev1.ServiceTypeNodePort, | ||||||
|  | 				Port: constants.DefaultHTTPPortInt32, | ||||||
|  | 			}, | ||||||
|  | 			JenkinsAPISettings: v1alpha2.JenkinsAPISettings{AuthorizationStrategy: v1alpha2.CreateUserAuthorizationStrategy}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return jenkins | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -222,6 +222,7 @@ subjects: | ||||||
| - kind: ServiceAccount | - kind: ServiceAccount | ||||||
|   name: jenkins-operator |   name: jenkins-operator | ||||||
| --- | --- | ||||||
|  | --- | ||||||
| apiVersion: apps/v1 | apiVersion: apps/v1 | ||||||
| kind: Deployment | kind: Deployment | ||||||
| metadata: | metadata: | ||||||
|  | @ -246,7 +247,7 @@ spec: | ||||||
|         - /manager |         - /manager | ||||||
|         args: |         args: | ||||||
|         - --leader-elect |         - --leader-elect | ||||||
|         image: jenkins-operator:6f33fe82-dirty |         image: jenkins-operator:37d0eac4-dirty | ||||||
|         name: jenkins-operator |         name: jenkins-operator | ||||||
|         imagePullPolicy: IfNotPresent |         imagePullPolicy: IfNotPresent | ||||||
|         securityContext: |         securityContext: | ||||||
|  | @ -265,11 +266,11 @@ spec: | ||||||
|           periodSeconds: 10 |           periodSeconds: 10 | ||||||
|         resources: |         resources: | ||||||
|           limits: |           limits: | ||||||
|             cpu: 100m |             cpu: 200m | ||||||
|             memory: 30Mi |             memory: 200Mi | ||||||
|           requests: |           requests: | ||||||
|             cpu: 100m |             cpu: 100m | ||||||
|             memory: 20Mi |             memory: 80Mi | ||||||
|         env: |         env: | ||||||
|           - name: WATCH_NAMESPACE |           - name: WATCH_NAMESPACE | ||||||
|             valueFrom: |             valueFrom: | ||||||
|  | @ -278,7 +279,6 @@ spec: | ||||||
|         volumeMounts: |         volumeMounts: | ||||||
|           - mountPath: /tmp/k8s-webhook-server/serving-certs |           - mountPath: /tmp/k8s-webhook-server/serving-certs | ||||||
|             name: cert        |             name: cert        | ||||||
|             readOnly: true |  | ||||||
|       volumes: |       volumes: | ||||||
|       - name: cert |       - name: cert | ||||||
|         secret: |         secret: | ||||||
|  | @ -315,7 +315,6 @@ spec: | ||||||
| apiVersion: admissionregistration.k8s.io/v1 | apiVersion: admissionregistration.k8s.io/v1 | ||||||
| kind: ValidatingWebhookConfiguration | kind: ValidatingWebhookConfiguration | ||||||
| metadata: | metadata: | ||||||
|   creationTimestamp: null |  | ||||||
|   name: validating-webhook-configuration |   name: validating-webhook-configuration | ||||||
|   annotations: |   annotations: | ||||||
|     cert-manager.io/inject-ca-from: default/webhook-certificate |     cert-manager.io/inject-ca-from: default/webhook-certificate | ||||||
|  | @ -330,6 +329,7 @@ webhooks: | ||||||
|       path: /validate-jenkins-io-v1alpha2-jenkins |       path: /validate-jenkins-io-v1alpha2-jenkins | ||||||
|   failurePolicy: Fail |   failurePolicy: Fail | ||||||
|   name: vjenkins.kb.io |   name: vjenkins.kb.io | ||||||
|  |   timeoutSeconds: 30 | ||||||
|   rules: |   rules: | ||||||
|   - apiGroups: |   - apiGroups: | ||||||
|     - jenkins.io |     - jenkins.io | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | --- | ||||||
| apiVersion: apps/v1 | apiVersion: apps/v1 | ||||||
| kind: Deployment | kind: Deployment | ||||||
| metadata: | metadata: | ||||||
|  | @ -22,6 +23,7 @@ spec: | ||||||
|         - /manager |         - /manager | ||||||
|         args: |         args: | ||||||
|         - --leader-elect |         - --leader-elect | ||||||
|  |         - --validate-security-warnings  | ||||||
|         image: {DOCKER_REGISTRY}:{GITCOMMIT} |         image: {DOCKER_REGISTRY}:{GITCOMMIT} | ||||||
|         name: jenkins-operator |         name: jenkins-operator | ||||||
|         imagePullPolicy: IfNotPresent |         imagePullPolicy: IfNotPresent | ||||||
|  | @ -41,11 +43,11 @@ spec: | ||||||
|           periodSeconds: 10 |           periodSeconds: 10 | ||||||
|         resources: |         resources: | ||||||
|           limits: |           limits: | ||||||
|             cpu: 100m |             cpu: 200m | ||||||
|             memory: 30Mi |             memory: 200Mi | ||||||
|           requests: |           requests: | ||||||
|             cpu: 100m |             cpu: 100m | ||||||
|             memory: 20Mi |             memory: 80Mi | ||||||
|         env: |         env: | ||||||
|           - name: WATCH_NAMESPACE |           - name: WATCH_NAMESPACE | ||||||
|             valueFrom: |             valueFrom: | ||||||
|  | @ -54,7 +56,6 @@ spec: | ||||||
|         volumeMounts: |         volumeMounts: | ||||||
|           - mountPath: /tmp/k8s-webhook-server/serving-certs |           - mountPath: /tmp/k8s-webhook-server/serving-certs | ||||||
|             name: cert        |             name: cert        | ||||||
|             readOnly: true |  | ||||||
|       volumes: |       volumes: | ||||||
|       - name: cert |       - name: cert | ||||||
|         secret: |         secret: | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| apiVersion: admissionregistration.k8s.io/v1 | apiVersion: admissionregistration.k8s.io/v1 | ||||||
| kind: ValidatingWebhookConfiguration | kind: ValidatingWebhookConfiguration | ||||||
| metadata: | metadata: | ||||||
|   creationTimestamp: null |  | ||||||
|   name: validating-webhook-configuration |   name: validating-webhook-configuration | ||||||
|   annotations: |   annotations: | ||||||
|     cert-manager.io/inject-ca-from: default/webhook-certificate |     cert-manager.io/inject-ca-from: default/webhook-certificate | ||||||
|  | @ -16,6 +15,7 @@ webhooks: | ||||||
|       path: /validate-jenkins-io-v1alpha2-jenkins |       path: /validate-jenkins-io-v1alpha2-jenkins | ||||||
|   failurePolicy: Fail |   failurePolicy: Fail | ||||||
|   name: vjenkins.kb.io |   name: vjenkins.kb.io | ||||||
|  |   timeoutSeconds: 30 | ||||||
|   rules: |   rules: | ||||||
|   - apiGroups: |   - apiGroups: | ||||||
|     - jenkins.io |     - jenkins.io | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue