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

Merge branch 'main' into sign-and-generate-sbom

This commit is contained in:
Jim Bugwadia 2021-10-05 14:49:06 -07:00 committed by GitHub
commit 8437582622
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
151 changed files with 9528 additions and 7081 deletions

View file

@ -113,6 +113,16 @@ jobs:
run: |
make docker-build-kyverno
- name: Trivy Scan Image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'ghcr.io/kyverno/kyverno:latest'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
build-kyverno-cli:
runs-on: ubuntu-latest
needs: pre-checks

View file

@ -122,6 +122,16 @@ jobs:
run: |
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyverno:${KYVERNO_VERSION}
cosign attach sbom -sbom ./*-bom.cdx.json -type cyclonedx ghcr.io/kyverno/kyverno:latest
- name: Trivy Scan Image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'ghcr.io/kyverno/kyverno:latest'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
release-kyverno-cli:
runs-on: ubuntu-latest

View file

@ -2,20 +2,74 @@
We welcome all contributions, suggestions, and feedback, so please do not hesitate to reach out!
The easiest way to reach us is on the [Kubernetes slack #kyverno channel](https://app.slack.com/client/T09NY5SBT/CLGR9BJU9).
## Ways you can contribute
- Report potential bugs
- Request a feature
- Request a sample policy
- Join our community meetings
- Submit a PR for [open issues](https://github.com/kyverno/kyverno/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
- Fix or improve documentation
## Ways you can contribute:
- [Report Issues](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md#report-issues)
- [Submit Pull Requests](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md#submit-pull-requests)
- [Fix or Improve Documentation](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md#fix-or-improve-documentation)
- [Join Our Community Meetings](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md#join-our-community-meetings)
### Report issues
- Report potential bugs
- Request a feature
- Request a sample policy
### Submit Pull Requests
#### Setup local development environments
- Please refer to [Running in development mode](https://github.com/kyverno/kyverno/wiki/Running-in-development-mode) for local setup.
#### Submit a PR for [open issues](https://github.com/kyverno/kyverno/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
### Fix or Improve Documentation
- [Kyverno Docs](https://github.com/kyverno/website)
#### Get started
Head over to project repository on github and click the **"Fork"** button. With the forked copy, you can try new ideas and implement changes to the project.
- **Clone the repository to your device:**
Get the link of your forked repository, paste it in your device terminal and clone it using the command.
```
$ git clone https://hostname/YOUR-USERNAME/YOUR-REPOSITORY
```
- **Create a branch:**
Create a new brach and navigate to the branch using this command.
```
$ git checkout -b <new-branch>
```
Great, its time to start hacking, You can now go ahead to make all the changes you want.
- **Stage, Commit and Push changes:**
Now that we have implemented the required changes, use the command below to stage the changes and commit them
```
$ git add .
```
```
$ git commit -s -m "Commit message"
```
The -s signifies that you have signed off the the commit.
Go ahead and push your changes to github using this command.
```
$ git push
```
The [Kyverno Wiki](https://github.com/kyverno/kyverno/wiki) contains details on code design, building, and testing. Please review all sections.
Before you contribute, please review and agree to abide by our community [Code of Conduct](/CODE_OF_CONDUCT.md).
### Join Our Community Meetings
The easiest way to reach us is on the [Kubernetes slack #kyverno channel](https://app.slack.com/client/T09NY5SBT/CLGR9BJU9).
## Developer Certificate of Origin (DCO) Sign off
For contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project, we are requiring everyone to acknowledge this by signing their work.

View file

@ -148,21 +148,25 @@ create-e2e-infrastruture:
## variables
BIN_DIR := $(GOPATH)/bin
GO_ACC := $(BIN_DIR)/go-acc
GO_ACC := $(BIN_DIR)/go-acc@latest
CODE_COVERAGE_FILE:= coverage
CODE_COVERAGE_FILE_TXT := $(CODE_COVERAGE_FILE).txt
CODE_COVERAGE_FILE_HTML := $(CODE_COVERAGE_FILE).html
## targets
$(GO_ACC):
@echo " downloading testing tools"
go get -v github.com/ory/go-acc
@echo " installing testing tools"
go install -v github.com/ory/go-acc@latest
$(eval export PATH=$(GO_ACC):$(PATH))
# go test provides code coverage per packages only.
# go-acc merges the result for pks so that it be used by
# go tool cover for reporting
test: test-unit test-e2e test-cmd
test: test-clean test-unit test-e2e test-cmd
test-clean:
@echo " cleaning test cache"
go clean -testcache ./...
# go get downloads and installs the binary
@ -198,6 +202,7 @@ test-e2e-local:
#Test TestCmd Policy
test-cmd: cli
$(PWD)/$(CLI_PATH)/kyverno test https://github.com/kyverno/policies/main
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-mutate
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-policy && exit 1 || exit 0
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-rule && exit 1 || exit 0

View file

@ -92,6 +92,9 @@ The following table lists the configurable parameters of the kyverno chart and t
| `podAnnotations` | annotations to add to each pod | `{}` |
| `podLabels` | additional labels to add to each pod | `{}` |
| `podSecurityContext` | security context for the pod | `{}` |
| `podDisruptionBudget.enabled` | Adds a PodDisruptionBudget for the kyverno deployment | `false` |
| `podDisruptionBudget.minAvailable` | Configures the minimum available pods for kyverno disruptions. Cannot used if `maxUnavailable` is set. | `0` |
| `podDisruptionBudget.maxUnavailable` | Configures the maximum unavailable pods for kyverno disruptions. Cannot used if `minAvailable` is set. | `nil` |
| `priorityClassName` | priorityClassName | `nil` |
| `rbac.create` | create ClusterRoles, ClusterRoleBindings, and ServiceAccount | `true` |
| `rbac.serviceAccount.create` | create a ServiceAccount | `true` |

View file

@ -91,3 +91,16 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{ default "default" .Values.rbac.serviceAccount.name }}
{{- end -}}
{{- end -}}
{{/* Create the default PodDisruptionBudget to use */}}
{{- define "podDisruptionBudget.spec" -}}
{{- if and .Values.podDisruptionBudget.minAvailable .Values.podDisruptionBudget.maxUnavailable }}
{{- fail "Cannot set both .Values.podDisruptionBudget.minAvailable and .Values.podDisruptionBudget.maxUnavailable" -}}
{{- end }}
{{- if not .Values.podDisruptionBudget.maxUnavailable }}
minAvailable: {{ default 0 .Values.podDisruptionBudget.minAvailable }}
{{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
{{- end }}
{{- end }}

File diff suppressed because it is too large Load diff

View file

@ -33,14 +33,16 @@ spec:
{{- if .Values.antiAffinity.enable }}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- kyverno
topologyKey: {{ .Values.antiAffinity.topologyKey }}
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- kyverno
topologyKey: {{ .Values.antiAffinity.topologyKey }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{ tpl (toYaml .) $ | nindent 8 }}

View file

@ -15,20 +15,20 @@ spec:
ingress:
- from:
{{- with .Values.networkPolicy }}
namespaceSelector:
matchExpressions:
{{- toYaml .namespaceExpressions | nindent 8 }}
matchLabels:
{{- range $key, $value := .namespaceLabels }}
{{ $key | quote }}: {{ $value | quote }}
{{- end }}
podSelector:
matchExpressions:
{{- toYaml .podExpressions | nindent 8 }}
matchLabels:
{{- range $key, $value := .podLabels }}
{{ $key | quote }}: {{ $value | quote }}
{{- end }}
- namespaceSelector:
matchExpressions:
{{- toYaml .namespaceExpressions | nindent 10 }}
matchLabels:
{{- range $key, $value := .namespaceLabels }}
{{ $key | quote }}: {{ $value | quote }}
{{- end }}
podSelector:
matchExpressions:
{{- toYaml .podExpressions | nindent 10 }}
matchLabels:
{{- range $key, $value := .podLabels }}
{{ $key | quote }}: {{ $value | quote }}
{{- end }}
{{- end }}
ports:
- protocol: TCP

View file

@ -0,0 +1,14 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: {{ template "kyverno.fullname" . }}
labels: {{ include "kyverno.labels" . | nindent 4 }}
app: kyverno
namespace: {{ template "kyverno.namespace" . }}
spec:
{{- include "podDisruptionBudget.spec" . | indent 2 }}
selector:
matchLabels: {{ include "kyverno.matchLabels" . | nindent 6 }}
app: kyverno
{{- end }}

View file

@ -32,7 +32,7 @@ testImage:
repository:
# testImage.tag defaults to "latest" if omitted
tag:
# testImage.pullPolicy defaults to image.pullPolicy if ommitted
# testImage.pullPolicy defaults to image.pullPolicy if omitted
pullPolicy:
replicaCount: 1
@ -52,6 +52,14 @@ antiAffinity:
# Changing this to a region would allow you to spread pods across regions
topologyKey: "kubernetes.io/hostname"
podDisruptionBudget:
enabled: false
# minAvailable: 1
# maxUnavailable: 1
# minAvailable and maxUnavailable can either be set to an integer (e.g. 1)
# or a percentage value (e.g. 25%)
nodeSelector: {}
tolerations: []

View file

@ -10,8 +10,6 @@ import (
"strings"
"time"
"github.com/kyverno/kyverno/pkg/cosign"
"github.com/prometheus/client_golang/prometheus/promhttp"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/klog/v2"
@ -23,6 +21,7 @@ import (
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/cosign"
dclient "github.com/kyverno/kyverno/pkg/dclient"
event "github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/generate"
@ -59,6 +58,7 @@ var (
genWorkers int
profile bool
disableMetricsExport bool
autoUpdateWebhooks bool
policyControllerResyncPeriod time.Duration
imagePullSecrets string
imageSignatureRepository string
@ -71,7 +71,8 @@ func main() {
flag.StringVar(&filterK8sResources, "filterK8sResources", "", "Resource in format [kind,namespace,name] where policy is not evaluated by the admission webhook. For example, --filterK8sResources \"[Deployment, kyverno, kyverno],[Events, *, *]\"")
flag.StringVar(&excludeGroupRole, "excludeGroupRole", "", "")
flag.StringVar(&excludeUsername, "excludeUsername", "", "")
flag.IntVar(&webhookTimeout, "webhooktimeout", 3, "Timeout for webhook configurations")
// deprecated
flag.IntVar(&webhookTimeout, "webhooktimeout", int(webhookconfig.DefaultWebhookTimeout), "Timeout for webhook configurations. Deprecated and will be removed in 1.6.0.")
flag.IntVar(&genWorkers, "gen-workers", 10, "Workers for generate controller")
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&serverIP, "serverIP", "", "IP address where Kyverno controller runs. Only required if out-of-cluster.")
@ -82,6 +83,7 @@ func main() {
flag.DurationVar(&policyControllerResyncPeriod, "background-scan", time.Hour, "Perform background scan every given interval, e.g., 30s, 15m, 1h.")
flag.StringVar(&imagePullSecrets, "imagePullSecrets", "", "Secret resource names for image registry access credentials.")
flag.StringVar(&imageSignatureRepository, "imageSignatureRepository", "", "Alternate repository for image signatures. Can be overridden per rule via `verifyImages.Repository`.")
flag.BoolVar(&autoUpdateWebhooks, "auto-update-webhooks", true, "Set this flag to 'false' to disable auto-configuration of the webhook.")
if err := flag.Set("v", "2"); err != nil {
setupLog.Error(err, "failed to set log level")
@ -218,10 +220,15 @@ func main() {
webhookCfg := webhookconfig.NewRegister(
clientConfig,
client,
pclient,
rCache,
pInformer.Kyverno().V1().ClusterPolicies(),
pInformer.Kyverno().V1().Policies(),
serverIP,
int32(webhookTimeout),
debug,
autoUpdateWebhooks,
stopCh,
log.Log)
webhookMonitor, err := webhookconfig.NewMonitor(kubeClient, log.Log.WithName("WebhookMonitor"))
@ -381,7 +388,9 @@ func main() {
os.Exit(1)
}
go webhookCfg.UpdateWebhookConfigurations(configData)
if !autoUpdateWebhooks {
go webhookCfg.UpdateWebhookConfigurations(configData)
}
if registrationErr := registerWrapperRetry(); registrationErr != nil {
setupLog.Error(err, "Timeout registering admission control webhooks")
os.Exit(1)

View file

@ -25,6 +25,10 @@ spec:
- jsonPath: .spec.validationFailureAction
name: Action
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
type: string
@ -56,6 +60,15 @@ spec:
that are only available in the admission review request (e.g. user
name).
type: boolean
failurePolicy:
description: FailurePolicy defines how unrecognized errors from the
admission endpoint are handled. Rules within the same policy share
the same failure behavior. Allowed values are Ignore or Fail. Defaults
to Fail.
enum:
- Ignore
- Fail
type: string
rules:
description: Rules is a list of Rule instances. A Policy contains
multiple rules and each rule can validate, mutate, or generate resources.
@ -558,7 +571,9 @@ spec:
type: array
resources:
description: ResourceDescription contains information about
the resource being created or modified.
the resource being created or modified. Specifying ResourceDescription
directly under exclude is being deprecated. Please specify
under "any" or "all" instead.
properties:
annotations:
additionalProperties:
@ -1232,7 +1247,9 @@ spec:
resources:
description: ResourceDescription contains information about
the resource being created or modified. Requires at least
one tag to be specified when under MatchResources.
one tag to be specified when under MatchResources. Specifying
ResourceDescription directly under match is being deprecated.
Please specify under "any" or "all" instead.
properties:
annotations:
additionalProperties:
@ -1490,6 +1507,160 @@ spec:
in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
foreach:
description: ForEach applies policy rule checks to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
deny:
description: Deny defines conditions used to pass or
fail a validation rule.
properties:
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct list
of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
message:
description: Message specifies a custom message to be displayed
on failure.
@ -1538,6 +1709,14 @@ spec:
or allow (audit) the admission review request and report an error
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the maximum time in seconds
allowed to apply this policy. After the configured time expires,
the admission request may fail, or may simply ignore the policy
results, based on the failure policy. The default timeout is 10s,
the value must be between 1 and 30 seconds.
format: int32
type: integer
type: object
status:
description: Status contains policy runtime data.

View file

@ -60,6 +60,15 @@ spec:
context:
description: Context ...
properties:
admissionRequestInfo:
properties:
admissionRequest:
type: string
operation:
description: Operation is the type of resource operation being
checked for admission control
type: string
type: object
userInfo:
description: RequestInfo contains permission info carried in an
admission request.

View file

@ -25,6 +25,10 @@ spec:
- jsonPath: .spec.validationFailureAction
name: Action
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
type: string
@ -57,6 +61,15 @@ spec:
that are only available in the admission review request (e.g. user
name).
type: boolean
failurePolicy:
description: FailurePolicy defines how unrecognized errors from the
admission endpoint are handled. Rules within the same policy share
the same failure behavior. Allowed values are Ignore or Fail. Defaults
to Fail.
enum:
- Ignore
- Fail
type: string
rules:
description: Rules is a list of Rule instances. A Policy contains
multiple rules and each rule can validate, mutate, or generate resources.
@ -559,7 +572,9 @@ spec:
type: array
resources:
description: ResourceDescription contains information about
the resource being created or modified.
the resource being created or modified. Specifying ResourceDescription
directly under exclude is being deprecated. Please specify
under "any" or "all" instead.
properties:
annotations:
additionalProperties:
@ -1233,7 +1248,9 @@ spec:
resources:
description: ResourceDescription contains information about
the resource being created or modified. Requires at least
one tag to be specified when under MatchResources.
one tag to be specified when under MatchResources. Specifying
ResourceDescription directly under match is being deprecated.
Please specify under "any" or "all" instead.
properties:
annotations:
additionalProperties:
@ -1491,6 +1508,160 @@ spec:
in the next major release. See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
foreach:
description: ForEach applies policy rule checks to nested
elements.
properties:
anyPattern:
description: AnyPattern specifies list of validation
patterns. At least one of the patterns must be satisfied
for the validation rule to succeed.
x-kubernetes-preserve-unknown-fields: true
context:
description: Context defines variables and data sources
that can be used during rule execution.
items:
description: ContextEntry adds variables and data
sources to a rule Context. Either a ConfigMap reference
or a APILookup must be provided.
properties:
apiCall:
description: APICall defines an HTTP request to
the Kubernetes API server. The JSON data retrieved
is stored in the context.
properties:
jmesPath:
description: JMESPath is an optional JSON
Match Expression that can be used to transform
the JSON response returned from the API
server. For example a JMESPath of "items
| length(@)" applied to the API server response
to the URLPath "/apis/apps/v1/deployments"
will return the total count of deployments
across all namespaces.
type: string
urlPath:
description: URLPath is the URL path to be
used in the HTTP GET request to the Kubernetes
API server (e.g. "/api/v1/namespaces" or "/apis/apps/v1/deployments").
The format required is the same format used
by the `kubectl get --raw` command.
type: string
required:
- urlPath
type: object
configMap:
description: ConfigMap is the ConfigMap reference.
properties:
name:
description: Name is the ConfigMap name.
type: string
namespace:
description: Namespace is the ConfigMap namespace.
type: string
required:
- name
type: object
name:
description: Name is the variable name.
type: string
type: object
type: array
deny:
description: Deny defines conditions used to pass or
fail a validation rule.
properties:
conditions:
description: 'Multiple conditions can be declared
under an `any` or `all` statement. A direct list
of conditions (without `any` or `all` statements)
is also supported for backwards compatibility
but will be deprecated in the next major release.
See: https://kyverno.io/docs/writing-policies/validate/#deny-rules'
x-kubernetes-preserve-unknown-fields: true
type: object
list:
description: List specifies a JMESPath expression that
results in one or more elements to which the validation
logic is applied.
type: string
pattern:
description: Pattern specifies an overlay-style pattern
used to check resources.
x-kubernetes-preserve-unknown-fields: true
preconditions:
description: 'Preconditions are used to determine if
a policy rule should be applied by evaluating a set
of conditions. The declaration can contain nested
`any` or `all` statements. See: https://kyverno.io/docs/writing-policies/preconditions/'
properties:
all:
description: AllConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, all of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
any:
description: AnyConditions enable variable-based
conditional rule execution. This is useful for
finer control of when an rule is applied. A condition
can reference object data using JMESPath notation.
Here, at least one of the conditions need to pass
items:
description: Condition defines variable-based
conditional criteria for rule execution.
properties:
key:
description: Key is the context entry (using
JMESPath) for conditional rule evaluation.
x-kubernetes-preserve-unknown-fields: true
operator:
description: Operator is the operation to
perform. Valid operators are Equals, NotEquals,
In and NotIn.
enum:
- Equals
- NotEquals
- In
- NotIn
type: string
value:
description: Value is the conditional value,
or set of values. The values can be fixed
set or can be variables declared using using
JMESPath.
x-kubernetes-preserve-unknown-fields: true
type: object
type: array
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
message:
description: Message specifies a custom message to be displayed
on failure.
@ -1539,6 +1710,14 @@ spec:
or allow (audit) the admission review request and report an error
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the maximum time in seconds
allowed to apply this policy. After the configured time expires,
the admission request may fail, or may simply ignore the policy
results, based on the failure policy. The default timeout is 10s,
the value must be between 1 and 30 seconds.
format: int32
type: integer
type: object
status:
description: Status contains policy runtime information. Deprecated. Policy

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,3 +8,4 @@ resources:
- ./metricsconfigmap.yaml
- ./service.yaml
- ./serviceaccount.yaml
- ./poddisruptionbudget.yaml

View file

@ -0,0 +1,14 @@
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: kyverno
labels:
app: kyverno
namespace: kyverno
spec:
minAvailable: 0
selector:
matchLabels:
app: kyverno
# do not remove
app.kubernetes.io/name: kyverno

View file

@ -23,14 +23,16 @@ spec:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- kyverno
topologyKey: "kubernetes.io/hostname"
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- kyverno
topologyKey: "kubernetes.io/hostname"
serviceAccountName: kyverno-service-account
securityContext:
runAsNonRoot: true

3
go.mod
View file

@ -23,8 +23,7 @@ require (
github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23
github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a
github.com/minio/minio v0.0.0-20200114012931-30922148fbb5
github.com/minio/pkg v1.0.7
github.com/minio/pkg v1.1.3
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.15.0
github.com/onsi/gomega v1.11.0

109
go.sum
View file

@ -66,7 +66,6 @@ contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0Wk
contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
@ -89,7 +88,6 @@ github.com/Azure/go-amqp v0.13.4/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE
github.com/Azure/go-amqp v0.13.7/go.mod h1:wbpCKA8tR5MLgRyIu+bb+S6ECdIDdYJ0NlpFE9xsBPI=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
@ -140,7 +138,6 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
github.com/GoogleCloudPlatform/cloudsql-proxy v1.22.0/go.mod h1:mAm5O/zik2RFmcpigNjg6nMotDL8ZXJaxKzgGVcSMFA=
@ -189,7 +186,6 @@ github.com/ReneKroon/ttlcache/v2 v2.7.0 h1:sZeaSwA2UN/y/h7CvkW15Kovd2Oiy76CBDORi
github.com/ReneKroon/ttlcache/v2 v2.7.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
@ -200,14 +196,12 @@ github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABc
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/participle v0.2.1/go.mod h1:SW6HZGeZgSIpcUWX3fXpfZhuaWHnmoD5KCVaqSaNTkk=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@ -225,7 +219,6 @@ github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30/go.mod h1:4AJxU
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -246,7 +239,6 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@ -262,10 +254,7 @@ github.com/aws/aws-sdk-go v1.40.7 h1:dD5+UZxedqHeE4WakJHEhTsEARYlq8kHkYEf89R1tEo
github.com/aws/aws-sdk-go v1.40.7/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@ -284,7 +273,6 @@ github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
@ -312,7 +300,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/cheggaaa/pb v1.0.28/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@ -423,12 +410,10 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD
github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -488,7 +473,6 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/distribution v2.7.1+incompatible h1:aGFx4EvJWKEh//lHPLwFhFgwFHKH06TzNVPamrMn04M=
github.com/distribution/distribution v2.7.1+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc=
github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is=
@ -518,12 +502,9 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@ -560,7 +541,6 @@ github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -606,7 +586,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8=
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -812,7 +791,6 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -837,7 +815,6 @@ github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
@ -928,22 +905,18 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c
github.com/googleapis/gnostic v0.5.4 h1:ynbQIWjLw7iv6HAFdixb30U7Uvcmx+f4KlLJpmhkTK0=
github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc=
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ=
github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -976,8 +949,6 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
@ -986,14 +957,12 @@ github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY=
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
@ -1020,20 +989,15 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/raft v1.1.1-0.20190703171940-f639636d18e0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8=
github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE=
github.com/hashicorp/vault/api v1.1.0/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
github.com/hashicorp/vault/api v1.1.1/go.mod h1:29UXcn/1cLOPHQNMWA7bCz2By4PSd0VKPAydKXS5yN0=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
github.com/hashicorp/vault/sdk v0.2.0/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY=
github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
@ -1051,7 +1015,6 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/in-toto/in-toto-golang v0.2.1-0.20210627200632-886210ae2ab9 h1:j7klXz5kh0ydPmHkBtJ/Al27G1/au4sH7OkGhkgRJWg=
github.com/in-toto/in-toto-golang v0.2.1-0.20210627200632-886210ae2ab9/go.mod h1:Skbg04kmfB7IAnEIsspKPg/ny1eiFt/TgPr9SDCHusA=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@ -1059,7 +1022,6 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jedisct1/go-minisign v0.0.0-20210703085342-c1f07ee84431 h1:zqyV5j9xEuPQw2ma4RzzS9O74UwTq3vcMmpoHyL6xlI=
github.com/jedisct1/go-minisign v0.0.0-20210703085342-c1f07ee84431/go.mod h1:3VIJLjlf5Iako82IX/5KOoCzDmogK5mO+bl+DRItnR8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@ -1102,10 +1064,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.4/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@ -1113,13 +1072,9 @@ github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.0 h1:2T7tUoQrQT+fQWdaY5rjWztFGAFwbGD04iPJg90ZiOs=
github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/cpuid v1.2.2/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/readahead v1.3.1/go.mod h1:AH9juHzNH7xqdqFHrMRSHeH2Ps+vFf+kblDqzPFiLJg=
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -1135,7 +1090,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyverno/go-jmespath v0.4.1-0.20210511164400-a1d46efa2ed6 h1:3toVuFm87/kV8FSub2JSnjSuLz3l521ON4sOpvuTNbk=
github.com/kyverno/go-jmespath v0.4.1-0.20210511164400-a1d46efa2ed6/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@ -1173,7 +1127,6 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -1198,12 +1151,10 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190805055040-f9202b1cfdeb/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
@ -1226,28 +1177,17 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J
github.com/mediocregopher/radix/v4 v4.0.0-beta.1/go.mod h1:Z74pilm773ghbGV4EEoPvi6XWgkAfr0VCNkfa8gI1PU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/minio/argon2 v1.0.0/go.mod h1:XtOGJ7MjwUJDPtCqqrisx5QwVB/jDx+adQHigJVsQHQ=
github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
github.com/minio/gokrb5/v7 v7.2.5/go.mod h1:z6fE6twrvMN004M+KRTHnmtfpxsBIztP0PVsak0/4f8=
github.com/minio/hdfs/v3 v3.0.1/go.mod h1:6ALh9HsAwG9xAXdpdrZJcSY0vR6z3K+9XIz6Y9pQG/c=
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pArKA=
github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
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/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
github.com/minio/parquet-go v0.0.0-20191231003236-20b3c07bcd2c/go.mod h1:sl82d+TnCE7qeaNJazHdNoG9Gpyl9SZYfleDAQWrsls=
github.com/minio/pkg v1.0.7 h1:+vUH/qWfjVpysbVJeebkhCh8QqQ8H6uYdmqLfb34X2E=
github.com/minio/pkg v1.0.7/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
github.com/minio/pkg v1.1.3 h1:J4vGnlNSxc/o9gDOQMZ3k0L3koA7ZgBQ7GRMrUpt/OY=
github.com/minio/pkg v1.1.3/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/minio/sio v0.2.0/go.mod h1:nKM5GIWSrqbOZp0uhyj6M1iA0X6xQzSGtYSaTKSCut0=
github.com/minio/sio v0.2.1/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -1298,24 +1238,16 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ=
github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
github.com/nats-io/go-nats-streaming v0.4.4/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server v1.4.1/go.mod h1:c8f/fHd2B6Hgms3LtCaI7y6pC4WD1f4SUxcCud5vhBc=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats-streaming-server v0.14.2/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nats-io/stan.go v0.4.5/go.mod h1:Ji7mK6gRZJSH1nc3ZJH6vi7zn/QnZhpR9Arm4iuzsUQ=
github.com/ncw/directio v1.0.5/go.mod h1:rX/pKEYkOXBGOggmcyJeJGloCkleSvphPx2eV3t6ROk=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -1398,7 +1330,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -1416,7 +1347,6 @@ github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaL
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -1488,7 +1418,6 @@ github.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr
github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
github.com/qur/ar v0.0.0-20130629153254-282534b91770/go.mod h1:SjlYv2m9lpV0UW6K7lDqVJwEIIvSjaHbGk7nIfY8Hxw=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
@ -1500,7 +1429,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -1521,7 +1449,6 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/secure-io/sio-go v0.3.0/go.mod h1:D3KmXgKETffyYxBdFRN+Hpd2WzhzqS0EQwT3XWsAcBU=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
@ -1531,7 +1458,6 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shibumi/go-pathspec v1.2.0 h1:KVKEDHYk7bQolRMs7nfzjT3SBOCgcXFJzccnj9bsGbA=
github.com/shibumi/go-pathspec v1.2.0/go.mod h1:bDxCftD0fST3qXIlHoQ/fChsU4mWMVklXp1yPErQaaY=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v3 v3.21.4/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sigstore/cosign v1.0.0 h1:jvsRP8ZfEc5jAnj2cGQo5S02VQ7h7rXwpiXYJF4n0+0=
@ -1556,9 +1482,7 @@ github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fy
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/skyrings/skyring-common v0.0.0-20160929130248-d1c0bb1cbd5e/go.mod h1:d8hQseuYt4rJoOo21lFzYJdhMjmDqLY++ayArbgYjWI=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
@ -1638,12 +1562,9 @@ github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpu
github.com/theupdateframework/go-tuf v0.0.0-20210630170422-22a94818d17b/go.mod h1:L+uU/NRFK/7h0NYAnsmvsX9EghDB5QVCcHCIrK2h5nw=
github.com/theupdateframework/go-tuf v0.0.0-20210722233521-90e262754396 h1:j4odVZMwglHp54CYsNHd0wls+lkQzxloQU9AQjQu0W4=
github.com/theupdateframework/go-tuf v0.0.0-20210722233521-90e262754396/go.mod h1:L+uU/NRFK/7h0NYAnsmvsX9EghDB5QVCcHCIrK2h5nw=
github.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
github.com/tilinna/clock v1.1.0/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@ -1662,10 +1583,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@ -1676,7 +1595,6 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vdemeester/k8s-pkg-credentialprovider v1.19.7 h1:MJ5fV2Z0OyIuPvFVs0vi6VjTjxpdK1QT8oX/aWiUjYM=
github.com/vdemeester/k8s-pkg-credentialprovider v1.19.7/go.mod h1:K2nMO14cgZitdwBqdQps9tInJgcaXcU/7q5F59lpbNI=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
@ -1698,7 +1616,6 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
@ -1824,14 +1741,12 @@ golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -2027,7 +1942,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -2042,7 +1956,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -2058,7 +1971,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -2153,7 +2065,6 @@ golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fq
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -2311,7 +2222,6 @@ google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@ -2323,12 +2233,10 @@ google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190513181449-d00d292a067c/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
@ -2446,7 +2354,6 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -2468,20 +2375,11 @@ gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWd
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ldap.v3 v3.0.3/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/olivere/elastic.v5 v5.0.80/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0FBrx5D6pV0=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@ -2492,7 +2390,6 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzW
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

View file

@ -13,6 +13,7 @@ import (
// +kubebuilder:resource:path=clusterpolicies,scope="Cluster",shortName=cpol
// +kubebuilder:printcolumn:name="Background",type="string",JSONPath=".spec.background"
// +kubebuilder:printcolumn:name="Action",type="string",JSONPath=".spec.validationFailureAction"
// +kubebuilder:printcolumn:name="Failure Policy",type="string",JSONPath=".spec.failurePolicy",priority=1
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.ready`
type ClusterPolicy struct {
metav1.TypeMeta `json:",inline,omitempty" yaml:",inline,omitempty"`

View file

@ -1,6 +1,7 @@
package v1
import (
"k8s.io/api/admission/v1beta1"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -45,6 +46,15 @@ type GenerateRequestSpec struct {
type GenerateRequestContext struct {
// +optional
UserRequestInfo RequestInfo `json:"userInfo,omitempty" yaml:"userInfo,omitempty"`
// +optional
AdmissionRequestInfo AdmissionRequestInfoObject `json:"admissionRequestInfo,omitempty" yaml:"admissionRequestInfo,omitempty"`
}
type AdmissionRequestInfoObject struct {
// +optional
AdmissionRequest string `json:"admissionRequest,omitempty" yaml:"admissionRequest,omitempty"`
// +optional
Operation v1beta1.Operation `json:"operation,omitempty" yaml:"operation,omitempty"`
}
// RequestInfo contains permission info carried in an admission request.

View file

@ -22,6 +22,7 @@ type PolicyList struct {
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Background",type="string",JSONPath=".spec.background"
// +kubebuilder:printcolumn:name="Action",type="string",JSONPath=".spec.validationFailureAction"
// +kubebuilder:printcolumn:name="Failure Policy",type="string",JSONPath=".spec.failurePolicy",priority=1
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.ready`
// +kubebuilder:resource:shortName=pol
type Policy struct {
@ -44,6 +45,12 @@ type Spec struct {
// each rule can validate, mutate, or generate resources.
Rules []Rule `json:"rules,omitempty" yaml:"rules,omitempty"`
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled.
// Rules within the same policy share the same failure behavior.
// Allowed values are Ignore or Fail. Defaults to Fail.
// +optional
FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" yaml:"failurePolicy,omitempty"`
// ValidationFailureAction controls if a validation policy rule failure should disallow
// the admission review request (enforce), or allow (audit) the admission review request
// and report an error in a policy report. Optional. The default value is "audit".
@ -60,6 +67,11 @@ type Spec struct {
// Optional. The default value is set to "true", it must be set to "false" to disable the validation checks.
// +optional
SchemaValidation *bool `json:"schemaValidation,omitempty" yaml:"schemaValidation,omitempty"`
// WebhookTimeoutSeconds specifies the maximum time in seconds allowed to apply this policy.
// After the configured time expires, the admission request may fail, or may simply ignore the policy results,
// based on the failure policy. The default timeout is 10s, the value must be between 1 and 30 seconds.
WebhookTimeoutSeconds *int32 `json:"webhookTimeoutSeconds,omitempty" yaml:"webhookTimeoutSeconds,omitempty"`
}
// Rule defines a validation, mutation, or generation control for matching resources.
@ -113,6 +125,17 @@ type Rule struct {
VerifyImages []*ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"`
}
// FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled.
// +kubebuilder:validation:Enum=Ignore;Fail
type FailurePolicyType string
const (
// Ignore means that an error calling the webhook is ignored.
Ignore FailurePolicyType = "Ignore"
// Fail means that an error calling the webhook causes the admission to fail.
Fail FailurePolicyType = "Fail"
)
// AnyAllCondition consists of conditions wrapped denoting a logical criteria to be fulfilled.
// AnyConditions get fulfilled when at least one of its sub-conditions passes.
// AllConditions get fulfilled only when all of its sub-conditions pass.
@ -243,11 +266,15 @@ type MatchResources struct {
All ResourceFilters `json:"all,omitempty" yaml:"all,omitempty"`
// UserInfo contains information about the user performing the operation.
// Specifying UserInfo directly under match is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
UserInfo `json:",omitempty" yaml:",omitempty"`
// ResourceDescription contains information about the resource being created or modified.
// Requires at least one tag to be specified when under MatchResources.
// Specifying ResourceDescription directly under match is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
ResourceDescription `json:"resources,omitempty" yaml:"resources,omitempty"`
}
@ -264,10 +291,14 @@ type ExcludeResources struct {
All ResourceFilters `json:"all,omitempty" yaml:"all,omitempty"`
// UserInfo contains information about the user performing the operation.
// Specifying UserInfo directly under exclude is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
UserInfo `json:",omitempty" yaml:",omitempty"`
// ResourceDescription contains information about the resource being created or modified.
// Specifying ResourceDescription directly under exclude is being deprecated.
// Please specify under "any" or "all" instead.
// +optional
ResourceDescription `json:"resources,omitempty" yaml:"resources,omitempty"`
}
@ -399,6 +430,8 @@ type Validation struct {
// +optional
Message string `json:"message,omitempty" yaml:"message,omitempty"`
ForEachValidation *ForEachValidation `json:"foreach,omitempty" yaml:"foreach,omitempty"`
// Pattern specifies an overlay-style pattern used to check resources.
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
@ -425,6 +458,40 @@ type Deny struct {
AnyAllConditions apiextensions.JSON `json:"conditions,omitempty" yaml:"conditions,omitempty"`
}
// ForEach applies policy rule checks to nested elements.
type ForEachValidation struct {
// List specifies a JMESPath expression that results in one or more elements
// to which the validation logic is applied.
List string `json:"list,omitempty" yaml:"list,omitempty"`
// Context defines variables and data sources that can be used during rule execution.
// +optional
Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
// Preconditions are used to determine if a policy rule should be applied by evaluating a
// set of conditions. The declaration can contain nested `any` or `all` statements.
// See: https://kyverno.io/docs/writing-policies/preconditions/
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyAllConditions *AnyAllConditions `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
// Pattern specifies an overlay-style pattern used to check resources.
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
Pattern apiextensions.JSON `json:"pattern,omitempty" yaml:"pattern,omitempty"`
// AnyPattern specifies list of validation patterns. At least one of the patterns
// must be satisfied for the validation rule to succeed.
// +kubebuilder:validation:XPreserveUnknownFields
// +optional
AnyPattern apiextensions.JSON `json:"anyPattern,omitempty" yaml:"anyPattern,omitempty"`
// Deny defines conditions used to pass or fail a validation rule.
// +optional
Deny *Deny `json:"deny,omitempty" yaml:"deny,omitempty"`
}
// ImageVerification validates that images that match the specified pattern
// are signed with the supplied public key. Once the image is verified it is
// mutated to include the SHA digest retrieved during the registration.

View file

@ -38,6 +38,28 @@ func (p *ClusterPolicy) HasMutate() bool {
return false
}
// HasValidate checks for validate rule types
func (p *ClusterPolicy) HasValidate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasValidate() {
return true
}
}
return false
}
// HasGenerate checks for generate rule types
func (p *ClusterPolicy) HasGenerate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasGenerate() {
return true
}
}
return false
}
//HasVerifyImages checks for image verification rule types
func (p *ClusterPolicy) HasVerifyImages() bool {
for _, rule := range p.Spec.Rules {
@ -78,6 +100,29 @@ func (r Rule) HasGenerate() bool {
return !reflect.DeepEqual(r.Generation, Generation{})
}
func (r Rule) MatchKinds() []string {
matchKinds := r.MatchResources.ResourceDescription.Kinds
for _, value := range r.MatchResources.All {
matchKinds = append(matchKinds, value.ResourceDescription.Kinds...)
}
for _, value := range r.MatchResources.Any {
matchKinds = append(matchKinds, value.ResourceDescription.Kinds...)
}
return matchKinds
}
func (r Rule) ExcludeKinds() []string {
excludeKinds := r.ExcludeResources.ResourceDescription.Kinds
for _, value := range r.ExcludeResources.All {
excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...)
}
for _, value := range r.ExcludeResources.Any {
excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...)
}
return excludeKinds
}
// DeserializeAnyPattern deserialize apiextensions.JSON to []interface{}
func (in *Validation) DeserializeAnyPattern() ([]interface{}, error) {
if in.AnyPattern == nil {
@ -199,6 +244,6 @@ type ViolatedRule struct {
// +optional
Message string `json:"message" yaml:"message"`
// +optional
Check string `json:"check" yaml:"check"`
// Status shows the rule response status
Status string `json:"status" yaml:"status"`
}

View file

@ -657,6 +657,11 @@ func (in *Spec) DeepCopyInto(out *Spec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.FailurePolicy != nil {
in, out := &in.FailurePolicy, &out.FailurePolicy
*out = new(FailurePolicyType)
**out = **in
}
if in.Background != nil {
in, out := &in.Background, &out.Background
*out = new(bool)
@ -667,6 +672,11 @@ func (in *Spec) DeepCopyInto(out *Spec) {
*out = new(bool)
**out = **in
}
if in.WebhookTimeoutSeconds != nil {
in, out := &in.WebhookTimeoutSeconds, &out.WebhookTimeoutSeconds
*out = new(int32)
**out = **in
}
return
}

View file

@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View file

@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View file

@ -144,8 +144,6 @@ func NewConfigData(rclient kubernetes.Interface, cmInformer informers.ConfigMapI
cd.restrictDevelopmentUsername = []string{"minikube-user", "kubernetes-admin"}
//TODO: this has been added to backward support command line arguments
// will be removed in future and the configuration will be set only via configmaps
if filterK8sResources != "" {
cd.log.Info("init configuration from commandline arguments for filterK8sResources")
cd.initFilters(filterK8sResources)
@ -320,8 +318,6 @@ func (cd *ConfigData) load(cm v1.ConfigMap) (reconcilePolicyReport, updateWebhoo
return
}
//TODO: this has been added to backward support command line arguments
// will be removed in future and the configuration will be set only via configmaps
func (cd *ConfigData) initFilters(filters string) {
logger := cd.log
// parse and load the configuration
@ -380,7 +376,6 @@ func parseKinds(list string) []k8Resource {
element = strings.Trim(element, "[")
element = strings.Trim(element, "]")
elements := strings.Split(element, ",")
//TODO: wildcards for namespace and name
if len(elements) == 0 {
continue
}

View file

@ -76,8 +76,6 @@ func (c *Client) NewDynamicSharedInformerFactory(defaultResync time.Duration) dy
}
//GetEventsInterface provides typed interface for events
//TODO: can we use dynamic client to fetch the typed interface
// or generate a kube client value to access the interface
func (c *Client) GetEventsInterface() (event.EventInterface, error) {
return c.kclient.CoreV1().Events(""), nil
}

View file

@ -58,7 +58,7 @@ func (nh NegationHandler) Handle(handler resourceElementHandler, resourceMap map
// if anchor is present in the resource then fail
if _, ok := resourceMap[anchorKey]; ok {
// no need to process elements in value as key cannot be present in resource
return currentPath, fmt.Errorf("Validation rule failed at %s, field %s is disallowed", currentPath, anchorKey)
return currentPath, fmt.Errorf("%s/%s is not allowed", currentPath, anchorKey)
}
// key is not defined in the resource
return "", nil
@ -118,7 +118,7 @@ func (dh DefaultHandler) Handle(handler resourceElementHandler, resourceMap map[
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
return dh.path, fmt.Errorf("%s/%s not found", dh.path, dh.element)
} else {
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath, ac)
if err != nil {
@ -153,7 +153,7 @@ func (ch ConditionAnchorHandler) Handle(handler resourceElementHandler, resource
// validate the values of the pattern
returnPath, err := handler(log.Log, value, ch.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = common.NewConditionalAnchorError(fmt.Sprintf("condition anchor did not satisfy: %s", err.Error()))
ac.AnchorError = common.NewConditionalAnchorError(err.Error())
return returnPath, ac.AnchorError.Error()
}
return "", nil
@ -187,7 +187,7 @@ func (gh GlobalAnchorHandler) Handle(handler resourceElementHandler, resourceMap
// validate the values of the pattern
returnPath, err := handler(log.Log, value, gh.pattern, originPattern, currentPath, ac)
if err != nil {
ac.AnchorError = common.NewGlobalAnchorError(fmt.Sprintf("global anchor did not satisfy: %s", err.Error()))
ac.AnchorError = common.NewGlobalAnchorError(err.Error())
return returnPath, ac.AnchorError.Error()
}
return "", nil

View file

@ -16,7 +16,7 @@ func IsConditionalAnchorError(msg string) bool {
return false
}
// IsGlobalAnchorError checks if error message has conditional anchor error string
// IsGlobalAnchorError checks if error message has global anchor error string
func IsGlobalAnchorError(msg string) bool {
return strings.Contains(msg, GlobalAnchorErrMsg)
}
@ -74,10 +74,10 @@ type ValidateAnchorError struct {
}
// ConditionalAnchorErrMsg - the error message for conditional anchor error
var ConditionalAnchorErrMsg = "conditionalAnchorError"
var ConditionalAnchorErrMsg = "conditional anchor mismatch"
// GlobalAnchorErrMsg - the error message for global anchor error
var GlobalAnchorErrMsg = "globalAnchorError"
var GlobalAnchorErrMsg = "global anchor mismatch"
// AnchorKey - contains map of anchors
type AnchorKey struct {

View file

@ -1,5 +1,7 @@
package common
import "encoding/json"
// CopyMap creates a full copy of the target map
func CopyMap(m map[string]interface{}) map[string]interface{} {
mapCopy := make(map[string]interface{})
@ -17,3 +19,22 @@ func CopySlice(s []interface{}) []interface{} {
return sliceCopy
}
func ToMap(data interface{}) (map[string]interface{}, error) {
if m, ok := data.(map[string]interface{}); ok {
return m, nil
}
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
mapData := make(map[string]interface{})
err = json.Unmarshal(b, &mapData)
if err != nil {
return nil, err
}
return mapData, nil
}

View file

@ -53,21 +53,22 @@ type EvalInterface interface {
//Context stores the data resources as JSON
type Context struct {
mutex sync.RWMutex
jsonRaw []byte
jsonRawCheckpoint []byte
builtInVars []string
images *Images
log logr.Logger
mutex sync.RWMutex
jsonRaw []byte
jsonRawCheckpoints [][]byte
builtInVars []string
images *Images
log logr.Logger
}
//NewContext returns a new context
// builtInVars is the list of known variables (e.g. serviceAccountName)
func NewContext(builtInVars ...string) *Context {
ctx := Context{
jsonRaw: []byte(`{}`), // empty json struct
builtInVars: builtInVars,
log: log.Log.WithName("context"),
jsonRaw: []byte(`{}`), // empty json struct
builtInVars: builtInVars,
log: log.Log.WithName("context"),
jsonRawCheckpoints: make([][]byte, 0),
}
return &ctx
@ -98,6 +99,16 @@ func (ctx *Context) AddJSON(dataRaw []byte) error {
return nil
}
// AddJSON merges json data
func (ctx *Context) AddJSONObject(jsonData interface{}) error {
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
return err
}
return ctx.AddJSON(jsonBytes)
}
// AddRequest adds an admission request to context
func (ctx *Context) AddRequest(request *v1beta1.AdmissionRequest) error {
modifiedResource := struct {
@ -187,6 +198,7 @@ func (ctx *Context) AddResourceAsObject(data interface{}) error {
ctx.log.Error(err, "failed to marshal the resource")
return err
}
return ctx.AddJSON(objRaw)
}
@ -306,28 +318,45 @@ func (ctx *Context) ImageInfo() *Images {
return ctx.images
}
// Checkpoint creates a copy of the internal state.
// Prior checkpoints will be overridden.
// Checkpoint creates a copy of the current internal state and
// pushes it into a stack of stored states.
func (ctx *Context) Checkpoint() {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
ctx.jsonRawCheckpoint = make([]byte, len(ctx.jsonRaw))
copy(ctx.jsonRawCheckpoint, ctx.jsonRaw)
jsonRawCheckpoint := make([]byte, len(ctx.jsonRaw))
copy(jsonRawCheckpoint, ctx.jsonRaw)
ctx.jsonRawCheckpoints = append(ctx.jsonRawCheckpoints, jsonRawCheckpoint)
}
// Restore restores internal state from a prior checkpoint, if one exists.
// If a prior checkpoint does not exist, the state will not be changed.
// Restore sets the internal state to the last checkpoint, and removes the checkpoint.
func (ctx *Context) Restore() {
ctx.reset(true)
}
// Reset sets the internal state to the last checkpoint, but does not remove the checkpoint.
func (ctx *Context) Reset() {
ctx.reset(false)
}
func (ctx *Context) reset(remove bool) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
if ctx.jsonRawCheckpoint == nil || len(ctx.jsonRawCheckpoint) == 0 {
if len(ctx.jsonRawCheckpoints) == 0 {
return
}
ctx.jsonRaw = make([]byte, len(ctx.jsonRawCheckpoint))
copy(ctx.jsonRaw, ctx.jsonRawCheckpoint)
n := len(ctx.jsonRawCheckpoints) - 1
jsonRawCheckpoint := ctx.jsonRawCheckpoints[n]
ctx.jsonRaw = make([]byte, len(jsonRawCheckpoint))
copy(ctx.jsonRaw, jsonRawCheckpoint)
if remove {
ctx.jsonRawCheckpoints = ctx.jsonRawCheckpoints[:n]
}
}
// AddBuiltInVars adds given pattern to the builtInVars

View file

@ -115,6 +115,6 @@ func Test_addResourceAndUserContext(t *testing.T) {
expectedResult = "nirmata"
t.Log(result)
if !reflect.DeepEqual(expectedResult, result) {
t.Error("exected result does not match")
t.Error("expected result does not match")
}
}

View file

@ -83,7 +83,7 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
if rule.Mutation.Patches != nil {
var resp response.RuleResponse
resp, resource = mutate.ProcessPatches(logger.WithValues("rule", rule.Name), rule.Name, rule.Mutation, resource)
if !resp.Success {
if resp.Status != response.RuleStatusPass {
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
}
}
@ -91,7 +91,7 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
if rule.Mutation.PatchStrategicMerge != nil {
var resp response.RuleResponse
resp, resource = mutate.ProcessStrategicMergePatch(rule.Name, rule.Mutation.PatchStrategicMerge, resource, logger.WithValues("rule", rule.Name))
if !resp.Success {
if resp.Status != response.RuleStatusPass {
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
}
}
@ -104,11 +104,10 @@ func ForceMutate(ctx context.EvalInterface, policy kyverno.ClusterPolicy, resour
}
resp, resource = mutate.ProcessPatchJSON6902(rule.Name, jsonPatches, resource, logger.WithValues("rule", rule.Name))
if !resp.Success {
if resp.Status != response.RuleStatusPass {
return unstructured.Unstructured{}, fmt.Errorf(resp.Message)
}
}
}
return resource, nil

View file

@ -75,14 +75,14 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR
logger := log.Log.WithName("Generate").WithValues("policy", policy.Name,
"kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err != nil {
if err = MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err != nil {
// if the oldResource matched, return "false" to delete GR for it
if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err == nil {
if err = MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels, ""); err == nil {
return &response.RuleResponse{
Name: rule.Name,
Type: "Generation",
Success: false,
Name: rule.Name,
Type: "Generation",
Status: response.RuleStatusFail,
RuleStats: response.RuleStats{
ProcessingTime: time.Since(startTime),
RuleExecutionTimestamp: startTime.Unix(),
@ -123,9 +123,9 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR
// build rule Response
return &response.RuleResponse{
Name: ruleCopy.Name,
Type: "Generation",
Success: true,
Name: ruleCopy.Name,
Type: "Generation",
Status: response.RuleStatusPass,
RuleStats: response.RuleStats{
ProcessingTime: time.Since(startTime),
RuleExecutionTimestamp: startTime.Unix(),

View file

@ -3,15 +3,16 @@ package engine
import (
"encoding/json"
"fmt"
"time"
"github.com/go-logr/logr"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/cosign"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/minio/minio/pkg/wildcard"
"github.com/minio/pkg/wildcard"
"sigs.k8s.io/controller-runtime/pkg/log"
"time"
)
func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineResponse) {
@ -34,7 +35,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
startTime := time.Now()
defer func() {
buildResponse(logger, policyContext, resp, startTime)
buildResponse(policyContext, resp, startTime)
logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "rulesApplied", resp.PolicyResponse.RulesAppliedCount)
}()
@ -42,7 +43,7 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
defer policyContext.JSONContext.Restore()
for i := range policyContext.Policy.Spec.Rules {
rule := policyContext.Policy.Spec.Rules[i]
rule := &policyContext.Policy.Spec.Rules[i]
if len(rule.VerifyImages) == 0 {
continue
}
@ -53,8 +54,8 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (resp *response.EngineRe
policyContext.JSONContext.Restore()
for _, imageVerify := range rule.VerifyImages {
verifyAndPatchImages(logger, policyContext, &rule, imageVerify, images.Containers, resp)
verifyAndPatchImages(logger, policyContext, &rule, imageVerify, images.InitContainers, resp)
verifyAndPatchImages(logger, policyContext, rule, imageVerify, images.Containers, resp)
verifyAndPatchImages(logger, policyContext, rule, imageVerify, images.InitContainers, resp)
}
}
@ -95,11 +96,11 @@ func verifyAndPatchImages(logger logr.Logger, policyContext *PolicyContext, rule
digest, err := cosign.Verify(image, []byte(key), repository, logger)
if err != nil {
logger.Info("failed to verify image", "image", image, "key", key, "error", err, "duration", time.Since(start).Seconds())
ruleResp.Success = false
ruleResp.Status = response.RuleStatusFail
ruleResp.Message = fmt.Sprintf("image verification failed for %s: %v", image, err)
} else {
logger.V(3).Info("verified image", "image", image, "digest", digest, "duration", time.Since(start).Seconds())
ruleResp.Success = true
ruleResp.Status = response.RuleStatusPass
ruleResp.Message = fmt.Sprintf("image %s verified", image)
// add digest to image

View file

@ -101,7 +101,7 @@ func (h patchesJSON6902Handler) Handle() (resp response.RuleResponse, patchedRes
patchesJSON6902, err := convertPatchesToJSON(h.mutation.PatchesJSON6902)
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
h.logger.Error(err, "error in type conversion")
resp.Message = err.Error()
return resp, h.patchedResource

View file

@ -38,25 +38,25 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
case conditionNotPresent:
logger.V(3).Info("skip applying rule", "reason", "conditionNotPresent")
resp.Success = true
resp.Status = response.RuleStatusPass
return resp, resource
case conditionFailure:
logger.V(3).Info("skip applying rule", "reason", "conditionFailure")
//TODO: send zero response and not consider this as applied?
resp.Success = true
resp.Status = response.RuleStatusPass
resp.Message = overlayerr.ErrorMsg()
return resp, resource
case overlayFailure:
logger.Info("failed to process overlay")
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to process overlay: %v", overlayerr.ErrorMsg())
return resp, resource
default:
logger.Info("failed to process overlay")
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("Unknown type of error: %v", overlayerr.Error())
return resp, resource
}
@ -64,14 +64,14 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
logger.V(4).Info("processing overlay rule", "patches", len(patches))
if len(patches) == 0 {
resp.Success = true
resp.Status = response.RuleStatusPass
return resp, resource
}
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
@ -82,7 +82,7 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
patchResource, err = utils.ApplyPatches(resourceRaw, patches)
if err != nil {
msg := fmt.Sprintf("failed to apply JSON patches: %v", err)
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = msg
return resp, resource
}
@ -91,13 +91,13 @@ func ProcessOverlay(log logr.Logger, ruleName string, overlay interface{}, resou
err = patchedResource.UnmarshalJSON(patchResource)
if err != nil {
logger.Error(err, "failed to unmarshal resource")
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
// rule application successfully
resp.Success = true
resp.Status = response.RuleStatusPass
resp.Message = fmt.Sprintf("successfully processed overlay")
resp.Patches = patches

View file

@ -27,7 +27,7 @@ func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unst
resourceRaw, err := resource.MarshalJSON()
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to marshal resource: %v", err)
return resp, resource
@ -35,7 +35,7 @@ func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unst
patchedResourceRaw, err := applyPatchesWithOptions(resourceRaw, patchesJSON6902)
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "unable to apply RFC 6902 patches")
resp.Message = fmt.Sprintf("unable to apply RFC 6902 patches: %v", err)
return resp, resource
@ -43,7 +43,7 @@ func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unst
patchesBytes, err := generatePatches(resourceRaw, patchedResourceRaw)
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "unable generate patch bytes from base and patched document, apply patchesJSON6902 directly")
resp.Message = fmt.Sprintf("unable generate patch bytes from base and patched document, apply patchesJSON6902 directly: %v", err)
return resp, resource
@ -56,12 +56,12 @@ func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unst
err = patchedResource.UnmarshalJSON(patchedResourceRaw)
if err != nil {
logger.Error(err, "failed to unmarshal resource")
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to unmarshal resource: %v", err)
return resp, resource
}
resp.Success = true
resp.Status = response.RuleStatusPass
resp.Message = fmt.Sprintf("successfully process JSON6902 patches")
resp.Patches = patchesBytes
return resp, patchedResource

View file

@ -2,6 +2,7 @@ package mutate
import (
"fmt"
"github.com/kyverno/kyverno/pkg/engine/response"
"testing"
"github.com/ghodss/yaml"
@ -50,7 +51,7 @@ func TestTypeConversion(t *testing.T) {
assert.Nil(t, err)
// apply patches
resp, _ := ProcessPatchJSON6902("type-conversion", jsonPatches, resource, log.Log)
if !assert.Equal(t, true, resp.Success) {
if !assert.Equal(t, response.RuleStatusPass, resp.Status) {
t.Fatal(resp.Message)
}

View file

@ -35,7 +35,7 @@ func ProcessPatches(log logr.Logger, ruleName string, mutation kyverno.Mutation,
// convert to RAW
resourceRaw, err := resource.MarshalJSON()
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
@ -52,7 +52,6 @@ func ProcessPatches(log logr.Logger, ruleName string, mutation kyverno.Mutation,
continue
}
patchResource, err := applyPatch(resourceRaw, patchRaw)
// TODO: continue on error if one of the patches fails, will add the failure event in such case
if err != nil && patch.Operation == "remove" {
log.Error(err, "failed to process JSON path or patch is a 'remove' operation")
continue
@ -67,7 +66,7 @@ func ProcessPatches(log logr.Logger, ruleName string, mutation kyverno.Mutation,
// error while processing JSON patches
if len(errs) > 0 {
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", func() string {
var str []string
for _, err := range errs {
@ -79,14 +78,14 @@ func ProcessPatches(log logr.Logger, ruleName string, mutation kyverno.Mutation,
}
err = patchedResource.UnmarshalJSON(resourceRaw)
if err != nil {
logger.Error(err, "failed to unmmarshal resource")
resp.Success = false
logger.Error(err, "failed to unmarshal resource")
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to process JSON patches: %v", err)
return resp, resource
}
// JSON patches processed successfully
resp.Success = true
resp.Status = response.RuleStatusPass
resp.Message = fmt.Sprintf("successfully process JSON patches")
resp.Patches = patches
return resp, patchedResource

View file

@ -1,6 +1,7 @@
package mutate
import (
"github.com/kyverno/kyverno/pkg/engine/response"
"testing"
"gotest.tools/assert"
@ -43,7 +44,7 @@ func TestProcessPatches_EmptyPatches(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, "", emptyRule.Mutation, *resourceUnstructured)
assert.Check(t, rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusPass)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -72,14 +73,14 @@ func makeRuleWithPatches(patches []types.Patch) types.Rule {
func TestProcessPatches_EmptyDocument(t *testing.T) {
rule := makeRuleWithPatch(makeAddIsMutatedLabelPatch())
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, unstructured.Unstructured{})
assert.Assert(t, !rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusFail)
assert.Assert(t, len(rr.Patches) == 0)
}
func TestProcessPatches_AllEmpty(t *testing.T) {
emptyRule := types.Rule{}
rr, _ := ProcessPatches(log.Log, "", emptyRule.Mutation, unstructured.Unstructured{})
assert.Check(t, !rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusFail)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -92,7 +93,7 @@ func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, !rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusFail)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -104,7 +105,7 @@ func TestProcessPatches_RemovePathDoesntExist(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusPass)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -117,7 +118,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, !rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusFail)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -131,7 +132,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusPass)
assert.Assert(t, len(rr.Patches) != 0)
assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, rr.Patches[0])
}
@ -144,7 +145,7 @@ func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusPass)
assert.Assert(t, len(rr.Patches) == 0)
}
@ -157,7 +158,7 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
t.Error(err)
}
rr, _ := ProcessPatches(log.Log, rule.Name, rule.Mutation, *resourceUnstructured)
assert.Check(t, rr.Success)
assert.Equal(t, rr.Status, response.RuleStatusPass)
assert.Assert(t, len(rr.Patches) == 1)
assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, rr.Patches[0])
}

View file

@ -31,7 +31,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
overlayBytes, err := json.Marshal(overlay)
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
return resp, resource
@ -39,7 +39,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
base, err := json.Marshal(resource.Object)
if err != nil {
resp.Success = false
resp.Status = response.RuleStatusFail
logger.Error(err, "failed to marshal resource")
resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
return resp, resource
@ -48,7 +48,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
if err != nil {
log.Error(err, "failed to apply patchStrategicMerge")
msg := fmt.Sprintf("failed to apply patchStrategicMerge: %v", err)
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = msg
return resp, resource
}
@ -56,7 +56,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
err = patchedResource.UnmarshalJSON(patchedBytes)
if err != nil {
logger.Error(err, "failed to unmarshal resource")
resp.Success = false
resp.Status = response.RuleStatusFail
resp.Message = fmt.Sprintf("failed to process patchStrategicMerge: %v", err)
return resp, resource
}
@ -66,7 +66,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
jsonPatches, err := generatePatches(base, patchedBytes)
if err != nil {
msg := fmt.Sprintf("failed to generated JSON patches from patched resource: %v", err.Error())
resp.Success = false
resp.Status = response.RuleStatusFail
log.Info(msg)
resp.Message = msg
return resp, patchedResource
@ -76,7 +76,7 @@ func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource u
log.V(5).Info("generated patch", "patch", string(p))
}
resp.Success = true
resp.Status = response.RuleStatusPass
resp.Patches = jsonPatches
resp.Message = "successfully processed strategic merge patch"
return resp, patchedResource

View file

@ -335,9 +335,12 @@ func checkCondition(logger logr.Logger, pattern *yaml.RNode, resource *yaml.RNod
return err
}
_, err = validate.ValidateResourceWithPattern(logger, resourceInterface, patternInterface)
err = validate.MatchPattern(logger, resourceInterface, patternInterface)
if err != nil {
return err
}
return err
return nil
}
func deleteConditionsFromNestedMaps(pattern *yaml.RNode) (bool, error) {

View file

@ -913,7 +913,7 @@ func Test_CheckConditionAnchor_Matches(t *testing.T) {
resource := yaml.MustParse(string(resourceRaw))
err := checkCondition(log.Log, pattern, resource)
assert.NilError(t, err)
assert.Equal(t, err, nil)
}
func Test_CheckConditionAnchor_DoesNotMatch(t *testing.T) {
@ -924,7 +924,7 @@ func Test_CheckConditionAnchor_DoesNotMatch(t *testing.T) {
resource := yaml.MustParse(string(resourceRaw))
err := checkCondition(log.Log, pattern, resource)
assert.Error(t, err, "Validation rule failed at '/key1/' to validate value 'sample' with pattern 'value*'")
assert.Error(t, err, "resource value 'sample' does not match 'value*' at path /key1/")
}
func Test_ValidateConditions_MapWithOneCondition_Matches(t *testing.T) {

View file

@ -65,7 +65,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
excludeResource = policyContext.ExcludeGroupRole
}
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels); err != nil {
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.Namespace); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
continue
}
@ -75,13 +75,13 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
// Restore() is meant for restoring context loaded from external lookup (APIServer & ConfigMap)
// while we need to keep updated resource in the JSON context as rules can be chained
resource, err := policyContext.JSONContext.Query("request.object")
policyContext.JSONContext.Restore()
policyContext.JSONContext.Reset()
if err == nil && resource != nil {
if err := ctx.AddResourceAsObject(resource.(map[string]interface{})); err != nil {
logger.WithName("RestoreContext").Error(err, "unable to update resource object")
}
} else {
logger.WithName("RestoreContext").Error(err, "failed to quey resource object")
logger.WithName("RestoreContext").Error(err, "failed to query resource object")
}
if err := LoadContext(logger, rule.Context, resCache, policyContext, rule.Name); err != nil {
@ -116,9 +116,9 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
if *ruleCopy, err = variables.SubstituteAllInRule(logger, ctx, *ruleCopy); err != nil {
ruleResp := response.RuleResponse{
Name: ruleCopy.Name,
Type: utils.Validation.String(),
Message: fmt.Sprintf("variable substitution failed for rule %s: %s", ruleCopy.Name, err.Error()),
Success: true,
Type: utils.Mutation.String(),
Message: fmt.Sprintf("variable substitution failed: %s", err.Error()),
Status: response.RuleStatusPass,
}
incrementAppliedCount(resp)
@ -131,7 +131,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
mutation := ruleCopy.Mutation.DeepCopy()
mutateHandler := mutate.CreateMutateHandler(ruleCopy.Name, mutation, patchedResource, ctx, logger)
ruleResponse, patchedResource = mutateHandler.Handle()
if ruleResponse.Success {
if ruleResponse.Status == response.RuleStatusPass {
// - overlay pattern does not match the resource conditions
if ruleResponse.Patches == nil {
continue

View file

@ -157,7 +157,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Mutate(policyContext)
expectedErrorStr := "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path"
expectedErrorStr := "variable substitution failed: Unknown key \"name1\" in path"
assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
}

View file

@ -20,6 +20,9 @@ type PolicyContext struct {
// OldResource is the prior resource for an update, or nil
OldResource unstructured.Unstructured
// Element is set when the context is used for processing a foreach loop
Element unstructured.Unstructured
// AdmissionInfo contains the admission request information
AdmissionInfo kyverno.RequestInfo
@ -40,3 +43,18 @@ type PolicyContext struct {
// NamespaceLabels stores the label of namespace to be processed by namespace selector
NamespaceLabels map[string]string
}
func (pc *PolicyContext) Copy() *PolicyContext {
return &PolicyContext{
Policy: pc.Policy,
NewResource: pc.NewResource,
OldResource: pc.OldResource,
AdmissionInfo: pc.AdmissionInfo,
Client: pc.Client,
ExcludeGroupRole: pc.ExcludeGroupRole,
ExcludeResourceFunc: pc.ExcludeResourceFunc,
ResourceCache: pc.ResourceCache,
JSONContext: pc.JSONContext,
NamespaceLabels: pc.NamespaceLabels,
}
}

View file

@ -54,26 +54,38 @@ func (rs ResourceSpec) GetKey() string {
//PolicyStats stores statistics for the single policy application
type PolicyStats struct {
// time required to process the policy rules on a resource
ProcessingTime time.Duration `json:"processingTime"`
// Count of rules that were applied successfully
RulesAppliedCount int `json:"rulesAppliedCount"`
// Count of rules that with execution errors
RulesErrorCount int `json:"rulesErrorCount"`
// Timestamp of the instant the Policy was triggered
PolicyExecutionTimestamp int64 `json:"policyExecutionTimestamp"`
}
//RuleResponse details for each rule application
type RuleResponse struct {
// rule name specified in policy
Name string `json:"name"`
// rule type (Mutation,Generation,Validation) for Kyverno Policy
Type string `json:"type"`
// message response from the rule application
Message string `json:"message"`
// JSON patches, for mutation rules
Patches [][]byte `json:"patches,omitempty"`
// success/fail
Success bool `json:"success"`
// rule status
Status RuleStatus `json:"status"`
// statistics
RuleStats `json:",inline"`
}
@ -94,21 +106,23 @@ type RuleStats struct {
//IsSuccessful checks if any rule has failed or not
func (er EngineResponse) IsSuccessful() bool {
for _, r := range er.PolicyResponse.Rules {
if !r.Success {
if r.Status == RuleStatusFail || r.Status == RuleStatusError {
return false
}
}
return true
}
//IsFailed checks if any rule has succeeded or not
func (er EngineResponse) IsFailed() bool {
for _, r := range er.PolicyResponse.Rules {
if r.Success {
return false
if r.Status == RuleStatusFail {
return true
}
}
return true
return false
}
//GetPatches returns all the patches joined
@ -125,12 +139,12 @@ func (er EngineResponse) GetPatches() [][]byte {
//GetFailedRules returns failed rules
func (er EngineResponse) GetFailedRules() []string {
return er.getRules(false)
return er.getRules(RuleStatusFail)
}
//GetSuccessRules returns success rules
func (er EngineResponse) GetSuccessRules() []string {
return er.getRules(true)
return er.getRules(RuleStatusPass)
}
// GetResourceSpec returns resourceSpec of er
@ -144,10 +158,10 @@ func (er EngineResponse) GetResourceSpec() ResourceSpec {
}
}
func (er EngineResponse) getRules(success bool) []string {
func (er EngineResponse) getRules(status RuleStatus) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {
if r.Success == success {
if r.Status == status {
rules = append(rules, r.Name)
}
}

View file

@ -0,0 +1,30 @@
package response
import (
"gopkg.in/yaml.v2"
"gotest.tools/assert"
"testing"
)
var sourceYAML = `
policy:
name: disallow-bind-mounts
resource:
kind: Pod
apiVersion: v1
name: image-with-hostpath
rules:
- name: validate-hostPath
type: Validation
status: fail
`
func Test_parse_yaml(t *testing.T) {
var pr PolicyResponse
if err := yaml.Unmarshal([]byte(sourceYAML), &pr); err != nil {
t.Errorf("failed to parse YAML: %v", err)
return
}
assert.Equal(t, 1, len(pr.Rules))
assert.Equal(t, RuleStatusFail, pr.Rules[0].Status)
}

View file

@ -0,0 +1,97 @@
package response
import (
"encoding/json"
"fmt"
"strings"
)
// RuleStatus represents the status of rule execution
type RuleStatus int
// RuleStatusPass is used to report the result of processing a rule.
const (
// RuleStatusPass indicates that the resources meets the policy rule requirements
RuleStatusPass RuleStatus = iota
// Fail indicates that the resource does not meet the policy rule requirements
RuleStatusFail
// Warn indicates that the the resource does not meet the policy rule requirements, but the policy is not scored
RuleStatusWarn
// Error indicates that the policy rule could not be evaluated due to a processing error, for
// example when a variable cannot be resolved in the policy rule definition. Note that variables
// that cannot be resolved in preconditions are replaced with empty values to allow existence
// checks.
RuleStatusError
// Skip indicates that the policy rule was not selected based on user inputs or applicability, for example
// when preconditions are not met, or when conditional or global anchors are not satistied.
RuleStatusSkip
)
func (s *RuleStatus) String() string {
return toString[*s]
}
var toString = map[RuleStatus]string{
RuleStatusPass: "pass",
RuleStatusFail: "fail",
RuleStatusWarn: "warning",
RuleStatusError: "error",
RuleStatusSkip: "skip",
}
var toID = map[string]RuleStatus{
"pass": RuleStatusPass,
"fail": RuleStatusFail,
"warning": RuleStatusWarn,
"error": RuleStatusError,
"skip": RuleStatusSkip,
}
// MarshalJSON marshals the enum as a quoted json string
func (s *RuleStatus) MarshalJSON() ([]byte, error) {
var b strings.Builder
fmt.Fprintf(&b, "\"%s\"", toString[*s])
return []byte(b.String()), nil
}
// UnmarshalJSON unmarshals a quoted json string to the enum value
func (s *RuleStatus) UnmarshalJSON(b []byte) error {
var strVal string
err := json.Unmarshal(b, &strVal)
if err != nil {
return err
}
statusVal, err := getRuleStatus(strVal)
if err != nil {
return err
}
*s = *statusVal
return nil
}
func getRuleStatus(s string) (*RuleStatus, error) {
for k, v := range toID {
if s == k {
return &v, nil
}
}
return nil, fmt.Errorf("invalid status: %s", s)
}
func (v *RuleStatus) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
statusVal, err := getRuleStatus(s)
if err != nil {
return err
}
*v = *statusVal
return nil
}

View file

@ -256,15 +256,18 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User
}
//MatchesResourceDescription checks if the resource matches resource description of the rule or not
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string) error {
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string, policyNamespace string) error {
rule := *ruleRef.DeepCopy()
rule := ruleRef.DeepCopy()
resource := *resourceRef.DeepCopy()
admissionInfo := *admissionInfoRef.DeepCopy()
var reasonsForFailure []error
if policyNamespace != "" && policyNamespace != resourceRef.GetNamespace() {
return errors.New(" The policy and resource namespace are different. Therefore, policy skip this resource.")
}
if len(rule.MatchResources.Any) > 0 {
// inlcude object if ANY of the criterias match
// include object if ANY of the criteria match
// so if one matches then break from loop
oneMatched := false
for _, rmr := range rule.MatchResources.Any {
@ -392,7 +395,8 @@ func transformConditions(original apiextensions.JSON) (interface{}, error) {
case []kyverno.Condition: // backwards compatibility
return copyOldConditions(typedValue), nil
}
return nil, fmt.Errorf("wrongfully configured data")
return nil, fmt.Errorf("invalid preconditions")
}
// excludeResource checks if the resource has ownerRef set

View file

@ -898,7 +898,7 @@ func TestMatchesResourceDescription(t *testing.T) {
resource, _ := utils.ConvertToUnstructured(tc.Resource)
for _, rule := range policy.Spec.Rules {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil)
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil, "")
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v", i+1, err)
@ -966,7 +966,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
@ -1027,7 +1027,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1087,7 +1087,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1155,7 +1155,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1224,7 +1224,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -1304,7 +1304,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err == nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil, ""); err == nil {
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
}
}

View file

@ -42,10 +42,8 @@ func ValidateValueWithPattern(log logr.Logger, value, pattern interface{}) bool
case nil:
return validateValueWithNilPattern(log, value)
case map[string]interface{}:
// TODO: check if this is ever called?
return validateValueWithMapPattern(log, value, typedPattern)
case []interface{}:
// TODO: check if this is ever called?
log.Info("arrays are not supported as patterns")
return false
default:
@ -57,7 +55,6 @@ func ValidateValueWithPattern(log logr.Logger, value, pattern interface{}) bool
func validateValueWithMapPattern(log logr.Logger, value interface{}, typedPattern map[string]interface{}) bool {
// verify the type of the resource value is map[string]interface,
// we only check for existence of object, not the equality of content and value
//TODO: check if adding
_, ok := value.(map[string]interface{})
if !ok {
log.Info("Expected type map[string]interface{}", "type", fmt.Sprintf("%T", value), "value", value)

View file

@ -35,6 +35,10 @@ func hasNestedAnchors(pattern interface{}) bool {
func getSortedNestedAnchorResource(resources map[string]interface{}) *list.List {
sortedResourceKeys := list.New()
for k, v := range resources {
if commonAnchors.IsGlobalAnchor(k) {
sortedResourceKeys.PushFront(k)
continue
}
if hasNestedAnchors(v) {
sortedResourceKeys.PushFront(k)
} else {
@ -48,7 +52,7 @@ func getSortedNestedAnchorResource(resources map[string]interface{}) *list.List
func getAnchorsFromMap(anchorsMap map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for key, value := range anchorsMap {
if commonAnchors.IsConditionAnchor(key) || commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) {
if commonAnchors.IsConditionAnchor(key) || commonAnchors.IsExistenceAnchor(key) || commonAnchors.IsEqualityAnchor(key) || commonAnchors.IsNegationAnchor(key) || commonAnchors.IsGlobalAnchor(key) {
result[key] = value
}
}

View file

@ -12,24 +12,43 @@ import (
"github.com/kyverno/kyverno/pkg/engine/wildcards"
)
// ValidateResourceWithPattern is a start of element-by-element validation process
type PatternError struct {
Err error
Path string
Skip bool
}
func (e *PatternError) Error() string {
if e.Err == nil {
return ""
}
return e.Err.Error()
}
// MatchPattern is a start of element-by-element pattern validation process.
// It assumes that validation is started from root, so "/" is passed
func ValidateResourceWithPattern(logger logr.Logger, resource, pattern interface{}) (string, error) {
func MatchPattern(logger logr.Logger, resource, pattern interface{}) error {
// newAnchorMap - to check anchor key has values
ac := common.NewAnchorMap()
elemPath, err := validateResourceElement(logger, resource, pattern, pattern, "/", ac)
if err != nil {
// if conditional or global anchors report errors, the rule does not apply to the resource
if common.IsConditionalAnchorError(err.Error()) || common.IsGlobalAnchorError(err.Error()) {
logger.V(3).Info(ac.AnchorError.Message)
return "", nil
logger.V(3).Info("skipping resource as anchor does not apply", "msg", ac.AnchorError.Error())
return &PatternError{err, "", true}
}
if !ac.IsAnchorError() {
return elemPath, err
// check if an anchor defined in the policy rule is missing in the resource
if ac.IsAnchorError() {
logger.V(3).Info("missing anchor in resource")
return &PatternError{err, "", false}
}
return &PatternError{err, elemPath, false}
}
return "", nil
return nil
}
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
@ -44,7 +63,7 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
log.V(4).Info("Pattern and resource have different structures.", "path", path, "expected", fmt.Sprintf("%T", patternElement), "current", fmt.Sprintf("%T", resourceElement))
return path, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
}
// CheckAnchorInResource - check anchor anchor key exists in resource and update the AnchorKey fields.
// CheckAnchorInResource - check anchor key exists in resource and update the AnchorKey fields.
ac.CheckAnchorInResource(typedPatternElement, typedResourceElement)
return validateMap(log, typedResourceElement, typedPatternElement, originPattern, path, ac)
// array
@ -63,19 +82,19 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
case []interface{}:
for _, res := range resource {
if !ValidateValueWithPattern(log, res, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
}
}
return "", nil
default:
if !ValidateValueWithPattern(log, resourceElement, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
return path, fmt.Errorf("resource value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
}
}
default:
log.V(4).Info("Pattern contains unknown type", "path", path, "current", fmt.Sprintf("%T", patternElement))
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
return path, fmt.Errorf("failed at '%s', pattern contains unknown type", path)
}
return "", nil
}
@ -83,7 +102,6 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
func validateMap(log logr.Logger, resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
patternMap = wildcards.ExpandInMetadata(patternMap, resourceMap)
// check if there is anchor in pattern
// Phase 1 : Evaluate all the anchors

View file

@ -1383,22 +1383,11 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-x",
pattern: []byte(`{"spec": {"containers": [{"name": "*","(image)": "!*:* | *:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-3",
pattern: []byte(`{"spec": {"containers": [{"name": "*","(image)": "*:latest | !*:*","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
}, {
name: "check global anchor",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "imagePullPolicy": "Always"}]}}`),
nilErr: true,
},
{
name: "test-4",
@ -1508,22 +1497,146 @@ func TestConditionalAnchorWithMultiplePatterns(t *testing.T) {
resource: []byte(`{"spec": {"containers": [{"name": "busybox","image": "busybox:1.2.3", "imagePullPolicy": "Always"},{"name": "nginx","image": "nginx:1.2.3", "imagePullPolicy": "Always"}]}}`),
nilErr: true,
},
{
name: "test-22",
pattern: []byte(`{"spec": {"containers": [{"name": "*","(image)": "!*:* | *:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-23",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-24",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-25",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo1", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-26",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-27",
pattern: []byte(`{"spec": {"containers": [{"name": "*", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo1", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-28",
pattern: []byte(`{"spec": {"containers": [{"name": "*", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo", "value": "bar" }],"imagePullPolicy": "IfNotpresent"}]}}`),
nilErr: true,
},
{
name: "test-29",
pattern: []byte(`{"spec": {"containers": [{"name": "*", "env": [{"<(name)": "foo", "<(value)": "bar" }],"imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx", "env": [{"name": "foo", "value": "bar" }],"imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "test-30",
pattern: []byte(`{"metadata": {"<(name)": "nginx"},"spec": {"imagePullSecrets": [{"name": "regcred"}]}}`),
resource: []byte(`{"metadata": {"name": "somename"},"spec": {"containers": [{"name": "nginx","image": "nginx:latest"}], "imagePullSecrets": [{"name": "cred"}]}}`),
nilErr: false,
},
{
name: "test-31",
pattern: []byte(`{"metadata": {"<(name)": "nginx"},"spec": {"imagePullSecrets": [{"name": "regcred"}]}}`),
resource: []byte(`{"metadata": {"name": "nginx"},"spec": {"containers": [{"name": "nginx","image": "nginx:latest"}], "imagePullSecrets": [{"name": "cred"}]}}`),
nilErr: false,
},
{
name: "test-32",
pattern: []byte(`{"metadata": {"labels": {"<(foo)": "bar"}},"spec": {"containers": [{"name": "nginx","image": "!*:latest"}]}}`),
resource: []byte(`{"metadata": {"name": "nginx","labels": {"foo": "bar"}},"spec": {"containers": [{"name": "nginx","image": "nginx"}]}}`),
nilErr: true,
},
{
name: "test-33",
pattern: []byte(`{"metadata": {"labels": {"<(foo)": "bar"}},"spec": {"containers": [{"name": "nginx","image": "!*:latest"}]}}`),
resource: []byte(`{"metadata": {"name": "nginx","labels": {"foo": "bar"}},"spec": {"containers": [{"name": "nginx","image": "nginx:latest"}]}}`),
nilErr: false,
},
{
name: "test-34",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx"}],"imagePullSecrets": [{"name": "my-registry-secret"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx"}], "imagePullSecrets": [{"name": "cred"}]}}`),
nilErr: false,
},
{
name: "test-35",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx"}],"imagePullSecrets": [{"name": "my-registry-secret"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "somepod"}], "imagePullSecrets": [{"name": "cred"}]}}`),
nilErr: false,
},
{
name: "test-36",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "nginx"}],"imagePullSecrets": [{"name": "my-registry-secret"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx"}], "imagePullSecrets": [{"name": "my-registry-secret"}]}}`),
nilErr: true,
},
}
for _, testCase := range testCases {
var pattern, resource interface{}
err := json.Unmarshal(testCase.pattern, &pattern)
assert.NilError(t, err)
err = json.Unmarshal(testCase.resource, &resource)
assert.NilError(t, err)
_, err = ValidateResourceWithPattern(log.Log, resource, pattern)
if testCase.nilErr {
assert.NilError(t, err, fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
} else {
assert.Assert(t,
err != nil,
fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\nmsg: %v", testCase.name, pattern, resource, err))
}
testMatchPattern(t, testCase)
}
}
func Test_global_anchor(t *testing.T) {
testCases := []struct {
name string
pattern []byte
resource []byte
nilErr bool
}{
{
name: "check global anchor_skip",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:v1", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
{
name: "check global anchor_apply",
pattern: []byte(`{"spec": {"containers": [{"name": "*","<(image)": "*:latest","imagePullPolicy": "!Always"}]}}`),
resource: []byte(`{"spec": {"containers": [{"name": "nginx","image": "nginx:latest", "imagePullPolicy": "Always"}]}}`),
nilErr: false,
},
}
testMatchPattern(t, testCases[0])
testMatchPattern(t, testCases[1])
}
func testMatchPattern(t *testing.T, testCase struct {
name string
pattern []byte
resource []byte
nilErr bool
}) {
var pattern, resource interface{}
err := json.Unmarshal(testCase.pattern, &pattern)
assert.NilError(t, err)
err = json.Unmarshal(testCase.resource, &resource)
assert.NilError(t, err)
err = MatchPattern(log.Log, resource, pattern)
if testCase.nilErr {
assert.NilError(t, err, fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\n", testCase.name, pattern, resource))
} else {
assert.Assert(t,
err != nil,
fmt.Sprintf("\ntest: %s\npattern: %s\nresource: %s\nmsg: %v", testCase.name, pattern, resource, err))
}
}

View file

@ -1,16 +1,19 @@
package engine
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/kyverno/kyverno/pkg/engine/common"
"github.com/pkg/errors"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/common"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/validate"
@ -27,7 +30,7 @@ func Validate(policyContext *PolicyContext) (resp *response.EngineResponse) {
logger := buildLogger(policyContext)
logger.V(4).Info("start policy processing", "startTime", startTime)
defer func() {
buildResponse(logger, policyContext, resp, startTime)
buildResponse(policyContext, resp, startTime)
logger.V(4).Info("finished policy processing", "processingTime", resp.PolicyResponse.ProcessingTime.String(), "validationRulesApplied", resp.PolicyResponse.RulesAppliedCount)
}()
@ -46,14 +49,14 @@ func buildLogger(ctx *PolicyContext) logr.Logger {
return logger
}
func buildResponse(logger logr.Logger, ctx *PolicyContext, resp *response.EngineResponse, startTime time.Time) {
func buildResponse(ctx *PolicyContext, resp *response.EngineResponse, startTime time.Time) {
if reflect.DeepEqual(resp, response.EngineResponse{}) {
return
}
if reflect.DeepEqual(resp.PatchedResource, unstructured.Unstructured{}) {
// for delete requests patched resource will be oldResource since newResource is empty
var resource unstructured.Unstructured = ctx.NewResource
var resource = ctx.NewResource
if reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) {
resource = ctx.OldResource
}
@ -76,135 +79,354 @@ func incrementAppliedCount(resp *response.EngineResponse) {
resp.PolicyResponse.RulesAppliedCount++
}
func incrementErrorCount(resp *response.EngineResponse) {
resp.PolicyResponse.RulesErrorCount++
}
func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineResponse {
resp := &response.EngineResponse{}
if ManagedPodResource(ctx.Policy, ctx.NewResource) {
log.V(5).Info("skip policy as direct changes to pods managed by workload controllers are not allowed", "policy", ctx.Policy.GetName())
log.V(5).Info("skip validation of pods managed by workload controllers", "policy", ctx.Policy.GetName())
return resp
}
ctx.JSONContext.Checkpoint()
defer ctx.JSONContext.Restore()
for _, rule := range ctx.Policy.Spec.Rules {
var err error
for i := range ctx.Policy.Spec.Rules {
rule := &ctx.Policy.Spec.Rules[i]
if !rule.HasValidate() {
continue
}
log = log.WithValues("rule", rule.Name)
if !matches(log, rule, ctx) {
continue
}
ctx.JSONContext.Restore()
if err := LoadContext(log, rule.Context, ctx.ResourceCache, ctx, rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
log.V(3).Info("failed to load context", "reason", err.Error())
} else {
log.Error(err, "failed to load context")
}
continue
}
log.V(3).Info("matched validate rule")
ctx.JSONContext.Reset()
startTime := time.Now()
ruleCopy := rule.DeepCopy()
ruleCopy.AnyAllConditions, err = variables.SubstituteAllInPreconditions(log, ctx.JSONContext, ruleCopy.AnyAllConditions)
if err != nil {
log.V(4).Info("failed to substitute vars in preconditions, skip current rule", "rule name", rule.Name)
return nil
}
preconditions, err := transformConditions(ruleCopy.AnyAllConditions)
if err != nil {
log.V(2).Info("wrongfully configured data", "reason", err.Error())
continue
}
// evaluate pre-conditions
if !variables.EvaluateConditions(log, ctx.JSONContext, preconditions) {
log.V(4).Info("resource fails the preconditions")
continue
}
if rule.Validation.Pattern != nil || rule.Validation.AnyPattern != nil {
if *ruleCopy, err = substituteAll(log, ctx, *ruleCopy, resp); err != nil {
continue
}
ruleResponse := validateResourceWithRule(log, ctx, *ruleCopy)
if ruleResponse != nil {
if !common.IsConditionalAnchorError(ruleResponse.Message) {
incrementAppliedCount(resp)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResponse)
}
}
} else if rule.Validation.Deny != nil {
ruleCopy.Validation.Deny.AnyAllConditions, err = variables.SubstituteAllInPreconditions(log, ctx.JSONContext, ruleCopy.Validation.Deny.AnyAllConditions)
if err != nil {
log.V(4).Info("failed to substitute vars in preconditions, skip current rule", "rule name", rule.Name)
continue
}
if *ruleCopy, err = substituteAll(log, ctx, *ruleCopy, resp); err != nil {
continue
}
denyConditions, err := transformConditions(ruleCopy.Validation.Deny.AnyAllConditions)
if err != nil {
log.V(2).Info("wrongfully configured data", "reason", err.Error())
continue
}
deny := variables.EvaluateConditions(log, ctx.JSONContext, denyConditions)
ruleResp := response.RuleResponse{
Name: ruleCopy.Name,
Type: utils.Validation.String(),
Message: ruleCopy.Validation.Message,
Success: !deny,
}
incrementAppliedCount(resp)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResp)
ruleResp := processValidationRule(log, ctx, rule)
if ruleResp != nil {
addRuleResponse(log, resp, ruleResp, startTime)
}
}
return resp
}
func validateResourceWithRule(log logr.Logger, ctx *PolicyContext, rule kyverno.Rule) (resp *response.RuleResponse) {
if reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
resp := validatePatterns(log, ctx.JSONContext, ctx.NewResource, rule)
return &resp
func processValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *response.RuleResponse {
v := newValidator(log, ctx, rule)
if rule.Validation.ForEachValidation != nil {
return v.validateForEach()
}
if reflect.DeepEqual(ctx.NewResource, unstructured.Unstructured{}) {
log.V(3).Info("skipping validation on deleted resource")
return v.validate()
}
func addRuleResponse(log logr.Logger, resp *response.EngineResponse, ruleResp *response.RuleResponse, startTime time.Time) {
ruleResp.RuleStats.ProcessingTime = time.Since(startTime)
ruleResp.RuleStats.RuleExecutionTimestamp = startTime.Unix()
log.V(4).Info("finished processing rule", "processingTime", ruleResp.RuleStats.ProcessingTime.String())
if ruleResp.Status == response.RuleStatusPass || ruleResp.Status == response.RuleStatusFail {
incrementAppliedCount(resp)
} else if ruleResp.Status == response.RuleStatusError {
incrementErrorCount(resp)
}
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
}
type validator struct {
log logr.Logger
ctx *PolicyContext
rule *kyverno.Rule
contextEntries []kyverno.ContextEntry
anyAllConditions apiextensions.JSON
pattern apiextensions.JSON
anyPattern apiextensions.JSON
deny *kyverno.Deny
}
func newValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *validator {
ruleCopy := rule.DeepCopy()
return &validator{
log: log,
rule: ruleCopy,
ctx: ctx,
contextEntries: ruleCopy.Context,
anyAllConditions: ruleCopy.AnyAllConditions,
pattern: ruleCopy.Validation.Pattern,
anyPattern: ruleCopy.Validation.AnyPattern,
deny: ruleCopy.Validation.Deny,
}
}
func newForeachValidator(log logr.Logger, ctx *PolicyContext, rule *kyverno.Rule) *validator {
ruleCopy := rule.DeepCopy()
// Variable substitution expects JSON data, so we convert to a map
anyAllConditions, err := common.ToMap(ruleCopy.Validation.ForEachValidation.AnyAllConditions)
if err != nil {
log.Error(err, "failed to convert ruleCopy.Validation.ForEachValidation.AnyAllConditions")
}
return &validator{
log: log,
ctx: ctx,
rule: ruleCopy,
contextEntries: ruleCopy.Validation.ForEachValidation.Context,
anyAllConditions: anyAllConditions,
pattern: ruleCopy.Validation.ForEachValidation.Pattern,
anyPattern: ruleCopy.Validation.ForEachValidation.AnyPattern,
deny: ruleCopy.Validation.ForEachValidation.Deny,
}
}
func (v *validator) validate() *response.RuleResponse {
if err := v.loadContext(); err != nil {
return ruleError(v.rule, "failed to load context", err)
}
preconditionsPassed, err := v.checkPreconditions()
if err != nil {
return ruleError(v.rule, "failed to evaluate preconditions", err)
} else if !preconditionsPassed {
return ruleResponse(v.rule, "preconditions not met", response.RuleStatusSkip)
}
if v.pattern != nil || v.anyPattern != nil {
if err = v.substitutePatterns(); err != nil {
return ruleError(v.rule, "variable substitution failed", err)
}
ruleResponse := v.validateResourceWithRule()
return ruleResponse
} else if v.deny != nil {
ruleResponse := v.validateDeny()
return ruleResponse
}
v.log.Info("invalid validation rule: either patterns or deny conditions are expected")
return nil
}
func (v *validator) validateForEach() *response.RuleResponse {
if err := v.loadContext(); err != nil {
return ruleError(v.rule, "failed to load context", err)
}
preconditionsPassed, err := v.checkPreconditions()
if err != nil {
return ruleError(v.rule, "failed to evaluate preconditions", err)
} else if !preconditionsPassed {
return ruleResponse(v.rule, "preconditions not met", response.RuleStatusSkip)
}
foreach := v.rule.Validation.ForEachValidation
if foreach == nil {
return nil
}
oldResp := validatePatterns(log, ctx.JSONContext, ctx.OldResource, rule)
newResp := validatePatterns(log, ctx.JSONContext, ctx.NewResource, rule)
elements, err := v.evaluateList(foreach.List)
if err != nil {
msg := fmt.Sprintf("failed to evaluate list %s", foreach.List)
return ruleError(v.rule, msg, err)
}
v.ctx.JSONContext.Checkpoint()
defer v.ctx.JSONContext.Restore()
applyCount := 0
for _, e := range elements {
v.ctx.JSONContext.Reset()
ctx := v.ctx.Copy()
if err := addElementToContext(ctx, e); err != nil {
v.log.Error(err, "failed to add element to context")
return ruleError(v.rule, "failed to process foreach", err)
}
foreachValidator := newForeachValidator(v.log, ctx, v.rule)
r := foreachValidator.validate()
if r == nil {
v.log.Info("skipping rule due to empty result")
continue
} else if r.Status == response.RuleStatusSkip {
v.log.Info("skipping rule as preconditions were not met")
continue
} else if r.Status != response.RuleStatusPass {
msg := fmt.Sprintf("validation failed in foreach rule for %v", r.Message)
return ruleResponse(v.rule, msg, r.Status)
}
applyCount++
}
if applyCount == 0 {
return ruleResponse(v.rule, "rule skipped", response.RuleStatusSkip)
}
return ruleResponse(v.rule, "rule passed", response.RuleStatusPass)
}
func addElementToContext(ctx *PolicyContext, e interface{}) error {
data, err := common.ToMap(e)
if err != nil {
return err
}
jsonData := map[string]interface{}{
"element": data,
}
if err := ctx.JSONContext.AddJSONObject(jsonData); err != nil {
return errors.Wrapf(err, "failed to add element (%v) to JSON context", e)
}
u := unstructured.Unstructured{}
u.SetUnstructuredContent(data)
ctx.Element = u
return nil
}
func (v *validator) evaluateList(jmesPath string) ([]interface{}, error) {
i, err := v.ctx.JSONContext.Query(jmesPath)
if err != nil {
return nil, err
}
l, ok := i.([]interface{})
if !ok {
return []interface{}{i}, nil
}
return l, nil
}
func (v *validator) loadContext() error {
if err := LoadContext(v.log, v.contextEntries, v.ctx.ResourceCache, v.ctx, v.rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
v.log.V(3).Info("failed to load context", "reason", err.Error())
} else {
v.log.Error(err, "failed to load context")
}
return err
}
return nil
}
func (v *validator) checkPreconditions() (bool, error) {
preconditions, err := variables.SubstituteAllInPreconditions(v.log, v.ctx.JSONContext, v.anyAllConditions)
if err != nil {
return false, errors.Wrapf(err, "failed to substitute variables in preconditions")
}
typeConditions, err := transformConditions(preconditions)
if err != nil {
return false, errors.Wrapf(err, "failed to parse preconditions")
}
pass := variables.EvaluateConditions(v.log, v.ctx.JSONContext, typeConditions)
return pass, nil
}
func (v *validator) validateDeny() *response.RuleResponse {
anyAllCond := v.deny.AnyAllConditions
anyAllCond, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, anyAllCond)
if err != nil {
return ruleError(v.rule, "failed to substitute variables in deny conditions", err)
}
if err = v.substituteDeny(); err != nil {
return ruleError(v.rule, "failed to substitute variables in rule", err)
}
denyConditions, err := transformConditions(anyAllCond)
if err != nil {
return ruleError(v.rule, "invalid deny conditions", err)
}
deny := variables.EvaluateConditions(v.log, v.ctx.JSONContext, denyConditions)
if deny {
return ruleResponse(v.rule, v.getDenyMessage(deny), response.RuleStatusFail)
}
return ruleResponse(v.rule, v.getDenyMessage(deny), response.RuleStatusPass)
}
func (v *validator) getDenyMessage(deny bool) string {
if !deny {
return fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
}
msg := v.rule.Validation.Message
if msg == "" {
return fmt.Sprintf("validation error: rule %s failed", v.rule.Name)
}
raw, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, msg)
if err != nil {
return msg
}
return raw.(string)
}
func (v *validator) validateResourceWithRule() *response.RuleResponse {
if !isEmptyUnstructured(&v.ctx.Element) {
resp := v.validatePatterns(v.ctx.Element)
return resp
}
if !isEmptyUnstructured(&v.ctx.OldResource) {
resp := v.validatePatterns(v.ctx.NewResource)
return resp
}
if isEmptyUnstructured(&v.ctx.NewResource) {
v.log.V(3).Info("skipping validation on deleted resource")
return nil
}
oldResp := v.validatePatterns(v.ctx.OldResource)
newResp := v.validatePatterns(v.ctx.NewResource)
if isSameRuleResponse(oldResp, newResp) {
log.V(3).Info("skipping modified resource as validation results have not changed")
v.log.V(3).Info("skipping modified resource as validation results have not changed")
return nil
}
return &newResp
return newResp
}
func isEmptyUnstructured(u *unstructured.Unstructured) bool {
if u == nil {
return true
}
if reflect.DeepEqual(*u, unstructured.Unstructured{}) {
return true
}
return false
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(logger logr.Logger, rule kyverno.Rule, ctx *PolicyContext) bool {
err := MatchesResourceDescription(ctx.NewResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
func matches(logger logr.Logger, rule *kyverno.Rule, ctx *PolicyContext) bool {
err := MatchesResourceDescription(ctx.NewResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "")
if err == nil {
return true
}
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(ctx.OldResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
err := MatchesResourceDescription(ctx.OldResource, *rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels, "")
if err == nil {
return true
}
@ -214,7 +436,7 @@ func matches(logger logr.Logger, rule kyverno.Rule, ctx *PolicyContext) bool {
return false
}
func isSameRuleResponse(r1 response.RuleResponse, r2 response.RuleResponse) bool {
func isSameRuleResponse(r1 *response.RuleResponse, r2 *response.RuleResponse) bool {
if r1.Name != r2.Name {
return false
}
@ -227,7 +449,7 @@ func isSameRuleResponse(r1 response.RuleResponse, r2 response.RuleResponse) bool
return false
}
if r1.Success != r2.Success {
if r1.Status != r2.Status {
return false
}
@ -235,57 +457,58 @@ func isSameRuleResponse(r1 response.RuleResponse, r2 response.RuleResponse) bool
}
// validatePatterns validate pattern and anyPattern
func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstructured.Unstructured, rule kyverno.Rule) (resp response.RuleResponse) {
startTime := time.Now()
logger := log.WithValues("rule", rule.Name, "name", resource.GetName(), "kind", resource.GetKind())
logger.V(5).Info("start processing rule", "startTime", startTime)
resp.Name = rule.Name
resp.Type = utils.Validation.String()
defer func() {
resp.RuleStats.ProcessingTime = time.Since(startTime)
resp.RuleStats.RuleExecutionTimestamp = startTime.Unix()
logger.V(4).Info("finished processing rule", "processingTime", resp.RuleStats.ProcessingTime.String())
}()
func (v *validator) validatePatterns(resource unstructured.Unstructured) *response.RuleResponse {
if v.pattern != nil {
if err := validate.MatchPattern(v.log, resource.Object, v.pattern); err != nil {
if pe, ok := err.(*validate.PatternError); ok {
v.log.V(3).Info("validation error", "path", pe.Path, "error", err.Error())
validationRule := rule.Validation.DeepCopy()
if validationRule.Pattern != nil {
pattern := validationRule.Pattern
if pe.Skip {
return ruleResponse(v.rule, pe.Error(), response.RuleStatusSkip)
}
if path, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern); err != nil {
logger.V(3).Info("validation failed", "path", path, "error", err.Error())
resp.Success = false
resp.Message = buildErrorMessage(rule, path)
return resp
if pe.Path == "" {
return ruleResponse(v.rule, v.buildErrorMessage(err, ""), response.RuleStatusError)
}
return ruleResponse(v.rule, v.buildErrorMessage(err, pe.Path), response.RuleStatusFail)
} else {
return ruleResponse(v.rule, v.buildErrorMessage(err, pe.Path), response.RuleStatusError)
}
}
logger.V(4).Info("successfully processed rule")
resp.Success = true
resp.Message = fmt.Sprintf("validation rule '%s' passed.", rule.Name)
return resp
v.log.V(4).Info("successfully processed rule")
msg := fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
return ruleResponse(v.rule, msg, response.RuleStatusPass)
}
if validationRule.AnyPattern != nil {
if v.anyPattern != nil {
var failedAnyPatternsErrors []error
var err error
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
anyPatterns, err := deserializeAnyPattern(v.anyPattern)
if err != nil {
resp.Success = false
resp.Message = fmt.Sprintf("failed to deserialize anyPattern, expected type array: %v", err)
return resp
msg := fmt.Sprintf("failed to deserialize anyPattern, expected type array: %v", err)
return ruleResponse(v.rule, msg, response.RuleStatusError)
}
for idx, pattern := range anyPatterns {
path, err := validate.ValidateResourceWithPattern(logger, resource.Object, pattern)
err := validate.MatchPattern(v.log, resource.Object, pattern)
if err == nil {
resp.Success = true
resp.Message = fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", rule.Name, idx)
return resp
msg := fmt.Sprintf("validation rule '%s' anyPattern[%d] passed.", v.rule.Name, idx)
return ruleResponse(v.rule, msg, response.RuleStatusPass)
}
logger.V(4).Info("validation rule failed", "anyPattern[%d]", idx, "path", path)
patternErr := fmt.Errorf("Rule %s[%d] failed at path %s.", rule.Name, idx, path)
failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr)
if pe, ok := err.(*validate.PatternError); ok {
v.log.V(3).Info("validation rule failed", "anyPattern[%d]", idx, "path", pe.Path)
if pe.Path == "" {
patternErr := fmt.Errorf("Rule %s[%d] failed: %s.", v.rule.Name, idx, err.Error())
failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr)
} else {
patternErr := fmt.Errorf("Rule %s[%d] failed at path %s.", v.rule.Name, idx, pe.Path)
failedAnyPatternsErrors = append(failedAnyPatternsErrors, patternErr)
}
}
}
// Any Pattern validation errors
@ -295,30 +518,60 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr
errorStr = append(errorStr, err.Error())
}
log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", rule.Name, errorStr))
resp.Success = false
resp.Message = buildAnyPatternErrorMessage(rule, errorStr)
return resp
v.log.V(4).Info(fmt.Sprintf("Validation rule '%s' failed. %s", v.rule.Name, errorStr))
msg := buildAnyPatternErrorMessage(v.rule, errorStr)
return ruleResponse(v.rule, msg, response.RuleStatusFail)
}
}
return resp
return ruleResponse(v.rule, v.rule.Validation.Message, response.RuleStatusPass)
}
func buildErrorMessage(rule kyverno.Rule, path string) string {
if rule.Validation.Message == "" {
return fmt.Sprintf("validation error: rule %s failed at path %s", rule.Name, path)
func deserializeAnyPattern(anyPattern apiextensions.JSON) ([]interface{}, error) {
if anyPattern == nil {
return nil, nil
}
if strings.HasSuffix(rule.Validation.Message, ".") {
return fmt.Sprintf("validation error: %s Rule %s failed at path %s", rule.Validation.Message, rule.Name, path)
ap, err := json.Marshal(anyPattern)
if err != nil {
return nil, err
}
return fmt.Sprintf("validation error: %s. Rule %s failed at path %s", rule.Validation.Message, rule.Name, path)
var res []interface{}
if err := json.Unmarshal(ap, &res); err != nil {
return nil, err
}
return res, nil
}
func buildAnyPatternErrorMessage(rule kyverno.Rule, errors []string) string {
func (v *validator) buildErrorMessage(err error, path string) string {
if v.rule.Validation.Message == "" {
if path != "" {
return fmt.Sprintf("validation error: rule %s failed at path %s", v.rule.Name, path)
}
return fmt.Sprintf("validation error: rule %s execution error: %s", v.rule.Name, err.Error())
}
msgRaw, sErr := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.rule.Validation.Message)
if sErr != nil {
v.log.Info("failed to substitute variables in message: %v", sErr)
}
msg := msgRaw.(string)
if !strings.HasSuffix(msg, ".") {
msg = msg + "."
}
if path != "" {
return fmt.Sprintf("validation error: %s Rule %s failed at path %s", msg, v.rule.Name, path)
}
return fmt.Sprintf("validation error: %s Rule %s execution error: %s", msg, v.rule.Name, err.Error())
}
func buildAnyPatternErrorMessage(rule *kyverno.Rule, errors []string) string {
errStr := strings.Join(errors, " ")
if rule.Validation.Message == "" {
return fmt.Sprintf("validation error: %s", errStr)
@ -331,28 +584,54 @@ func buildAnyPatternErrorMessage(rule kyverno.Rule, errors []string) string {
return fmt.Sprintf("validation error: %s. %s", rule.Validation.Message, errStr)
}
func substituteAll(log logr.Logger, ctx *PolicyContext, rule kyverno.Rule, resp *response.EngineResponse) (kyverno.Rule, error) {
var err error
if rule, err = variables.SubstituteAllInRule(log, ctx.JSONContext, rule); err != nil {
ruleResp := response.RuleResponse{
Name: rule.Name,
Type: utils.Validation.String(),
Message: fmt.Sprintf("variable substitution failed for rule %s: %s", rule.Name, err.Error()),
Success: true,
func (v *validator) substitutePatterns() error {
if v.pattern != nil {
i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.pattern)
if err != nil {
return err
}
incrementAppliedCount(resp)
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResp)
switch err.(type) {
case gojmespath.NotFoundError:
log.V(2).Info("failed to substitute variables, skip current rule", "info", err.Error(), "rule name", rule.Name)
default:
log.Error(err, "failed to substitute variables, skip current rule", "rule name", rule.Name)
}
return rule, err
v.pattern = i.(apiextensions.JSON)
return nil
}
return rule, nil
if v.anyPattern != nil {
i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.anyPattern)
if err != nil {
return err
}
v.anyPattern = i.(apiextensions.JSON)
return nil
}
return nil
}
func (v *validator) substituteDeny() error {
if v.deny == nil {
return nil
}
i, err := variables.SubstituteAll(v.log, v.ctx.JSONContext, v.deny)
if err != nil {
return err
}
v.deny = i.(*kyverno.Deny)
return nil
}
func ruleError(rule *kyverno.Rule, msg string, err error) *response.RuleResponse {
msg = fmt.Sprintf("%s: %s", msg, err.Error())
return ruleResponse(rule, msg, response.RuleStatusError)
}
func ruleResponse(rule *kyverno.Rule, msg string, status response.RuleStatus) *response.RuleResponse {
return &response.RuleResponse{
Name: rule.Name,
Type: utils.Validation.String(),
Message: msg,
Status: status,
}
}

View file

@ -2,6 +2,7 @@ package engine
import (
"encoding/json"
"github.com/kyverno/kyverno/pkg/engine/response"
"testing"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
@ -127,10 +128,12 @@ func TestValidate_image_tag_fail(t *testing.T) {
"validation rule 'validate-tag' passed.",
"validation error: imagePullPolicy 'Always' required with tag 'latest'. Rule validate-latest failed at path /spec/containers/0/imagePullPolicy/",
}
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
}
assert.Assert(t, !er.IsSuccessful())
}
@ -1474,9 +1477,9 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
assert.Equal(t, er.PolicyResponse.Rules[0].Message,
"variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
"variable substitution failed: Unknown key \"name1\" in path")
}
func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSubstitutionFails(t *testing.T) {
@ -1566,8 +1569,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed: Unknown key \"name1\" in path")
}
func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
@ -1625,7 +1628,7 @@ func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
}
@ -1716,8 +1719,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: Unknown key \"name1\" in path")
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusError)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed: Unknown key \"name1\" in path")
}
func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
@ -1808,7 +1811,7 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
assert.Equal(t, er.PolicyResponse.Rules[0].Message,
"validation error: Rule test-path-not-exist[0] failed at path /spec/template/spec/containers/0/name/. Rule test-path-not-exist[1] failed at path /spec/template/spec/containers/0/name/.")
}
@ -1912,41 +1915,41 @@ func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, response.RuleStatusFail)
assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.")
}
func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
tests := []struct {
name string
policyRaw []byte
resourceRaw []byte
expectedResult bool
expectedMessage string
name string
policyRaw []byte
resourceRaw []byte
expectedResults []response.RuleStatus
expectedMessages []string
}{
{
name: "path-not-present",
policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
// referred variable path not present
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`),
expectedResult: false,
expectedMessage: "spec.sourceRef.namespace must be the same as metadata.namespace",
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`),
expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusError},
expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "failed to substitute variables in deny conditions: Unknown key \"namespace\" in path"},
},
{
name: "resource-with-violation",
policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace {{request.object.spec.sourceRef.namespace}} must be the same as metadata.namespace {{request.object.metadata.namespace}}","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
// referred variable path present with different value
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"default"},"prune":true,"validation":"client"}}`),
expectedResult: false,
expectedMessage: "spec.sourceRef.namespace default must be the same as metadata.namespace apps",
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"default"},"prune":true,"validation":"client"}}`),
expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusFail},
expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "spec.sourceRef.namespace default must be the same as metadata.namespace apps"},
},
{
name: "resource-comply",
policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
// referred variable path present with same value - validate passes
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"apps"},"prune":true,"validation":"client"}}`),
expectedResult: true,
expectedMessage: "spec.sourceRef.namespace must be the same as metadata.namespace",
resourceRaw: []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"apps"},"prune":true,"validation":"client"}}`),
expectedResults: []response.RuleStatus{response.RuleStatusPass, response.RuleStatusPass},
expectedMessages: []string{"validation rule 'serviceAccountName' passed.", "validation rule 'sourceRefNamespace' passed."},
},
}
@ -1967,10 +1970,8 @@ func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
er := Validate(policyContext)
for i, rule := range er.PolicyResponse.Rules {
if rule.Name == "sourceRefNamespace" {
assert.Equal(t, er.PolicyResponse.Rules[i].Success, test.expectedResult)
assert.Equal(t, er.PolicyResponse.Rules[i].Message, test.expectedMessage, "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessage, rule.Message)
}
assert.Equal(t, er.PolicyResponse.Rules[i].Status, test.expectedResults[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedResults[i].String(), er.PolicyResponse.Rules[i].Status.String())
assert.Equal(t, er.PolicyResponse.Rules[i].Message, test.expectedMessages[i], "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessages[i], rule.Message)
}
}
}
@ -2417,3 +2418,329 @@ func Test_StringInDenyCondition(t *testing.T) {
er := Validate(&PolicyContext{Policy: policy, NewResource: *resourceUnstructured, JSONContext: ctx})
assert.Assert(t, er.IsSuccessful())
}
func Test_foreach_container_pass(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
{"name": "pod2-valid", "image": "nginx/nginx:v2"},
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test-path-not-exist",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
}
}
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
}
func Test_foreach_container_fail(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"pattern": {
"name": "*-valid"
}
}
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
}
func Test_foreach_container_deny_fail(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
{"name": "pod2-invalid", "image": "docker.io/nginx/nginx:v2"},
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
}
}
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
}
func Test_foreach_container_deny_success(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match('{{element.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
}
}
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
}
func Test_foreach_container_deny_error(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "pod1-valid", "image": "nginx/nginx:v1"},
{"name": "pod2-invalid", "image": "nginx/nginx:v2"},
{"name": "pod3-valid", "image": "nginx/nginx:v3"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"deny": {
"conditions": [
{"key": "{{ regex_match_INVALID('{{request.object.image}}', 'docker.io') }}", "operator": "Equals", "value": false}
]
}
}
}}]}}`)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusError)
}
func Test_foreach_context_preconditions(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "podvalid", "image": "nginx/nginx:v1"},
{"name": "podinvalid", "image": "nginx/nginx:v2"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
"preconditions": { "all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": ["podvalid"]
}
]},
"deny": {
"conditions": [
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
]
}
}
}}]}}`)
configMapVariableContext := store.Context{
Policies: []store.Policy{
{
Name: "test",
Rules: []store.Rule{
{
Name: "test",
Values: map[string]string{
"img.data.podvalid": "nginx/nginx:v1",
"img.data.podinvalid": "nginx/nginx:v2",
},
},
},
},
},
}
store.SetContext(configMapVariableContext)
store.SetMock(true)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusPass)
}
func Test_foreach_context_preconditions_fail(t *testing.T) {
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Deployment",
"metadata": {"name": "test"},
"spec": { "template": { "spec": {
"containers": [
{"name": "podvalid", "image": "nginx/nginx:v1"},
{"name": "podinvalid", "image": "nginx/nginx:v2"}
]
}}}}`)
policyraw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "test"},
"spec": {
"rules": [
{
"name": "test",
"match": {"resources": { "kinds": [ "Deployment" ] } },
"validate": {
"foreach": {
"list": "request.object.spec.template.spec.containers",
"context": [{"name": "img", "configMap": {"name": "mycmap", "namespace": "default"}}],
"preconditions": { "all": [
{
"key": "{{element.name}}",
"operator": "In",
"value": ["podvalid", "podinvalid"]
}
]},
"deny": {
"conditions": [
{"key": "{{ element.image }}", "operator": "NotEquals", "value": "{{ img.data.{{ element.name }} }}"}
]
}
}
}}]}}`)
configMapVariableContext := store.Context{
Policies: []store.Policy{
{
Name: "test",
Rules: []store.Rule{
{
Name: "test",
Values: map[string]string{
"img.data.podvalid": "nginx/nginx:v1",
"img.data.podinvalid": "nginx/nginx:v1",
},
},
},
},
},
}
store.SetContext(configMapVariableContext)
store.SetMock(true)
testForEach(t, policyraw, resourceRaw, "", response.RuleStatusFail)
}
func testForEach(t *testing.T, policyraw []byte, resourceRaw []byte, msg string, status response.RuleStatus) {
var policy kyverno.ClusterPolicy
assert.NilError(t, json.Unmarshal(policyraw, &policy))
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(resourceRaw)
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: policy,
JSONContext: ctx,
NewResource: *resourceUnstructured}
er := Validate(policyContext)
assert.Equal(t, er.PolicyResponse.Rules[0].Status, status)
if msg != "" {
assert.Equal(t, er.PolicyResponse.Rules[0].Message, msg)
}
}

