1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

Merge branch 'master' into 744_deny_requests

# Conflicts:
#	pkg/utils/util.go
#	pkg/webhooks/server.go
This commit is contained in:
Shuting Zhao 2020-05-18 18:05:22 -07:00
commit 416f5ecc00
14 changed files with 197 additions and 116 deletions

View file

@ -7,14 +7,13 @@ import (
"flag"
"fmt"
"os"
"regexp"
"strconv"
"sync"
"time"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/signal"
"github.com/nirmata/kyverno/pkg/utils"
"k8s.io/apimachinery/pkg/api/errors"
rest "k8s.io/client-go/rest"
clientcmd "k8s.io/client-go/tools/clientcmd"
@ -63,7 +62,9 @@ func main() {
// Exit for unsupported version of kubernetes cluster
// https://github.com/nirmata/kyverno/issues/700
// - supported from v1.12.7+
isVersionSupported(client)
if !utils.CompareKubernetesVersion(client, log.Log, 1, 12, 7) {
os.Exit(1)
}
requests := []request{
// Resource
@ -222,39 +223,3 @@ func merge(done <-chan struct{}, stopCh <-chan struct{}, processes ...<-chan err
}()
return out
}
func isVersionSupported(client *client.Client) {
logger := log.Log
serverVersion, err := client.DiscoveryClient.GetServerVersion()
if err != nil {
logger.Error(err, "Failed to get kubernetes server version")
os.Exit(1)
}
exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`)
groups := exp.FindAllStringSubmatch(serverVersion.String(), -1)
if len(groups) != 1 || len(groups[0]) != 4 {
logger.Error(err, "Failed to extract kubernetes server version", "serverVersion", serverVersion)
os.Exit(1)
}
// convert string to int
// assuming the version are always intergers
major, err := strconv.Atoi(groups[0][1])
if err != nil {
logger.Error(err, "Failed to extract kubernetes major server version", "serverVersion", serverVersion)
os.Exit(1)
}
minor, err := strconv.Atoi(groups[0][2])
if err != nil {
logger.Error(err, "Failed to extract kubernetes minor server version", "serverVersion", serverVersion)
os.Exit(1)
}
sub, err := strconv.Atoi(groups[0][3])
if err != nil {
logger.Error(err, "Failed to extract kubernetes sub minor server version", "serverVersion", serverVersion)
os.Exit(1)
}
if major <= 1 && minor <= 12 && sub < 7 {
logger.Info("Unsupported kubernetes server version %s. Kyverno is supported from version v1.12.7+", "serverVersion", serverVersion)
os.Exit(1)
}
}

12
pkg/constant/constant.go Normal file
View file

@ -0,0 +1,12 @@
package constant
import "time"
const (
CRDControllerResync = 10 * time.Minute
PolicyViolationControllerResync = 5 * time.Minute
PolicyControllerResync = time.Second
EventControllerResync = time.Second
GenerateControllerResync = time.Second
GenerateRequestControllerResync = time.Second
)

View file

@ -1,13 +1,12 @@
package event
import (
"time"
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/client/clientset/versioned/scheme"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/constant"
client "github.com/nirmata/kyverno/pkg/dclient"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -67,7 +66,7 @@ func initRecorder(client *client.Client, eventSource Source, log logr.Logger) re
return nil
}
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartLogging(klog.V(5).Infof)
eventInterface, err := client.GetEventsInterface()
if err != nil {
log.Error(err, "failed to get event interface for logging")
@ -109,7 +108,7 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
}
for i := 0; i < workers; i++ {
go wait.Until(gen.runWorker, time.Second, stopCh)
go wait.Until(gen.runWorker, constant.EventControllerResync, stopCh)
}
<-stopCh
}

View file

@ -8,6 +8,7 @@ import (
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/constant"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -199,7 +200,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
return
}
for i := 0; i < workers; i++ {
go wait.Until(c.worker, time.Second, stopCh)
go wait.Until(c.worker, constant.GenerateRequestControllerResync, stopCh)
}
<-stopCh
}

View file

@ -8,6 +8,7 @@ import (
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/constant"
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystatus"
@ -219,7 +220,7 @@ func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
return
}
for i := 0; i < workers; i++ {
go wait.Until(c.worker, time.Second, stopCh)
go wait.Until(c.worker, constant.GenerateControllerResync, stopCh)
}
<-stopCh
}

View file

@ -4,7 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"time"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -19,6 +18,7 @@ import (
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
log "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/nirmata/kyverno/pkg/constant"
client "github.com/nirmata/kyverno/pkg/dclient"
"k8s.io/apimachinery/pkg/util/wait"
)
@ -28,6 +28,36 @@ type crdSync struct {
controller *Controller
}
// crdDefinitionPrior represents CRD's version prior to 1.16
var crdDefinitionPrior struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Validation struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"validation"`
} `json:"spec"`
}
// crdDefinitionNew represents CRD in version 1.16+
var crdDefinitionNew struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Versions []struct {
Schema struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"schema"`
Storage bool `json:"storage"`
} `json:"versions"`
} `json:"spec"`
}
var crdVersion struct {
}
func NewCRDSync(client *client.Client, controller *Controller) *crdSync {
if controller == nil {
panic(fmt.Errorf("nil controller sent into crd sync"))
@ -54,7 +84,7 @@ func (c *crdSync) Run(workers int, stopCh <-chan struct{}) {
c.sync()
for i := 0; i < workers; i++ {
go wait.Until(c.sync, time.Second*25, stopCh)
go wait.Until(c.sync, constant.CRDControllerResync, stopCh)
}
}
@ -90,39 +120,42 @@ func (o *Controller) deleteCRDFromPreviousSync() {
func (o *Controller) parseCRD(crd unstructured.Unstructured) {
var err error
var crdDefinition struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Validation struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"validation"`
} `json:"spec"`
}
crdRaw, _ := json.Marshal(crd.Object)
_ = json.Unmarshal(crdRaw, &crdDefinition)
_ = json.Unmarshal(crdRaw, &crdDefinitionPrior)
crdName := crdDefinition.Spec.Names.Kind
openV3schema := crdDefinitionPrior.Spec.Validation.OpenAPIV3Schema
crdName := crdDefinitionPrior.Spec.Names.Kind
var schema yaml.MapSlice
schemaRaw, _ := json.Marshal(crdDefinition.Spec.Validation.OpenAPIV3Schema)
if openV3schema == nil {
_ = json.Unmarshal(crdRaw, &crdDefinitionNew)
for _, crdVersion := range crdDefinitionNew.Spec.Versions {
if crdVersion.Storage {
openV3schema = crdVersion.Schema.OpenAPIV3Schema
crdName = crdDefinitionNew.Spec.Names.Kind
break
}
}
}
schemaRaw, _ := json.Marshal(openV3schema)
if len(schemaRaw) < 1 {
log.Log.V(4).Info("could not parse crd schema")
log.Log.V(3).Info("could not parse crd schema", "name", crdName)
return
}
schemaRaw, err = addingDefaultFieldsToSchema(schemaRaw)
if err != nil {
log.Log.Error(err, "could not parse crd schema:")
log.Log.Error(err, "could not parse crd schema", "name", crdName)
return
}
var schema yaml.MapSlice
_ = yaml.Unmarshal(schemaRaw, &schema)
parsedSchema, err := openapi_v2.NewSchema(schema, compiler.NewContext("schema", nil))
if err != nil {
log.Log.Error(err, "could not parse crd schema:")
log.Log.Error(err, "could not parse crd schema", "name", crdName)
return
}

