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
7
Makefile
7
Makefile
|
|
@ -96,7 +96,8 @@ 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)
|
||||||
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 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 {
|
||||||
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
|
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 {
|
||||||
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
|
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}}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
entries:
|
entries:
|
||||||
jenkins-operator:
|
jenkins-operator:
|
||||||
|
- 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
File diff suppressed because it is too large
Load Diff
|
|
@ -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:
|
||||||
|
|
@ -52,4 +53,4 @@ spec:
|
||||||
targets: "cicd/jobs/*.jenkins"
|
targets: "cicd/jobs/*.jenkins"
|
||||||
description: "Jenkins Operator repository"
|
description: "Jenkins Operator repository"
|
||||||
repositoryBranch: master
|
repositoryBranch: master
|
||||||
repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
|
repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git
|
||||||
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=
|
||||||
|
|
|
||||||
17
main.go
17
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,8 +180,10 @@ func main() {
|
||||||
fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug)
|
fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil {
|
if ValidateSecurityWarnings {
|
||||||
fatal(errors.Wrap(err, "unable to create Webhook"), *debug)
|
if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil {
|
||||||
|
fatal(errors.Wrap(err, "unable to create Webhook"), *debug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// +kubebuilder:scaffold:builder
|
// +kubebuilder:scaffold:builder
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred(), string(output))
|
|
||||||
|
|
||||||
e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins)
|
|
||||||
e2e.WaitForJenkinsUserConfigurationToComplete(jenkins)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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.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:
|
||||||
|
|
@ -277,13 +278,12 @@ spec:
|
||||||
fieldPath: metadata.namespace
|
fieldPath: metadata.namespace
|
||||||
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:
|
||||||
defaultMode: 420
|
defaultMode: 420
|
||||||
secretName: webhook-server-cert
|
secretName: webhook-server-cert
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: cert-manager.io/v1
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -53,12 +55,11 @@ spec:
|
||||||
fieldPath: metadata.namespace
|
fieldPath: metadata.namespace
|
||||||
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:
|
||||||
defaultMode: 420
|
defaultMode: 420
|
||||||
secretName: webhook-server-cert
|
secretName: webhook-server-cert
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -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