View file

@ -18,7 +18,12 @@ import (
)
var RegexVariables = regexp.MustCompile(`\{\{[^{}]*\}\}`)
var RegexReferences = regexp.MustCompile(`\$\(.[^\ ]*\)`)
// Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\'
var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
// Regex for '\$(...)'
var RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`)
// IsVariable returns true if the element contains a 'valid' variable {{}}
func IsVariable(value string) bool {
@ -38,22 +43,13 @@ func ReplaceAllVars(src string, repl func(string) string) string {
return RegexVariables.ReplaceAllStringFunc(src, repl)
}
func SubstituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}) (_ interface{}, err error) {
document, err = substituteReferences(log, document)
if err != nil {
return kyverno.Rule{}, err
}
return substituteVars(log, ctx, document, DefaultVariableResolver)
}
func newPreconditionsVariableResolver(log logr.Logger) VariableResolver {
// PreconditionsVariableResolver is used to substitute vars in preconditions.
// It returns empty string if error occured during substitution
// It returns an empty string if an error occurs during the substitution.
return func(ctx context.EvalInterface, variable string) (interface{}, error) {
value, err := DefaultVariableResolver(ctx, variable)
if err != nil {
log.V(4).Info(fmt.Sprintf("Variable \"%s\" is not resolved in preconditions. Considering it as an empty string", variable))
log.V(4).Info(fmt.Sprintf("using empty string for unresolved variable \"%s\" in preconditions", variable))
return "", nil
}
@ -61,13 +57,68 @@ func newPreconditionsVariableResolver(log logr.Logger) VariableResolver {
}
}
// SubstituteAll substitutes variables and references in the document. The document must be JSON data
// i.e. string, []interface{}, map[string]interface{}
func SubstituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}) (_ interface{}, err error) {
return substituteAll(log, ctx, document, DefaultVariableResolver)
}
func SubstituteAllInPreconditions(log logr.Logger, ctx context.EvalInterface, document interface{}) (_ interface{}, err error) {
document, err = substituteReferences(log, document)
return substituteAll(log, ctx, document, newPreconditionsVariableResolver(log))
}
func SubstituteAllInRule(log logr.Logger, ctx context.EvalInterface, typedRule kyverno.Rule) (_ kyverno.Rule, err error) {
var rule interface{}
rule, err = RuleToUntyped(typedRule)
if err != nil {
return typedRule, err
}
rule, err = SubstituteAll(log, ctx, rule)
if err != nil {
return typedRule, err
}
return UntypedToRule(rule)
}
func RuleToUntyped(rule kyverno.Rule) (interface{}, error) {
jsonRule, err := json.Marshal(rule)
if err != nil {
return nil, err
}
var untyped interface{}
err = json.Unmarshal(jsonRule, &untyped)
if err != nil {
return nil, err
}
return untyped, nil
}
func UntypedToRule(untyped interface{}) (kyverno.Rule, error) {
jsonRule, err := json.Marshal(untyped)
if err != nil {
return kyverno.Rule{}, err
}
return substituteVars(log, ctx, document, newPreconditionsVariableResolver(log))
var rule kyverno.Rule
err = json.Unmarshal(jsonRule, &rule)
if err != nil {
return kyverno.Rule{}, err
}
return rule, nil
}
func substituteAll(log logr.Logger, ctx context.EvalInterface, document interface{}, resolver VariableResolver) (_ interface{}, err error) {
document, err = substituteReferences(log, document)
if err != nil {
return document, err
}
return substituteVars(log, ctx, document, resolver)
}
func SubstituteAllForceMutate(log logr.Logger, ctx context.EvalInterface, typedRule kyverno.Rule) (_ kyverno.Rule, err error) {
@ -95,8 +146,6 @@ func SubstituteAllForceMutate(log logr.Logger, ctx context.EvalInterface, typedR
return UntypedToRule(rule)
}
//SubstituteVars replaces the variables with the values defined in the context
// - if any variable is invalid or has nil value, it is considered as a failed variable substitution
func substituteVars(log logr.Logger, ctx context.EvalInterface, rule interface{}, vr VariableResolver) (interface{}, error) {
return jsonUtils.NewTraversal(rule, substituteVariablesIfAny(log, ctx, vr)).TraverseJSON()
}
@ -156,6 +205,13 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
}
for _, v := range RegexReferences.FindAllString(value, -1) {
initial := v[:2] == `$(`
v_old := v
if !initial {
v = v[1:]
}
resolvedReference, err := resolveReference(log, data.Document, v, data.Path)
if err != nil {
switch err.(type) {
@ -173,7 +229,15 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
log.V(3).Info("reference resolved", "reference", v, "value", resolvedReference, "path", data.Path)
if val, ok := resolvedReference.(string); ok {
value = strings.Replace(value, v, val, -1)
replace_with := ""
if !initial {
replace_with = string(v_old[0])
}
replace_with += val
value = strings.Replace(value, v_old, replace_with, 1)
continue
}
@ -183,6 +247,10 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
}
}
for _, v := range RegexEscpReferences.FindAllString(value, -1) {
value = strings.Replace(value, v, v[1:], -1)
}
return value, nil
})
}
@ -202,6 +270,8 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
return data.Element, nil
}
isDeleteRequest := isDeleteRequest(ctx)
vars := RegexVariables.FindAllString(value, -1)
for len(vars) > 0 {
originalPattern := value
@ -213,8 +283,7 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
variable = strings.Replace(variable, "@", fmt.Sprintf("request.object.%s", getJMESPath(data.Path)), -1)
}
operation, err := ctx.Query("request.operation")
if err == nil && operation == "DELETE" {
if isDeleteRequest {
variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
}
@ -250,6 +319,19 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface, vr Var
})
}
func isDeleteRequest(ctx context.EvalInterface) bool {
if ctx == nil {
return false
}
operation, err := ctx.Query("request.operation")
if err == nil && operation == "DELETE" {
return true
}
return false
}
// getJMESPath converts path to JMES format
func getJMESPath(rawPath string) string {
tokens := strings.Split(rawPath, "/")[3:] // skip empty element and two non-resource (like mutate.overlay)
@ -329,6 +411,12 @@ func valFromReferenceToString(value interface{}, operator string) (string, error
func FindAndShiftReferences(log logr.Logger, value, shift, pivot string) string {
for _, reference := range RegexReferences.FindAllString(value, -1) {
initial := reference[:2] == `$(`
reference_old := reference
if !initial {
reference = reference[1:]
}
index := strings.Index(reference, pivot)
if index == -1 {
@ -341,8 +429,16 @@ func FindAndShiftReferences(log logr.Logger, value, shift, pivot string) string
pivot = pivot + "/" + ruleIndex
}
shiftedReference := strings.Replace(reference, pivot, pivot+"/"+shift, 1)
value = strings.Replace(value, reference, shiftedReference, -1)
shiftedReference := strings.Replace(reference, pivot, pivot+"/"+shift, -1)
replace_with := ""
if !initial {
replace_with = string(reference_old[0])
}
replace_with += shiftedReference
value = strings.Replace(value, reference_old, replace_with, 1)
}
return value
@ -373,57 +469,6 @@ func getValueFromReference(fullDocument interface{}, path string) (interface{},
return element, nil
}
func SubstituteAllInRule(log logr.Logger, ctx context.EvalInterface, typedRule kyverno.Rule) (_ kyverno.Rule, err error) {
var rule interface{}
rule, err = RuleToUntyped(typedRule)
if err != nil {
return typedRule, err
}
rule, err = substituteReferences(log, rule)
if err != nil {
return typedRule, err
}
rule, err = substituteVars(log, ctx, rule, DefaultVariableResolver)
if err != nil {
return typedRule, err
}
return UntypedToRule(rule)
}
func RuleToUntyped(rule kyverno.Rule) (interface{}, error) {
jsonRule, err := json.Marshal(rule)
if err != nil {
return nil, err
}
var untyped interface{}
err = json.Unmarshal(jsonRule, &untyped)
if err != nil {
return nil, err
}
return untyped, nil
}
func UntypedToRule(untyped interface{}) (kyverno.Rule, error) {
jsonRule, err := json.Marshal(untyped)
if err != nil {
return kyverno.Rule{}, err
}
var rule kyverno.Rule
err = json.Unmarshal(jsonRule, &rule)
if err != nil {
return kyverno.Rule{}, err
}
return rule, nil
}
func replaceSubstituteVariables(document interface{}) interface{} {
rawDocument, err := json.Marshal(document)
if err != nil {

View file

@ -1063,3 +1063,58 @@ func TestFindAndShiftReferences_AnyPatternPositiveCase(t *testing.T) {
assert.Equal(t, expectedMessage, actualMessage)
}
func Test_EscpReferenceSubstitution(t *testing.T) {
jsonRaw := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1",
"annotations": {
"test1": "$(../../../../spec/namespace)",
"test2": "\\$(ENV_VAR)",
"test3": "\\${ENV_VAR}",
"test4": "\\\\\\${ENV_VAR}"
}
},
"(spec)": {
"namespace": "n1",
"name": "temp1"
}
}`)
expectedJSON := []byte(`
{
"metadata": {
"name": "temp",
"namespace": "n1",
"annotations": {
"test1": "n1",
"test2": "$(ENV_VAR)",
"test3": "\\${ENV_VAR}",
"test4": "\\\\\\${ENV_VAR}"
}
},
"(spec)": {
"namespace": "n1",
"name": "temp1"
}
}`)
var document interface{}
err := json.Unmarshal(jsonRaw, &document)
assert.NilError(t, err)
var expectedDocument interface{}
err = json.Unmarshal(expectedJSON, &expectedDocument)
assert.NilError(t, err)
ctx := context.NewContext()
err = ctx.AddResource(jsonRaw)
assert.NilError(t, err)
actualDocument, err := SubstituteAll(log.Log, ctx, document)
assert.NilError(t, err)
assert.DeepEqual(t, expectedDocument, actualDocument)
}