View file

@ -10,6 +10,7 @@ import (
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/config"
"github.com/nirmata/kyverno/pkg/constant"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystore"
@ -264,7 +265,7 @@ func (pc *PolicyController) Run(workers int, stopCh <-chan struct{}) {
}
for i := 0; i < workers; i++ {
go wait.Until(pc.worker, time.Second, stopCh)
go wait.Until(pc.worker, constant.PolicyControllerResync, stopCh)
}
<-stopCh

View file

@ -97,9 +97,14 @@ func getResourcesPerNamespace(kind string, client *client.Client, namespace stri
// ls := mergeLabelSectors(rule.MatchResources.Selector, rule.ExcludeResources.Selector)
// list resources
log.V(4).Info("list resources to be processed")
if kind == "Namespace" {
namespace = ""
}
list, err := client.ListResource(kind, namespace, ls)
if err != nil {
log.Error(err, "failed to list resources", "kind", kind)
log.Error(err, "failed to list resources", "kind", kind, "namespace", namespace)
return nil
}
// filter based on name

View file

@ -6,7 +6,6 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -14,6 +13,7 @@ import (
kyvernov1 "github.com/nirmata/kyverno/pkg/client/clientset/versioned/typed/kyverno/v1"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/constant"
"github.com/nirmata/kyverno/pkg/policystatus"
dclient "github.com/nirmata/kyverno/pkg/dclient"
@ -153,7 +153,7 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
}
for i := 0; i < workers; i++ {
go wait.Until(gen.runWorker, time.Second, stopCh)
go wait.Until(gen.runWorker, constant.PolicyViolationControllerResync, stopCh)
}
<-stopCh
}

View file

@ -3,6 +3,8 @@ package utils
import (
"fmt"
"reflect"
"regexp"
"strconv"
engineutils "github.com/nirmata/kyverno/pkg/engine/utils"
"k8s.io/api/admission/v1beta1"
@ -134,3 +136,40 @@ func ConvertResource(raw []byte, group, version, kind, namespace string) (unstru
obj.SetNamespace(namespace)
return *obj, nil
}
// CompareKubernetesVersion compare kuberneates client version to user given version
func CompareKubernetesVersion(client *client.Client, log logr.Logger, k8smajor, k8sminor, k8ssub int) bool {
logger := log.WithName("CompareKubernetesVersion")
serverVersion, err := client.DiscoveryClient.GetServerVersion()
if err != nil {
logger.Error(err, "Failed to get kubernetes server version")
return false
}
exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`)
groups := exp.FindAllStringSubmatch(serverVersion.String(), -1)
if len(groups) != 1 || len(groups[0]) != 4 {
logger.Error(err, "Failed to extract kubernetes server version", "serverVersion", serverVersion)
return false
}
// convert string to int
// assuming the version are always intergers
major, err := strconv.Atoi(groups[0][1])
if err != nil {
logger.Error(err, "Failed to extract kubernetes major server version", "serverVersion", serverVersion)
return false
}
minor, err := strconv.Atoi(groups[0][2])
if err != nil {
logger.Error(err, "Failed to extract kubernetes minor server version", "serverVersion", serverVersion)
return false
}
sub, err := strconv.Atoi(groups[0][3])
if err != nil {
logger.Error(err, "Failed to extract kubernetes sub minor server version", "serverVersion", serverVersion)
return false
}
if major <= k8smajor && minor <= k8sminor && sub < k8ssub {
return false
}
return true
}

View file

@ -338,3 +338,8 @@ func (wrc *WebhookRegistrationClient) removePolicyValidatingWebhookConfiguration
logger.V(4).Info("successfully deleted policy validating webhook configutation")
}
// GetWebhookTimeOut returns the value of webhook timeout
func (wrc *WebhookRegistrationClient) GetWebhookTimeOut() time.Duration {
return time.Duration(wrc.timeoutSeconds)
}

View file

@ -8,6 +8,7 @@ import (
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
"github.com/nirmata/kyverno/pkg/constant"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
)
@ -60,7 +61,7 @@ func (g *Generator) Run(workers int) {
logger.V(4).Info("shutting down")
}()
for i := 0; i < workers; i++ {
go wait.Until(g.process, time.Second, g.stopCh)
go wait.Until(g.process, constant.GenerateControllerResync, g.stopCh)
}
<-g.stopCh
}

View file

@ -0,0 +1,15 @@
package webhooks
import (
"net/http"
"time"
)
func timeoutHandler(h http.Handler, timeout time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var timeoutHandler http.Handler
msg := "ok"
timeoutHandler = http.TimeoutHandler(h, timeout*time.Second, msg)
timeoutHandler.ServeHTTP(w, r)
}
}

View file

@ -10,28 +10,24 @@ import (
"net/http"
"time"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/go-logr/logr"
"github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/checker"
kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/policystatus"
"github.com/nirmata/kyverno/pkg/policystore"
"github.com/nirmata/kyverno/pkg/policyviolation"
tlsutils "github.com/nirmata/kyverno/pkg/tls"
userinfo "github.com/nirmata/kyverno/pkg/userinfo"
"github.com/nirmata/kyverno/pkg/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
"github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -136,12 +132,15 @@ func NewWebhookServer(
log: log,
openAPIController: openAPIController,
}
mux := httprouter.New()
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, ws.handlerFunc(ws.resourceMutation, true))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, ws.handlerFunc(ws.resourceValidation, true))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, ws.handlerFunc(ws.policyMutation, true))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, ws.handlerFunc(ws.policyValidation, true))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, ws.handlerFunc(ws.verifyHandler, false))
webhookTimeout := ws.webhookRegistrationClient.GetWebhookTimeOut()
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.resourceMutation, true), webhookTimeout))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.resourceValidation, true), webhookTimeout))
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.policyMutation, true), webhookTimeout))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.policyValidation, true), webhookTimeout))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, timeoutHandler(ws.handlerFunc(ws.verifyHandler, false), webhookTimeout))
ws.server = http.Server{
Addr: ":443", // Listen on port for HTTPS requests
TLSConfig: &tlsConfig,
@ -219,13 +218,8 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
}
}
raw := request.Object.Raw
if request.Operation == v1beta1.Delete {
raw = request.OldObject.Raw
}
// convert RAW to unstructured
resource, err := utils.ConvertResource(raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
resource, err := utils.ConvertResource(request.Object.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
logger.Error(err, "failed to convert RAW resource to unstructured format")
@ -268,25 +262,31 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
logger.Error(err, "failed to load service account in context")
}
// MUTATION
// mutation failure should not block the resource creation
// any mutation failure is reported as the violation
patches := ws.HandleMutation(request, resource, policies, ctx, userRequestInfo)
var patches []byte
patchedResource := request.Object.Raw
// patch the resource with patches before handling validation rules
patchedResource := processResourceWithPatches(patches, request.Object.Raw, logger)
versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0)
if versionCheck {
// MUTATION
// mutation failure should not block the resource creation
// any mutation failure is reported as the violation
patches := ws.HandleMutation(request, resource, policies, ctx, userRequestInfo)
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" {
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, patchedResource, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
// patch the resource with patches before handling validation rules
patchedResource = processResourceWithPatches(patches, request.Object.Raw, logger)
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" {
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, patchedResource, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
}
}
@ -308,6 +308,7 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
}
}
}
// Succesfful processing of mutation & validation rules in policy
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
@ -318,6 +319,7 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
Patch: patches,
PatchType: &patchType,
}
}
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
@ -388,16 +390,18 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
}
}
// VALIDATION
ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
versionCheck := utils.CompareKubernetesVersion(ws.client, ws.log, 1, 14, 0)
if !versionCheck {
ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: msg,
},
}
}
}