diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 623d50c573..707809e583 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -1,11 +1,9 @@
name: prereleaser
-
on:
push:
tags:
- - '*'
+ - '*'
-
jobs:
releaser:
runs-on: ubuntu-latest
@@ -36,6 +34,8 @@ jobs:
access-token: ${{ secrets.ACCESS_TOKEN }}
deploy-branch: gh-pages
charts-folder: charts
+ - name: Update new version in krew-index
+ uses: rajatjindal/krew-release-bot@v0.0.38
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 2595128777..34ad611465 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -14,7 +14,6 @@ builds:
- windows
goarch:
- amd64
- goarm: [6, 7]
archives:
- id: kyverno-cli-archive
name_template: |-
@@ -26,12 +25,12 @@ archives:
{{- end -}}
builds:
- kyverno-cli
- replacements:
- 386: i386
- amd64: x86_64
format_overrides:
- goos: windows
format: zip
+ replacements:
+ 386: i386
+ amd64: x86_64
files: ["LICENSE"]
checksum:
name_template: "checksums.txt"
diff --git a/.krew.yaml b/.krew.yaml
new file mode 100644
index 0000000000..5a49128f63
--- /dev/null
+++ b/.krew.yaml
@@ -0,0 +1,46 @@
+apiVersion: krew.googlecontainertools.github.com/v1alpha2
+kind: Plugin
+metadata:
+ name: kyverno
+spec:
+ version: {{ .TagName }}
+ homepage: https://github.com/nirmata/kyverno
+ platforms:
+ - selector:
+ matchLabels:
+ os: linux
+ arch: amd64
+ {{addURIAndSha "https://github.com/nirmata/kyverno/releases/download/{{ .TagName }}/kyverno-cli_{{ .TagName }}_linux_x86_64.tar.gz" .TagName }}
+ files:
+ - from: kyverno
+ to: .
+ - from: LICENSE
+ to: .
+ bin: kyverno
+ - selector:
+ matchLabels:
+ os: darwin
+ arch: amd64
+ {{addURIAndSha "https://github.com/nirmata/kyverno/releases/download/{{ .TagName }}/kyverno-cli_{{ .TagName }}_darwin_x86_64.tar.gz" .TagName }}
+ files:
+ - from: kyverno
+ to: .
+ - from: LICENSE
+ to: .
+ bin: kyverno
+ - selector:
+ matchLabels:
+ os: windows
+ arch: amd64
+ {{addURIAndSha "https://github.com/nirmata/kyverno/releases/download/{{ .TagName }}/kyverno-cli_{{ .TagName }}_windows_x86_64.zip" .TagName }}
+ files:
+ - from: kyverno.exe
+ to: .
+ - from: LICENSE
+ to: .
+ bin: kyverno.exe
+ shortDescription: Kyverno is a policy engine for kubernetes
+ description: |+2
+ Kyverno is used to test kyverno policies and apply policies to resources files
+ caveats: |
+ The plugin requires access to create Policy and CustomResources
diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go
index 1dbb7b65fb..66bd944ea7 100644
--- a/cmd/kyverno/main.go
+++ b/cmd/kyverno/main.go
@@ -209,7 +209,6 @@ func main() {
pInformer.Kyverno().V1().ClusterPolicies(),
pInformer.Kyverno().V1().GenerateRequests(),
eventGenerator,
- pvgen,
kubedynamicInformer,
statusSync.Listener,
log.Log.WithName("GenerateController"),
@@ -231,6 +230,16 @@ func main() {
log.Log.WithName("PolicyCacheController"),
)
+ auditHandler := webhooks.NewValidateAuditHandler(
+ pCacheController.Cache,
+ eventGenerator,
+ statusSync.Listener,
+ pvgen,
+ kubeInformer.Rbac().V1().RoleBindings(),
+ kubeInformer.Rbac().V1().ClusterRoleBindings(),
+ log.Log.WithName("ValidateAuditHandler"),
+ )
+
// CONFIGURE CERTIFICATES
tlsPair, err := client.InitTLSPemPair(clientConfig, fqdncn)
if err != nil {
@@ -282,6 +291,7 @@ func main() {
pvgen,
grgen,
rWebhookWatcher,
+ auditHandler,
supportMudateValidate,
cleanUp,
log.Log.WithName("WebhookServer"),
@@ -307,6 +317,7 @@ func main() {
go pvgen.Run(1, stopCh)
go statusSync.Run(1, stopCh)
go pCacheController.Run(1, stopCh)
+ go auditHandler.Run(10, stopCh)
openAPISync.Run(1, stopCh)
// verifys if the admission control is enabled and active
diff --git a/documentation/kyverno-cli.md b/documentation/kyverno-cli.md
index 85ff9d3b7f..b399f8175d 100644
--- a/documentation/kyverno-cli.md
+++ b/documentation/kyverno-cli.md
@@ -1,13 +1,10 @@
-_[documentation](/README.md#documentation) / kyverno-cli_
+*[documentation](/README.md#documentation) / kyverno-cli*
+
# Kyverno CLI
The Kyverno Command Line Interface (CLI) is designed to validate policies and test the behavior of applying policies to resources before adding the policy to a cluster. It can be used as a kubectl plugin and as a standalone CLI.
-## Install the CLI
-
-The Kyverno CLI binary is distributed with each release. You can install the CLI for your platform from the [releases](https://github.com/nirmata/kyverno/releases) site.
-
## Build the CLI
You can build the CLI binary locally, then move the binary into a directory in your PATH.
@@ -19,10 +16,14 @@ make cli
mv ./cmd/cli/kubectl-kyverno/kyverno /usr/local/bin/kyverno
```
-You can also use curl to install kyverno-cli
-
+You can also use [Krew](https://github.com/kubernetes-sigs/krew)
```bash
-curl -L https://raw.githubusercontent.com/nirmata/kyverno/master/scripts/install-cli.sh | bash
+# Install kyverno using krew plugin manager
+kubectl krew install kyverno
+
+#example
+kuberctl kyverno version
+
```
## Install via AUR (archlinux)
@@ -39,55 +40,39 @@ yay -S kyverno-git
Prints the version of kyverno used by the CLI.
-Example:
-
+Example:
```
kyverno version
```
#### Validate
-
-Validates a policy, can validate multiple policy resource description files or even an entire folder containing policy resource description
-files. Currently supports files with resource description in YAML.
+Validates a policy, can validate multiple policy resource description files or even an entire folder containing policy resource description
+files. Currently supports files with resource description in yaml.
Example:
-
```
kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies
```
#### Apply
-
Applies policies on resources, and supports applying multiple policies on multiple resources in a single command.
Also supports applying the given policies to an entire cluster. The current kubectl context will be used to access the cluster.
-Will return results to stdout.
+ Will return results to stdout.
Apply to a resource:
-
-```bash
+```
kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml
```
Apply to all matching resources in a cluster:
-
-```bash
+```
kyverno apply /path/to/policy.yaml --cluster > policy-results.txt
```
Apply multiple policies to multiple resources:
-
-```bash
+```
kyverno apply /path/to/policy1.yaml /path/to/folderFullOfPolicies --resource /path/to/resource1.yaml --resource /path/to/resource2.yaml --cluster
```
-##### Exit Codes
-The CLI exits with diffenent exit codes:
-
-| Message | Exit Code |
-| ------------------------------------- | --------- |
-| executes successfully | 0 |
-| one or more policy rules are violated | 1 |
-| policy validation failed | 2 |
-
-_Read Next >> [Sample Policies](/samples/README.md)_
+*Read Next >> [Sample Policies](/samples/README.md)*
diff --git a/go.mod b/go.mod
index 0c734f61ed..4534fef0c4 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/julienschmidt/httprouter v1.3.0
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5
github.com/ory/go-acc v0.2.1 // indirect
+ github.com/pkg/errors v0.8.1
github.com/prometheus/common v0.4.1
github.com/rogpeppe/godef v1.1.2 // indirect
github.com/spf13/cobra v0.0.5
diff --git a/go.sum b/go.sum
index bf2c17eaab..a05d684930 100644
--- a/go.sum
+++ b/go.sum
@@ -606,6 +606,7 @@ github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5 h1:CjDeQ78sVdDrENJff3EUwVMUv9GfTL4NyLvjE/Bvrd8=
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5/go.mod h1:HH1U0HOUzfjsCGlGCncWDh8L3zPejMhMDDsLAETqXs0=
+github.com/minio/minio-go/v6 v6.0.44 h1:CVwVXw+uCOcyMi7GvcOhxE8WgV+Xj8Vkf2jItDf/EGI=
github.com/minio/minio-go/v6 v6.0.44/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/parquet-go v0.0.0-20191231003236-20b3c07bcd2c/go.mod h1:sl82d+TnCE7qeaNJazHdNoG9Gpyl9SZYfleDAQWrsls=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
@@ -748,6 +749,7 @@ github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2Ry
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
diff --git a/pkg/engine/forceMutate.go b/pkg/engine/forceMutate.go
index ba677a5f72..0fae6085c0 100644
--- a/pkg/engine/forceMutate.go
+++ b/pkg/engine/forceMutate.go
@@ -55,6 +55,9 @@ func mutateResourceWithOverlay(resource unstructured.Unstructured, overlay inter
// ForceMutate does not check any conditions, it simply mutates the given resource
func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (unstructured.Unstructured, error) {
var err error
+ logger := log.Log.WithName("EngineForceMutate").WithValues("policy", policy.Name, "kind", resource.GetKind(),
+ "namespace", resource.GetNamespace(), "name", resource.GetName())
+
for _, rule := range policy.Spec.Rules {
if !rule.HasMutate() {
continue
@@ -81,7 +84,7 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
if rule.Mutation.Patches != nil {
var resp response.RuleResponse
- resp, resource = mutate.ProcessPatches(log.Log, rule, resource)
+ resp, resource = mutate.ProcessPatches(logger.WithValues("rule", rule.Name), rule, resource)
if !resp.Success {
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
}
diff --git a/pkg/engine/mutate/overlay.go b/pkg/engine/mutate/overlay.go
index 256efa2871..a1c0c88f82 100644
--- a/pkg/engine/mutate/overlay.go
+++ b/pkg/engine/mutate/overlay.go
@@ -29,7 +29,7 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
resp.Type = utils.Mutation.String()
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
- logger.V(4).Info("finished applying overlay rule", "processingTime", resp.RuleStats.ProcessingTime)
+ logger.V(4).Info("finished applying overlay rule", "processingTime", resp.RuleStats.ProcessingTime.String())
}()
patches, overlayerr := processOverlayPatches(logger, resource.UnstructuredContent(), overlay)
@@ -351,8 +351,8 @@ func processSubtree(overlay interface{}, path string, op string) ([]byte, error)
// explicitly handle boolean type in annotation
// keep the type boolean as it is in any other fields
- if strings.Contains(path, "/metadata/annotations") ||
- strings.Contains(path, "/metadata/labels"){
+ if strings.Contains(path, "/metadata/annotations") ||
+ strings.Contains(path, "/metadata/labels") {
patchStr = wrapBoolean(patchStr)
}
diff --git a/pkg/engine/mutate/overlayCondition.go b/pkg/engine/mutate/overlayCondition.go
index 97d0210e80..641169fa75 100755
--- a/pkg/engine/mutate/overlayCondition.go
+++ b/pkg/engine/mutate/overlayCondition.go
@@ -27,9 +27,9 @@ func checkConditions(log logr.Logger, resource, overlay interface{}, path string
// condition never be true in this case
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
if hasNestedAnchors(overlay) {
- log.V(4).Info(fmt.Sprintf("Found anchor on different types of element at path %s: overlay %T, resource %T", path, overlay, resource))
+ log.V(4).Info(fmt.Sprintf("element type mismatch at path %s: overlay %T, resource %T", path, overlay, resource))
return path, newOverlayError(conditionFailure,
- fmt.Sprintf("Found anchor on different types of element at path %s: overlay %T %v, resource %T %v", path, overlay, overlay, resource, resource))
+ fmt.Sprintf("element type mismatch at path %s: overlay %T %v, resource %T %v", path, overlay, overlay, resource, resource))
}
return "", overlayError{}
@@ -112,8 +112,8 @@ func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, pat
// resource - A: B2
func compareOverlay(resource, overlay interface{}, path string) (string, overlayError) {
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
- log.Log.V(4).Info("Found anchor on different types of element", "overlay", overlay, "resource", resource)
- return path, newOverlayError(conditionFailure, fmt.Sprintf("Found anchor on different types of element: overlay %T, resource %T", overlay, resource))
+ log.Log.V(4).Info("element type mismatch", "overlay", overlay, "resource", resource)
+ return path, newOverlayError(conditionFailure, fmt.Sprintf("element type mismatch: overlay %T, resource %T", overlay, resource))
}
switch typedOverlay := overlay.(type) {
diff --git a/pkg/engine/mutate/overlayCondition_test.go b/pkg/engine/mutate/overlayCondition_test.go
index 8da6eefee9..4c9d43d14c 100644
--- a/pkg/engine/mutate/overlayCondition_test.go
+++ b/pkg/engine/mutate/overlayCondition_test.go
@@ -150,7 +150,7 @@ func TestMeetConditions_DifferentTypes(t *testing.T) {
// anchor exist
_, err = meetConditions(log.Log, resource, overlay)
- assert.Assert(t, strings.Contains(err.Error(), "Found anchor on different types of element at path /subsets/"))
+ assert.Assert(t, strings.Contains(err.Error(), "element type mismatch at path /subsets/"))
}
func TestMeetConditions_anchosInSameObject(t *testing.T) {
diff --git a/pkg/engine/mutate/patches.go b/pkg/engine/mutate/patches.go
index 7b294bbca9..51aaaae026 100644
--- a/pkg/engine/mutate/patches.go
+++ b/pkg/engine/mutate/patches.go
@@ -28,7 +28,7 @@ func ProcessPatches(log logr.Logger, rule kyverno.Rule, resource unstructured.Un
resp.Type = utils.Mutation.String()
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
- logger.V(4).Info("finished JSON patch", "processingTime", resp.RuleStats.ProcessingTime)
+ logger.V(4).Info("applied JSON patch", "processingTime", resp.RuleStats.ProcessingTime.String())
}()
// convert to RAW
diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go
index 6d4fa3f102..712aa16821 100644
--- a/pkg/engine/mutation.go
+++ b/pkg/engine/mutation.go
@@ -22,23 +22,24 @@ const (
PodControllersAnnotation = "pod-policies.kyverno.io/autogen-controllers"
//PodTemplateAnnotation defines the annotation key for Pod-Template
PodTemplateAnnotation = "pod-policies.kyverno.io/autogen-applied"
- PodControllerRuleName = "autogen-pod-ctrl-annotation"
)
// Mutate performs mutation. Overlay first and then mutation patches
func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
startTime := time.Now()
policy := policyContext.Policy
- resource := policyContext.NewResource
+ patchedResource := policyContext.NewResource
ctx := policyContext.Context
- logger := log.Log.WithName("Mutate").WithValues("policy", policy.Name, "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
+ logger := log.Log.WithName("EngineMutate").WithValues("policy", policy.Name, "kind", patchedResource.GetKind(),
+ "namespace", patchedResource.GetNamespace(), "name", patchedResource.GetName())
+
logger.V(4).Info("start policy processing", "startTime", startTime)
- startMutateResultResponse(&resp, policy, resource)
+
+ startMutateResultResponse(&resp, policy, patchedResource)
defer endMutateResultResponse(logger, &resp, startTime)
- patchedResource := policyContext.NewResource
-
- if autoGenAnnotationApplied(patchedResource) && policy.HasAutoGenAnnotation() {
+ if policy.HasAutoGenAnnotation() && excludePod(patchedResource) {
+ logger.V(5).Info("Skip applying policy, Pod has ownerRef set", "policy", policy.GetName())
resp.PatchedResource = patchedResource
return
}
@@ -47,14 +48,14 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
var ruleResponse response.RuleResponse
logger := logger.WithValues("rule", rule.Name)
//TODO: to be checked before calling the resources as well
- if !rule.HasMutate() && !strings.Contains(PodControllers, resource.GetKind()) {
+ if !rule.HasMutate() && !strings.Contains(PodControllers, patchedResource.GetKind()) {
continue
}
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont satisfy a policy rule resource description
- if err := MatchesResourceDescription(resource, rule, policyContext.AdmissionInfo); err != nil {
+ if err := MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo); err != nil {
logger.V(3).Info("resource not matched", "reason", err.Error())
continue
}
@@ -112,22 +113,6 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
return resp
}
- // skip inserting on existing resource
- if policy.HasAutoGenAnnotation() && strings.Contains(PodControllers, resource.GetKind()) {
- if !patchedResourceHasPodControllerAnnotation(patchedResource) {
- var ruleResponse response.RuleResponse
- ruleResponse, patchedResource = mutate.ProcessOverlay(logger, PodControllerRuleName, podTemplateRule.Mutation.Overlay, patchedResource)
- if !ruleResponse.Success {
- logger.Info("failed to insert annotation for podTemplate", "error", ruleResponse.Message)
- } else {
- if ruleResponse.Success && ruleResponse.Patches != nil {
- logger.V(3).Info("inserted annotation for podTemplate")
- resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse)
- }
- }
- }
- }
-
// send the patched resource
resp.PatchedResource = patchedResource
return resp
@@ -170,23 +155,5 @@ func startMutateResultResponse(resp *response.EngineResponse, policy kyverno.Clu
func endMutateResultResponse(logger logr.Logger, resp *response.EngineResponse, startTime time.Time) {
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
- logger.V(4).Info("finished processing policy", "processingTime", resp.PolicyResponse.ProcessingTime, "mutationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
-}
-
-// podTemplateRule mutate pod template with annotation
-// pod-policies.kyverno.io/autogen-applied=true
-var podTemplateRule = kyverno.Rule{
- Mutation: kyverno.Mutation{
- Overlay: map[string]interface{}{
- "spec": map[string]interface{}{
- "template": map[string]interface{}{
- "metadata": map[string]interface{}{
- "annotations": map[string]interface{}{
- "+(" + PodTemplateAnnotation + ")": "true",
- },
- },
- },
- },
- },
- },
+ logger.V(4).Info("finished processing policy", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "mutationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
}
diff --git a/pkg/engine/response/response.go b/pkg/engine/response/response.go
index 8d4440ca03..eeb37dd2cb 100644
--- a/pkg/engine/response/response.go
+++ b/pkg/engine/response/response.go
@@ -113,7 +113,7 @@ func (er EngineResponse) GetSuccessRules() []string {
func (er EngineResponse) getRules(success bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
- if r.Success == success{
+ if r.Success == success {
rules = append(rules, r.Name)
}
}
diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go
index 79c252e823..cc6179ce2e 100644
--- a/pkg/engine/utils.go
+++ b/pkg/engine/utils.go
@@ -201,11 +201,10 @@ func copyConditions(original []kyverno.Condition) []kyverno.Condition {
return copy
}
-// autoGenAnnotationApplied checks if a Pod has annotation "pod-policies.kyverno.io/autogen-applied"
-func autoGenAnnotationApplied(resource unstructured.Unstructured) bool {
+// excludePod checks if a Pod has ownerRef set
+func excludePod(resource unstructured.Unstructured) bool {
if resource.GetKind() == "Pod" {
- ann := resource.GetAnnotations()
- if _, ok := ann[PodTemplateAnnotation]; ok {
+ if len(resource.GetOwnerReferences()) > 0 {
return true
}
}
diff --git a/pkg/engine/validate/validate.go b/pkg/engine/validate/validate.go
index f148f88f85..d31a8c075b 100644
--- a/pkg/engine/validate/validate.go
+++ b/pkg/engine/validate/validate.go
@@ -125,15 +125,18 @@ func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, o
}
default:
// In all other cases - detect type and handle each array element with validateResourceElement
- for i, patternElement := range patternArray {
- currentPath := path + strconv.Itoa(i) + "/"
- path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath)
- if err != nil {
- return path, err
+ if len(resourceArray) >= len(patternArray) {
+ for i, patternElement := range patternArray {
+ currentPath := path + strconv.Itoa(i) + "/"
+ path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath)
+ if err != nil {
+ return path, err
+ }
}
+ }else{
+ return "", fmt.Errorf("Validate Array failed, array length mismatch, resource Array len is %d and pattern Array len is %d", len(resourceArray), len(patternArray))
}
}
-
return "", nil
}
diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go
index e86e87130c..07ac7fff56 100644
--- a/pkg/engine/validation.go
+++ b/pkg/engine/validation.go
@@ -24,7 +24,7 @@ func Validate(policyContext PolicyContext) (resp response.EngineResponse) {
oldR := policyContext.OldResource
ctx := policyContext.Context
admissionInfo := policyContext.AdmissionInfo
- logger := log.Log.WithName("Validate").WithValues("policy", policy.Name)
+ logger := log.Log.WithName("EngineValidate").WithValues("policy", policy.Name)
if reflect.DeepEqual(newR, unstructured.Unstructured{}) {
logger = logger.WithValues("kind", oldR.GetKind(), "namespace", oldR.GetNamespace(), "name", oldR.GetName())
@@ -93,7 +93,7 @@ func startResultResponse(resp *response.EngineResponse, policy kyverno.ClusterPo
func endResultResponse(log logr.Logger, resp *response.EngineResponse, startTime time.Time) {
resp.PolicyResponse.ProcessingTime = time.Since(startTime)
- log.V(4).Info("finshed processing", "processingTime", resp.PolicyResponse.ProcessingTime, "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
+ log.V(4).Info("finshed processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
}
func incrementAppliedCount(resp *response.EngineResponse) {
@@ -103,6 +103,10 @@ func incrementAppliedCount(resp *response.EngineResponse) {
func isRequestDenied(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &response.EngineResponse{}
+ if policy.HasAutoGenAnnotation() && excludePod(resource) {
+ log.V(5).Info("Skip applying policy, Pod has ownerRef set", "policy", policy.GetName())
+ return resp
+ }
for _, rule := range policy.Spec.Rules {
if !rule.HasValidate() {
@@ -142,7 +146,8 @@ func isRequestDenied(log logr.Logger, ctx context.EvalInterface, policy kyverno.
func validateResource(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &response.EngineResponse{}
- if autoGenAnnotationApplied(resource) && policy.HasAutoGenAnnotation() {
+ if policy.HasAutoGenAnnotation() && excludePod(resource) {
+ log.V(5).Info("Skip applying policy, Pod has ownerRef set", "policy", policy.GetName())
return resp
}
@@ -226,7 +231,7 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr
resp.Type = utils.Validation.String()
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
- logger.V(4).Info("finished processing rule", "processingTime", resp.RuleStats.ProcessingTime)
+ logger.V(4).Info("finished processing rule", "processingTime", resp.RuleStats.ProcessingTime.String())
}()
// work on a copy of validation rule
diff --git a/pkg/generate/cleanup/controller.go b/pkg/generate/cleanup/controller.go
index 2bfe339232..43c6d84f7d 100644
--- a/pkg/generate/cleanup/controller.go
+++ b/pkg/generate/cleanup/controller.go
@@ -248,7 +248,7 @@ func (c *Controller) syncGenerateRequest(key string) error {
startTime := time.Now()
logger.Info("started syncing generate request", "startTime", startTime)
defer func() {
- logger.V(4).Info("finished syncying generate request", "processingTIme", time.Since(startTime))
+ logger.V(4).Info("finished syncying generate request", "processingTIme", time.Since(startTime).String())
}()
_, grName, err := cache.SplitMetaNamespaceKey(key)
if errors.IsNotFound(err) {
diff --git a/pkg/generate/controller.go b/pkg/generate/controller.go
index af5121e6b1..61f7967640 100644
--- a/pkg/generate/controller.go
+++ b/pkg/generate/controller.go
@@ -13,7 +13,6 @@ import (
dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystatus"
- "github.com/nirmata/kyverno/pkg/policyviolation"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
@@ -52,8 +51,6 @@ type Controller struct {
pSynced cache.InformerSynced
// grSynced returns true if the Generate Request store has been synced at least once
grSynced cache.InformerSynced
- // policy violation generator
- pvGenerator policyviolation.GeneratorInterface
// dyanmic sharedinformer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory
//TODO: list of generic informers
@@ -70,7 +67,6 @@ func NewController(
pInformer kyvernoinformer.ClusterPolicyInformer,
grInformer kyvernoinformer.GenerateRequestInformer,
eventGen event.Interface,
- pvGenerator policyviolation.GeneratorInterface,
dynamicInformer dynamicinformer.DynamicSharedInformerFactory,
policyStatus policystatus.Listener,
log logr.Logger,
@@ -79,7 +75,6 @@ func NewController(
client: client,
kyvernoClient: kyvernoclient,
eventGen: eventGen,
- pvGenerator: pvGenerator,
//TODO: do the math for worst case back off and make sure cleanup runs after that
// as we dont want a deleted GR to be re-queue
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request"),
@@ -268,7 +263,7 @@ func (c *Controller) syncGenerateRequest(key string) error {
startTime := time.Now()
logger.Info("started sync", "key", key, "startTime", startTime)
defer func() {
- logger.V(4).Info("finished sync", "key", key, "processingTime", time.Since(startTime))
+ logger.V(4).Info("finished sync", "key", key, "processingTime", time.Since(startTime).String())
}()
_, grName, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go
index 59f8d448f7..b7cc8c6fd6 100644
--- a/pkg/policy/apply.go
+++ b/pkg/policy/apply.go
@@ -28,7 +28,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
name = ns + "/" + name
}
- logger.V(3).Info("applyPolicy", "resource", name, "processingTime", time.Since(startTime))
+ logger.V(3).Info("applyPolicy", "resource", name, "processingTime", time.Since(startTime).String())
}()
var engineResponses []response.EngineResponse
@@ -81,10 +81,6 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
for index, rule := range engineResponse.PolicyResponse.Rules {
log.V(4).Info("verifying if policy rule was applied before", "rule", rule.Name)
- if rule.Name == engine.PodControllerRuleName {
- continue
- }
-
patches := rule.Patches
patch, err := jsonpatch.DecodePatch(utils.JoinPatches(patches))
diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go
index 172ee743ae..0fc91fcfc1 100644
--- a/pkg/policy/controller.go
+++ b/pkg/policy/controller.go
@@ -304,7 +304,7 @@ func (pc *PolicyController) syncPolicy(key string) error {
startTime := time.Now()
logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime)
defer func() {
- logger.V(4).Info("finished syncing policy", "key", key, "processingTime", time.Since(startTime))
+ logger.V(4).Info("finished syncing policy", "key", key, "processingTime", time.Since(startTime).String())
}()
policy, err := pc.pLister.Get(key)
diff --git a/pkg/policy/namespacedpv.go b/pkg/policy/namespacedpv.go
index 90335c52f3..05c887225e 100644
--- a/pkg/policy/namespacedpv.go
+++ b/pkg/policy/namespacedpv.go
@@ -109,6 +109,7 @@ func (pc *PolicyController) getPolicyForNamespacedPolicyViolation(pv *kyverno.Po
logger := pc.log.WithValues("kind", pv.Kind, "namespace", pv.Namespace, "name", pv.Name)
policies, err := pc.pLister.GetPolicyForNamespacedPolicyViolation(pv)
if err != nil || len(policies) == 0 {
+ logger.V(4).Info("missing policy for namespaced policy violation", "reason", err.Error())
return nil
}
// Because all PolicyViolations's belonging to a Policy should have a unique label key,
diff --git a/pkg/policycache/cache.go b/pkg/policycache/cache.go
index 685417d35a..7354046df6 100644
--- a/pkg/policycache/cache.go
+++ b/pkg/policycache/cache.go
@@ -12,6 +12,9 @@ import (
type pMap struct {
sync.RWMutex
dataMap map[PolicyType][]*kyverno.ClusterPolicy
+
+ // nameCacheMap stores the names of all existing policies in dataMap
+ nameCacheMap map[PolicyType]map[string]bool
}
// policyCache ...
@@ -29,9 +32,17 @@ type Interface interface {
// newPolicyCache ...
func newPolicyCache(log logr.Logger) Interface {
+ namesCache := map[PolicyType]map[string]bool{
+ Mutate: make(map[string]bool),
+ ValidateEnforce: make(map[string]bool),
+ ValidateAudit: make(map[string]bool),
+ Generate: make(map[string]bool),
+ }
+
return &policyCache{
pMap{
- dataMap: make(map[PolicyType][]*kyverno.ClusterPolicy),
+ dataMap: make(map[PolicyType][]*kyverno.ClusterPolicy),
+ nameCacheMap: namesCache,
},
log,
}
@@ -54,29 +65,15 @@ func (pc *policyCache) Remove(policy *kyverno.ClusterPolicy) {
pc.Logger.V(4).Info("policy is removed from cache", "name", policy.GetName())
}
-// buildCacheMap builds the map to store the names of all existing
-// policies in the cache, it is used to aviod adding duplicate policies
-func buildCacheMap(policies []*kyverno.ClusterPolicy) map[string]bool {
- cacheMap := make(map[string]bool)
-
- for _, p := range policies {
- name := p.GetName()
- if !cacheMap[name] {
- cacheMap[p.GetName()] = true
- }
- }
-
- return cacheMap
-}
-
func (m *pMap) add(policy *kyverno.ClusterPolicy) {
m.Lock()
defer m.Unlock()
enforcePolicy := policy.Spec.ValidationFailureAction == "enforce"
- mutateMap := buildCacheMap(m.dataMap[Mutate])
- validateMap := buildCacheMap(m.dataMap[ValidateEnforce])
- generateMap := buildCacheMap(m.dataMap[Generate])
+ mutateMap := m.nameCacheMap[Mutate]
+ validateEnforceMap := m.nameCacheMap[ValidateEnforce]
+ validateAuditMap := m.nameCacheMap[ValidateAudit]
+ generateMap := m.nameCacheMap[Generate]
pName := policy.GetName()
for _, rule := range policy.Spec.Rules {
@@ -90,12 +87,23 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
continue
}
- if rule.HasValidate() && enforcePolicy {
- if !validateMap[pName] {
- validateMap[pName] = true
+ if rule.HasValidate() {
+ if enforcePolicy {
+ if !validateEnforceMap[pName] {
+ validateEnforceMap[pName] = true
- validatePolicy := m.dataMap[ValidateEnforce]
- m.dataMap[ValidateEnforce] = append(validatePolicy, policy)
+ validatePolicy := m.dataMap[ValidateEnforce]
+ m.dataMap[ValidateEnforce] = append(validatePolicy, policy)
+ }
+ continue
+ }
+
+ // ValidateAudit
+ if !validateAuditMap[pName] {
+ validateAuditMap[pName] = true
+
+ validatePolicy := m.dataMap[ValidateAudit]
+ m.dataMap[ValidateAudit] = append(validatePolicy, policy)
}
continue
}
@@ -110,6 +118,11 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
continue
}
}
+
+ m.nameCacheMap[Mutate] = mutateMap
+ m.nameCacheMap[ValidateEnforce] = validateEnforceMap
+ m.nameCacheMap[ValidateAudit] = validateAuditMap
+ m.nameCacheMap[Generate] = generateMap
}
func (m *pMap) get(key PolicyType) []*kyverno.ClusterPolicy {
diff --git a/pkg/policycache/cache_test.go b/pkg/policycache/cache_test.go
index 03a2b7886f..dd2d0346db 100644
--- a/pkg/policycache/cache_test.go
+++ b/pkg/policycache/cache_test.go
@@ -55,6 +55,26 @@ func Test_Add_Duplicate_Policy(t *testing.T) {
}
}
+func Test_Add_Validate_Audit(t *testing.T) {
+ pCache := newPolicyCache(log.Log)
+ policy := newPolicy(t)
+
+ pCache.Add(policy)
+ pCache.Add(policy)
+
+ policy.Spec.ValidationFailureAction = "audit"
+ pCache.Add(policy)
+ pCache.Add(policy)
+
+ if len(pCache.Get(ValidateEnforce)) != 1 {
+ t.Errorf("expected 1 validate enforce policy, found %v", len(pCache.Get(ValidateEnforce)))
+ }
+
+ if len(pCache.Get(ValidateAudit)) != 1 {
+ t.Errorf("expected 1 validate audit policy, found %v", len(pCache.Get(ValidateAudit)))
+ }
+}
+
func Test_Remove_From_Empty_Cache(t *testing.T) {
pCache := newPolicyCache(log.Log)
policy := newPolicy(t)
diff --git a/pkg/policycache/type.go b/pkg/policycache/type.go
index 1349b0c5de..ae75481193 100644
--- a/pkg/policycache/type.go
+++ b/pkg/policycache/type.go
@@ -5,5 +5,6 @@ type PolicyType uint8
const (
Mutate PolicyType = 1 << iota
ValidateEnforce
+ ValidateAudit
Generate
)
diff --git a/pkg/policystatus/main.go b/pkg/policystatus/main.go
index 34d849cc55..451e17ac4b 100644
--- a/pkg/policystatus/main.go
+++ b/pkg/policystatus/main.go
@@ -47,10 +47,10 @@ func (l Listener) Send(s statusUpdater) {
//since it contains access to all the persistant data present
//in this package.
type Sync struct {
- cache *cache
- Listener Listener
- client *versioned.Clientset
- lister kyvernolister.ClusterPolicyLister
+ cache *cache
+ Listener Listener
+ client *versioned.Clientset
+ lister kyvernolister.ClusterPolicyLister
}
type cache struct {
@@ -66,9 +66,9 @@ func NewSync(c *versioned.Clientset, lister kyvernolister.ClusterPolicyLister) *
data: make(map[string]v1.PolicyStatus),
keyToMutex: newKeyToMutex(),
},
- client: c,
- lister: lister,
- Listener: make(chan statusUpdater, 20),
+ client: c,
+ lister: lister,
+ Listener: make(chan statusUpdater, 20),
}
}
diff --git a/pkg/policystatus/status_test.go b/pkg/policystatus/status_test.go
index 1f768c21e7..c69383b328 100644
--- a/pkg/policystatus/status_test.go
+++ b/pkg/policystatus/status_test.go
@@ -29,7 +29,6 @@ func (d dummyStatusUpdater) PolicyName() string {
return "policy1"
}
-
type dummyLister struct {
}
diff --git a/pkg/policyviolation/generator.go b/pkg/policyviolation/generator.go
index f395ef1475..c574f9fb0f 100644
--- a/pkg/policyviolation/generator.go
+++ b/pkg/policyviolation/generator.go
@@ -159,7 +159,7 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
}
func (gen *Generator) runWorker() {
- for gen.processNextWorkitem() {
+ for gen.processNextWorkItem() {
}
}
@@ -186,7 +186,7 @@ func (gen *Generator) handleErr(err error, key interface{}) {
logger.Error(err, "dropping key out of the queue", "key", key)
}
-func (gen *Generator) processNextWorkitem() bool {
+func (gen *Generator) processNextWorkItem() bool {
logger := gen.log
obj, shutdown := gen.queue.Get()
if shutdown {
@@ -197,11 +197,13 @@ func (gen *Generator) processNextWorkitem() bool {
defer gen.queue.Done(obj)
var keyHash string
var ok bool
+
if keyHash, ok = obj.(string); !ok {
gen.queue.Forget(obj)
logger.Info("incorrect type; expecting type 'string'", "obj", obj)
return nil
}
+
// lookup data store
info := gen.dataStore.lookup(keyHash)
if reflect.DeepEqual(info, Info{}) {
@@ -210,14 +212,17 @@ func (gen *Generator) processNextWorkitem() bool {
logger.Info("empty key")
return nil
}
+
err := gen.syncHandler(info)
gen.handleErr(err, obj)
return nil
}(obj)
+
if err != nil {
logger.Error(err, "failed to process item")
return true
}
+
return true
}
diff --git a/pkg/testrunner/testrunner_test.go b/pkg/testrunner/testrunner_test.go
index 163c34cf13..d3a65421c8 100644
--- a/pkg/testrunner/testrunner_test.go
+++ b/pkg/testrunner/testrunner_test.go
@@ -6,9 +6,9 @@ func Test_Mutate_EndPoint(t *testing.T) {
testScenario(t, "/test/scenarios/other/scenario_mutate_endpoint.yaml")
}
-// func Test_Mutate_Validate_qos(t *testing.T) {
-// testScenario(t, "/test/scenarios/other/scenario_mutate_validate_qos.yaml")
-// }
+func Test_Mutate_Validate_qos(t *testing.T) {
+ testScenario(t, "/test/scenarios/other/scenario_mutate_validate_qos.yaml")
+}
func Test_disallow_root_user(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/disallow_root_user.yaml")
diff --git a/pkg/webhookconfig/registration.go b/pkg/webhookconfig/registration.go
index 7f5cb305bd..5573af61b1 100644
--- a/pkg/webhookconfig/registration.go
+++ b/pkg/webhookconfig/registration.go
@@ -251,7 +251,7 @@ func (wrc *WebhookRegistrationClient) removeWebhookConfigurations() {
startTime := time.Now()
wrc.log.Info("removing prior webhook configurations")
defer func() {
- wrc.log.V(4).Info("removed webhookcongfigurations", "processingTime", time.Since(startTime))
+ wrc.log.V(4).Info("removed webhookcongfigurations", "processingTime", time.Since(startTime).String())
}()
var wg sync.WaitGroup
diff --git a/pkg/webhookconfig/rwebhookregister.go b/pkg/webhookconfig/rwebhookregister.go
index 9180d32f69..63d2d964c0 100644
--- a/pkg/webhookconfig/rwebhookregister.go
+++ b/pkg/webhookconfig/rwebhookregister.go
@@ -118,7 +118,7 @@ func (rww *ResourceWebhookRegister) Run(stopCh <-chan struct{}) {
}
// RemoveResourceWebhookConfiguration removes the resource webhook configurations
-func (rww *ResourceWebhookRegister) RemoveResourceWebhookConfiguration() {
+func (rww *ResourceWebhookRegister) RemoveResourceWebhookConfiguration() {
rww.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
if rww.RunValidationInMutatingWebhook != "true" {
diff --git a/pkg/webhooks/common.go b/pkg/webhooks/common.go
index c6839b4e5a..a0839a6d94 100644
--- a/pkg/webhooks/common.go
+++ b/pkg/webhooks/common.go
@@ -29,11 +29,11 @@ func isResponseSuccesful(engineReponses []response.EngineResponse) bool {
func toBlockResource(engineReponses []response.EngineResponse, log logr.Logger) bool {
for _, er := range engineReponses {
if !er.IsSuccessful() && er.PolicyResponse.ValidationFailureAction == Enforce {
- log.Info("spec.ValidationFailureAction set to enforcel blocking resource request", "policy", er.PolicyResponse.Policy)
+ log.Info("spec.ValidationFailureAction set to enforce blocking resource request", "policy", er.PolicyResponse.Policy)
return true
}
}
- log.V(4).Info("sepc.ValidationFailureAction set to auit for all applicable policies;allowing resource reques; reporting with policy violation ")
+ log.V(4).Info("sepc.ValidationFailureAction set to auit for all applicable policies, won't block resource operation")
return false
}
diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go
index b11caeafe3..8d57d76543 100644
--- a/pkg/webhooks/generation.go
+++ b/pkg/webhooks/generation.go
@@ -1,8 +1,10 @@
package webhooks
import (
+ "fmt"
"reflect"
"sort"
+ "strings"
"time"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@@ -11,25 +13,27 @@ import (
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils"
+ "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
//HandleGenerate handles admission-requests for policies with generate rules
-func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, policies []*kyverno.ClusterPolicy, ctx *context.Context, userRequestInfo kyverno.RequestInfo) (bool, string) {
+func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, policies []*kyverno.ClusterPolicy, ctx *context.Context, userRequestInfo kyverno.RequestInfo) {
logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
logger.V(4).Info("incoming request")
var engineResponses []response.EngineResponse
if len(policies) == 0 {
- return true, ""
+ return
}
// convert RAW to unstructured
resource, err := utils.ConvertToUnstructured(request.Object.Raw)
if err != nil {
//TODO: skip applying the admission control ?
logger.Error(err, "failed to convert RAR resource to unstructured format")
- return true, ""
+ return
}
// CREATE resources, do not have name, assigned in admission-request
@@ -52,10 +56,14 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
})
}
}
+
// Adds Generate Request to a channel(queue size 1000) to generators
- if err := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil {
- //TODO: send appropriate error
- return false, "Kyverno blocked: failed to create Generate Requests"
+ if failedResponse := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil {
+ // report failure event
+ for _, failedGR := range failedResponse {
+ events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, *resource)
+ ws.eventGen.Add(events...)
+ }
}
// Generate Stats wont be used here, as we delegate the generate rule
@@ -66,16 +74,19 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
// HandleGeneration always returns success
// Filter Policies
- return true, ""
+ return
}
-func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo, action v1beta1.Operation, engineResponses ...response.EngineResponse) error {
+func applyGenerateRequest(gnGenerator generate.GenerateRequests, userRequestInfo kyverno.RequestInfo,
+ action v1beta1.Operation, engineResponses ...response.EngineResponse) (failedGenerateRequest []generateRequestResponse) {
+
for _, er := range engineResponses {
- if err := gnGenerator.Apply(transform(userRequestInfo, er), action); err != nil {
- return err
+ gr := transform(userRequestInfo, er)
+ if err := gnGenerator.Apply(gr, action); err != nil {
+ failedGenerateRequest = append(failedGenerateRequest, generateRequestResponse{gr: gr, err: err})
}
}
- return nil
+ return
}
func transform(userRequestInfo kyverno.RequestInfo, er response.EngineResponse) kyverno.GenerateRequestSpec {
@@ -162,3 +173,41 @@ func updateAverageTime(newTime time.Duration, oldAverageTimeString string, avera
newAverageTimeInNanoSeconds := numerator / denominator
return time.Duration(newAverageTimeInNanoSeconds) * time.Nanosecond
}
+
+type generateRequestResponse struct {
+ gr v1.GenerateRequestSpec
+ err error
+}
+
+func (resp generateRequestResponse) info() string {
+ return strings.Join([]string{resp.gr.Resource.Kind, resp.gr.Resource.Namespace, resp.gr.Resource.Name}, "/")
+}
+
+func (resp generateRequestResponse) error() string {
+ return resp.err.Error()
+}
+
+func failedEvents(err error, gr kyverno.GenerateRequestSpec, resource unstructured.Unstructured) []event.Info {
+ var events []event.Info
+ // Cluster Policy
+ pe := event.Info{}
+ pe.Kind = "ClusterPolicy"
+ // cluserwide-resource
+ pe.Name = gr.Policy
+ pe.Reason = event.PolicyFailed.String()
+ pe.Source = event.GeneratePolicyController
+ pe.Message = fmt.Sprintf("policy failed to apply on resource %s/%s/%s: %v", resource.GetKind(), resource.GetNamespace(), resource.GetName(), err)
+ events = append(events, pe)
+
+ // Resource
+ re := event.Info{}
+ re.Kind = resource.GetKind()
+ re.Namespace = resource.GetNamespace()
+ re.Name = resource.GetName()
+ re.Reason = event.PolicyFailed.String()
+ re.Source = event.GeneratePolicyController
+ re.Message = fmt.Sprintf("policy %s failed to apply: %v", gr.Policy, err)
+ events = append(events, re)
+
+ return events
+}
diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go
index 790ae25c74..72fe530751 100644
--- a/pkg/webhooks/mutation.go
+++ b/pkg/webhooks/mutation.go
@@ -72,7 +72,7 @@ func (ws *WebhookServer) HandleMutation(
if len(policyPatches) > 0 {
patches = append(patches, policyPatches...)
rules := engineResponse.GetSuccessRules()
- logger.Info("mutation rules from policy applied successfully", "policy", policy.Name, "rules", rules)
+ logger.Info("mutation rules from policy applied successfully", "policy", policy.Name, "rules", rules)
}
policyContext.NewResource = engineResponse.PatchedResource
diff --git a/pkg/webhooks/policyvalidation.go b/pkg/webhooks/policyvalidation.go
index 5b2cf94c18..a9bbf81579 100644
--- a/pkg/webhooks/policyvalidation.go
+++ b/pkg/webhooks/policyvalidation.go
@@ -10,8 +10,11 @@ import (
//HandlePolicyValidation performs the validation check on policy resource
func (ws *WebhookServer) policyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
+ logger := ws.log.WithValues("action", "policyvalidation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
+
//TODO: can this happen? wont this be picked by OpenAPI spec schema ?
if err := policyvalidate.Validate(request.Object.Raw, ws.client, false, ws.openAPIController); err != nil {
+ logger.Error(err, "faield to validate policy")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go
index 123107fca7..75b559686a 100644
--- a/pkg/webhooks/server.go
+++ b/pkg/webhooks/server.go
@@ -7,11 +7,12 @@ import (
"errors"
"fmt"
"io/ioutil"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"net/http"
"time"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
"github.com/go-logr/logr"
"github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@@ -22,6 +23,7 @@ import (
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
+ enginutils "github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/policycache"
@@ -30,7 +32,6 @@ import (
tlsutils "github.com/nirmata/kyverno/pkg/tls"
userinfo "github.com/nirmata/kyverno/pkg/userinfo"
"github.com/nirmata/kyverno/pkg/utils"
- enginutils "github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/webhookconfig"
"github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
@@ -98,8 +99,11 @@ type WebhookServer struct {
grGenerator *generate.Generator
resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister
- log logr.Logger
- openAPIController *openapi.Controller
+
+ auditHandler AuditHandler
+
+ log logr.Logger
+ openAPIController *openapi.Controller
supportMudateValidate bool
}
@@ -123,6 +127,7 @@ func NewWebhookServer(
pvGenerator policyviolation.GeneratorInterface,
grGenerator *generate.Generator,
resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister,
+ auditHandler AuditHandler,
supportMudateValidate bool,
cleanUp chan<- struct{},
log logr.Logger,
@@ -161,6 +166,7 @@ func NewWebhookServer(
pvGenerator: pvGenerator,
grGenerator: grGenerator,
resourceWebhookWatcher: resourceWebhookWatcher,
+ auditHandler: auditHandler,
log: log,
openAPIController: openAPIController,
supportMudateValidate: supportMudateValidate,
@@ -226,7 +232,7 @@ func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequ
admissionReview.Response = handler(request)
writeResponse(rw, admissionReview)
- logger.V(4).Info("request processed", "processingTime", time.Since(startTime).Milliseconds())
+ logger.V(4).Info("request processed", "processingTime", time.Since(startTime).String())
return
}
@@ -258,7 +264,6 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
}
}
-
mutatePolicies := ws.pCache.Get(policycache.Mutate)
validatePolicies := ws.pCache.Get(policycache.ValidateEnforce)
generatePolicies := ws.pCache.Get(policycache.Generate)
@@ -290,7 +295,7 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
userRequestInfo := v1.RequestInfo{
Roles: roles,
ClusterRoles: clusterRoles,
- AdmissionUserInfo: request.UserInfo}
+ AdmissionUserInfo: *request.UserInfo.DeepCopy()}
// build context
ctx := context2.NewContext()
@@ -325,8 +330,11 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
logger.V(6).Info("", "patchedResource", string(patchedResource))
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" {
+ // push admission request to audit handler, this won't block the admission request
+ ws.auditHandler.Add(request.DeepCopy())
+
// VALIDATION
- ok, msg := ws.HandleValidation(request, validatePolicies, patchedResource, ctx, userRequestInfo)
+ ok, msg := HandleValidation(request, validatePolicies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.pvGenerator, ws.log)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
@@ -344,25 +352,14 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
// GENERATE
// Only applied during resource creation and update
- // Success -> Generate Request CR created successsfully
+ // Success -> Generate Request CR created successfully
// Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update {
-
- ok, msg := ws.HandleGenerate(request, generatePolicies, ctx, userRequestInfo)
- if !ok {
- logger.Info("admission request denied")
- return &v1beta1.AdmissionResponse{
- Allowed: false,
- Result: &metav1.Status{
- Status: "Failure",
- Message: msg,
- },
- }
- }
+ go ws.HandleGenerate(request.DeepCopy(), generatePolicies, ctx, userRequestInfo)
}
- // Succesfful processing of mutation & validation rules in policy
+ // Succesful processing of mutation & validation rules in policy
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
@@ -377,8 +374,8 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("resourceValidation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
- logger.V(4).Info("DEBUG","request",request)
- checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister,ws.rLister, ws.crLister, request)
+ logger.V(4).Info("DEBUG", "request", request)
+ checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister, ws.rLister, ws.crLister, request)
if err != nil {
logger.Error(err, "failed to get RBAC infromation for request")
}
@@ -389,7 +386,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
var resource *unstructured.Unstructured
if request.Operation == v1beta1.Delete {
resource, err = enginutils.ConvertToUnstructured(request.OldObject.Raw)
- }else{
+ } else {
resource, err = enginutils.ConvertToUnstructured(request.Object.Raw)
}
if err != nil {
@@ -405,7 +402,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
}
}
- oldResource, err := ws.client.GetResource(resource.GetKind(),resource.GetNamespace(),resource.GetName());
+ oldResource, err := ws.client.GetResource(resource.GetKind(), resource.GetNamespace(), resource.GetName())
if err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to get resource")
@@ -420,7 +417,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
}
labels := oldResource.GetLabels()
if labels != nil {
- if labels["app.kubernetes.io/managed-by"] == "kyverno" && labels["app.kubernetes.io/synchronize"] == "enable" {
+ if labels["app.kubernetes.io/managed-by"] == "kyverno" && labels["app.kubernetes.io/synchronize"] == "enable" {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
@@ -452,6 +449,9 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
}
}
+ // push admission request to audit handler, this won't block the admission request
+ ws.auditHandler.Add(request.DeepCopy())
+
policies := ws.pCache.Get(policycache.ValidateEnforce)
if len(policies) == 0 {
logger.V(4).Info("No enforce Validation policy found, returning")
@@ -463,8 +463,14 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
if containRBACinfo(policies) {
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request)
if err != nil {
- // TODO(shuting): continue apply policy if error getting roleRef?
logger.Error(err, "failed to get RBAC information for request")
+ return &v1beta1.AdmissionResponse{
+ Allowed: false,
+ Result: &metav1.Status{
+ Status: "Failure",
+ Message: err.Error(),
+ },
+ }
}
}
@@ -489,7 +495,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
logger.Error(err, "failed to load service account in context")
}
- ok, msg := ws.HandleValidation(request, policies, nil, ctx, userRequestInfo)
+ ok, msg := HandleValidation(request, policies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.pvGenerator, ws.log)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{
diff --git a/pkg/webhooks/validate_audit.go b/pkg/webhooks/validate_audit.go
new file mode 100644
index 0000000000..ccef2ed936
--- /dev/null
+++ b/pkg/webhooks/validate_audit.go
@@ -0,0 +1,170 @@
+package webhooks
+
+import (
+ "github.com/go-logr/logr"
+ "github.com/minio/minio/cmd/logger"
+ v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
+ kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned"
+ "github.com/nirmata/kyverno/pkg/constant"
+ enginectx "github.com/nirmata/kyverno/pkg/engine/context"
+ "github.com/nirmata/kyverno/pkg/event"
+ "github.com/nirmata/kyverno/pkg/policycache"
+ "github.com/nirmata/kyverno/pkg/policystatus"
+ "github.com/nirmata/kyverno/pkg/policyviolation"
+ "github.com/nirmata/kyverno/pkg/userinfo"
+ "github.com/pkg/errors"
+ "k8s.io/api/admission/v1beta1"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/util/wait"
+ rbacinformer "k8s.io/client-go/informers/rbac/v1"
+ rbaclister "k8s.io/client-go/listers/rbac/v1"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/client-go/util/workqueue"
+)
+
+const (
+ workQueueName = "validate-audit-handler"
+ workQueueRetryLimit = 3
+)
+
+// Handler applies validate audit policies to the admission request
+// the handler adds the request to the work queue and returns immediately
+// the request is processed in background, with the exact same logic
+// when process the admission request in the webhook
+type AuditHandler interface {
+ Add(request *v1beta1.AdmissionRequest)
+ Run(workers int, stopCh <-chan struct{})
+}
+
+type auditHandler struct {
+ client *kyvernoclient.Clientset
+ queue workqueue.RateLimitingInterface
+ pCache policycache.Interface
+ eventGen event.Interface
+ statusListener policystatus.Listener
+ pvGenerator policyviolation.GeneratorInterface
+
+ rbLister rbaclister.RoleBindingLister
+ rbSynced cache.InformerSynced
+ crbLister rbaclister.ClusterRoleBindingLister
+ crbSynced cache.InformerSynced
+
+ log logr.Logger
+}
+
+// NewValidateAuditHandler returns a new instance of audit policy handler
+func NewValidateAuditHandler(pCache policycache.Interface,
+ eventGen event.Interface,
+ statusListener policystatus.Listener,
+ pvGenerator policyviolation.GeneratorInterface,
+ rbInformer rbacinformer.RoleBindingInformer,
+ crbInformer rbacinformer.ClusterRoleBindingInformer,
+ log logr.Logger) AuditHandler {
+
+ return &auditHandler{
+ pCache: pCache,
+ queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
+ eventGen: eventGen,
+ statusListener: statusListener,
+ pvGenerator: pvGenerator,
+ rbLister: rbInformer.Lister(),
+ rbSynced: rbInformer.Informer().HasSynced,
+ crbLister: crbInformer.Lister(),
+ crbSynced: crbInformer.Informer().HasSynced,
+ log: log,
+ }
+}
+
+func (h *auditHandler) Add(request *v1beta1.AdmissionRequest) {
+ h.log.V(4).Info("admission request added", "uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
+ h.queue.Add(request)
+}
+
+func (h *auditHandler) Run(workers int, stopCh <-chan struct{}) {
+ h.log.V(4).Info("starting")
+
+ defer func() {
+ utilruntime.HandleCrash()
+ h.log.V(4).Info("shutting down")
+ }()
+
+ if !cache.WaitForCacheSync(stopCh, h.rbSynced, h.crbSynced) {
+ logger.Info("failed to sync informer cache")
+ }
+
+ for i := 0; i < workers; i++ {
+ go wait.Until(h.runWorker, constant.GenerateControllerResync, stopCh)
+ }
+
+ <-stopCh
+}
+
+func (h *auditHandler) runWorker() {
+ for h.processNextWorkItem() {
+ }
+}
+
+func (h *auditHandler) processNextWorkItem() bool {
+ obj, shutdown := h.queue.Get()
+ if shutdown {
+ return false
+ }
+
+ defer h.queue.Done(obj)
+
+ request, ok := obj.(*v1beta1.AdmissionRequest)
+ if !ok {
+ h.queue.Forget(obj)
+ logger.Info("incorrect type: expecting type 'AdmissionRequest'", "object", obj)
+ return false
+ }
+
+ err := h.process(request)
+ h.handleErr(err)
+
+ return true
+}
+
+func (h *auditHandler) process(request *v1beta1.AdmissionRequest) error {
+ var roles, clusterRoles []string
+ var err error
+
+ logger := h.log.WithName("process")
+ policies := h.pCache.Get(policycache.ValidateAudit)
+
+ // getRoleRef only if policy has roles/clusterroles defined
+ if containRBACinfo(policies) {
+ roles, clusterRoles, err = userinfo.GetRoleRef(h.rbLister, h.crbLister, request)
+ if err != nil {
+ logger.Error(err, "failed to get RBAC information for request")
+ }
+ }
+
+ userRequestInfo := v1.RequestInfo{
+ Roles: roles,
+ ClusterRoles: clusterRoles,
+ AdmissionUserInfo: request.UserInfo}
+
+ // build context
+ ctx := enginectx.NewContext()
+ err = ctx.AddRequest(request)
+ if err != nil {
+ return errors.Wrap(err, "failed to load incoming request in context")
+ }
+
+ err = ctx.AddUserInfo(userRequestInfo)
+ if err != nil {
+ return errors.Wrap(err, "failed to load userInfo in context")
+ }
+ err = ctx.AddSA(userRequestInfo.AdmissionUserInfo.Username)
+ if err != nil {
+ return errors.Wrap(err, "failed to load service account in context")
+ }
+
+ HandleValidation(request, policies, nil, ctx, userRequestInfo, h.statusListener, h.eventGen, h.pvGenerator, logger)
+ return nil
+}
+
+func (h *auditHandler) handleErr(err error) {
+
+}
diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go
index 020620ceef..8e0cc2ad63 100644
--- a/pkg/webhooks/validation.go
+++ b/pkg/webhooks/validation.go
@@ -5,6 +5,9 @@ import (
"sort"
"time"
+ "github.com/go-logr/logr"
+ "github.com/nirmata/kyverno/pkg/event"
+ "github.com/nirmata/kyverno/pkg/policystatus"
"github.com/nirmata/kyverno/pkg/utils"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@@ -21,12 +24,16 @@ import (
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
// patchedResource is the (resource + patches) after applying mutation rules
-func (ws *WebhookServer) HandleValidation(
+func HandleValidation(
request *v1beta1.AdmissionRequest,
policies []*kyverno.ClusterPolicy,
patchedResource []byte,
ctx *context.Context,
- userRequestInfo kyverno.RequestInfo) (bool, string) {
+ userRequestInfo kyverno.RequestInfo,
+ statusListener policystatus.Listener,
+ eventGen event.Interface,
+ pvGenerator policyviolation.GeneratorInterface,
+ log logr.Logger) (bool, string) {
if len(policies) == 0 {
return true, ""
@@ -37,7 +44,7 @@ func (ws *WebhookServer) HandleValidation(
resourceName = request.Namespace + "/" + resourceName
}
- logger := ws.log.WithValues("action", "validate", "resource", resourceName, "operation", request.Operation)
+ logger := log.WithValues("action", "validate", "resource", resourceName, "operation", request.Operation)
// Get new and old resource
newR, oldR, err := utils.ExtractResources(patchedResource, request)
@@ -76,7 +83,7 @@ func (ws *WebhookServer) HandleValidation(
continue
}
engineResponses = append(engineResponses, engineResponse)
- ws.statusListener.Send(validateStats{
+ statusListener.Send(validateStats{
resp: engineResponse,
})
if !engineResponse.IsSuccessful() {
@@ -101,7 +108,7 @@ func (ws *WebhookServer) HandleValidation(
// all policies were applied succesfully.
// create an event on the resource
events := generateEvents(engineResponses, blocked, (request.Operation == v1beta1.Update), logger)
- ws.eventGen.Add(events...)
+ eventGen.Add(events...)
if blocked {
logger.V(4).Info("resource blocked")
return false, getEnforceFailureErrorMsg(engineResponses)
@@ -110,8 +117,8 @@ func (ws *WebhookServer) HandleValidation(
// ADD POLICY VIOLATIONS
// violations are created with resource on "audit"
pvInfos := policyviolation.GeneratePVsFromEngineResponse(engineResponses, logger)
- ws.pvGenerator.Add(pvInfos...)
- // report time end
+ pvGenerator.Add(pvInfos...)
+
return true, ""
}