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

merge master changes

This commit is contained in:
evalsocket 2020-07-10 15:25:05 -07:00
commit 26ae7e2052
38 changed files with 498 additions and 212 deletions

View file

@ -1,11 +1,9 @@
name: prereleaser name: prereleaser
on: on:
push: push:
tags: tags:
- '*' - '*'
jobs: jobs:
releaser: releaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -36,6 +34,8 @@ jobs:
access-token: ${{ secrets.ACCESS_TOKEN }} access-token: ${{ secrets.ACCESS_TOKEN }}
deploy-branch: gh-pages deploy-branch: gh-pages
charts-folder: charts charts-folder: charts
- name: Update new version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.38

View file

@ -14,7 +14,6 @@ builds:
- windows - windows
goarch: goarch:
- amd64 - amd64
goarm: [6, 7]
archives: archives:
- id: kyverno-cli-archive - id: kyverno-cli-archive
name_template: |- name_template: |-
@ -26,12 +25,12 @@ archives:
{{- end -}} {{- end -}}
builds: builds:
- kyverno-cli - kyverno-cli
replacements:
386: i386
amd64: x86_64
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip format: zip
replacements:
386: i386
amd64: x86_64
files: ["LICENSE"] files: ["LICENSE"]
checksum: checksum:
name_template: "checksums.txt" name_template: "checksums.txt"

46
.krew.yaml Normal file
View file

@ -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

View file

