Implemented validation logic for the webhook
- Created a single Validate() function to validate both updating and creating Jenkins CR. - Implemented the Validate function to fetch warnings from the API and do security check if being enabled. - Updated the helm charts and helm-e2e target to run the helm tests.
This commit is contained in:
parent
e87c7cac5f
commit
52fe5fe95e
3
Makefile
3
Makefile
|
|
@ -96,7 +96,8 @@ e2e: deepcopy-gen manifests ## Runs e2e tests, you can use EXTRA_ARGS
|
||||||
|
|
||||||
.PHONY: helm-e2e
|
.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 install-cert-manager 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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,15 @@ limitations under the License.
|
||||||
package v1alpha2
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/plugins"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"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"
|
||||||
|
|
@ -41,21 +50,142 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type Warnings struct {
|
||||||
|
Warnings []Warning `json:"securityWarnings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Warning struct {
|
||||||
|
Versions []Version `json:"versions"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
type Version struct {
|
||||||
|
FirstVersion string `json:"firstVersion"`
|
||||||
|
LastVersion string `json:"lastVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const APIURL string = "https://plugins.jenkins.io/api/plugin/"
|
||||||
|
|
||||||
|
func MakeSemanticVersion(version string) string {
|
||||||
|
version = "v" + version
|
||||||
|
return semver.Canonical(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompareVersions(firstVersion string, lastVersion string, pluginVersion string) bool {
|
||||||
|
firstSemVer := MakeSemanticVersion(firstVersion)
|
||||||
|
lastSemVer := MakeSemanticVersion(lastVersion)
|
||||||
|
pluginSemVer := MakeSemanticVersion(pluginVersion)
|
||||||
|
if semver.Compare(pluginSemVer, firstSemVer) == -1 || semver.Compare(pluginSemVer, lastSemVer) == 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckSecurityWarnings(pluginName string, pluginVersion string) (bool, error) {
|
||||||
|
jenkinslog.Info("checking security warnings", "plugin: ", pluginName)
|
||||||
|
pluginURL := APIURL + pluginName
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 30,
|
||||||
|
}
|
||||||
|
request, err := http.NewRequest("GET", pluginURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
request.Header.Add("Accept", "application/json")
|
||||||
|
request.Header.Add("Content-Type", "application/json")
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
securityWarnings := Warnings{}
|
||||||
|
|
||||||
|
jsonErr := json.Unmarshal(bodyBytes, &securityWarnings)
|
||||||
|
if jsonErr != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jenkinslog.Info("Validate()", "warnings", securityWarnings)
|
||||||
|
|
||||||
|
for _, warning := range securityWarnings.Warnings {
|
||||||
|
for _, version := range warning.Versions {
|
||||||
|
firstVersion := version.FirstVersion
|
||||||
|
lastVersion := version.LastVersion
|
||||||
|
if len(firstVersion) == 0 {
|
||||||
|
firstVersion = "0" // setting default value in case of empty string
|
||||||
|
}
|
||||||
|
if len(lastVersion) == 0 {
|
||||||
|
lastVersion = pluginVersion // setting default value in case of empty string
|
||||||
|
}
|
||||||
|
|
||||||
|
if CompareVersions(firstVersion, lastVersion, pluginVersion) {
|
||||||
|
jenkinslog.Info("security Vulnerabilities detected", "message", warning.Message, "Check security Advisory", warning.URL)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Validate(r Jenkins) error {
|
||||||
|
basePlugins := plugins.BasePlugins()
|
||||||
|
var warnings string = ""
|
||||||
|
|
||||||
|
for _, plugin := range basePlugins {
|
||||||
|
name := plugin.Name
|
||||||
|
version := plugin.Version
|
||||||
|
hasWarnings, err := CheckSecurityWarnings(name, version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasWarnings {
|
||||||
|
warnings += "Security Vulnerabilities detected in base plugin:" + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, plugin := range r.Spec.Master.Plugins {
|
||||||
|
name := plugin.Name
|
||||||
|
version := plugin.Version
|
||||||
|
hasWarnings, err := CheckSecurityWarnings(name, version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasWarnings {
|
||||||
|
warnings += "Security Vulnerabilities detected in the user defined plugin: " + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
return errors.New(warnings)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: webhook-certificate
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
duration: 2160h
|
||||||
|
renewBefore: 360h
|
||||||
|
secretName: webhook-server-cert
|
||||||
|
dnsNames:
|
||||||
|
- webhook-service.{{ .Release.Namespace }}.svc
|
||||||
|
- webhook-service.{{ .Release.Namespace }}.svc.cluster.local
|
||||||
|
issuerRef:
|
||||||
|
kind: Issuer
|
||||||
|
name: selfsigned
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Issuer
|
||||||
|
metadata:
|
||||||
|
name: selfsigned
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
selfSigned: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -32,6 +32,10 @@ spec:
|
||||||
command:
|
command:
|
||||||
- /manager
|
- /manager
|
||||||
args: []
|
args: []
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||||
|
name: cert
|
||||||
|
readOnly: true
|
||||||
env:
|
env:
|
||||||
- name: WATCH_NAMESPACE
|
- name: WATCH_NAMESPACE
|
||||||
value: {{ .Values.jenkins.namespace }}
|
value: {{ .Values.jenkins.namespace }}
|
||||||
|
|
@ -55,3 +59,9 @@ spec:
|
||||||
tolerations:
|
tolerations:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: cert
|
||||||
|
secret:
|
||||||
|
defaultMode: 420
|
||||||
|
secretName: webhook-server-cert
|
||||||
|
terminationGracePeriodSeconds: 10
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: validating-webhook-configuration
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/webhook-certificate
|
||||||
|
webhooks:
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
- v1beta1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: webhook-service
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
path: /validate-jenkins-io-v1alpha2-jenkins
|
||||||
|
failurePolicy: Fail
|
||||||
|
name: vjenkins.kb.io
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- jenkins.io
|
||||||
|
apiVersions:
|
||||||
|
- v1alpha2
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- jenkins
|
||||||
|
sideEffects: None
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: webhook-service
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 9443
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: {{ include "jenkins-operator.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
---
|
||||||
2
go.mod
2
go.mod
|
|
@ -27,4 +27,6 @@ require (
|
||||||
k8s.io/client-go v0.20.2
|
k8s.io/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=
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue