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:
commit
8437582622
151 changed files with 9528 additions and 7081 deletions
10
.github/workflows/build.yaml
vendored
10
.github/workflows/build.yaml
vendored
|
@ -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
|
||||
|
|
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
13
Makefile
13
Makefile
|
@ -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
|
||||
|
|
|
@ -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` |
|
||||
|
|
|
@ -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
|
@ -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 }}
|
||||
|
|
|
@ -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
|
||||
|
|
14
charts/kyverno/templates/poddisruptionbudget.yaml
Normal file
14
charts/kyverno/templates/poddisruptionbudget.yaml
Normal 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 }}
|
|
@ -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: []
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -8,3 +8,4 @@ resources:
|
|||
- ./metricsconfigmap.yaml
|
||||
- ./service.yaml
|
||||
- ./serviceaccount.yaml
|
||||
- ./poddisruptionbudget.yaml
|
||||
|
|
14
definitions/k8s-resource/poddisruptionbudget.yaml
Normal file
14
definitions/k8s-resource/poddisruptionbudget.yaml
Normal 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
|
|
@ -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
3
go.mod
|
@ -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
109
go.sum
|
@ -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=
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
30
pkg/engine/response/response_test.go
Normal file
30
pkg/engine/response/response_test.go
Normal 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)
|
||||
}
|
97
pkg/engine/response/status.go
Normal file
97
pkg/engine/response/status.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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...)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
126
pkg/policy/metrics.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package signal
|
||||
|
||||
//TODO: how to pick files based on OS compilation ?
|
||||
// import (
|
||||
// "os"
|
||||
// )
|
||||
|
||||
// var shutdownSignals = []os.Signal{os.Interrupt}
|
|
@ -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)
|
||||
}
|
||||
|
|
69
pkg/testrunner/scenario_test.go
Normal file
69
pkg/testrunner/scenario_test.go
Normal 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")
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
755
pkg/webhookconfig/configmanager.go
Normal file
755
pkg/webhookconfig/configmanager.go
Normal 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
Loading…
Add table
Reference in a new issue