@ -209,7 +209,6 @@ func main() {
pInformer.Kyverno().V1().ClusterPolicies(), pInformer.Kyverno().V1().ClusterPolicies(),
pInformer.Kyverno().V1().GenerateRequests(), pInformer.Kyverno().V1().GenerateRequests(),
eventGenerator, eventGenerator,
pvgen,
kubedynamicInformer, kubedynamicInformer,
statusSync.Listener, statusSync.Listener,
log.Log.WithName("GenerateController"), log.Log.WithName("GenerateController"),
@ -231,6 +230,16 @@ func main() {
log.Log.WithName("PolicyCacheController"), 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 // CONFIGURE CERTIFICATES
tlsPair, err := client.InitTLSPemPair(clientConfig, fqdncn) tlsPair, err := client.InitTLSPemPair(clientConfig, fqdncn)
if err != nil { if err != nil {
@ -282,6 +291,7 @@ func main() {
pvgen, pvgen,
grgen, grgen,
rWebhookWatcher, rWebhookWatcher,
auditHandler,
supportMudateValidate, supportMudateValidate,
cleanUp, cleanUp,
log.Log.WithName("WebhookServer"), log.Log.WithName("WebhookServer"),
@ -307,6 +317,7 @@ func main() {
go pvgen.Run(1, stopCh) go pvgen.Run(1, stopCh)
go statusSync.Run(1, stopCh) go statusSync.Run(1, stopCh)
go pCacheController.Run(1, stopCh) go pCacheController.Run(1, stopCh)
go auditHandler.Run(10, stopCh)
openAPISync.Run(1, stopCh) openAPISync.Run(1, stopCh)
// verifys if the admission control is enabled and active // verifys if the admission control is enabled and active

View file

@ -1,13 +1,10 @@
<small>_[documentation](/README.md#documentation) / kyverno-cli_</small> <small>*[documentation](/README.md#documentation) / kyverno-cli*</small>
# 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. 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 ## Build the CLI
You can build the CLI binary locally, then move the binary into a directory in your PATH. 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 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 ```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) ## Install via AUR (archlinux)
@ -39,55 +40,39 @@ yay -S kyverno-git
Prints the version of kyverno used by the CLI. Prints the version of kyverno used by the CLI.
Example: Example:
``` ```
kyverno version kyverno version
``` ```
#### Validate #### Validate
Validates a policy, can validate multiple policy resource description files or even an entire folder containing policy resource description
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.
files. Currently supports files with resource description in YAML.
Example: Example:
``` ```
kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies kyverno validate /path/to/policy1.yaml /path/to/policy2.yaml /path/to/folderFullOfPolicies
``` ```
#### Apply #### Apply
Applies policies on resources, and supports applying multiple policies on multiple resources in a single command. 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. 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: Apply to a resource:
```
```bash
kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml kyverno apply /path/to/policy.yaml --resource /path/to/resource.yaml
``` ```
Apply to all matching resources in a cluster: Apply to all matching resources in a cluster:
```
```bash
kyverno apply /path/to/policy.yaml --cluster > policy-results.txt kyverno apply /path/to/policy.yaml --cluster > policy-results.txt
``` ```
Apply multiple policies to multiple resources: 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 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: <small>*Read Next >> [Sample Policies](/samples/README.md)*</small>
| Message | Exit Code |
| ------------------------------------- | --------- |
| executes successfully | 0 |
| one or more policy rules are violated | 1 |
| policy validation failed | 2 |
<small>_Read Next >> [Sample Policies](/samples/README.md)_</small>

1
go.mod
View file

@ -16,6 +16,7 @@ require (
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5 github.com/minio/minio v0.0.0-20200114012931-30922148fbb5
github.com/ory/go-acc v0.2.1 // indirect 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/prometheus/common v0.4.1
github.com/rogpeppe/godef v1.1.2 // indirect github.com/rogpeppe/godef v1.1.2 // indirect
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5

2
go.sum
View file

@ -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/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 h1:CjDeQ78sVdDrENJff3EUwVMUv9GfTL4NyLvjE/Bvrd8=
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5/go.mod h1:HH1U0HOUzfjsCGlGCncWDh8L3zPejMhMDDsLAETqXs0= 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/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/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= 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/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/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/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/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/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= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=

View file

@ -55,6 +55,9 @@ func mutateResourceWithOverlay(resource unstructured.Unstructured, overlay inter
// ForceMutate does not check any conditions, it simply mutates the given resource // 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) { func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured) (unstructured.Unstructured, error) {
var err 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 { for _, rule := range policy.Spec.Rules {
if !rule.HasMutate() { if !rule.HasMutate() {
continue continue
@ -81,7 +84,7 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
if rule.Mutation.Patches != nil { if rule.Mutation.Patches != nil {
var resp response.RuleResponse 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 { if !resp.Success {
return unstructured.Unstructured{}, fmt.Errorf(resp.Message) return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
} }

View file

@ -29,7 +29,7 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
resp.Type = utils.Mutation.String() resp.Type = utils.Mutation.String()
defer func() { defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime) 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) 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 // explicitly handle boolean type in annotation
// keep the type boolean as it is in any other fields // keep the type boolean as it is in any other fields
if strings.Contains(path, "/metadata/annotations") || if strings.Contains(path, "/metadata/annotations") ||
strings.Contains(path, "/metadata/labels"){ strings.Contains(path, "/metadata/labels") {
patchStr = wrapBoolean(patchStr) patchStr = wrapBoolean(patchStr)
} }

View file

@ -27,9 +27,9 @@ func checkConditions(log logr.Logger, resource, overlay interface{}, path string
// condition never be true in this case // condition never be true in this case
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
if hasNestedAnchors(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, 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{} return "", overlayError{}
@ -112,8 +112,8 @@ func validateConditionAnchorMap(resourceMap, anchors map[string]interface{}, pat
// resource - A: B2 // resource - A: B2
func compareOverlay(resource, overlay interface{}, path string) (string, overlayError) { func compareOverlay(resource, overlay interface{}, path string) (string, overlayError) {
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) { if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
log.Log.V(4).Info("Found anchor on different types of element", "overlay", overlay, "resource", resource) log.Log.V(4).Info("element type mismatch", "overlay", overlay, "resource", resource)
return path, newOverlayError(conditionFailure, fmt.Sprintf("Found anchor on different types of element: overlay %T, resource %T", overlay, resource)) return path, newOverlayError(conditionFailure, fmt.Sprintf("element type mismatch: overlay %T, resource %T", overlay, resource))
} }
switch typedOverlay := overlay.(type) { switch typedOverlay := overlay.(type) {

View file

@ -150,7 +150,7 @@ func TestMeetConditions_DifferentTypes(t *testing.T) {
// anchor exist // anchor exist
_, err = meetConditions(log.Log, resource, overlay) _, 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) { func TestMeetConditions_anchosInSameObject(t *testing.T) {

View file

@ -28,7 +28,7 @@ func ProcessPatches(log logr.Logger, rule kyverno.Rule, resource unstructured.Un
resp.Type = utils.Mutation.String() resp.Type = utils.Mutation.String()
defer func() { defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime) 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 // convert to RAW

View file

@ -22,23 +22,24 @@ const (
PodControllersAnnotation = "pod-policies.kyverno.io/autogen-controllers" PodControllersAnnotation = "pod-policies.kyverno.io/autogen-controllers"
//PodTemplateAnnotation defines the annotation key for Pod-Template //PodTemplateAnnotation defines the annotation key for Pod-Template
PodTemplateAnnotation = "pod-policies.kyverno.io/autogen-applied" PodTemplateAnnotation = "pod-policies.kyverno.io/autogen-applied"
PodControllerRuleName = "autogen-pod-ctrl-annotation"
) )
// Mutate performs mutation. Overlay first and then mutation patches // Mutate performs mutation. Overlay first and then mutation patches
func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
startTime := time.Now() startTime := time.Now()
policy := policyContext.Policy policy := policyContext.Policy
resource := policyContext.NewResource patchedResource := policyContext.NewResource
ctx := policyContext.Context 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) logger.V(4).Info("start policy processing", "startTime", startTime)
startMutateResultResponse(&resp, policy, resource)
startMutateResultResponse(&resp, policy, patchedResource)
defer endMutateResultResponse(logger, &resp, startTime) defer endMutateResultResponse(logger, &resp, startTime)
patchedResource := policyContext.NewResource if policy.HasAutoGenAnnotation() && excludePod(patchedResource) {
logger.V(5).Info("Skip applying policy, Pod has ownerRef set", "policy", policy.GetName())
if autoGenAnnotationApplied(patchedResource) && policy.HasAutoGenAnnotation() {
resp.PatchedResource = patchedResource resp.PatchedResource = patchedResource
return return
} }
@ -47,14 +48,14 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
var ruleResponse response.RuleResponse var ruleResponse response.RuleResponse
logger := logger.WithValues("rule", rule.Name) logger := logger.WithValues("rule", rule.Name)
//TODO: to be checked before calling the resources as well //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 continue
} }
// check if the resource satisfies the filter conditions defined in the rule // 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 //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 // 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()) logger.V(3).Info("resource not matched", "reason", err.Error())
continue continue
} }
@ -112,22 +113,6 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) {
return resp 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 // send the patched resource
resp.PatchedResource = patchedResource resp.PatchedResource = patchedResource
return resp 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) { func endMutateResultResponse(logger logr.Logger, resp *response.EngineResponse, startTime time.Time) {
resp.PolicyResponse.ProcessingTime = time.Since(startTime) resp.PolicyResponse.ProcessingTime = time.Since(startTime)
logger.V(4).Info("finished processing policy", "processingTime", resp.PolicyResponse.ProcessingTime, "mutationRulesApplied", resp.PolicyResponse.RulesAppliedCount) logger.V(4).Info("finished processing policy", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "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",
},
},
},
},
},
},
} }

View file

@ -113,7 +113,7 @@ func (er EngineResponse) GetSuccessRules() []string {
func (er EngineResponse) getRules(success bool) []string { func (er EngineResponse) getRules(success bool) []string {
var rules []string var rules []string
for _, r := range er.PolicyResponse.Rules { for _, r := range er.PolicyResponse.Rules {
if r.Success == success{ if r.Success == success {
rules = append(rules, r.Name) rules = append(rules, r.Name)
} }
} }

View file

@ -201,11 +201,10 @@ func copyConditions(original []kyverno.Condition) []kyverno.Condition {
return copy return copy
} }
// autoGenAnnotationApplied checks if a Pod has annotation "pod-policies.kyverno.io/autogen-applied" // excludePod checks if a Pod has ownerRef set
func autoGenAnnotationApplied(resource unstructured.Unstructured) bool { func excludePod(resource unstructured.Unstructured) bool {
if resource.GetKind() == "Pod" { if resource.GetKind() == "Pod" {
ann := resource.GetAnnotations() if len(resource.GetOwnerReferences()) > 0 {
if _, ok := ann[PodTemplateAnnotation]; ok {
return true return true
} }
} }

View file

@ -125,15 +125,18 @@ func validateArray(log logr.Logger, resourceArray, patternArray []interface{}, o
} }
default: default:
// In all other cases - detect type and handle each array element with validateResourceElement // In all other cases - detect type and handle each array element with validateResourceElement
for i, patternElement := range patternArray { if len(resourceArray) >= len(patternArray) {
currentPath := path + strconv.Itoa(i) + "/" for i, patternElement := range patternArray {
path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath) currentPath := path + strconv.Itoa(i) + "/"
if err != nil { path, err := validateResourceElement(log, resourceArray[i], patternElement, originPattern, currentPath)
return path, err 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 return "", nil
} }

View file

@ -24,7 +24,7 @@ func Validate(policyContext PolicyContext) (resp response.EngineResponse) {
oldR := policyContext.OldResource oldR := policyContext.OldResource
ctx := policyContext.Context ctx := policyContext.Context
admissionInfo := policyContext.AdmissionInfo 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{}) { if reflect.DeepEqual(newR, unstructured.Unstructured{}) {
logger = logger.WithValues("kind", oldR.GetKind(), "namespace", oldR.GetNamespace(), "name", oldR.GetName()) 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) { func endResultResponse(log logr.Logger, resp *response.EngineResponse, startTime time.Time) {
resp.PolicyResponse.ProcessingTime = time.Since(startTime) 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) { 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 { func isRequestDenied(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &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 { for _, rule := range policy.Spec.Rules {
if !rule.HasValidate() { 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 { func validateResource(log logr.Logger, ctx context.EvalInterface, policy kyverno.ClusterPolicy, resource unstructured.Unstructured, admissionInfo kyverno.RequestInfo) *response.EngineResponse {
resp := &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 return resp
} }
@ -226,7 +231,7 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr
resp.Type = utils.Validation.String() resp.Type = utils.Validation.String()
defer func() { defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime) 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 // work on a copy of validation rule

View file

@ -248,7 +248,7 @@ func (c *Controller) syncGenerateRequest(key string) error {
startTime := time.Now() startTime := time.Now()
logger.Info("started syncing generate request", "startTime", startTime) logger.Info("started syncing generate request", "startTime", startTime)
defer func() { 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) _, grName, err := cache.SplitMetaNamespaceKey(key)
if errors.IsNotFound(err) { if errors.IsNotFound(err) {

View file

@ -13,7 +13,6 @@ import (
dclient "github.com/nirmata/kyverno/pkg/dclient" dclient "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/event" "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystatus" "github.com/nirmata/kyverno/pkg/policystatus"
"github.com/nirmata/kyverno/pkg/policyviolation"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@ -52,8 +51,6 @@ type Controller struct {
pSynced cache.InformerSynced pSynced cache.InformerSynced
// grSynced returns true if the Generate Request store has been synced at least once // grSynced returns true if the Generate Request store has been synced at least once
grSynced cache.InformerSynced grSynced cache.InformerSynced
// policy violation generator
pvGenerator policyviolation.GeneratorInterface
// dyanmic sharedinformer factory // dyanmic sharedinformer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory dynamicInformer dynamicinformer.DynamicSharedInformerFactory
//TODO: list of generic informers //TODO: list of generic informers
@ -70,7 +67,6 @@ func NewController(
pInformer kyvernoinformer.ClusterPolicyInformer, pInformer kyvernoinformer.ClusterPolicyInformer,
grInformer kyvernoinformer.GenerateRequestInformer, grInformer kyvernoinformer.GenerateRequestInformer,
eventGen event.Interface, eventGen event.Interface,
pvGenerator policyviolation.GeneratorInterface,
dynamicInformer dynamicinformer.DynamicSharedInformerFactory, dynamicInformer dynamicinformer.DynamicSharedInformerFactory,
policyStatus policystatus.Listener, policyStatus policystatus.Listener,
log logr.Logger, log logr.Logger,
@ -79,7 +75,6 @@ func NewController(
client: client, client: client,
kyvernoClient: kyvernoclient, kyvernoClient: kyvernoclient,
eventGen: eventGen, eventGen: eventGen,
pvGenerator: pvGenerator,
//TODO: do the math for worst case back off and make sure cleanup runs after that //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 // as we dont want a deleted GR to be re-queue
queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request"), queue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(1, 30), "generate-request"),
@ -268,7 +263,7 @@ func (c *Controller) syncGenerateRequest(key string) error {
startTime := time.Now() startTime := time.Now()
logger.Info("started sync", "key", key, "startTime", startTime) logger.Info("started sync", "key", key, "startTime", startTime)
defer func() { 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) _, grName, err := cache.SplitMetaNamespaceKey(key)
if err != nil { if err != nil {

View file

@ -28,7 +28,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
name = ns + "/" + name 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 var engineResponses []response.EngineResponse
@ -81,10 +81,6 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
for index, rule := range engineResponse.PolicyResponse.Rules { for index, rule := range engineResponse.PolicyResponse.Rules {
log.V(4).Info("verifying if policy rule was applied before", "rule", rule.Name) log.V(4).Info("verifying if policy rule was applied before", "rule", rule.Name)
if rule.Name == engine.PodControllerRuleName {
continue
}
patches := rule.Patches patches := rule.Patches
patch, err := jsonpatch.DecodePatch(utils.JoinPatches(patches)) patch, err := jsonpatch.DecodePatch(utils.JoinPatches(patches))

View file

@ -304,7 +304,7 @@ func (pc *PolicyController) syncPolicy(key string) error {
startTime := time.Now() startTime := time.Now()
logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime) logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime)
defer func() { 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) policy, err := pc.pLister.Get(key)

View file

@ -109,6 +109,7 @@ func (pc *PolicyController) getPolicyForNamespacedPolicyViolation(pv *kyverno.Po
logger := pc.log.WithValues("kind", pv.Kind, "namespace", pv.Namespace, "name", pv.Name) logger := pc.log.WithValues("kind", pv.Kind, "namespace", pv.Namespace, "name", pv.Name)
policies, err := pc.pLister.GetPolicyForNamespacedPolicyViolation(pv) policies, err := pc.pLister.GetPolicyForNamespacedPolicyViolation(pv)
if err != nil || len(policies) == 0 { if err != nil || len(policies) == 0 {
logger.V(4).Info("missing policy for namespaced policy violation", "reason", err.Error())
return nil return nil
} }
// Because all PolicyViolations's belonging to a Policy should have a unique label key, // Because all PolicyViolations's belonging to a Policy should have a unique label key,

View file

@ -12,6 +12,9 @@ import (
type pMap struct { type pMap struct {
sync.RWMutex sync.RWMutex
dataMap map[PolicyType][]*kyverno.ClusterPolicy dataMap map[PolicyType][]*kyverno.ClusterPolicy
// nameCacheMap stores the names of all existing policies in dataMap
nameCacheMap map[PolicyType]map[string]bool
} }
// policyCache ... // policyCache ...
@ -29,9 +32,17 @@ type Interface interface {
// newPolicyCache ... // newPolicyCache ...
func newPolicyCache(log logr.Logger) Interface { 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{ return &policyCache{
pMap{ pMap{
dataMap: make(map[PolicyType][]*kyverno.ClusterPolicy), dataMap: make(map[PolicyType][]*kyverno.ClusterPolicy),
nameCacheMap: namesCache,
}, },
log, 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()) 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) { func (m *pMap) add(policy *kyverno.ClusterPolicy) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
enforcePolicy := policy.Spec.ValidationFailureAction == "enforce" enforcePolicy := policy.Spec.ValidationFailureAction == "enforce"
mutateMap := buildCacheMap(m.dataMap[Mutate]) mutateMap := m.nameCacheMap[Mutate]
validateMap := buildCacheMap(m.dataMap[ValidateEnforce]) validateEnforceMap := m.nameCacheMap[ValidateEnforce]
generateMap := buildCacheMap(m.dataMap[Generate]) validateAuditMap := m.nameCacheMap[ValidateAudit]
generateMap := m.nameCacheMap[Generate]
pName := policy.GetName() pName := policy.GetName()
for _, rule := range policy.Spec.Rules { for _, rule := range policy.Spec.Rules {
@ -90,12 +87,23 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
continue continue
} }
if rule.HasValidate() && enforcePolicy { if rule.HasValidate() {
if !validateMap[pName] { if enforcePolicy {
validateMap[pName] = true if !validateEnforceMap[pName] {
validateEnforceMap[pName] = true
validatePolicy := m.dataMap[ValidateEnforce] validatePolicy := m.dataMap[ValidateEnforce]
m.dataMap[ValidateEnforce] = append(validatePolicy, policy) 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 continue
} }
@ -110,6 +118,11 @@ func (m *pMap) add(policy *kyverno.ClusterPolicy) {
continue 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 { func (m *pMap) get(key PolicyType) []*kyverno.ClusterPolicy {

View file

@ -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) { func Test_Remove_From_Empty_Cache(t *testing.T) {
pCache := newPolicyCache(log.Log) pCache := newPolicyCache(log.Log)
policy := newPolicy(t) policy := newPolicy(t)

View file

@ -5,5 +5,6 @@ type PolicyType uint8
const ( const (
Mutate PolicyType = 1 << iota Mutate PolicyType = 1 << iota
ValidateEnforce ValidateEnforce
ValidateAudit
Generate Generate
) )

View file

@ -47,10 +47,10 @@ func (l Listener) Send(s statusUpdater) {
//since it contains access to all the persistant data present //since it contains access to all the persistant data present
//in this package. //in this package.
type Sync struct { type Sync struct {
cache *cache cache *cache
Listener Listener Listener Listener
client *versioned.Clientset client *versioned.Clientset
lister kyvernolister.ClusterPolicyLister lister kyvernolister.ClusterPolicyLister
} }
type cache struct { type cache struct {
@ -66,9 +66,9 @@ func NewSync(c *versioned.Clientset, lister kyvernolister.ClusterPolicyLister) *
data: make(map[string]v1.PolicyStatus), data: make(map[string]v1.PolicyStatus),
keyToMutex: newKeyToMutex(), keyToMutex: newKeyToMutex(),
}, },
client: c, client: c,
lister: lister, lister: lister,
Listener: make(chan statusUpdater, 20), Listener: make(chan statusUpdater, 20),
} }
} }

View file

@ -29,7 +29,6 @@ func (d dummyStatusUpdater) PolicyName() string {
return "policy1" return "policy1"
} }
type dummyLister struct { type dummyLister struct {
} }

View file

@ -159,7 +159,7 @@ func (gen *Generator) Run(workers int, stopCh <-chan struct{}) {
} }
func (gen *Generator) runWorker() { 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) logger.Error(err, "dropping key out of the queue", "key", key)
} }
func (gen *Generator) processNextWorkitem() bool { func (gen *Generator) processNextWorkItem() bool {
logger := gen.log logger := gen.log
obj, shutdown := gen.queue.Get() obj, shutdown := gen.queue.Get()
if shutdown { if shutdown {
@ -197,11 +197,13 @@ func (gen *Generator) processNextWorkitem() bool {
defer gen.queue.Done(obj) defer gen.queue.Done(obj)
var keyHash string var keyHash string
var ok bool var ok bool
if keyHash, ok = obj.(string); !ok { if keyHash, ok = obj.(string); !ok {
gen.queue.Forget(obj) gen.queue.Forget(obj)
logger.Info("incorrect type; expecting type 'string'", "obj", obj) logger.Info("incorrect type; expecting type 'string'", "obj", obj)
return nil return nil
} }
// lookup data store // lookup data store
info := gen.dataStore.lookup(keyHash) info := gen.dataStore.lookup(keyHash)
if reflect.DeepEqual(info, Info{}) { if reflect.DeepEqual(info, Info{}) {
@ -210,14 +212,17 @@ func (gen *Generator) processNextWorkitem() bool {
logger.Info("empty key") logger.Info("empty key")
return nil return nil
} }
err := gen.syncHandler(info) err := gen.syncHandler(info)
gen.handleErr(err, obj) gen.handleErr(err, obj)
return nil return nil
}(obj) }(obj)
if err != nil { if err != nil {
logger.Error(err, "failed to process item") logger.Error(err, "failed to process item")
return true return true
} }
return true return true
} }

View file

@ -6,9 +6,9 @@ func Test_Mutate_EndPoint(t *testing.T) {
testScenario(t, "/test/scenarios/other/scenario_mutate_endpoint.yaml") testScenario(t, "/test/scenarios/other/scenario_mutate_endpoint.yaml")
} }
// func Test_Mutate_Validate_qos(t *testing.T) { func Test_Mutate_Validate_qos(t *testing.T) {
// testScenario(t, "/test/scenarios/other/scenario_mutate_validate_qos.yaml") testScenario(t, "/test/scenarios/other/scenario_mutate_validate_qos.yaml")
// } }
func Test_disallow_root_user(t *testing.T) { func Test_disallow_root_user(t *testing.T) {
testScenario(t, "test/scenarios/samples/best_practices/disallow_root_user.yaml") testScenario(t, "test/scenarios/samples/best_practices/disallow_root_user.yaml")

View file

@ -251,7 +251,7 @@ func (wrc *WebhookRegistrationClient) removeWebhookConfigurations() {
startTime := time.Now() startTime := time.Now()
wrc.log.Info("removing prior webhook configurations") wrc.log.Info("removing prior webhook configurations")
defer func() { 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 var wg sync.WaitGroup

View file

@ -118,7 +118,7 @@ func (rww *ResourceWebhookRegister) Run(stopCh <-chan struct{}) {
} }
// RemoveResourceWebhookConfiguration removes the resource webhook configurations // RemoveResourceWebhookConfiguration removes the resource webhook configurations
func (rww *ResourceWebhookRegister) RemoveResourceWebhookConfiguration() { func (rww *ResourceWebhookRegister) RemoveResourceWebhookConfiguration() {
rww.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration() rww.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
if rww.RunValidationInMutatingWebhook != "true" { if rww.RunValidationInMutatingWebhook != "true" {

View file

@ -29,11 +29,11 @@ func isResponseSuccesful(engineReponses []response.EngineResponse) bool {
func toBlockResource(engineReponses []response.EngineResponse, log logr.Logger) bool { func toBlockResource(engineReponses []response.EngineResponse, log logr.Logger) bool {
for _, er := range engineReponses { for _, er := range engineReponses {
if !er.IsSuccessful() && er.PolicyResponse.ValidationFailureAction == Enforce { 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 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 return false
} }

View file

@ -1,8 +1,10 @@
package webhooks package webhooks
import ( import (
"fmt"
"reflect" "reflect"
"sort" "sort"
"strings"
"time" "time"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" 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/context"
"github.com/nirmata/kyverno/pkg/engine/response" "github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils" "github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/webhooks/generate" "github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1" v1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
//HandleGenerate handles admission-requests for policies with generate rules //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 := 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") logger.V(4).Info("incoming request")
var engineResponses []response.EngineResponse var engineResponses []response.EngineResponse
if len(policies) == 0 { if len(policies) == 0 {
return true, "" return
} }
// convert RAW to unstructured // convert RAW to unstructured
resource, err := utils.ConvertToUnstructured(request.Object.Raw) resource, err := utils.ConvertToUnstructured(request.Object.Raw)
if err != nil { if err != nil {
//TODO: skip applying the admission control ? //TODO: skip applying the admission control ?
logger.Error(err, "failed to convert RAR resource to unstructured format") logger.Error(err, "failed to convert RAR resource to unstructured format")
return true, "" return
} }
// CREATE resources, do not have name, assigned in admission-request // 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 // Adds Generate Request to a channel(queue size 1000) to generators
if err := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil { if failedResponse := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil {
//TODO: send appropriate error // report failure event
return false, "Kyverno blocked: failed to create Generate Requests" 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 // 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 // HandleGeneration always returns success
// Filter Policies // 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 { for _, er := range engineResponses {
if err := gnGenerator.Apply(transform(userRequestInfo, er), action); err != nil { gr := transform(userRequestInfo, er)
return err 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 { 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 newAverageTimeInNanoSeconds := numerator / denominator
return time.Duration(newAverageTimeInNanoSeconds) * time.Nanosecond 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
}

View file

@ -72,7 +72,7 @@ func (ws *WebhookServer) HandleMutation(
if len(policyPatches) > 0 { if len(policyPatches) > 0 {
patches = append(patches, policyPatches...) patches = append(patches, policyPatches...)
rules := engineResponse.GetSuccessRules() 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 policyContext.NewResource = engineResponse.PatchedResource

View file

@ -10,8 +10,11 @@ import (
//HandlePolicyValidation performs the validation check on policy resource //HandlePolicyValidation performs the validation check on policy resource
func (ws *WebhookServer) policyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { 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 ? //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 { if err := policyvalidate.Validate(request.Object.Raw, ws.client, false, ws.openAPIController); err != nil {
logger.Error(err, "faield to validate policy")
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{
Allowed: false, Allowed: false,
Result: &metav1.Status{ Result: &metav1.Status{

View file

@ -7,11 +7,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"net/http" "net/http"
"time" "time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -22,6 +23,7 @@ import (
"github.com/nirmata/kyverno/pkg/config" "github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient" client "github.com/nirmata/kyverno/pkg/dclient"
context2 "github.com/nirmata/kyverno/pkg/engine/context" 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/event"
"github.com/nirmata/kyverno/pkg/openapi" "github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/policycache" "github.com/nirmata/kyverno/pkg/policycache"
@ -30,7 +32,6 @@ import (
tlsutils "github.com/nirmata/kyverno/pkg/tls" tlsutils "github.com/nirmata/kyverno/pkg/tls"
userinfo "github.com/nirmata/kyverno/pkg/userinfo" userinfo "github.com/nirmata/kyverno/pkg/userinfo"
"github.com/nirmata/kyverno/pkg/utils" "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/webhookconfig"
"github.com/nirmata/kyverno/pkg/webhooks/generate" "github.com/nirmata/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1" v1beta1 "k8s.io/api/admission/v1beta1"
@ -98,8 +99,11 @@ type WebhookServer struct {
grGenerator *generate.Generator grGenerator *generate.Generator
resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister
log logr.Logger
openAPIController *openapi.Controller auditHandler AuditHandler
log logr.Logger
openAPIController *openapi.Controller
supportMudateValidate bool supportMudateValidate bool
} }
@ -123,6 +127,7 @@ func NewWebhookServer(
pvGenerator policyviolation.GeneratorInterface, pvGenerator policyviolation.GeneratorInterface,
grGenerator *generate.Generator, grGenerator *generate.Generator,
resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister, resourceWebhookWatcher *webhookconfig.ResourceWebhookRegister,
auditHandler AuditHandler,
supportMudateValidate bool, supportMudateValidate bool,
cleanUp chan<- struct{}, cleanUp chan<- struct{},
log logr.Logger, log logr.Logger,
@ -161,6 +166,7 @@ func NewWebhookServer(
pvGenerator: pvGenerator, pvGenerator: pvGenerator,
grGenerator: grGenerator, grGenerator: grGenerator,
resourceWebhookWatcher: resourceWebhookWatcher, resourceWebhookWatcher: resourceWebhookWatcher,
auditHandler: auditHandler,
log: log, log: log,
openAPIController: openAPIController, openAPIController: openAPIController,
supportMudateValidate: supportMudateValidate, supportMudateValidate: supportMudateValidate,
@ -226,7 +232,7 @@ func (ws *WebhookServer) handlerFunc(handler func(request *v1beta1.AdmissionRequ
admissionReview.Response = handler(request) admissionReview.Response = handler(request)
writeResponse(rw, admissionReview) 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 return
} }
@ -258,7 +264,6 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
} }
} }
mutatePolicies := ws.pCache.Get(policycache.Mutate) mutatePolicies := ws.pCache.Get(policycache.Mutate)
validatePolicies := ws.pCache.Get(policycache.ValidateEnforce) validatePolicies := ws.pCache.Get(policycache.ValidateEnforce)
generatePolicies := ws.pCache.Get(policycache.Generate) generatePolicies := ws.pCache.Get(policycache.Generate)
@ -290,7 +295,7 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
userRequestInfo := v1.RequestInfo{ userRequestInfo := v1.RequestInfo{
Roles: roles, Roles: roles,
ClusterRoles: clusterRoles, ClusterRoles: clusterRoles,
AdmissionUserInfo: request.UserInfo} AdmissionUserInfo: *request.UserInfo.DeepCopy()}
// build context // build context
ctx := context2.NewContext() ctx := context2.NewContext()
@ -325,8 +330,11 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
logger.V(6).Info("", "patchedResource", string(patchedResource)) logger.V(6).Info("", "patchedResource", string(patchedResource))
if ws.resourceWebhookWatcher != nil && ws.resourceWebhookWatcher.RunValidationInMutatingWebhook == "true" { 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 // 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 { if !ok {
logger.Info("admission request denied") logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{
@ -344,25 +352,14 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
// GENERATE // GENERATE
// Only applied during resource creation and update // 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 // Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update { if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update {
go ws.HandleGenerate(request.DeepCopy(), generatePolicies, ctx, userRequestInfo)
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,
},
}
}
} }
// Succesfful processing of mutation & validation rules in policy // Succesful processing of mutation & validation rules in policy
patchType := v1beta1.PatchTypeJSONPatch patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{
Allowed: true, Allowed: true,
@ -377,8 +374,8 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { 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 := 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) logger.V(4).Info("DEBUG", "request", request)
checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister,ws.rLister, ws.crLister, request) checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister, ws.rLister, ws.crLister, request)
if err != nil { if err != nil {
logger.Error(err, "failed to get RBAC infromation for request") 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 var resource *unstructured.Unstructured
if request.Operation == v1beta1.Delete { if request.Operation == v1beta1.Delete {
resource, err = enginutils.ConvertToUnstructured(request.OldObject.Raw) resource, err = enginutils.ConvertToUnstructured(request.OldObject.Raw)
}else{ } else {
resource, err = enginutils.ConvertToUnstructured(request.Object.Raw) resource, err = enginutils.ConvertToUnstructured(request.Object.Raw)
} }
if err != nil { 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 err != nil {
if !apierrors.IsNotFound(err) { if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to get resource") logger.Error(err, "failed to get resource")
@ -420,7 +417,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
} }
labels := oldResource.GetLabels() labels := oldResource.GetLabels()
if labels != nil { 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{ return &v1beta1.AdmissionResponse{
Allowed: false, Allowed: false,
Result: &metav1.Status{ 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) policies := ws.pCache.Get(policycache.ValidateEnforce)
if len(policies) == 0 { if len(policies) == 0 {
logger.V(4).Info("No enforce Validation policy found, returning") logger.V(4).Info("No enforce Validation policy found, returning")
@ -463,8 +463,14 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
if containRBACinfo(policies) { if containRBACinfo(policies) {
roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request) roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request)
if err != nil { if err != nil {
// TODO(shuting): continue apply policy if error getting roleRef?
logger.Error(err, "failed to get RBAC information for request") 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") 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 { if !ok {
logger.Info("admission request denied") logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{

View file

@ -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) {
}

View file

@ -5,6 +5,9 @@ import (
"sort" "sort"
"time" "time"
"github.com/go-logr/logr"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/policystatus"
"github.com/nirmata/kyverno/pkg/utils" "github.com/nirmata/kyverno/pkg/utils"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
@ -21,12 +24,16 @@ import (
// HandleValidation handles validating webhook admission request // HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules // If there are no errors in validating rule we apply generation rules
// patchedResource is the (resource + patches) after applying mutation rules // patchedResource is the (resource + patches) after applying mutation rules
func (ws *WebhookServer) HandleValidation( func HandleValidation(
request *v1beta1.AdmissionRequest, request *v1beta1.AdmissionRequest,
policies []*kyverno.ClusterPolicy, policies []*kyverno.ClusterPolicy,
patchedResource []byte, patchedResource []byte,
ctx *context.Context, 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 { if len(policies) == 0 {
return true, "" return true, ""
@ -37,7 +44,7 @@ func (ws *WebhookServer) HandleValidation(
resourceName = request.Namespace + "/" + resourceName 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 // Get new and old resource
newR, oldR, err := utils.ExtractResources(patchedResource, request) newR, oldR, err := utils.ExtractResources(patchedResource, request)
@ -76,7 +83,7 @@ func (ws *WebhookServer) HandleValidation(
continue continue
} }
engineResponses = append(engineResponses, engineResponse) engineResponses = append(engineResponses, engineResponse)
ws.statusListener.Send(validateStats{ statusListener.Send(validateStats{
resp: engineResponse, resp: engineResponse,
}) })
if !engineResponse.IsSuccessful() { if !engineResponse.IsSuccessful() {
@ -101,7 +108,7 @@ func (ws *WebhookServer) HandleValidation(
// all policies were applied succesfully. // all policies were applied succesfully.
// create an event on the resource // create an event on the resource
events := generateEvents(engineResponses, blocked, (request.Operation == v1beta1.Update), logger) events := generateEvents(engineResponses, blocked, (request.Operation == v1beta1.Update), logger)
ws.eventGen.Add(events...) eventGen.Add(events...)
if blocked { if blocked {
logger.V(4).Info("resource blocked") logger.V(4).Info("resource blocked")
return false, getEnforceFailureErrorMsg(engineResponses) return false, getEnforceFailureErrorMsg(engineResponses)
@ -110,8 +117,8 @@ func (ws *WebhookServer) HandleValidation(
// ADD POLICY VIOLATIONS // ADD POLICY VIOLATIONS
// violations are created with resource on "audit" // violations are created with resource on "audit"
pvInfos := policyviolation.GeneratePVsFromEngineResponse(engineResponses, logger) pvInfos := policyviolation.GeneratePVsFromEngineResponse(engineResponses, logger)
ws.pvGenerator.Add(pvInfos...) pvGenerator.Add(pvInfos...)
// report time end
return true, "" return true, ""
} }