View file

@ -188,7 +188,6 @@ func (gen *Generator) syncHandler(key Info) error {
var err error
switch key.Kind {
case "ClusterPolicy":
//TODO: policy is clustered resource so wont need namespace
robj, err = gen.cpLister.Get(key.Name)
if err != nil {
logger.Error(err, "failed to get cluster policy", "name", key.Name)

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/kyverno/kyverno/pkg/engine/response"
"reflect"
"strings"
"time"
@ -19,6 +20,7 @@ import (
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
kyvernoutils "github.com/kyverno/kyverno/pkg/utils"
"k8s.io/api/admission/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -99,6 +101,22 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
return nil, err
}
requestString := gr.Spec.Context.AdmissionRequestInfo.AdmissionRequest
var request v1beta1.AdmissionRequest
err = json.Unmarshal([]byte(requestString), &request)
if err != nil {
logger.Error(err, "error parsing the request string")
}
if gr.Spec.Context.AdmissionRequestInfo.Operation == v1beta1.Update {
request.Operation = gr.Spec.Context.AdmissionRequestInfo.Operation
}
if err := ctx.AddRequest(&request); err != nil {
logger.Error(err, "failed to load request in context")
return nil, err
}
resourceRaw, err := resource.MarshalJSON()
if err != nil {
logger.Error(err, "failed to marshal resource")
@ -149,7 +167,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
var applicableRules []string
// Removing GR if rule is failed. Used when the generate condition failed but gr exist
for _, r := range engineResponse.PolicyResponse.Rules {
if !r.Success {
if r.Status != response.RuleStatusPass {
logger.V(4).Info("querying all generate requests")
selector := labels.SelectorFromSet(labels.Set(map[string]string{
"generate.kyverno.io/policy-name": engineResponse.PolicyResponse.Policy.Name,

View file

@ -4,6 +4,7 @@ import (
"reflect"
"time"
"k8s.io/api/admission/v1beta1"
"k8s.io/client-go/kubernetes"
"github.com/go-logr/logr"
@ -63,7 +64,6 @@ type Controller struct {
// dynamic shared informer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory
//TODO: list of generic informers
// only support Namespaces for re-evaluation on resource updates
nsInformer informers.GenericInformer
log logr.Logger
@ -230,6 +230,7 @@ func (c *Controller) updateGenericResource(old, cur interface{}) {
// re-evaluate the GR as the resource was updated
for _, gr := range grs {
gr.Spec.Context.AdmissionRequestInfo.Operation = v1beta1.Update
c.enqueueGenerateRequest(gr)
}
}
@ -286,6 +287,7 @@ func (c *Controller) updatePolicy(old, cur interface{}) {
// re-evaluate the GR as the policy was updated
for _, gr := range grs {
gr.Spec.Context.AdmissionRequestInfo.Operation = v1beta1.Update
c.enqueueGenerateRequest(gr)
}
}

View file

@ -55,12 +55,12 @@ func validateResourceElement(log logr.Logger, resourceElement, patternElement, o
// elementary values
case string, float64, int, int64, bool, nil:
if !validate.ValidateValueWithPattern(log, resourceElement, patternElement) {
return path, fmt.Errorf("Validation rule failed at '%s' to validate value '%v' with pattern '%v'", path, resourceElement, patternElement)
return path, fmt.Errorf("value '%v' does not match '%v' at path %s", resourceElement, patternElement, path)
}
default:
log.V(4).Info("Pattern contains unknown type", "path", path, "current", fmt.Sprintf("%T", patternElement))
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
return path, fmt.Errorf("failed at path '%s', pattern contains unknown type", path)
}
return "", nil
}
@ -145,7 +145,7 @@ func (dh Handler) Handle(handler resourceElementHandler, resourceMap map[string]
if dh.pattern == "*" && resourceMap[dh.element] != nil {
return "", nil
} else if dh.pattern == "*" && resourceMap[dh.element] == nil {
return dh.path, fmt.Errorf("Validation rule failed at %s, Field %s is not present", dh.path, dh.element)
return dh.path, fmt.Errorf("failed at path %s, field %s is not present", dh.path, dh.element)
} else {
path, err := handler(log.Log, resourceMap[dh.element], dh.pattern, originPattern, currentPath)
if err != nil {

View file

@ -286,7 +286,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
return rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
}
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc)
_, info, err := common.ApplyPolicyOnResource(policy, resource, mutateLogPath, mutateLogPathIsDir, thisPolicyResourceValues, policyReport, namespaceSelectorMap, stdin, rc, true)
if err != nil {
return rc, resources, skippedPolicies, pvInfos, sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}

View file

@ -108,7 +108,7 @@ func buildPolicyResults(infos []policyreport.Info) map[string][]*report.PolicyRe
result.Rule = rule.Name
result.Message = rule.Message
result.Result = report.PolicyResult(rule.Check)
result.Result = report.PolicyResult(rule.Status)
result.Source = policyreport.SourceValue
result.Timestamp = now
results[appname] = append(results[appname], &result)

View file

@ -86,7 +86,7 @@ var rawPolicy = []byte(`
}
`)
var rawEngRes = []byte(`{"PatchedResource":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx1","namespace":"default"},"spec":{"containers":[{"image":"nginx","imagePullPolicy":"IfNotPresent","name":"nginx","resources":{"limits":{"cpu":"200m","memory":"100Mi"},"requests":{"cpu":"100m","memory":"50Mi"}}}]}},"PolicyResponse":{"policy":{"name":"pod-requirements","namespace":""},"resource":{"kind":"Pod","apiVersion":"v1","namespace":"default","name":"nginx1","uid":""},"processingTime":974958,"rulesAppliedCount":2,"policyExecutionTimestamp":1630527712,"rules":[{"name":"pods-require-account","type":"Validation","message":"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/","success":false,"processingTime":28833,"ruleExecutionTimestamp":1630527712},{"name":"pods-require-limits","type":"Validation","message":"validation rule 'pods-require-limits' passed.","success":true,"processingTime":578625,"ruleExecutionTimestamp":1630527712}],"ValidationFailureAction":"audit"}}`)
var rawEngRes = []byte(`{"PatchedResource":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx1","namespace":"default"},"spec":{"containers":[{"image":"nginx","imagePullPolicy":"IfNotPresent","name":"nginx","resources":{"limits":{"cpu":"200m","memory":"100Mi"},"requests":{"cpu":"100m","memory":"50Mi"}}}]}},"PolicyResponse":{"policy":{"name":"pod-requirements","namespace":""},"resource":{"kind":"Pod","apiVersion":"v1","namespace":"default","name":"nginx1","uid":""},"processingTime":974958,"rulesAppliedCount":2,"policyExecutionTimestamp":1630527712,"rules":[{"name":"pods-require-account","type":"Validation","message":"validation error: User pods must include an account for charging. Rule pods-require-account failed at path /metadata/labels/","status":"fail","processingTime":28833,"ruleExecutionTimestamp":1630527712},{"name":"pods-require-limits","type":"Validation","message":"validation rule 'pods-require-limits' passed.","status":"pass","processingTime":578625,"ruleExecutionTimestamp":1630527712}],"ValidationFailureAction":"audit"}}`)
func Test_buildPolicyReports(t *testing.T) {
os.Setenv("POLICY-TYPE", common.PolicyReport)
@ -118,9 +118,9 @@ func Test_buildPolicyReports(t *testing.T) {
assert.Assert(t, report.GetName() == "policyreport-ns-default")
assert.Assert(t, report.GetKind() == "PolicyReport")
assert.Assert(t, len(report.UnstructuredContent()["results"].([]interface{})) == 2)
assert.Assert(t,
report.UnstructuredContent()["summary"].(map[string]interface{})[preport.StatusPass].(int64) == 1,
report.UnstructuredContent()["summary"].(map[string]interface{})[preport.StatusPass].(int64))
summary := report.UnstructuredContent()["summary"].(map[string]interface{})
assert.Assert(t, summary[preport.StatusPass].(int64) == 1, summary[preport.StatusPass].(int64))
}
}
}

View file

@ -524,16 +524,16 @@ func MutatePolices(policies []*v1.ClusterPolicy) ([]*v1.ClusterPolicy, error) {
// ApplyPolicyOnResource - function to apply policy on resource
func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured,
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts) (*response.EngineResponse, policyreport.Info, error) {
mutateLogPath string, mutateLogPathIsDir bool, variables map[string]string, policyReport bool, namespaceSelectorMap map[string]map[string]string, stdin bool, rc *ResultCounts, printPatchResource bool) ([]*response.EngineResponse, policyreport.Info, error) {
var engineResponses []*response.EngineResponse
namespaceLabels := make(map[string]string)
operationIsDelete := false
if variables["request.operation"] == "DELETE" {
operationIsDelete = true
}
namespaceLabels := make(map[string]string)
policyWithNamespaceSelector := false
for _, p := range policy.Spec.Rules {
if p.MatchResources.ResourceDescription.NamespaceSelector != nil ||
@ -547,7 +547,7 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
resourceNamespace := resource.GetNamespace()
namespaceLabels = namespaceSelectorMap[resource.GetNamespace()]
if resourceNamespace != "default" && len(namespaceLabels) < 1 {
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
return engineResponses, policyreport.Info{}, sanitizederror.NewWithError(fmt.Sprintf("failed to get namesapce labels for resource %s. use --values-file flag to pass the namespace labels", resource.GetName()), nil)
}
}
@ -575,10 +575,14 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
}
mutateResponse := engine.Mutate(&engine.PolicyContext{Policy: *policy, NewResource: *resource, JSONContext: ctx, NamespaceLabels: namespaceLabels})
err = processMutateEngineResponse(policy, mutateResponse, resPath, rc, mutateLogPath, stdin, mutateLogPathIsDir, resource.GetName())
if mutateResponse != nil {
engineResponses = append(engineResponses, mutateResponse)
}
err = processMutateEngineResponse(policy, mutateResponse, resPath, rc, mutateLogPath, stdin, mutateLogPathIsDir, resource.GetName(), printPatchResource)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
return &response.EngineResponse{}, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err)
return engineResponses, policyreport.Info{}, sanitizederror.NewWithError("failed to print mutated result", err)
}
}
@ -604,6 +608,9 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
validateResponse = engine.Validate(policyCtx)
info = ProcessValidateEngineResponse(policy, validateResponse, resPath, rc, policyReport)
}
if validateResponse != nil {
engineResponses = append(engineResponses, validateResponse)
}
var policyHasGenerate bool
for _, rule := range policy.Spec.Rules {
@ -624,10 +631,13 @@ func ApplyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unst
NamespaceLabels: namespaceLabels,
}
generateResponse := engine.Generate(policyContext)
if generateResponse != nil {
engineResponses = append(engineResponses, generateResponse)
}
processGenerateEngineResponse(policy, generateResponse, resPath, rc)
}
return validateResponse, info, nil
return engineResponses, info, nil
}
// PrintMutatedOutput - function to print output in provided file or directory
@ -768,20 +778,36 @@ func ProcessValidateEngineResponse(policy *v1.ClusterPolicy, validateResponse *r
Message: valResponseRule.Message,
}
if valResponseRule.Success {
switch valResponseRule.Status {
case response.RuleStatusPass:
rc.Pass++
vrule.Check = report.StatusPass
} else {
vrule.Status = report.StatusPass
case response.RuleStatusFail:
rc.Fail++
vrule.Status = report.StatusFail
if !policyReport {
if printCount < 1 {
fmt.Printf("\npolicy %s -> resource %s failed: \n", policy.Name, resPath)
printCount++
}
fmt.Printf("%d. %s: %s \n", i+1, valResponseRule.Name, valResponseRule.Message)
}
rc.Fail++
vrule.Check = report.StatusFail
case response.RuleStatusError:
rc.Error++
vrule.Status = report.StatusError
case response.RuleStatusWarn:
rc.Warn++
vrule.Status = report.StatusWarn
case response.RuleStatusSkip:
rc.Skip++
vrule.Status = report.StatusSkip
}
violatedRules = append(violatedRules, vrule)
continue
}
@ -793,7 +819,7 @@ func ProcessValidateEngineResponse(policy *v1.ClusterPolicy, validateResponse *r
Name: policyRule.Name,
Type: "Validation",
Message: policyRule.Validation.Message,
Check: report.StatusSkip,
Status: report.StatusSkip,
}
violatedRules = append(violatedRules, vruleSkip)
}
@ -823,7 +849,7 @@ func processGenerateEngineResponse(policy *v1.ClusterPolicy, generateResponse *r
for i, genResponseRule := range generateResponse.PolicyResponse.Rules {
if policyRule.Name == genResponseRule.Name {
ruleFoundInEngineResponse = true
if genResponseRule.Success {
if genResponseRule.Status == response.RuleStatusPass {
rc.Pass++
} else {
if printCount < 1 {
@ -876,7 +902,7 @@ func SetInStoreContext(mutatedPolicies []*v1.ClusterPolicy, variables map[string
return variables
}
func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *response.EngineResponse, resPath string, rc *ResultCounts, mutateLogPath string, stdin bool, mutateLogPathIsDir bool, resourceName string) error {
func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *response.EngineResponse, resPath string, rc *ResultCounts, mutateLogPath string, stdin bool, mutateLogPathIsDir bool, resourceName string, printPatchResource bool) error {
var policyHasMutate bool
for _, rule := range policy.Spec.Rules {
if rule.HasMutate() {
@ -894,7 +920,7 @@ func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *respo
for i, mutateResponseRule := range mutateResponse.PolicyResponse.Rules {
if policyRule.Name == mutateResponseRule.Name {
ruleFoundInEngineResponse = true
if mutateResponseRule.Success {
if mutateResponseRule.Status == response.RuleStatusPass {
rc.Pass++
printMutatedRes = true
} else {
@ -913,7 +939,7 @@ func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *respo
}
}
if printMutatedRes {
if printMutatedRes && printPatchResource {
yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object)
if err != nil {
return sanitizederror.NewWithError("failed to marshal", err)
@ -925,8 +951,7 @@ func processMutateEngineResponse(policy *v1.ClusterPolicy, mutateResponse *respo
if !stdin {
fmt.Printf("\nmutate policy %s applied to %s:", policy.Name, resPath)
}
fmt.Printf("\n" + mutatedResource)
fmt.Printf("\n")
fmt.Printf("\n" + mutatedResource + "\n")
}
} else {
err := PrintMutatedOutput(mutateLogPath, mutateLogPathIsDir, string(yamlEncodedResource), resourceName+"-mutated")
@ -993,3 +1018,34 @@ func GetKindsFromPolicy(policy *v1.ClusterPolicy) map[string]struct{} {
}
return kindOnwhichPolicyIsApplied
}
//GetPatchedResourceFromPath - get patchedResource from given path
func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (unstructured.Unstructured, error) {
var patchedResourceBytes []byte
var patchedResource unstructured.Unstructured
var err error
if isGit {
if len(path) > 0 {
filep, err := fs.Open(filepath.Join(policyResourcePath, path))
if err != nil {
fmt.Printf("Unable to open patchedResource file: %s. \nerror: %s", path, err)
}
patchedResourceBytes, err = ioutil.ReadAll(filep)
}
} else {
patchedResourceBytes, err = getFileBytes(path)
}
if err != nil {
fmt.Printf("\n----------------------------------------------------------------------\nfailed to load patchedResource: %s. \nerror: %s\n----------------------------------------------------------------------\n", path, err)
return patchedResource, err
}
patchedResource, err = GetPatchedResource(patchedResourceBytes)
if err != nil {
return patchedResource, err
}
return patchedResource, nil
}

View file

@ -98,7 +98,7 @@ func Test_NamespaceSelector(t *testing.T) {
for _, tc := range testcases {
policyArray, _ := ut.GetPolicy(tc.policy)
resourceArray, _ := GetResource(tc.resource)
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, rc)
ApplyPolicyOnResource(policyArray[0], resourceArray[0], "", false, nil, false, tc.namespaceSelectorMap, false, rc, false)
assert.Assert(t, int64(rc.Pass) == int64(tc.result.Pass))
assert.Assert(t, int64(rc.Fail) == int64(tc.result.Fail))
assert.Assert(t, int64(rc.Skip) == int64(tc.result.Skip))

View file

@ -275,6 +275,14 @@ func convertResourceToUnstructured(resourceYaml []byte) (*unstructured.Unstructu
return resource, nil
}
// GetPatchedResource converts raw bytes to unstructured object
func GetPatchedResource(patchResourceBytes []byte) (patchedResource unstructured.Unstructured, err error) {
getPatchedResource, err := GetResource(patchResourceBytes)
patchedResource = *getPatchedResource[0]
return patchedResource, nil
}
// getKindsFromPolicy will return the kinds from policy match block
func getKindsFromPolicy(rule v1.Rule) map[string]bool {
var resourceTypesMap = make(map[string]bool)

View file

@ -7,6 +7,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@ -14,11 +15,13 @@ import (
"github.com/fatih/color"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-logr/logr"
"github.com/kataras/tablewriter"
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/generate"
"github.com/kyverno/kyverno/pkg/kyverno/common"
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
"github.com/kyverno/kyverno/pkg/kyverno/store"
@ -30,17 +33,97 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
log "sigs.k8s.io/controller-runtime/pkg/log"
)
var longHelp = `
Test command provides a facility to test policies on resources. User should provide the path of the folder containing test.yaml file.
kyverno test <path_to_folder_Contaning_test.yamls>
or
kyverno test <path_to_githubRepository>
The test.yaml have 4 parts:
"policies" --> list of policies which are applied
"resources" --> list of resources on which the policies are applied
"variables" --> variable file path (this is an optinal parameter)
"results" --> list of result expected on applying the policies on the resources
`
var exampleHelp = `
test.yaml format:
For Validate Policy
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
result: <pass/fail/skip>
For Mutate Policy
1) Policy (Namespaced-policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <policy_namespace>/<policy_name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
2) ClusterPolicy(Cluster-wide policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
Result description:
pass --> patched Resource generated from engine equals to patched Resource provided by the user.
fail --> patched Resource generated from engine is not equals to patched provided by the user.
skip --> rule is not applied.
For more visit --> https://kyverno.io/docs/kyverno-cli/#test
`
// Command returns version command
func Command() *cobra.Command {
var cmd *cobra.Command
var valuesFile, fileName string
cmd = &cobra.Command{
Use: "test",
Short: "run tests from directory",
Use: "test",
Short: "run tests from directory",
Long: longHelp,
Example: exampleHelp,
RunE: func(cmd *cobra.Command, dirPath []string) (err error) {
defer func() {
if err != nil {
@ -50,11 +133,13 @@ func Command() *cobra.Command {
}
}
}()
_, err = testCommandExecute(dirPath, valuesFile, fileName)
if err != nil {
log.Log.V(3).Info("a directory is required")
return err
}
return nil
},
}
@ -76,6 +161,9 @@ type TestResults struct {
Result report.PolicyResult `json:"result"`
Status report.PolicyResult `json:"status"`
Resource string `json:"resource"`
Kind string `json:"kind"`
Namespace string `json:"namespace"`
PatchedResource string `json:"patchedResource"`
AutoGeneratedRule string `json:"auto_generated_rule"`
}
@ -192,14 +280,16 @@ func testCommandExecute(dirPath []string, valuesFile string, fileName string) (r
}
if len(errors) > 0 && log.Log.V(1).Enabled() {
fmt.Printf("ignoring errors: \n")
fmt.Printf("test errors: \n")
for _, e := range errors {
fmt.Printf(" %v \n", e.Error())
}
}
if rc.Fail > 0 {
os.Exit(1)
}
os.Exit(0)
return rc, nil
}
@ -235,13 +325,16 @@ func getLocalDirTestFiles(fs billy.Filesystem, path, fileName, valuesFile string
return errors
}
func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResults, infos []policyreport.Info) (map[string]report.PolicyReportResult, []TestResults) {
func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResults, infos []policyreport.Info, policyResourcePath string, fs billy.Filesystem, isGit bool) (map[string]report.PolicyReportResult, []TestResults) {
results := make(map[string]report.PolicyReportResult)
now := metav1.Timestamp{Seconds: time.Now().Unix()}
for _, resp := range resps {
policyName := resp.PolicyResponse.Policy.Name
resourceName := resp.PolicyResponse.Resource.Name
resourceKind := resp.PolicyResponse.Resource.Kind
resourceNamespace := resp.PolicyResponse.Resource.Namespace
policyNamespace := resp.PolicyResponse.Policy.Namespace
var rules []string
for _, rule := range resp.PolicyResponse.Rules {
@ -255,29 +348,84 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
Name: resourceName,
},
},
Message: buildMessage(resp),
}
var patcheResourcePath []string
for i, test := range testResults {
var userDefinedPolicyNamespace string
var userDefinedPolicyName string
found, err := isNamespacedPolicy(test.Policy)
if err != nil {
log.Log.V(3).Info("error while checking the policy is namespaced or not", "policy: ", test.Policy, "error: ", err)
continue
}
if found {
userDefinedPolicyNamespace, userDefinedPolicyName = getUserDefinedPolicyNameAndNamespace(test.Policy)
test.Policy = userDefinedPolicyName
}
if test.Policy == policyName && test.Resource == resourceName {
var resultsKey string
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
if !util.ContainsString(rules, test.Rule) {
if !util.ContainsString(rules, "autogen-"+test.Rule) {
if !util.ContainsString(rules, "autogen-cronjob-"+test.Rule) {
result.Result = report.StatusSkip
} else {
testResults[i].AutoGeneratedRule = "autogen-cronjob"
test.Rule = "autogen-cronjob-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
}
} else {
testResults[i].AutoGeneratedRule = "autogen"
test.Rule = "autogen-" + test.Rule
resultsKey = GetResultKeyAccordingToTestResults(userDefinedPolicyNamespace, test.Policy, test.Rule, test.Namespace, test.Kind, test.Resource)
}
if results[resultsKey].Result == "" {
result.Result = report.StatusSkip
results[resultsKey] = result
}
}
resultsKey := fmt.Sprintf("%s-%s-%s", test.Policy, test.Rule, test.Resource)
patcheResourcePath = append(patcheResourcePath, test.PatchedResource)
if _, ok := results[resultsKey]; !ok {
results[resultsKey] = result
}
}
}
for _, rule := range resp.PolicyResponse.Rules {
if rule.Type != utils.Mutation.String() {
continue
}
var resultsKey []string
var resultKey string
var result report.PolicyReportResult
resultsKey = GetAllPossibleResultsKey(policyNamespace, policyName, rule.Name, resourceNamespace, resourceKind, resourceName)
for _, resultK := range resultsKey {
if val, ok := results[resultK]; ok {
result = val
resultKey = resultK
} else {
continue
}
var x string
for _, path := range patcheResourcePath {
result.Result = report.StatusFail
x = getAndComparePatchedResource(path, resp.PatchedResource, isGit, policyResourcePath, fs)
if x == "pass" {
result.Result = report.StatusPass
break
}
}
results[resultKey] = result
}
}
}
@ -289,18 +437,23 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
}
var result report.PolicyReportResult
resultsKey := fmt.Sprintf("%s-%s-%s", info.PolicyName, rule.Name, infoResult.Resource.Name)
if val, ok := results[resultsKey]; ok {
result = val
} else {
continue
var resultsKey []string
var resultKey string
resultsKey = GetAllPossibleResultsKey("", info.PolicyName, rule.Name, infoResult.Resource.Namespace, infoResult.Resource.Kind, infoResult.Resource.Name)
for _, resultK := range resultsKey {
if val, ok := results[resultK]; ok {
result = val
resultKey = resultK
} else {
continue
}
}
result.Rule = rule.Name
result.Result = report.PolicyResult(rule.Check)
result.Result = report.PolicyResult(rule.Status)
result.Source = policyreport.SourceValue
result.Timestamp = now
results[resultsKey] = result
results[resultKey] = result
}
}
}
@ -308,20 +461,90 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
return results, testResults
}
func getPolicyResourceFullPath(path []string, policyResourcePath string, isGit bool) []string {
var pol []string
if !isGit {
for _, p := range path {
pol = append(pol, filepath.Join(policyResourcePath, p))
}
return pol
func GetAllPossibleResultsKey(policyNs, policy, rule, resourceNsnamespace, kind, resource string) []string {
var resultsKey []string
resultKey1 := fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
resultKey2 := fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNsnamespace, kind, resource)
resultKey3 := fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
resultKey4 := fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNsnamespace, kind, resource)
resultsKey = append(resultsKey, resultKey1, resultKey2, resultKey3, resultKey4)
return resultsKey
}
func GetResultKeyAccordingToTestResults(policyNs, policy, rule, resourceNs, kind, resource string) string {
var resultKey string
resultKey = fmt.Sprintf("%s-%s-%s-%s", policy, rule, kind, resource)
if policyNs != "" && resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", policyNs, policy, rule, resourceNs, kind, resource)
} else if policyNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policyNs, policy, rule, kind, resource)
} else if resourceNs != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", policy, rule, resourceNs, kind, resource)
}
return path
return resultKey
}
func isNamespacedPolicy(policyNames string) (bool, error) {
return regexp.MatchString("^[a-z]*/[a-z]*", policyNames)
}
func getUserDefinedPolicyNameAndNamespace(policyName string) (string, string) {
if strings.Contains(policyName, "/") {
policy_n_ns := strings.Split(policyName, "/")
namespace := policy_n_ns[0]
policy := policy_n_ns[1]
return namespace, policy
}
return "", policyName
}
// getAndComparePatchedResource --> Get the patchedResource from the path provided by user
// And compare this patchedResource with engine generated patcheResource.
func getAndComparePatchedResource(path string, enginePatchedResource unstructured.Unstructured, isGit bool, policyResourcePath string, fs billy.Filesystem) string {
var status string
patchedResources, err := common.GetPatchedResourceFromPath(fs, path, isGit, policyResourcePath)
if err != nil {
os.Exit(1)
}
var log logr.Logger
matched, err := generate.ValidateResourceWithPattern(log, enginePatchedResource.UnstructuredContent(), patchedResources.UnstructuredContent())
if err != nil {
status = "fail"
}
if matched == "" {
status = "pass"
}
return status
}
func buildMessage(resp *response.EngineResponse) string {
var bldr strings.Builder
for _, ruleResp := range resp.PolicyResponse.Rules {
fmt.Fprintf(&bldr, " %s: %s \n", ruleResp.Name, ruleResp.Status.String())
fmt.Fprintf(&bldr, " %s \n", ruleResp.Message)
}
return bldr.String()
}
func getFullPath(paths []string, policyResourcePath string, isGit bool) []string {
var pols []string
var pol string
if !isGit {
for _, path := range paths {
pol = filepath.Join(policyResourcePath, path)
pols = append(pols, pol)
}
return pols
}
return paths
}
func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile string, isGit bool, policyResourcePath string, rc *resultCounts) (err error) {
openAPIController, err := openapi.NewOpenAPIController()
validateEngineResponses := make([]*response.EngineResponse, 0)
engineResponses := make([]*response.EngineResponse, 0)
var dClient *client.Client
values := &Test{}
var variablesString string
@ -334,7 +557,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
}
fmt.Printf("\nExecuting %s...", values.Name)
valuesFile = values.Variables
variables, globalValMap, valuesMap, namespaceSelectorMap, err := common.GetVariable(variablesString, values.Variables, fs, isGit, policyResourcePath)
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
@ -343,10 +566,16 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return err
}
fullPolicyPath := getPolicyResourceFullPath(values.Policies, policyResourcePath, isGit)
fullResourcePath := getPolicyResourceFullPath(values.Resources, policyResourcePath, isGit)
policyFullPath := getFullPath(values.Policies, policyResourcePath, isGit)
resourceFullPath := getFullPath(values.Resources, policyResourcePath, isGit)
policies, err := common.GetPoliciesFromPaths(fs, fullPolicyPath, isGit, policyResourcePath)
for i, result := range values.Results {
arrPatchedResource := []string{result.PatchedResource}
patchedResourceFullPath := getFullPath(arrPatchedResource, policyResourcePath, isGit)
values.Results[i].PatchedResource = patchedResourceFullPath[0]
}
policies, err := common.GetPoliciesFromPaths(fs, policyFullPath, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load policies\nCause: %s\n", err)
os.Exit(1)
@ -364,7 +593,7 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError("failed to print mutated policy", err)
}
resources, err := common.GetResourceAccordingToResourcePath(fs, fullResourcePath, false, mutatedPolicies, dClient, "", false, isGit, policyResourcePath)
resources, err := common.GetResourceAccordingToResourcePath(fs, resourceFullPath, false, mutatedPolicies, dClient, "", false, isGit, policyResourcePath)
if err != nil {
fmt.Printf("Error: failed to load resources\nCause: %s\n", err)
os.Exit(1)
@ -384,9 +613,6 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
fmt.Printf("\napplying %s to %s... \n", msgPolicies, msgResources)
}
if variablesString != "" {
variables = common.SetInStoreContext(mutatedPolicies, variables)
}
for _, policy := range mutatedPolicies {
err := policy2.Validate(policy, nil, true, openAPIController)
if err != nil {
@ -414,20 +640,20 @@ func applyPoliciesFromPath(fs billy.Filesystem, policyBytes []byte, valuesFile s
return sanitizederror.NewWithError(fmt.Sprintf("policy `%s` have variables. pass the values for the variables for resource `%s` using set/values_file flag", policy.Name, resource.GetName()), err)
}
validateErs, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, &resultCounts)
ers, info, err := common.ApplyPolicyOnResource(policy, resource, "", false, thisPolicyResourceValues, true, namespaceSelectorMap, false, &resultCounts, false)
if err != nil {
return sanitizederror.NewWithError(fmt.Errorf("failed to apply policy %v on resource %v", policy.Name, resource.GetName()).Error(), err)
}
validateEngineResponses = append(validateEngineResponses, validateErs)
engineResponses = append(engineResponses, ers...)
pvInfos = append(pvInfos, info)
}
}
resultsMap, testResults := buildPolicyResults(validateEngineResponses, values.Results, pvInfos)
resultsMap, testResults := buildPolicyResults(engineResponses, values.Results, pvInfos, policyResourcePath, fs, isGit)
resultErr := printTestResult(resultsMap, testResults, rc)
if resultErr != nil {
return sanitizederror.NewWithError("Unable to genrate result. Error:", resultErr)
return sanitizederror.NewWithError("failed to print test result:", resultErr)
}
return
}
@ -438,13 +664,19 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
boldRed := color.New(color.FgRed).Add(color.Bold)
boldYellow := color.New(color.FgYellow).Add(color.Bold)
boldFgCyan := color.New(color.FgCyan).Add(color.Bold)
for i, v := range testResults {
res := new(Table)
res.ID = i + 1
res.Policy = boldFgCyan.Sprintf(v.Policy)
res.Rule = boldFgCyan.Sprintf(v.Rule)
res.Resource = boldFgCyan.Sprintf(v.Resource)
namespace := "default"
if v.Namespace != "" {
namespace = v.Namespace
}
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
var ruleNameInResultKey string
if v.AutoGeneratedRule != "" {
ruleNameInResultKey = fmt.Sprintf("%s-%s", v.AutoGeneratedRule, v.Rule)
@ -452,7 +684,20 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
ruleNameInResultKey = v.Rule
}
resultKey := fmt.Sprintf("%s-%s-%s", v.Policy, ruleNameInResultKey, v.Resource)
resultKey := fmt.Sprintf("%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
found, _ := isNamespacedPolicy(v.Policy)
var ns string
ns, v.Policy = getUserDefinedPolicyNameAndNamespace(v.Policy)
if found && v.Namespace != "" {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
} else if found {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", ns, v.Policy, ruleNameInResultKey, v.Kind, v.Resource)
res.Policy = boldFgCyan.Sprintf(ns) + "/" + boldFgCyan.Sprintf(v.Policy)
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
} else if v.Namespace != "" {
res.Resource = boldFgCyan.Sprintf(namespace) + "/" + boldFgCyan.Sprintf(v.Kind) + "/" + boldFgCyan.Sprintf(v.Resource)
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
}
var testRes report.PolicyReportResult
if val, ok := resps[resultKey]; ok {
@ -463,10 +708,13 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
table = append(table, res)
continue
}
if v.Result == "" && v.Status != "" {
v.Result = v.Status
}
if testRes.Result == v.Result {
res.Result = boldGreen.Sprintf("Pass")
if testRes.Result == report.StatusSkip {
res.Result = boldGreen.Sprintf("Pass")
rc.Skip++
@ -478,8 +726,10 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
res.Result = boldRed.Sprintf("Fail")
rc.Fail++
}
table = append(table, res)
}
printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
printer.CenterSeparator = "│"
printer.ColumnSeparator = "│"
@ -488,8 +738,10 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
printer.RowLengthTitle = func(rowsLength int) bool {
return rowsLength > 10
}
printer.HeaderBgColor = tablewriter.BgBlackColor
printer.HeaderFgColor = tablewriter.FgGreenColor
fmt.Printf("\n")
printer.Print(table)
return nil
}

View file

@ -47,7 +47,7 @@ func NewPromConfig(metricsConfigData *config.MetricsConfigData, log logr.Logger)
)
policyRuleInfoLabels := []string{
"policy_validation_mode", "policy_type", "policy_background_mode", "policy_namespace", "policy_name", "rule_name", "rule_type",
"policy_validation_mode", "policy_type", "policy_background_mode", "policy_namespace", "policy_name", "rule_name", "rule_type", "status_ready",
}
policyRuleInfoMetric := prom.NewGaugeVec(
prom.GaugeOpts{

View file

@ -84,7 +84,7 @@ func (pc PromConfig) ProcessEngineResponse(policy kyverno.ClusterPolicy, engineR
ruleName := rule.Name
ruleType := ParseRuleTypeFromEngineRuleResponse(rule)
ruleResult := metrics.Fail
if rule.Success {
if rule.Status == response.RuleStatusPass {
ruleResult = metrics.Pass
}

View file

@ -77,7 +77,7 @@ func (pc PromConfig) ProcessEngineResponse(policy kyverno.ClusterPolicy, engineR
ruleName := rule.Name
ruleType := ParseRuleTypeFromEngineRuleResponse(rule)
ruleResult := metrics.Fail
if rule.Success {
if rule.Status == response.RuleStatusPass {
ruleResult = metrics.Pass
}

View file

@ -15,6 +15,7 @@ func (pc PromConfig) registerPolicyRuleInfoMetric(
policyNamespace, policyName, ruleName string,
ruleType metrics.RuleType,
metricChangeType PolicyRuleInfoMetricChangeType,
ready bool,
) error {
var metricValue float64
switch metricChangeType {
@ -40,6 +41,11 @@ func (pc PromConfig) registerPolicyRuleInfoMetric(
policyNamespace = "-"
}
status := "false"
if ready {
status = "true"
}
pc.Metrics.PolicyRuleInfo.With(prom.Labels{
"policy_validation_mode": string(policyValidationMode),
"policy_type": string(policyType),
@ -48,6 +54,7 @@ func (pc PromConfig) registerPolicyRuleInfoMetric(
"policy_name": policyName,
"rule_name": ruleName,
"rule_type": string(ruleType),
"status_ready": status,
}).Set(metricValue)
return nil
@ -64,12 +71,13 @@ func (pc PromConfig) AddPolicy(policy interface{}) error {
policyType := metrics.Cluster
policyNamespace := "" // doesn't matter for cluster policy
policyName := inputPolicy.ObjectMeta.Name
ready := inputPolicy.Status.Ready
// registering the metrics on a per-rule basis
for _, rule := range inputPolicy.Spec.Rules {
ruleName := rule.Name
ruleType := metrics.ParseRuleType(rule)
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleCreated); err != nil {
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleCreated, ready); err != nil {
return err
}
}
@ -83,12 +91,13 @@ func (pc PromConfig) AddPolicy(policy interface{}) error {
policyType := metrics.Namespaced
policyNamespace := inputPolicy.ObjectMeta.Namespace
policyName := inputPolicy.ObjectMeta.Name
ready := inputPolicy.Status.Ready
// registering the metrics on a per-rule basis
for _, rule := range inputPolicy.Spec.Rules {
ruleName := rule.Name
ruleType := metrics.ParseRuleType(rule)
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleCreated); err != nil {
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleCreated, ready); err != nil {
return err
}
}
@ -112,8 +121,9 @@ func (pc PromConfig) RemovePolicy(policy interface{}) error {
policyName := inputPolicy.ObjectMeta.Name
ruleName := rule.Name
ruleType := metrics.ParseRuleType(rule)
ready := inputPolicy.Status.Ready
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleDeleted); err != nil {
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleDeleted, ready); err != nil {
return err
}
}
@ -130,8 +140,9 @@ func (pc PromConfig) RemovePolicy(policy interface{}) error {
policyName := inputPolicy.ObjectMeta.Name
ruleName := rule.Name
ruleType := metrics.ParseRuleType(rule)
ready := inputPolicy.Status.Ready
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleDeleted); err != nil {
if err = pc.registerPolicyRuleInfoMetric(policyValidationMode, policyType, policyBackgroundMode, policyNamespace, policyName, ruleName, ruleType, PolicyRuleDeleted, ready); err != nil {
return err
}
}

View file

@ -21,7 +21,11 @@ type Validation interface {
// - Mutate
// - Validation
// - Generate
func validateActions(idx int, rule kyverno.Rule, client *dclient.Client, mock bool) error {
func validateActions(idx int, rule *kyverno.Rule, client *dclient.Client, mock bool) error {
if rule == nil {
return nil
}
var checker Validation
// Mutate
@ -34,7 +38,7 @@ func validateActions(idx int, rule kyverno.Rule, client *dclient.Client, mock bo
// Validate
if rule.HasValidate() {
checker = validate.NewValidateFactory(rule.Validation)
checker = validate.NewValidateFactory(&rule.Validation)
if path, err := checker.Validate(); err != nil {
return fmt.Errorf("path: spec.rules[%d].validate.%s.: %v", idx, path, err)
}

View file

@ -128,7 +128,7 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
if !jsonpatch.Equal(patchedResource, rawResource) {
log.V(4).Info("policy rule conditions not satisfied by resource", "rule", rule.Name)
engineResponse.PolicyResponse.Rules[index].Success = false
engineResponse.PolicyResponse.Rules[index].Status = response.RuleStatusFail
engineResponse.PolicyResponse.Rules[index].Message = fmt.Sprintf("mutation json patches not found at resource path %s", extractPatchPath(patches, log))
}
}

View file

@ -25,6 +25,38 @@ func ContainsVariablesOtherThanObject(policy kyverno.ClusterPolicy) error {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/%s", idx, path)
}
if len(rule.MatchResources.Any) > 0 {
for i, value := range rule.MatchResources.Any {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/any[%d]/%s", idx, i, path)
}
}
}
if len(rule.MatchResources.All) > 0 {
for i, value := range rule.MatchResources.All {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/match/all[%d]/%s", idx, i, path)
}
}
}
if len(rule.ExcludeResources.All) > 0 {
for i, value := range rule.ExcludeResources.All {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/any[%d]/%s", idx, i, path)
}
}
}
if len(rule.ExcludeResources.Any) > 0 {
for i, value := range rule.ExcludeResources.Any {
if path := userInfoDefined(value.UserInfo); path != "" {
return fmt.Errorf("invalid variable used at path: spec/rules[%d]/exclude/all[%d]/%s", idx, i, path)
}
}
}
filterVars := []string{"request.object", "request.namespace", "images"}
ctx := context.NewContext(filterVars...)

View file

@ -19,7 +19,7 @@ func ValidatePattern(patternElement interface{}, path string, supportedAnchors [
//TODO? check operator
return "", nil
default:
return path, fmt.Errorf("Validation rule failed at '%s', pattern contains unknown type", path)
return path, fmt.Errorf("error at '%s', pattern contains unknown type", path)
}
}
func validateMap(patternMap map[string]interface{}, path string, supportedAnchors []commonAnchors.IsAnchor) (string, error) {

View file

@ -29,23 +29,8 @@ func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPoli
continue
}
match := rule.MatchResources
exclude := rule.ExcludeResources
for _, value := range match.Any {
pc.processExistingKinds(value.ResourceDescription.Kinds, policy, rule, logger)
}
for _, value := range match.All {
pc.processExistingKinds(value.ResourceDescription.Kinds, policy, rule, logger)
}
for _, value := range exclude.All {
pc.processExistingKinds(value.ResourceDescription.Kinds, policy, rule, logger)
}
for _, value := range exclude.Any {
pc.processExistingKinds(value.ResourceDescription.Kinds, policy, rule, logger)
}
pc.processExistingKinds(match.Kinds, policy, rule, logger)
matchKinds := rule.MatchKinds()
pc.processExistingKinds(matchKinds, policy, rule, logger)
}
}
@ -169,7 +154,6 @@ type resourceManager interface {
}
//Drop drop the cache after every rebuild interval mins
//TODO: or drop based on the size
func (rm *ResourceManager) Drop() {
timeSince := time.Since(rm.time)
if timeSince > time.Duration(rm.rebuildTime)*time.Second {

126
pkg/policy/metrics.go Normal file
View file

@ -0,0 +1,126 @@
package policy
import (
"reflect"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
policyChangesMetric "github.com/kyverno/kyverno/pkg/metrics/policychanges"
policyRuleInfoMetric "github.com/kyverno/kyverno/pkg/metrics/policyruleinfo"
)
func (pc *PolicyController) registerPolicyRuleInfoMetricAddPolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricUpdatePolicy(logger logr.Logger, oldP, curP *kyverno.ClusterPolicy) {
// removing the old rules associated metrics
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(oldP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
// adding the new rules associated metrics
err = policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(curP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricDeletePolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricAddPolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyCreated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricUpdatePolicy(logger logr.Logger, oldP, curP *kyverno.ClusterPolicy) {
if reflect.DeepEqual((*oldP).Spec, (*curP).Spec) {
return
}
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(oldP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", oldP.Name)
}
// curP will require a new kyverno_policy_changes_total metric if the above update involved change in the following fields:
if curP.Spec.Background != oldP.Spec.Background || curP.Spec.ValidationFailureAction != oldP.Spec.ValidationFailureAction {
err = policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(curP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", curP.Name)
}
}
}
func (pc *PolicyController) registerPolicyChangesMetricDeletePolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyDeleted)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricDeleteNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricAddNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyCreated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricUpdateNsPolicy(logger logr.Logger, oldP, curP *kyverno.Policy) {
if reflect.DeepEqual((*oldP).Spec, (*curP).Spec) {
return
}
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(oldP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", oldP.Name)
}
// curP will require a new kyverno_policy_changes_total metric if the above update involved change in the following fields:
if curP.Spec.Background != oldP.Spec.Background || curP.Spec.ValidationFailureAction != oldP.Spec.ValidationFailureAction {
err = policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(curP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", curP.Name)
}
}
}
func (pc *PolicyController) registerPolicyChangesMetricDeleteNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyDeleted)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricAddNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricUpdateNsPolicy(logger logr.Logger, oldP, curP *kyverno.Policy) {
// removing the old rules associated metrics
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(oldP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
// adding the new rules associated metrics
err = policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(curP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
}

View file

@ -21,7 +21,6 @@ import (
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/kyverno/common"
"github.com/kyverno/kyverno/pkg/metrics"
policyRuleInfoMetric "github.com/kyverno/kyverno/pkg/metrics/policyruleinfo"
pm "github.com/kyverno/kyverno/pkg/policymutation"
"github.com/kyverno/kyverno/pkg/policyreport"
"github.com/kyverno/kyverno/pkg/resourcecache"
@ -40,8 +39,6 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
policyChangesMetric "github.com/kyverno/kyverno/pkg/metrics/policychanges"
)
const (
@ -176,7 +173,6 @@ func NewPolicyController(
// resource manager
// rebuild after 300 seconds/ 5 mins
//TODO: pass the time in seconds instead of converting it internally
pc.rm = NewResourceManager(30)
return &pc, nil
@ -197,64 +193,6 @@ func (pc *PolicyController) canBackgroundProcess(p *kyverno.ClusterPolicy) bool
return true
}
func (pc *PolicyController) registerPolicyRuleInfoMetricAddPolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricUpdatePolicy(logger logr.Logger, oldP, curP *kyverno.ClusterPolicy) {
// removing the old rules associated metrics
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(oldP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
// adding the new rules associated metrics
err = policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(curP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricDeletePolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricAddPolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyCreated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricUpdatePolicy(logger logr.Logger, oldP, curP *kyverno.ClusterPolicy) {
if reflect.DeepEqual((*oldP).Spec, (*curP).Spec) {
return
}
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(oldP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", oldP.Name)
}
// curP will require a new kyverno_policy_changes_total metric if the above update involved change in the following fields:
if curP.Spec.Background != oldP.Spec.Background || curP.Spec.ValidationFailureAction != oldP.Spec.ValidationFailureAction {
err = policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(curP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", curP.Name)
}
}
}
func (pc *PolicyController) registerPolicyChangesMetricDeletePolicy(logger logr.Logger, p *kyverno.ClusterPolicy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyDeleted)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) addPolicy(obj interface{}) {
logger := pc.log
p := obj.(*kyverno.ClusterPolicy)
@ -353,64 +291,6 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricAddNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricUpdateNsPolicy(logger logr.Logger, oldP, curP *kyverno.Policy) {
// removing the old rules associated metrics
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(oldP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
// adding the new rules associated metrics
err = policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).AddPolicy(curP)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's updation", "name", oldP.Name)
}
}
func (pc *PolicyController) registerPolicyRuleInfoMetricDeleteNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyRuleInfoMetric.ParsePromConfig(*pc.promConfig).RemovePolicy(p)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_rule_info_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricAddNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyCreated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's creation", "name", p.Name)
}
}
func (pc *PolicyController) registerPolicyChangesMetricUpdateNsPolicy(logger logr.Logger, oldP, curP *kyverno.Policy) {
if reflect.DeepEqual((*oldP).Spec, (*curP).Spec) {
return
}
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(oldP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", oldP.Name)
}
// curP will require a new kyverno_policy_changes_total metric if the above update involved change in the following fields:
if curP.Spec.Background != oldP.Spec.Background || curP.Spec.ValidationFailureAction != oldP.Spec.ValidationFailureAction {
err = policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(curP, policyChangesMetric.PolicyUpdated)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's updation", "name", curP.Name)
}
}
}
func (pc *PolicyController) registerPolicyChangesMetricDeleteNsPolicy(logger logr.Logger, p *kyverno.Policy) {
err := policyChangesMetric.ParsePromConfig(*pc.promConfig).RegisterPolicy(p, policyChangesMetric.PolicyDeleted)
if err != nil {
logger.Error(err, "error occurred while registering kyverno_policy_changes_total metrics for the above policy's deletion", "name", p.Name)
}
}
func (pc *PolicyController) addNsPolicy(obj interface{}) {
logger := pc.log
p := obj.(*kyverno.Policy)

View file

@ -208,7 +208,7 @@ func generateFailEventsPerEr(log logr.Logger, er *response.EngineResponse) []eve
logger.V(4).Info("reporting fail results for policy")
for _, rule := range er.PolicyResponse.Rules {
if rule.Success {
if rule.Status != response.RuleStatusPass {
continue
}
// generate event on resource for each failed rule

View file

@ -10,13 +10,12 @@ import (
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/jmespath/go-jmespath"
c "github.com/kyverno/kyverno/pkg/common"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
comn "github.com/kyverno/kyverno/pkg/common"
dclient "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/kyverno/common"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
dclient "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/minio/pkg/wildcard"
@ -148,7 +147,7 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
// - Mutate
// - Validate
// - Generate
if err := validateActions(i, rule, client, mock); err != nil {
if err := validateActions(i, &p.Spec.Rules[i], client, mock); err != nil {
return err
}
@ -177,12 +176,41 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
}
// Validate Kind with match resource kinds
for _, kind := range rule.MatchResources.Kinds {
_, k := c.GetKindFromGVK(kind)
if k == p.Kind {
return fmt.Errorf("kind and match resource kind should not be the same.")
match := rule.MatchResources
exclude := rule.ExcludeResources
for _, value := range match.Any {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the any match resource is invalid")
}
}
for _, value := range match.All {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the all match resource is invalid")
}
}
for _, value := range exclude.Any {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the any exclude resource is invalid")
}
}
for _, value := range exclude.All {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the all exclude resource is invalid")
}
}
err := validateKinds(rule.MatchResources.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("match resource kind is invalid ")
}
err = validateKinds(rule.ExcludeResources.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("exclude resource kind is invalid ")
}
// Validate string values in labels
if !isLabelAndAnnotationsString(rule) {
@ -1027,3 +1055,19 @@ func jsonPatchOnPod(rule kyverno.Rule) bool {
return false
}
func validateKinds(kinds []string, mock bool, client *dclient.Client, p kyverno.ClusterPolicy) error {
for _, kind := range kinds {
gv, k := comn.GetKindFromGVK(kind)
if !mock {
_, _, err := client.DiscoveryClient.FindResource(gv, k)
if err != nil || strings.ToLower(k) == k {
return fmt.Errorf("match resource kind %s is invalid ", k)
}
}
if k == p.Kind {
return fmt.Errorf("kind and match resource kind should not be the same")
}
}
return nil
}

View file

@ -2,42 +2,43 @@ package validate
import (
"fmt"
"strings"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
commonAnchors "github.com/kyverno/kyverno/pkg/engine/anchor/common"
"github.com/kyverno/kyverno/pkg/policy/common"
)
// Validate provides implementation to validate 'validate' rule
// Validate validates a 'validate' rule
type Validate struct {
// rule to hold 'validate' rule specifications
rule kyverno.Validation
rule *kyverno.Validation
}
//NewValidateFactory returns a new instance of Mutate validation checker
func NewValidateFactory(rule kyverno.Validation) *Validate {
func NewValidateFactory(rule *kyverno.Validation) *Validate {
m := Validate{
rule: rule,
}
return &m
}
//Validate validates the 'validate' rule
func (v *Validate) Validate() (string, error) {
rule := v.rule
if err := v.validateOverlayPattern(); err != nil {
if err := v.validateElements(); err != nil {
// no need to proceed ahead
return "", err
}
if rule.Pattern != nil {
if path, err := common.ValidatePattern(rule.Pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
if v.rule.Pattern != nil {
if path, err := common.ValidatePattern(v.rule.Pattern, "/", []commonAnchors.IsAnchor{commonAnchors.IsConditionAnchor, commonAnchors.IsExistenceAnchor, commonAnchors.IsEqualityAnchor, commonAnchors.IsNegationAnchor, commonAnchors.IsGlobalAnchor}); err != nil {
return fmt.Sprintf("pattern.%s", path), err
}
}
if rule.AnyPattern != nil {
anyPattern, err := rule.DeserializeAnyPattern()
if v.rule.AnyPattern != nil {
anyPattern, err := v.rule.DeserializeAnyPattern()
if err != nil {
return "anyPattern", fmt.Errorf("failed to deserialize anyPattern, expect array: %v", err)
}
@ -47,19 +48,92 @@ func (v *Validate) Validate() (string, error) {
}
}
}
if v.rule.ForEachValidation != nil {
if err := v.validateForEach(v.rule.ForEachValidation); err != nil {
return "", err
}
}
return "", nil
}
// validateOverlayPattern checks one of pattern/anyPattern must exist
func (v *Validate) validateOverlayPattern() error {
rule := v.rule
if rule.Pattern == nil && rule.AnyPattern == nil && rule.Deny == nil {
return fmt.Errorf("pattern, anyPattern or deny must be specified")
func (v *Validate) validateElements() error {
count := validationElemCount(v.rule)
if count == 0 {
return fmt.Errorf("one of pattern, anyPattern, deny, foreach must be specified")
}
if rule.Pattern != nil && rule.AnyPattern != nil {
return fmt.Errorf("only one operation allowed per validation rule(pattern or anyPattern)")
if count > 1 {
return fmt.Errorf("only one of pattern, anyPattern, deny, foreach can be specified")
}
return nil
}
func validationElemCount(v *kyverno.Validation) int {
if v == nil {
return 0
}
count := 0
if v.Pattern != nil {
count++
}
if v.AnyPattern != nil {
count++
}
if v.Deny != nil {
count++
}
if v.ForEachValidation != nil {
count++
}
return count
}
func (v *Validate) validateForEach(foreach *kyverno.ForEachValidation) error {
if foreach.List == "" {
return fmt.Errorf("foreach.list is required")
}
if !strings.HasPrefix(foreach.List, "request.object") {
return fmt.Errorf("foreach.list must start with 'request.object' e.g. 'request.object.spec.containers'.")
}
count := foreachElemCount(foreach)
if count == 0 {
return fmt.Errorf("one of pattern, anyPattern, deny must be specified")
}
if count > 1 {
return fmt.Errorf("only one of pattern, anyPattern, deny can be specified")
}
return nil
}
func foreachElemCount(foreach *kyverno.ForEachValidation) int {
if foreach == nil {
return 0
}
count := 0
if foreach.Pattern != nil {
count++
}
if foreach.AnyPattern != nil {
count++
}
if foreach.Deny != nil {
count++
}
return count
}

View file

@ -16,7 +16,7 @@ func Test_Validate_OverlayPattern_Empty(t *testing.T) {
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -30,7 +30,7 @@ func Test_Validate_OverlayPattern_Nil_PatternAnypattern(t *testing.T) {
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -68,7 +68,7 @@ func Test_Validate_OverlayPattern_Exist_PatternAnypattern(t *testing.T) {
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -106,7 +106,7 @@ func Test_Validate_OverlayPattern_Valid(t *testing.T) {
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
@ -139,7 +139,7 @@ func Test_Validate_ExistingAnchor_AnchorOnMap(t *testing.T) {
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -169,7 +169,7 @@ func Test_Validate_ExistingAnchor_AnchorOnString(t *testing.T) {
var validation kyverno.Validation
err := json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -202,7 +202,7 @@ func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker := NewValidateFactory(validation)
checker := NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -227,7 +227,7 @@ func Test_Validate_ExistingAnchor_Valid(t *testing.T) {
} `)
err = json.Unmarshal(rawValidation, &validation)
assert.NilError(t, err)
checker = NewValidateFactory(validation)
checker = NewValidateFactory(&validation)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -268,7 +268,7 @@ func Test_Validate_Validate_ValidAnchor(t *testing.T) {
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
checker := NewValidateFactory(&validate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
@ -290,7 +290,7 @@ func Test_Validate_Validate_ValidAnchor(t *testing.T) {
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker = NewValidateFactory(validate)
checker = NewValidateFactory(&validate)
if _, err := checker.Validate(); err != nil {
assert.NilError(t, err)
}
@ -317,7 +317,7 @@ func Test_Validate_Validate_Mismatched(t *testing.T) {
var validate kyverno.Validation
err := json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
checker := NewValidateFactory(&validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -347,7 +347,7 @@ func Test_Validate_Validate_Unsupported(t *testing.T) {
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker := NewValidateFactory(validate)
checker := NewValidateFactory(&validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}
@ -373,7 +373,7 @@ func Test_Validate_Validate_Unsupported(t *testing.T) {
err = json.Unmarshal(rawValidate, &validate)
assert.NilError(t, err)
checker = NewValidateFactory(validate)
checker = NewValidateFactory(&validate)
if _, err := checker.Validate(); err != nil {
assert.Assert(t, err != nil)
}

View file

@ -38,6 +38,11 @@ func GenerateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, log logr.Logg
updateMsgs = append(updateMsgs, updateMsg)
}
if patch, updateMsg := defaultFailurePolicy(policy, log); patch != nil {
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
patch, errs := GeneratePodControllerRule(*policy, log)
if len(errs) > 0 {
var errMsgs []string
@ -307,6 +312,33 @@ func defaultvalidationFailureAction(policy *kyverno.ClusterPolicy, log logr.Logg
return nil, ""
}
func defaultFailurePolicy(policy *kyverno.ClusterPolicy, log logr.Logger) ([]byte, string) {
// set failurePolicy to Fail if not present
failurePolicy := string(kyverno.Fail)
if policy.Spec.FailurePolicy == nil {
log.V(4).Info("setting default value", "spec.failurePolicy", failurePolicy)
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value string `json:"value"`
}{
"/spec/failurePolicy",
"add",
string(kyverno.Fail),
}
patchByte, err := json.Marshal(jsonPatch)
if err != nil {
log.Error(err, "failed to set default value", "spec.failurePolicy", failurePolicy)
return nil, ""
}
log.V(3).Info("generated JSON Patch to set default", "spec.failurePolicy", failurePolicy)
return patchByte, fmt.Sprintf("default failurePolicy to '%s'", failurePolicy)
}
return nil, ""
}
// podControllersKey annotation could be:
// scenario A: not exist, set default to "all", which generates on all pod controllers
@ -534,20 +566,10 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr.
match := rule.MatchResources
exclude := rule.ExcludeResources
matchResourceDescriptionsKinds := match.ResourceDescription.Kinds
for _, value := range match.All {
matchResourceDescriptionsKinds = append(matchResourceDescriptionsKinds, value.ResourceDescription.Kinds...)
}
for _, value := range match.Any {
matchResourceDescriptionsKinds = append(matchResourceDescriptionsKinds, value.ResourceDescription.Kinds...)
}
excludeResourceDescriptionsKinds := exclude.ResourceDescription.Kinds
for _, value := range exclude.All {
excludeResourceDescriptionsKinds = append(excludeResourceDescriptionsKinds, value.ResourceDescription.Kinds...)
}
for _, value := range exclude.Any {
excludeResourceDescriptionsKinds = append(excludeResourceDescriptionsKinds, value.ResourceDescription.Kinds...)
}
matchResourceDescriptionsKinds := rule.MatchKinds()
excludeResourceDescriptionsKinds := rule.ExcludeKinds()
if !utils.ContainsString(matchResourceDescriptionsKinds, "Pod") ||
(len(excludeResourceDescriptionsKinds) != 0 && !utils.ContainsString(excludeResourceDescriptionsKinds, "Pod")) {
return kyvernoRule{}

View file

@ -160,7 +160,7 @@ func (builder *requestBuilder) buildRCRResult(policy string, resource response.R
result.Rule = rule.Name
result.Message = rule.Message
result.Result = report.PolicyResult(rule.Check)
result.Result = report.PolicyResult(rule.Status)
if result.Result == "fail" && !av.scored {
result.Result = "warn"
}
@ -263,15 +263,31 @@ func buildViolatedRules(er *response.EngineResponse) []kyverno.ViolatedRule {
Type: rule.Type,
Message: rule.Message,
}
vrule.Check = report.StatusFail
if rule.Success {
vrule.Check = report.StatusPass
}
vrule.Status = toPolicyResult(rule.Status)
violatedRules = append(violatedRules, vrule)
}
return violatedRules
}
func toPolicyResult(status response.RuleStatus) string {
switch status {
case response.RuleStatusPass:
return report.StatusPass
case response.RuleStatusFail:
return report.StatusFail
case response.RuleStatusError:
return report.StatusError
case response.RuleStatusWarn:
return report.StatusWarn
case response.RuleStatusSkip:
return report.StatusSkip
}
return ""
}
const categoryLabel string = "policies.kyverno.io/category"
const severityLabel string = "policies.kyverno.io/severity"
const scoredLabel string = "policies.kyverno.io/scored"

View file

@ -1,8 +0,0 @@
package signal
//TODO: how to pick files based on OS compilation ?
// import (
// "os"
// )
// var shutdownSignals = []os.Signal{os.Interrupt}

View file

@ -3,9 +3,11 @@ package testrunner
import (
"bytes"
"encoding/json"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
ospath "path"
"path/filepath"
"reflect"
"testing"
@ -14,83 +16,94 @@ import (
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
k8sRuntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apiyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes/scheme"
"path"
"runtime"
)
type scenarioT struct {
testCases []scaseT
type Scenario struct {
TestCases []TestCase
}
//scase defines input and output for a case
type scaseT struct {
Input sInput `yaml:"input"`
Expected sExpected `yaml:"expected"`
//CaseT defines input and output for a case
type TestCase struct {
Input Input `yaml:"input"`
Expected Expected `yaml:"expected"`
}
//sInput defines input for a test scenario
type sInput struct {
//Input defines input for a test scenario
type Input struct {
Policy string `yaml:"policy"`
Resource string `yaml:"resource"`
LoadResources []string `yaml:"loadresources,omitempty"`
}
type sExpected struct {
Mutation sMutation `yaml:"mutation,omitempty"`
Validation sValidation `yaml:"validation,omitempty"`
Generation sGeneration `yaml:"generation,omitempty"`
type Expected struct {
Mutation Mutation `yaml:"mutation,omitempty"`
Validation Validation `yaml:"validation,omitempty"`
Generation Generation `yaml:"generation,omitempty"`
}
type sMutation struct {
type Mutation struct {
// path to the patched resource to be compared with
PatchedResource string `yaml:"patchedresource,omitempty"`
// expected response from the policy engine
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
type sValidation struct {
type Validation struct {
// expected response from the policy engine
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
type sGeneration struct {
type Generation struct {
// generated resources
GeneratedResources []kyverno.ResourceSpec `yaml:"generatedResources"`
// expected response from the policy engine
PolicyResponse response.PolicyResponse `yaml:"policyresponse"`
}
//getRelativePath expects a path relative to project and builds the complete path
func getRelativePath(path string) string {
gp := os.Getenv("GOPATH")
ap := ospath.Join(gp, projectPath)
return ospath.Join(ap, path)
// RootDir returns the kyverno project directory based on the location of the current file.
// It assumes that the project directory is 2 levels up. This means if this function is moved
// it may not work as expected.
func RootDir() string {
_, b, _, _ := runtime.Caller(0)
d := path.Join(path.Dir(b))
d = filepath.Dir(d)
return filepath.Dir(d)
}
func loadScenario(t *testing.T, path string) (*scenarioT, error) {
fileBytes, err := loadFile(t, path)
if err != nil {
return nil, err
}
//getRelativePath expects a path relative to project and builds the complete path
func getRelativePath(path string) string {
root := RootDir()
return ospath.Join(root, path)
}
var testCases []scaseT
func loadScenario(t *testing.T, path string) (*Scenario, error) {
fileBytes, err := loadFile(t, path)
assert.Nil(t, err)
var testCases []TestCase
// load test cases separated by '---'
// each test case defines an input & expected result
scenariosBytes := bytes.Split(fileBytes, []byte("---"))
for _, scenarioBytes := range scenariosBytes {
tc := scaseT{}
if err := yaml.Unmarshal([]byte(scenarioBytes), &tc); err != nil {
for _, testCaseBytes := range scenariosBytes {
var tc TestCase
if err := yaml.Unmarshal(testCaseBytes, &tc); err != nil {
t.Errorf("failed to decode test case YAML: %v", err)
continue
}
testCases = append(testCases, tc)
}
scenario := scenarioT{
testCases: testCases,
scenario := Scenario{
TestCases: testCases,
}
return &scenario, nil
@ -106,14 +119,14 @@ func loadFile(t *testing.T, path string) ([]byte, error) {
return ioutil.ReadFile(path)
}
func runScenario(t *testing.T, s *scenarioT) bool {
for _, tc := range s.testCases {
func runScenario(t *testing.T, s *Scenario) bool {
for _, tc := range s.TestCases {
runTestCase(t, tc)
}
return true
}
func runTestCase(t *testing.T, tc scaseT) bool {
func runTestCase(t *testing.T, tc TestCase) bool {
policy := loadPolicy(t, tc.Input.Policy)
if policy == nil {
t.Error("Policy not loaded")
@ -310,8 +323,8 @@ func compareRules(t *testing.T, rule response.RuleResponse, expectedRule respons
// }
// success
if rule.Success != expectedRule.Success {
t.Errorf("rule success: expected %t, received %t", expectedRule.Success, rule.Success)
if rule.Status != expectedRule.Status {
t.Errorf("rule status mismatch: expected %s, received %s", expectedRule.Status.String(), rule.Status.String())
}
}
@ -330,7 +343,7 @@ func loadPolicyResource(t *testing.T, file string) *unstructured.Unstructured {
}
func getClient(t *testing.T, files []string) *client.Client {
var objects []runtime.Object
var objects []k8sRuntime.Object
if files != nil {
for _, file := range files {
@ -338,7 +351,7 @@ func getClient(t *testing.T, files []string) *client.Client {
}
}
// create mock client
scheme := runtime.NewScheme()
scheme := k8sRuntime.NewScheme()
// mock client expects the resource to be as runtime.Object
c, err := client.NewMockClient(scheme, nil, objects...)
if err != nil {
@ -352,7 +365,7 @@ func getClient(t *testing.T, files []string) *client.Client {
return c
}
func getGVRForResources(objects []runtime.Object) []schema.GroupVersionResource {
func getGVRForResources(objects []k8sRuntime.Object) []schema.GroupVersionResource {
var gvrs []schema.GroupVersionResource
for _, obj := range objects {
gvk := obj.GetObjectKind().GroupVersionKind()
@ -380,7 +393,7 @@ func loadResource(t *testing.T, path string) []*unstructured.Unstructured {
continue
}
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&obj)
data, err := k8sRuntime.DefaultUnstructuredConverter.ToUnstructured(&obj)
if err != nil {
t.Logf("failed to unmarshall resource. %v", err)
continue
@ -392,8 +405,8 @@ func loadResource(t *testing.T, path string) []*unstructured.Unstructured {
return unstrResources
}
func loadObjects(t *testing.T, path string) []runtime.Object {
var resources []runtime.Object
func loadObjects(t *testing.T, path string) []k8sRuntime.Object {
var resources []k8sRuntime.Object
t.Logf("loading objects from %s", path)
data, err := loadFile(t, path)
if err != nil {
@ -408,7 +421,6 @@ func loadObjects(t *testing.T, path string) []runtime.Object {
continue
}
t.Log(gvk)
//TODO: add more details
t.Logf("loaded object %s", gvk.Kind)
resources = append(resources, obj)
}

View file

@ -0,0 +1,69 @@
package testrunner
import (
"github.com/kyverno/kyverno/pkg/engine/response"
"gopkg.in/yaml.v3"
"gotest.tools/assert"
"io/ioutil"
"testing"
)
var sourceYAML = `
input:
policy: test/best_practices/disallow_bind_mounts.yaml
resource: test/resources/disallow_host_filesystem.yaml
expected:
validation:
policyresponse:
policy:
namespace: ''
name: disallow-bind-mounts
resource:
kind: Pod
apiVersion: v1
namespace: ''
name: image-with-hostpath
rules:
- name: validate-hostPath
type: Validation
status: fail
`
func Test_parse_yaml(t *testing.T) {
var s TestCase
if err := yaml.Unmarshal([]byte(sourceYAML), &s); err != nil {
t.Errorf("failed to parse YAML: %v", err)
return
}
assert.Equal(t, s.Expected.Validation.PolicyResponse.Policy.Name, "disallow-bind-mounts")
assert.Equal(t, 1, len(s.Expected.Validation.PolicyResponse.Rules), "invalid rule count")
assert.Equal(t, response.RuleStatusFail, s.Expected.Validation.PolicyResponse.Rules[0].Status, "invalid status")
}
func Test_parse_file(t *testing.T) {
s, err := loadScenario(t, "test/scenarios/samples/best_practices/disallow_bind_mounts_fail.yaml")
assert.NilError(t, err)
assert.Equal(t, 1, len(s.TestCases))
assert.Equal(t, s.TestCases[0].Expected.Validation.PolicyResponse.Policy.Name, "disallow-bind-mounts")
assert.Equal(t, 1, len(s.TestCases[0].Expected.Validation.PolicyResponse.Rules), "invalid rule count")
assert.Equal(t, response.RuleStatusFail, s.TestCases[0].Expected.Validation.PolicyResponse.Rules[0].Status, "invalid status")
}
func Test_parse_file2(t *testing.T) {
path := getRelativePath("test/scenarios/samples/best_practices/disallow_bind_mounts_fail.yaml")
data, err := ioutil.ReadFile(path)
assert.NilError(t, err)
strData := string(data)
var s TestCase
if err := yaml.Unmarshal([]byte(strData), &s); err != nil {
t.Errorf("failed to parse YAML: %v", err)
return
}
assert.Equal(t, s.Expected.Validation.PolicyResponse.Policy.Name, "disallow-bind-mounts")
assert.Equal(t, 1, len(s.Expected.Validation.PolicyResponse.Rules), "invalid rule count")
assert.Equal(t, response.RuleStatusFail, s.Expected.Validation.PolicyResponse.Rules[0].Status, "invalid status")
}

View file

@ -8,10 +8,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
)
var (
projectPath = envOr("PROJECT_PATH", "src/github.com/kyverno/kyverno")
)
// LoadFile loads file in byte buffer
func LoadFile(path string) ([]byte, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {

View file

@ -40,6 +40,12 @@ func GetPolicy(bytes []byte) (clusterPolicies []*v1.ClusterPolicy, err error) {
return nil, fmt.Errorf(msg)
}
if policy.Namespace != "" || (policy.Namespace == "" && policy.Kind == "Policy") {
if policy.Namespace == "" {
policy.Namespace = "default"
}
policy.Kind = "ClusterPolicy"
}
clusterPolicies = append(clusterPolicies, policy)
}

View file

@ -9,7 +9,6 @@ import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/tls"
ktls "github.com/kyverno/kyverno/pkg/tls"
v1 "k8s.io/api/core/v1"
informerv1 "k8s.io/client-go/informers/core/v1"
@ -29,14 +28,14 @@ type Interface interface {
GetTLSPemPair() (*ktls.PemPair, error)
}
type certManager struct {
renewer *tls.CertRenewer
renewer *ktls.CertRenewer
secretInformer informerv1.SecretInformer
secretQueue chan bool
stopCh <-chan struct{}
log logr.Logger
}
func NewCertManager(secretInformer informerv1.SecretInformer, kubeClient kubernetes.Interface, certRenewer *tls.CertRenewer, log logr.Logger, stopCh <-chan struct{}) (Interface, error) {
func NewCertManager(secretInformer informerv1.SecretInformer, kubeClient kubernetes.Interface, certRenewer *ktls.CertRenewer, log logr.Logger, stopCh <-chan struct{}) (Interface, error) {
manager := &certManager{
renewer: certRenewer,
secretInformer: secretInformer,
@ -59,7 +58,7 @@ func (m *certManager) addSecretFunc(obj interface{}) {
return
}
val, ok := secret.GetAnnotations()[tls.SelfSignedAnnotation]
val, ok := secret.GetAnnotations()[ktls.SelfSignedAnnotation]
if !ok || val != "true" {
return
}
@ -74,7 +73,7 @@ func (m *certManager) updateSecretFunc(oldObj interface{}, newObj interface{}) {
return
}
val, ok := new.GetAnnotations()[tls.SelfSignedAnnotation]
val, ok := new.GetAnnotations()[ktls.SelfSignedAnnotation]
if !ok || val != "true" {
return
}
@ -127,7 +126,7 @@ func (m *certManager) Run(stopCh <-chan struct{}) {
})
m.log.Info("start managing certificate")
certsRenewalTicker := time.NewTicker(tls.CertRenewalInterval)
certsRenewalTicker := time.NewTicker(ktls.CertRenewalInterval)
defer certsRenewalTicker.Stop()
for {
@ -137,7 +136,7 @@ func (m *certManager) Run(stopCh <-chan struct{}) {
if err != nil {
m.log.Error(err, "failed to validate cert")
if !strings.Contains(err.Error(), tls.ErrorsNotFound) {
if !strings.Contains(err.Error(), ktls.ErrorsNotFound) {
continue
}
}
@ -157,7 +156,7 @@ func (m *certManager) Run(stopCh <-chan struct{}) {
if err != nil {
m.log.Error(err, "failed to validate cert")
if !strings.Contains(err.Error(), tls.ErrorsNotFound) {
if !strings.Contains(err.Error(), ktls.ErrorsNotFound) {
continue
}
}

View file

@ -2,10 +2,11 @@ package webhookconfig
import (
"io/ioutil"
"reflect"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/tls"
admregapi "k8s.io/api/admissionregistration/v1beta1"
admregapi "k8s.io/api/admissionregistration/v1"
apps "k8s.io/api/apps/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -95,76 +96,66 @@ func (wrc *Register) GetKubePolicyDeployment() (*apps.Deployment, *unstructured.
}
// debug mutating webhook
func generateDebugMutatingWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, resources []string, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.MutatingWebhook {
func generateDebugMutatingWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, rule admregapi.Rule, operationTypes []admregapi.OperationType, failurePolicy admregapi.FailurePolicyType) admregapi.MutatingWebhook {
sideEffect := admregapi.SideEffectClassNoneOnDryRun
failurePolicy := admregapi.Ignore
reinvocationPolicy := admregapi.NeverReinvocationPolicy
return admregapi.MutatingWebhook{
w := admregapi.MutatingWebhook{
ReinvocationPolicy: &reinvocationPolicy,
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
URL: &url,
CABundle: caData,
},
SideEffects: &sideEffect,
Rules: []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: resources,
},
},
},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1beta1"},
TimeoutSeconds: &timeoutSeconds,
FailurePolicy: &failurePolicy,
}
if !reflect.DeepEqual(rule, admregapi.Rule{}) {
w.Rules = []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: rule,
},
}
}
return w
}
func generateDebugValidatingWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, resources []string, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.ValidatingWebhook {
func generateDebugValidatingWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, rule admregapi.Rule, operationTypes []admregapi.OperationType, failurePolicy admregapi.FailurePolicyType) admregapi.ValidatingWebhook {
sideEffect := admregapi.SideEffectClassNoneOnDryRun
failurePolicy := admregapi.Ignore
return admregapi.ValidatingWebhook{
w := admregapi.ValidatingWebhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
URL: &url,
CABundle: caData,
},
SideEffects: &sideEffect,
Rules: []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: resources,
},
},
},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1beta1"},
TimeoutSeconds: &timeoutSeconds,
FailurePolicy: &failurePolicy,
}
if !reflect.DeepEqual(rule, admregapi.Rule{}) {
w.Rules = []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: rule,
},
}
}
return w
}
// mutating webhook
func generateMutatingWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, resources []string, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.MutatingWebhook {
func generateMutatingWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, rule admregapi.Rule, operationTypes []admregapi.OperationType, failurePolicy admregapi.FailurePolicyType) admregapi.MutatingWebhook {
sideEffect := admregapi.SideEffectClassNoneOnDryRun
failurePolicy := admregapi.Ignore
reinvocationPolicy := admregapi.NeverReinvocationPolicy
reinvocationPolicy := admregapi.IfNeededReinvocationPolicy
return admregapi.MutatingWebhook{
w := admregapi.MutatingWebhook{
ReinvocationPolicy: &reinvocationPolicy,
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
@ -175,32 +166,27 @@ func generateMutatingWebhook(name, servicePath string, caData []byte, validation
},
CABundle: caData,
},
SideEffects: &sideEffect,
Rules: []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: resources,
},
},
},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1beta1"},
TimeoutSeconds: &timeoutSeconds,
FailurePolicy: &failurePolicy,
}
if !reflect.DeepEqual(rule, admregapi.Rule{}) {
w.Rules = []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: rule,
},
}
}
return w
}
// validating webhook
func generateValidatingWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, resources []string, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.ValidatingWebhook {
func generateValidatingWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, rule admregapi.Rule, operationTypes []admregapi.OperationType, failurePolicy admregapi.FailurePolicyType) admregapi.ValidatingWebhook {
sideEffect := admregapi.SideEffectClassNoneOnDryRun
failurePolicy := admregapi.Ignore
return admregapi.ValidatingWebhook{
w := admregapi.ValidatingWebhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
Service: &admregapi.ServiceReference{
@ -210,23 +196,19 @@ func generateValidatingWebhook(name, servicePath string, caData []byte, validati
},
CABundle: caData,
},
SideEffects: &sideEffect,
Rules: []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: resources,
},
},
},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1beta1"},
TimeoutSeconds: &timeoutSeconds,
FailurePolicy: &failurePolicy,
}
if !reflect.DeepEqual(rule, admregapi.Rule{}) {
w.Rules = []admregapi.RuleWithOperations{
{
Operations: operationTypes,
Rule: rule,
},
}
}
return w
}

View file

@ -0,0 +1,755 @@
package webhookconfig
import (
"context"
"fmt"
"reflect"
"strings"
"sync/atomic"
"time"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/resourcecache"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/pkg/errors"
admregapi "k8s.io/api/admissionregistration/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
var DefaultWebhookTimeout int64 = 10
// webhookConfigManager manges the webhook configuration dynamically
// it is NOT multi-thread safe
type webhookConfigManager struct {
client *client.Client
kyvernoClient *kyvernoclient.Clientset
pInformer kyvernoinformer.ClusterPolicyInformer
npInformer kyvernoinformer.PolicyInformer
// pLister can list/get policy from the shared informer's store
pLister kyvernolister.ClusterPolicyLister
// npLister can list/get namespace policy from the shared informer's store
npLister kyvernolister.PolicyLister
// pListerSynced returns true if the cluster policy store has been synced at least once
pListerSynced cache.InformerSynced
// npListerSynced returns true if the namespace policy store has been synced at least once
npListerSynced cache.InformerSynced
resCache resourcecache.ResourceCache
mutateInformer cache.SharedIndexInformer
validateInformer cache.SharedIndexInformer
mutateInformerSynced cache.InformerSynced
validateInformerSynced cache.InformerSynced
queue workqueue.RateLimitingInterface
// wildcardPolicy indicates the number of policies that matches all kinds (*) defined
wildcardPolicy int64
createDefaultWebhook chan<- string
stopCh <-chan struct{}
log logr.Logger
}
type manage interface {
start()
}
func newWebhookConfigManager(
client *client.Client,
kyvernoClient *kyvernoclient.Clientset,
pInformer kyvernoinformer.ClusterPolicyInformer,
npInformer kyvernoinformer.PolicyInformer,
resCache resourcecache.ResourceCache,
createDefaultWebhook chan<- string,
stopCh <-chan struct{},
log logr.Logger) manage {
m := &webhookConfigManager{
client: client,
kyvernoClient: kyvernoClient,
pInformer: pInformer,
npInformer: npInformer,
resCache: resCache,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "configmanager"),
wildcardPolicy: 0,
createDefaultWebhook: createDefaultWebhook,
stopCh: stopCh,
log: log,
}
m.pLister = pInformer.Lister()
m.npLister = npInformer.Lister()
m.pListerSynced = pInformer.Informer().HasSynced
m.npListerSynced = npInformer.Informer().HasSynced
mutateCache, _ := m.resCache.GetGVRCache(kindMutating)
m.mutateInformer = mutateCache.GetInformer()
m.mutateInformerSynced = mutateCache.GetInformer().HasSynced
validateCache, _ := m.resCache.GetGVRCache(kindValidating)
m.validateInformer = validateCache.GetInformer()
m.validateInformerSynced = validateCache.GetInformer().HasSynced
return m
}
func (m *webhookConfigManager) handleErr(err error, key interface{}) {
logger := m.log
if err == nil {
m.queue.Forget(key)
return
}
if m.queue.NumRequeues(key) < 3 {
logger.Error(err, "failed to sync policy", "key", key)
m.queue.AddRateLimited(key)
return
}
utilruntime.HandleError(err)
logger.V(2).Info("dropping policy out of queue", "key", key)
m.queue.Forget(key)
}
func (m *webhookConfigManager) addClusterPolicy(obj interface{}) {
p := obj.(*kyverno.ClusterPolicy)
if hasWildcard(p) {
atomic.AddInt64(&m.wildcardPolicy, int64(1))
}
m.enqueue(p)
}
func (m *webhookConfigManager) updateClusterPolicy(old, cur interface{}) {
oldP := old.(*kyverno.ClusterPolicy)
curP := cur.(*kyverno.ClusterPolicy)
if reflect.DeepEqual(oldP.Spec, curP.Spec) {
return
}
if hasWildcard(oldP) && !hasWildcard(curP) {
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
} else if !hasWildcard(oldP) && hasWildcard(curP) {
atomic.AddInt64(&m.wildcardPolicy, int64(1))
}
m.enqueue(curP)
}
func (m *webhookConfigManager) deleteClusterPolicy(obj interface{}) {
p, ok := obj.(*kyverno.ClusterPolicy)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
utilruntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
return
}
p, ok = tombstone.Obj.(*kyverno.ClusterPolicy)
if !ok {
utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
return
}
m.log.V(4).Info("Recovered deleted ClusterPolicy '%s' from tombstone", "name", p.GetName())
}
if hasWildcard(p) {
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
}
m.enqueue(p)
}
func (m *webhookConfigManager) addPolicy(obj interface{}) {
p := obj.(*kyverno.Policy)
if hasWildcard(p) {
atomic.AddInt64(&m.wildcardPolicy, int64(1))
}
pol := kyverno.ClusterPolicy(*p)
m.enqueue(&pol)
}
func (m *webhookConfigManager) updatePolicy(old, cur interface{}) {
oldP := old.(*kyverno.Policy)
curP := cur.(*kyverno.Policy)
if reflect.DeepEqual(oldP.Spec, curP.Spec) {
return
}
if hasWildcard(oldP) && !hasWildcard(curP) {
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
} else if !hasWildcard(oldP) && hasWildcard(curP) {
atomic.AddInt64(&m.wildcardPolicy, int64(1))
}
pol := kyverno.ClusterPolicy(*curP)
m.enqueue(&pol)
}
func (m *webhookConfigManager) deletePolicy(obj interface{}) {
p, ok := obj.(*kyverno.Policy)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
utilruntime.HandleError(fmt.Errorf("error decoding object, invalid type"))
return
}
p, ok = tombstone.Obj.(*kyverno.Policy)
if !ok {
utilruntime.HandleError(fmt.Errorf("error decoding object tombstone, invalid type"))
return
}
m.log.V(4).Info("Recovered deleted ClusterPolicy '%s' from tombstone", "name", p.GetName())
}
if hasWildcard(p) {
atomic.AddInt64(&m.wildcardPolicy, ^int64(0))
}
pol := kyverno.ClusterPolicy(*p)
m.enqueue(&pol)
}
func (m *webhookConfigManager) deleteWebhook(obj interface{}) {
m.log.WithName("deleteWebhook").Info("resource webhook configuration was deleted, recreating...")
if webhook, ok := obj.(*unstructured.Unstructured); ok {
k := webhook.GetKind()
if (k == kindMutating && webhook.GetName() == config.MutatingWebhookConfigurationName) ||
(k == kindValidating && webhook.GetName() == config.ValidatingWebhookConfigurationName) {
m.enqueueAllPolicies()
}
}
}
func (m *webhookConfigManager) enqueueAllPolicies() {
logger := m.log.WithName("enqueueAllPolicies")
cpols, err := m.listPolicies("")
if err != nil {
logger.Error(err, "unabled to list clusterpolicies")
}
for _, cpol := range cpols {
m.enqueue(cpol)
logger.V(4).Info("added CLusterPolicy to the queue", "name", cpol.GetName())
}
nsCache, ok := m.resCache.GetGVRCache("Namespace")
if !ok {
nsCache, err = m.resCache.CreateGVKInformer("Namespace")
if err != nil {
logger.Error(err, "unabled to create Namespace listser")
return
}
}
namespaces, err := nsCache.Lister().List(labels.Everything())
if err != nil {
logger.Error(err, "unabled to list namespaces")
return
}
for _, ns := range namespaces {
pols, err := m.listPolicies(ns.GetName())
if err != nil {
logger.Error(err, "unabled to list policies", "namespace", ns.GetName())
}
for _, p := range pols {
m.enqueue(p)
logger.V(4).Info("added Policy to the queue", "namespace", p.GetName(), "name", p.GetName())
}
}
}
func (m *webhookConfigManager) enqueue(policy *kyverno.ClusterPolicy) {
logger := m.log
key, err := cache.MetaNamespaceKeyFunc(policy)
if err != nil {
logger.Error(err, "failed to enqueue policy")
return
}
m.queue.Add(key)
}
// start is a blocking call to configure webhook
func (m *webhookConfigManager) start() {
defer utilruntime.HandleCrash()
defer m.queue.ShutDown()
m.log.Info("starting")
defer m.log.Info("shutting down")
if !cache.WaitForCacheSync(m.stopCh, m.pListerSynced, m.npListerSynced, m.mutateInformerSynced, m.validateInformerSynced) {
m.log.Info("failed to sync informer cache")
return
}
m.pInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: m.addClusterPolicy,
UpdateFunc: m.updateClusterPolicy,
DeleteFunc: m.deleteClusterPolicy,
})
m.npInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: m.addPolicy,
UpdateFunc: m.updatePolicy,
DeleteFunc: m.deletePolicy,
})
m.mutateInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
DeleteFunc: m.deleteWebhook,
})
m.validateInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
DeleteFunc: m.deleteWebhook,
})
for m.processNextWorkItem() {
}
}
func (m *webhookConfigManager) processNextWorkItem() bool {
key, quit := m.queue.Get()
if quit {
return false
}
defer m.queue.Done(key)
err := m.sync(key.(string))
m.handleErr(err, key)
return true
}
func (m *webhookConfigManager) sync(key string) error {
logger := m.log.WithName("sync")
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).String())
}()
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
logger.Info("invalid resource key", "key", key)
return nil
}
return m.reconcileWebhook(namespace, name)
}
func (m *webhookConfigManager) reconcileWebhook(namespace, name string) error {
logger := m.log.WithName("reconcileWebhook").WithValues("namespace", namespace, "policy", name)
policy, err := m.getPolicy(namespace, name)
if err != nil && !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "unable to get policy object %s/%s", namespace, name)
}
webhooks, err := m.buildWebhooks(namespace)
if err != nil {
return err
}
if err := m.updateWebhookConfig(webhooks); err != nil {
return errors.Wrapf(err, "failed to update webhook configurations for policy %s/%s", namespace, name)
}
// DELETION of the policy
if policy == nil {
return nil
}
if err := m.updateStatus(policy); err != nil {
return errors.Wrapf(err, "failed to update policy status %s/%s", namespace, name)
}
logger.Info("policy is ready to serve admission requests")
return nil
}
func (m *webhookConfigManager) getPolicy(namespace, name string) (*kyverno.ClusterPolicy, error) {
// TODO: test default/policy
if namespace == "" {
return m.pLister.Get(name)
}
nsPolicy, err := m.npLister.Policies(namespace).Get(name)
if err == nil && nsPolicy != nil {
p := kyverno.ClusterPolicy(*nsPolicy)
return &p, err
}
return nil, err
}
func (m *webhookConfigManager) listPolicies(namespace string) ([]*kyverno.ClusterPolicy, error) {
if namespace != "" {
polList, err := m.npLister.Policies(namespace).List(labels.Everything())
if err != nil {
return nil, errors.Wrapf(err, "failed to list Policy")
}
policies := make([]*kyverno.ClusterPolicy, len(polList))
for i, pol := range polList {
p := kyverno.ClusterPolicy(*pol)
policies[i] = &p
}
return policies, nil
}
cpolList, err := m.pLister.List(labels.Everything())
if err != nil {
return nil, errors.Wrapf(err, "failed to list ClusterPolicy")
}
return cpolList, nil
}
const (
apiGroups string = "apiGroups"
apiVersions string = "apiVersions"
resources string = "resources"
)
// webhook is the instance that aggregates the GVK of existing policies
// based on kind, failurePolicy and webhookTimeout
type webhook struct {
kind string
maxWebhookTimeout int64
failurePolicy kyverno.FailurePolicyType
// rule represents the same rule struct of the webhook using a map object
// https://github.com/kubernetes/api/blob/master/admissionregistration/v1/types.go#L25
rule map[string]interface{}
}
func (m *webhookConfigManager) buildWebhooks(namespace string) (res []*webhook, err error) {
mutateIgnore := newWebhook(kindMutating, DefaultWebhookTimeout, kyverno.Ignore)
mutateFail := newWebhook(kindMutating, DefaultWebhookTimeout, kyverno.Fail)
validateIgnore := newWebhook(kindValidating, DefaultWebhookTimeout, kyverno.Ignore)
validateFail := newWebhook(kindValidating, DefaultWebhookTimeout, kyverno.Fail)
if atomic.LoadInt64(&m.wildcardPolicy) != 0 {
for _, w := range []*webhook{mutateIgnore, mutateFail, validateIgnore, validateFail} {
setWildcardConfig(w)
}
m.log.V(4).WithName("buildWebhooks").Info("warning: found wildcard policy, setting webhook configurations to accept admission requests of all kinds")
return append(res, mutateIgnore, mutateFail, validateIgnore, validateFail), nil
}
policies, err := m.listPolicies(namespace)
if err != nil {
return nil, errors.Wrap(err, "unable to list current policies")
}
for _, p := range policies {
if p.HasValidate() || p.HasGenerate() {
if p.Spec.FailurePolicy != nil && *p.Spec.FailurePolicy == kyverno.Ignore {
m.mergeWebhook(validateIgnore, p)
} else {
m.mergeWebhook(validateFail, p)
}
}
if p.HasMutate() || p.HasGenerate() {
if p.Spec.FailurePolicy != nil && *p.Spec.FailurePolicy == kyverno.Ignore {
m.mergeWebhook(mutateIgnore, p)
} else {
m.mergeWebhook(mutateFail, p)
}
}
}
res = append(res, mutateIgnore, mutateFail, validateIgnore, validateFail)
return res, nil
}
func (m *webhookConfigManager) updateWebhookConfig(webhooks []*webhook) error {
logger := m.log.WithName("updateWebhookConfig")
webhooksMap := make(map[string]interface{}, len(webhooks))
for _, w := range webhooks {
key := webhookKey(w.kind, string(w.failurePolicy))
webhooksMap[key] = w
}
var errs []string
if err := m.compareAndUpdateWebhook(kindMutating, getResourceMutatingWebhookConfigName(""), webhooksMap); err != nil {
logger.V(4).Info("failed to update mutatingwebhookconfigurations", "error", err.Error())
errs = append(errs, err.Error())
}
if err := m.compareAndUpdateWebhook(kindValidating, getResourceValidatingWebhookConfigName(""), webhooksMap); err != nil {
logger.V(4).Info("failed to update validatingwebhookconfigurations", "error", err.Error())
errs = append(errs, err.Error())
}
if len(errs) != 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
}
func (m *webhookConfigManager) getWebhook(webhookKind, webhookName string) (resourceWebhook *unstructured.Unstructured, err error) {
get := func() error {
webhookCache, _ := m.resCache.GetGVRCache(webhookKind)
resourceWebhook, err = webhookCache.Lister().Get(webhookName)
if err != nil && !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "unable to get %s/%s", webhookKind, webhookName)
} else if apierrors.IsNotFound(err) {
m.createDefaultWebhook <- webhookKind
return err
}
return nil
}
retryGetWebhook := common.RetryFunc(time.Second, 10*time.Second, get, m.log)
if err := retryGetWebhook(); err != nil {
return nil, err
}
return resourceWebhook, nil
}
func (m *webhookConfigManager) compareAndUpdateWebhook(webhookKind, webhookName string, webhooksMap map[string]interface{}) error {
logger := m.log.WithName("compareAndUpdateWebhook").WithValues("kind", webhookKind, "name", webhookName)
resourceWebhook, err := m.getWebhook(webhookKind, webhookName)
if err != nil {
return err
}
webhooksUntyped, _, err := unstructured.NestedSlice(resourceWebhook.UnstructuredContent(), "webhooks")
if err != nil {
return errors.Wrapf(err, "unable to fetch tag webhooks for %s/%s", webhookKind, webhookName)
}
newWebooks := make([]interface{}, len(webhooksUntyped))
copy(newWebooks, webhooksUntyped)
var changed bool
for i, webhookUntyed := range webhooksUntyped {
existingWebhook, ok := webhookUntyed.(map[string]interface{})
if !ok {
logger.Error(errors.New("type mismatched"), "expected map[string]interface{}, got %T", webhooksUntyped)
continue
}
failurePolicy, _, err := unstructured.NestedString(existingWebhook, "failurePolicy")
if err != nil {
logger.Error(errors.New("type mismatched"), "expected string, got %T", failurePolicy)
continue
}
rules, _, err := unstructured.NestedSlice(existingWebhook, "rules")
if err != nil {
logger.Error(err, "type mismatched, expected []interface{}, got %T", rules)
continue
}
newWebhook := webhooksMap[webhookKey(webhookKind, failurePolicy)]
w, ok := newWebhook.(*webhook)
if !ok {
logger.Error(errors.New("type mismatched"), "expected *webhook, got %T", newWebooks)
continue
}
if !reflect.DeepEqual(rules, []interface{}{w.rule}) {
changed = true
tmpRules, ok := newWebooks[i].(map[string]interface{})["rules"].([]interface{})
if !ok {
// init operations
ops := []string{string(admregapi.Create), string(admregapi.Update), string(admregapi.Delete), string(admregapi.Connect)}
if webhookKind == kindMutating {
ops = []string{string(admregapi.Create), string(admregapi.Update)}
}
tmpRules = []interface{}{map[string]interface{}{}}
if err = unstructured.SetNestedStringSlice(tmpRules[0].(map[string]interface{}), ops, "operations"); err != nil {
return errors.Wrapf(err, "unable to set webhooks[%d].rules[0].%s", i, apiGroups)
}
}
if w.rule == nil || reflect.DeepEqual(w.rule, map[string]interface{}{}) {
// zero kyverno policy with the current failurePolicy, reset webhook rules to empty
newWebooks[i].(map[string]interface{})["rules"] = []interface{}{}
continue
}
if err = unstructured.SetNestedStringSlice(tmpRules[0].(map[string]interface{}), w.rule[apiGroups].([]string), apiGroups); err != nil {
return errors.Wrapf(err, "unable to set webhooks[%d].rules[0].%s", i, apiGroups)
}
if err = unstructured.SetNestedStringSlice(tmpRules[0].(map[string]interface{}), w.rule[apiVersions].([]string), apiVersions); err != nil {
return errors.Wrapf(err, "unable to set webhooks[%d].rules[0].%s", i, apiVersions)
}
if err = unstructured.SetNestedStringSlice(tmpRules[0].(map[string]interface{}), w.rule[resources].([]string), resources); err != nil {
return errors.Wrapf(err, "unable to set webhooks[%d].rules[0].%s", i, resources)
}
newWebooks[i].(map[string]interface{})["rules"] = tmpRules
}
if err = unstructured.SetNestedField(newWebooks[i].(map[string]interface{}), w.maxWebhookTimeout, "timeoutSeconds"); err != nil {
return errors.Wrapf(err, "unable to set webhooks[%d].timeoutSeconds to %v", i, w.maxWebhookTimeout)
}
}
if changed {
logger.V(4).Info("webhook configuration has been changed, updating")
if err := unstructured.SetNestedSlice(resourceWebhook.UnstructuredContent(), newWebooks, "webhooks"); err != nil {
return errors.Wrap(err, "unable to set new webhooks")
}
if _, err := m.client.UpdateResource(resourceWebhook.GetAPIVersion(), resourceWebhook.GetKind(), "", resourceWebhook, false); err != nil {
return errors.Wrapf(err, "unable to update %s/%s: %s", resourceWebhook.GetAPIVersion(), resourceWebhook.GetKind(), resourceWebhook.GetName())
}
logger.V(4).Info("successfully updated the webhook configuration")
}
return nil
}
func (m *webhookConfigManager) updateStatus(policy *kyverno.ClusterPolicy) error {
policyCopy := policy.DeepCopy()
policyCopy.Status.Ready = true
if policy.GetNamespace() == "" {
_, err := m.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(context.TODO(), policyCopy, v1.UpdateOptions{})
return err
}
_, err := m.kyvernoClient.KyvernoV1().Policies(policyCopy.GetNamespace()).UpdateStatus(context.TODO(), (*kyverno.Policy)(policyCopy), v1.UpdateOptions{})
return err
}
// mergeWebhook merges the matching kinds of the policy to webhook.rule
func (m *webhookConfigManager) mergeWebhook(dst *webhook, policy *kyverno.ClusterPolicy) {
matchedGVK := make([]string, 0)
for _, rule := range policy.Spec.Rules {
matchedGVK = append(matchedGVK, rule.MatchKinds()...)
if rule.HasGenerate() {
matchedGVK = append(matchedGVK, rule.Generation.ResourceSpec.Kind)
}
}
gvkMap := make(map[string]int)
gvrList := make([]schema.GroupVersionResource, 0)
for _, gvk := range matchedGVK {
if _, ok := gvkMap[gvk]; !ok {
gvkMap[gvk] = 1
// note: webhook stores GVR in its rules while policy stores GVK in its rules definition
gv, k := common.GetKindFromGVK(gvk)
_, gvr, err := m.client.DiscoveryClient.FindResource(gv, k)
if err != nil {
continue
}
gvrList = append(gvrList, gvr)
}
}
var groups, versions, rsrcs []string
if val, ok := dst.rule[apiGroups]; ok {
groups = make([]string, len(val.([]string)))
copy(groups, val.([]string))
}
if val, ok := dst.rule[apiVersions]; ok {
versions = make([]string, len(val.([]string)))
copy(versions, val.([]string))
}
if val, ok := dst.rule[resources]; ok {
rsrcs = make([]string, len(val.([]string)))
copy(rsrcs, val.([]string))
}
for _, gvr := range gvrList {
groups = append(groups, gvr.Group)
versions = append(versions, gvr.Version)
rsrcs = append(rsrcs, gvr.Resource)
}
dst.rule[apiGroups] = removeDuplicates(groups)
dst.rule[apiVersions] = removeDuplicates(versions)
dst.rule[resources] = removeDuplicates(rsrcs)
if policy.Spec.WebhookTimeoutSeconds != nil {
if dst.maxWebhookTimeout < int64(*policy.Spec.WebhookTimeoutSeconds) {
dst.maxWebhookTimeout = int64(*policy.Spec.WebhookTimeoutSeconds)
}
}
}
func removeDuplicates(items []string) (res []string) {
set := make(map[string]int)
for _, item := range items {
if _, ok := set[item]; !ok {
set[item] = 1
res = append(res, item)
}
}
return
}
func newWebhook(kind string, timeout int64, failurePolicy kyverno.FailurePolicyType) *webhook {
return &webhook{
kind: kind,
maxWebhookTimeout: timeout,
failurePolicy: failurePolicy,
rule: make(map[string]interface{}),
}
}
func webhookKey(webhookKind, failurePolicy string) string {
return strings.Join([]string{webhookKind, failurePolicy}, "/")
}
func hasWildcard(policy interface{}) bool {
if p, ok := policy.(*kyverno.ClusterPolicy); ok {
for _, rule := range p.Spec.Rules {
if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") {
return true
}
}
}
if p, ok := policy.(*kyverno.Policy); ok {
for _, rule := range p.Spec.Rules {
if kinds := rule.MatchKinds(); utils.ContainsString(kinds, "*") {
return true
}
}
}
return false
}
func setWildcardConfig(w *webhook) {
w.rule[apiGroups] = []string{"*"}
w.rule[apiVersions] = []string{"*"}
w.rule[resources] = []string{"*/*"}
}

Some files were not shown because too many files have changed in this diff Show more