1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-08 10:04:25 +00:00

merge main

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
Jim Bugwadia 2021-10-06 10:51:43 -07:00
commit 0bb35aa302
79 changed files with 2437 additions and 486 deletions

View file

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

View file

@ -18,6 +18,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: login to GitHub Container Registry
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
@ -31,6 +36,11 @@ jobs:
run: |
make docker-publish-initContainer
- name: Sign image
run: |
KYVERNO_IMAGE_VERSION=$(git describe --match "v[0-9]*")
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyvernopre:${KYVERNO_IMAGE_VERSION}
push-kyverno:
runs-on: ubuntu-latest
steps:
@ -45,6 +55,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: login to GitHub Container Registry
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
@ -58,6 +73,11 @@ jobs:
run: |
make docker-publish-kyverno
- name: Sign image
run: |
KYVERNO_IMAGE_VERSION=$(git describe --match "v[0-9]*")
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyverno:${KYVERNO_IMAGE_VERSION}
push-kyverno-cli:
runs-on: ubuntu-latest
steps:
@ -72,6 +92,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: login to GitHub Container Registry
run: echo ${{ secrets.CR_PAT }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
@ -83,4 +108,9 @@ jobs:
- name: docker images publish
run: |
make docker-publish-cli
make docker-publish-cli
- name: Sign image
run: |
KYVERNO_IMAGE_VERSION=$(git describe --match "v[0-9]*")
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyverno-cli:${KYVERNO_IMAGE_VERSION}

View file

@ -17,6 +17,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: Cache Go modules
uses: actions/cache@v1
with:
@ -40,10 +45,18 @@ jobs:
with:
install: true
- name: Set version
run: |
echo "KYVERNO_VERSION=$(git describe --match "v[0-9]*")"
- name : docker images publish
run: |
make docker-publish-initContainer
- name: Sign image
run: |
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyvernopre:${KYVERNO_VERSION}
release-kyverno:
runs-on: ubuntu-latest
steps:
@ -57,6 +70,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: Cache Go modules
uses: actions/cache@v1
with:
@ -80,10 +98,41 @@ jobs:
with:
install: true
- name: Set version
run: |
echo "KYVERNO_VERSION=$(git describe --match "v[0-9]*")"
- name: Generate SBOM JSON
uses: CycloneDX/gh-gomod-generate-sbom@v0.3.0
with:
json: true
output: kyverno-v${{ env.KYVERNO_VERSION }}-bom.cdx.json
resolve-licenses: true
version: ^v0
- uses: actions/upload-artifact@v2
with:
name: kyverno-bom-cdx
path: kyverno-v*-bom.cdx.json
- name : docker images publish
run: |
make docker-publish-kyverno
- name: Sign image and SBOM
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
steps:
@ -97,6 +146,11 @@ jobs:
with:
go-version: 1.16
- name: Install Cosign
uses: sigstore/cosign-installer@main
with:
cosign-release: 'v1.2.1'
- name: Cache Go modules
uses: actions/cache@v1
with:
@ -120,9 +174,17 @@ jobs:
with:
install: true
- name: Set version
run: |
echo "KYVERNO_VERSION=$(git describe --match "v[0-9]*")"
- name : docker images publish
run: |
make docker-publish-cli
- name: Sign image
run: |
echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY_PASSWORD }}" | cosign sign -key <(echo -n "${{ secrets.KYVERNO_COSIGN_PRIVATE_KEY }}") ghcr.io/kyverno/kyverno-cli:${KYVERNO_VERSION}
create-release:
runs-on: ubuntu-latest

View file

@ -1,3 +1,10 @@
## v1.5.0-rc1
### Note
- With the change of dynamic webhooks, the readiness of the policy is reflected by `.status.ready`, When ready, it means the policy is ready to serve the admission requests.
### Deprecation
- To add a consistent style in flag names the following flags have been deprecated `webhooktimeout`, `gen-workers`,`disable-metrics`, `background-scan`, `auto-update-webhooks` these will be removed in 1.6.0. The new flags are `webhookTimeout`, `genWorkers`, `disablMetrics`, `backgroundScan`, `autoUpdateWebhooks`.
## v1.4.3
## v1.4.3-rc2

View file

@ -202,6 +202,7 @@ test-e2e-local:
#Test TestCmd Policy
test-cmd: cli
$(PWD)/$(CLI_PATH)/kyverno test https://github.com/kyverno/policies/main
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-mutate
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-policy && exit 1 || exit 0
$(PWD)/$(CLI_PATH)/kyverno test ./test/cli/test-fail/missing-rule && exit 1 || exit 0

View file

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

View file

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

View file

@ -33,6 +33,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -576,7 +577,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:
@ -1250,7 +1253,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:
@ -1812,10 +1817,11 @@ spec:
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for
this policy. After the timeout passes, the admission request will
fail based on the failure policy. The default timeout is 3s, the
value must be between 1 and 30 seconds. Default to 10 seconds.
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
@ -3439,6 +3445,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -3983,7 +3990,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:
@ -4657,7 +4666,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:
@ -5219,10 +5230,11 @@ spec:
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for
this policy. After the timeout passes, the admission request will
fail based on the failure policy. The default timeout is 3s, the
value must be between 1 and 30 seconds. Default to 10 seconds.
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

View file

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

View file

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

View file

@ -10,8 +10,6 @@ import (
"strings"
"time"
"github.com/kyverno/kyverno/pkg/cosign"
"github.com/prometheus/client_golang/prometheus/promhttp"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/klog/v2"
@ -23,6 +21,7 @@ import (
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/cosign"
dclient "github.com/kyverno/kyverno/pkg/dclient"
event "github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/generate"
@ -59,6 +58,7 @@ var (
genWorkers int
profile bool
disableMetricsExport bool
autoUpdateWebhooks bool
policyControllerResyncPeriod time.Duration
imagePullSecrets string
imageSignatureRepository string
@ -71,17 +71,28 @@ 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")
flag.IntVar(&genWorkers, "gen-workers", 10, "Workers for generate controller")
// deprecated
flag.IntVar(&webhookTimeout, "webhooktimeout", int(webhookconfig.DefaultWebhookTimeout), "Timeout for webhook configurations. Deprecated and will be removed in 1.6.0.")
flag.IntVar(&webhookTimeout, "webhookTimeout", int(webhookconfig.DefaultWebhookTimeout), "Timeout for webhook configurations.")
// deprecated
flag.IntVar(&genWorkers, "gen-workers", 10, "Workers for generate controller. Deprecated and will be removed in 1.6.0. ")
flag.IntVar(&genWorkers, "genWorkers", 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.")
flag.BoolVar(&profile, "profile", false, "Set this flag to 'true', to enable profiling.")
flag.StringVar(&profilePort, "profile-port", "6060", "Enable profiling at given port, defaults to 6060.")
flag.BoolVar(&disableMetricsExport, "disable-metrics", false, "Set this flag to 'true', to enable exposing the metrics.")
// deprecated
flag.BoolVar(&disableMetricsExport, "disable-metrics", false, "Set this flag to 'true', to enable exposing the metrics. Deprecated and will be removed in 1.6.0. ")
flag.BoolVar(&disableMetricsExport, "disableMetrics", false, "Set this flag to 'true', to enable exposing the metrics.")
flag.StringVar(&metricsPort, "metrics-port", "8000", "Expose prometheus metrics at the given port, default to 8000.")
flag.DurationVar(&policyControllerResyncPeriod, "background-scan", time.Hour, "Perform background scan every given interval, e.g., 30s, 15m, 1h.")
// deprecated
flag.DurationVar(&policyControllerResyncPeriod, "background-scan", time.Hour, "Perform background scan every given interval, e.g., 30s, 15m, 1h. Deprecated and will be removed in 1.6.0. ")
flag.DurationVar(&policyControllerResyncPeriod, "backgroundScan", 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`.")
// deprecated
flag.BoolVar(&autoUpdateWebhooks, "auto-update-webhooks", true, "Set this flag to 'false' to disable auto-configuration of the webhook. Deprecated and will be removed in 1.6.0.")
flag.BoolVar(&autoUpdateWebhooks, "autoUpdateWebhooks", 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 +229,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 +397,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)

4
cosign.pub Normal file
View file

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExxWHpvn2uMYqg174TmTcnGELOXXM
7/cGqLZW88FFceihl1WA24yKxtMBZqw/s06XqPqujqRzhkaSKa2zkRUWUA==
-----END PUBLIC KEY-----

View file

@ -27,6 +27,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -570,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:
@ -1244,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:
@ -1806,10 +1811,11 @@ spec:
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for
this policy. After the timeout passes, the admission request will
fail based on the failure policy. The default timeout is 3s, the
value must be between 1 and 30 seconds. Default to 10 seconds.
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

View file

@ -27,6 +27,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -571,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:
@ -1245,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:
@ -1807,10 +1812,11 @@ spec:
in a policy report. Optional. The default value is "audit".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for
this policy. After the timeout passes, the admission request will
fail based on the failure policy. The default timeout is 3s, the
value must be between 1 and 30 seconds. Default to 10 seconds.
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

View file

@ -45,6 +45,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -374,7 +375,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified.
description: 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.
properties:
annotations:
additionalProperties:
@ -781,7 +782,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources.
description: 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.
properties:
annotations:
additionalProperties:
@ -1138,7 +1139,7 @@ spec:
description: 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".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for this policy. After the timeout passes, the admission request will fail based on the failure policy. The default timeout is 3s, the value must be between 1 and 30 seconds. Default to 10 seconds.
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
@ -2363,6 +2364,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -2692,7 +2694,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified.
description: 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.
properties:
annotations:
additionalProperties:
@ -3099,7 +3101,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources.
description: 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.
properties:
annotations:
additionalProperties:
@ -3456,7 +3458,7 @@ spec:
description: 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".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for this policy. After the timeout passes, the admission request will fail based on the failure policy. The default timeout is 3s, the value must be between 1 and 30 seconds. Default to 10 seconds.
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
@ -5089,3 +5091,23 @@ spec:
securityContext:
runAsNonRoot: true
serviceAccountName: kyverno-service-account
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
app: kyverno
app.kubernetes.io/component: kyverno
app.kubernetes.io/instance: kyverno
app.kubernetes.io/managed-by: Kustomize
app.kubernetes.io/name: kyverno
app.kubernetes.io/part-of: kyverno
app.kubernetes.io/version: v1.4.3
name: kyverno
namespace: kyverno
spec:
minAvailable: 0
selector:
matchLabels:
app: kyverno
app.kubernetes.io/name: kyverno

View file

@ -32,6 +32,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -361,7 +362,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified.
description: 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.
properties:
annotations:
additionalProperties:
@ -768,7 +769,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources.
description: 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.
properties:
annotations:
additionalProperties:
@ -1125,7 +1126,7 @@ spec:
description: 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".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for this policy. After the timeout passes, the admission request will fail based on the failure policy. The default timeout is 3s, the value must be between 1 and 30 seconds. Default to 10 seconds.
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
@ -2322,6 +2323,7 @@ spec:
type: string
- jsonPath: .spec.failurePolicy
name: Failure Policy
priority: 1
type: string
- jsonPath: .status.ready
name: Ready
@ -2651,7 +2653,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified.
description: 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.
properties:
annotations:
additionalProperties:
@ -3058,7 +3060,7 @@ spec:
type: string
type: array
resources:
description: ResourceDescription contains information about the resource being created or modified. Requires at least one tag to be specified when under MatchResources.
description: 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.
properties:
annotations:
additionalProperties:
@ -3415,7 +3417,7 @@ spec:
description: 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".
type: string
webhookTimeoutSeconds:
description: WebhookTimeoutSeconds specifies the webhook timeout for this policy. After the timeout passes, the admission request will fail based on the failure policy. The default timeout is 3s, the value must be between 1 and 30 seconds. Default to 10 seconds.
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
@ -4782,3 +4784,17 @@ spec:
selector:
app: kyverno
app.kubernetes.io/name: kyverno
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
labels:
app: kyverno
name: kyverno
namespace: kyverno
spec:
minAvailable: 0
selector:
matchLabels:
app: kyverno
app.kubernetes.io/name: kyverno

View file

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

View file

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

View file

@ -65,11 +65,11 @@ spec:
args:
- "--filterK8sResources=[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*][Binding,*,*][ReplicaSet,*,*][ReportChangeRequest,*,*][ClusterReportChangeRequest,*,*][PolicyReport,*,*][ClusterPolicyReport,*,*]"
# customize webhook timeout
#- "--webhooktimeout=4"
#- "--webhookTimeout=4"
# enable profiling
# - "--profile"
# configure the workers for generate controller
# - --gen-workers=20
# - --genWorkers=20
- "-v=2"
ports:
- containerPort: 9443

View file

@ -13,7 +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"
// +kubebuilder:printcolumn:name="Failure Policy",type="string",JSONPath=".spec.failurePolicy",priority=1
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.ready`
type ClusterPolicy struct {
metav1.TypeMeta `json:",inline,omitempty" yaml:",inline,omitempty"`

View file

@ -22,7 +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"
// +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 {
@ -68,10 +68,9 @@ type Spec struct {
// +optional
SchemaValidation *bool `json:"schemaValidation,omitempty" yaml:"schemaValidation,omitempty"`
// WebhookTimeoutSeconds specifies the webhook timeout for this policy.
// After the timeout passes, the admission request will fail based on the failure policy.
// The default timeout is 3s, the value must be between 1 and 30 seconds.
// Default to 10 seconds.
// 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"`
}
@ -267,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"`
}
@ -288,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"`
}

View file

@ -38,6 +38,28 @@ func (p *ClusterPolicy) HasMutate() bool {
return false
}
// HasValidate checks for validate rule types
func (p *ClusterPolicy) HasValidate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasValidate() {
return true
}
}
return false
}
// HasGenerate checks for generate rule types
func (p *ClusterPolicy) HasGenerate() bool {
for _, rule := range p.Spec.Rules {
if rule.HasGenerate() {
return true
}
}
return false
}
//HasVerifyImages checks for image verification rule types
func (p *ClusterPolicy) HasVerifyImages() bool {
for _, rule := range p.Spec.Rules {
@ -78,6 +100,29 @@ func (r Rule) HasGenerate() bool {
return !reflect.DeepEqual(r.Generation, Generation{})
}
func (r Rule) MatchKinds() []string {
matchKinds := r.MatchResources.ResourceDescription.Kinds
for _, value := range r.MatchResources.All {
matchKinds = append(matchKinds, value.ResourceDescription.Kinds...)
}
for _, value := range r.MatchResources.Any {
matchKinds = append(matchKinds, value.ResourceDescription.Kinds...)
}
return matchKinds
}
func (r Rule) ExcludeKinds() []string {
excludeKinds := r.ExcludeResources.ResourceDescription.Kinds
for _, value := range r.ExcludeResources.All {
excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...)
}
for _, value := range r.ExcludeResources.Any {
excludeKinds = append(excludeKinds, value.ResourceDescription.Kinds...)
}
return excludeKinds
}
// DeserializeAnyPattern deserialize apiextensions.JSON to []interface{}
func (in *Validation) DeserializeAnyPattern() ([]interface{}, error) {
if in.AnyPattern == nil {
@ -122,6 +167,11 @@ func (in *Validation) DeepCopyInto(out *Validation) {
*out = *in
}
}
func (in *ForEachValidation) DeepCopyInto(out *ForEachValidation) {
if out != nil {
*out = *in
}
}
func (gen *Generation) DeepCopyInto(out *Generation) {
if out != nil {
*out = *gen

View file

@ -43,6 +43,22 @@ func (in *APICall) DeepCopy() *APICall {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionRequestInfoObject) DeepCopyInto(out *AdmissionRequestInfoObject) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequestInfoObject.
func (in *AdmissionRequestInfoObject) DeepCopy() *AdmissionRequestInfoObject {
if in == nil {
return nil
}
out := new(AdmissionRequestInfoObject)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AnyAllConditions) DeepCopyInto(out *AnyAllConditions) {
*out = *in
@ -244,6 +260,16 @@ func (in *ExcludeResources) DeepCopy() *ExcludeResources {
return out
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForEachValidation.
func (in *ForEachValidation) DeepCopy() *ForEachValidation {
if in == nil {
return nil
}
out := new(ForEachValidation)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GenerateRequest) DeepCopyInto(out *GenerateRequest) {
*out = *in
@ -276,6 +302,7 @@ func (in *GenerateRequest) DeepCopyObject() runtime.Object {
func (in *GenerateRequestContext) DeepCopyInto(out *GenerateRequestContext) {
*out = *in
in.UserRequestInfo.DeepCopyInto(&out.UserRequestInfo)
out.AdmissionRequestInfo = in.AdmissionRequestInfo
return
}

View file

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

View file

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

View file

@ -215,6 +215,7 @@ func (ctx *Context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error {
ctx.log.Error(err, "failed to marshal the UserInfo")
return err
}
ctx.log.V(4).Info("Adding user info logs", "userRequestInfo", userRequestInfo)
return ctx.AddJSON(objRaw)
}
@ -263,7 +264,7 @@ func (ctx *Context) AddServiceAccount(userName string) error {
if err := ctx.AddJSON(saNsRaw); err != nil {
return err
}
ctx.log.V(4).Info("Adding service account", "service account name", saName, "service account namespace", saNamespace)
return nil
}

View file

@ -75,10 +75,10 @@ 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",

View file

@ -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
@ -79,7 +78,7 @@ func ProcessPatches(log logr.Logger, ruleName string, mutation kyverno.Mutation,
}
err = patchedResource.UnmarshalJSON(resourceRaw)
if err != nil {
logger.Error(err, "failed to unmmarshal resource")
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

View file

@ -65,7 +65,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
excludeResource = policyContext.ExcludeGroupRole
}
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels); err != nil {
if err = MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels, policyContext.Policy.Namespace); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
continue
}
@ -116,7 +116,7 @@ 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(),
Type: utils.Mutation.String(),
Message: fmt.Sprintf("variable substitution failed: %s", err.Error()),
Status: response.RuleStatusPass,
}

View file

@ -256,13 +256,16 @@ 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()
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 {
// include object if ANY of the criteria match
// so if one matches then break from loop

View file

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

View file

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

View file

@ -420,13 +420,13 @@ func isEmptyUnstructured(u *unstructured.Unstructured) bool {
// 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)
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
}

View file

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

View file

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

View file

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

View file

@ -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
@ -892,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() {
@ -929,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)
@ -941,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")
@ -1009,3 +1018,34 @@ func GetKindsFromPolicy(policy *v1.ClusterPolicy) map[string]struct{} {
}
return kindOnwhichPolicyIsApplied
}
//GetPatchedResourceFromPath - get patchedResource from given path
func GetPatchedResourceFromPath(fs billy.Filesystem, path string, isGit bool, policyResourcePath string) (unstructured.Unstructured, error) {
var patchedResourceBytes []byte
var patchedResource unstructured.Unstructured
var err error
if isGit {
if len(path) > 0 {
filep, err := fs.Open(filepath.Join(policyResourcePath, path))
if err != nil {
fmt.Printf("Unable to open patchedResource file: %s. \nerror: %s", path, err)
}
patchedResourceBytes, err = ioutil.ReadAll(filep)
}
} else {
patchedResourceBytes, err = getFileBytes(path)
}
if err != nil {
fmt.Printf("\n----------------------------------------------------------------------\nfailed to load patchedResource: %s. \nerror: %s\n----------------------------------------------------------------------\n", path, err)
return patchedResource, err
}
patchedResource, err = GetPatchedResource(patchedResourceBytes)
if err != nil {
return patchedResource, err
}
return patchedResource, nil
}

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
@ -14,11 +15,13 @@ import (
"github.com/fatih/color"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-logr/logr"
"github.com/kataras/tablewriter"
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha2"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/generate"
"github.com/kyverno/kyverno/pkg/kyverno/common"
sanitizederror "github.com/kyverno/kyverno/pkg/kyverno/sanitizedError"
"github.com/kyverno/kyverno/pkg/kyverno/store"
@ -30,17 +33,97 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"
log "sigs.k8s.io/controller-runtime/pkg/log"
)
var longHelp = `
Test command provides a facility to test policies on resources. User should provide the path of the folder containing test.yaml file.
kyverno test <path_to_folder_Contaning_test.yamls>
or
kyverno test <path_to_githubRepository>
The test.yaml have 4 parts:
"policies" --> list of policies which are applied
"resources" --> list of resources on which the policies are applied
"variables" --> variable file path (this is an optinal parameter)
"results" --> list of result expected on applying the policies on the resources
`
var exampleHelp = `
test.yaml format:
For Validate Policy
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
result: <pass/fail/skip>
For Mutate Policy
1) Policy (Namespaced-policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <policy_namespace>/<policy_name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
2) ClusterPolicy(Cluster-wide policy)
- name: test-1
policies:
- <path>
- <path>
resources:
- <path>
- <path>
results:
- policy: <name>
rule: <name>
resource: <name>
namespace: <name> (OPTIONAL)
kind: <name>
patchedResource: <path>
result: <pass/fail/skip>
Result description:
pass --> patched Resource generated from engine equals to patched Resource provided by the user.
fail --> patched Resource generated from engine is not equals to patched provided by the user.
skip --> rule is not applied.
For more visit --> https://kyverno.io/docs/kyverno-cli/#test
`
// Command returns version command
func Command() *cobra.Command {
var cmd *cobra.Command
var valuesFile, fileName string
cmd = &cobra.Command{
Use: "test",
Short: "run tests from directory",
Use: "test",
Short: "run tests from directory",
Long: longHelp,
Example: exampleHelp,
RunE: func(cmd *cobra.Command, dirPath []string) (err error) {
defer func() {
if err != nil {
@ -78,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"`
}
@ -239,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 {
@ -262,27 +351,81 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
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
}
}
}
@ -294,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.Status)
result.Source = policyreport.SourceValue
result.Timestamp = now
results[resultsKey] = result
results[resultKey] = result
}
}
}
@ -313,6 +461,64 @@ func buildPolicyResults(resps []*response.EngineResponse, testResults []TestResu
return results, testResults
}
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 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 {
@ -323,20 +529,22 @@ func buildMessage(resp *response.EngineResponse) string {
return bldr.String()
}
func getPolicyResourceFullPath(path []string, policyResourcePath string, isGit bool) []string {
var pol []string
func getFullPath(paths []string, policyResourcePath string, isGit bool) []string {
var pols []string
var pol string
if !isGit {
for _, p := range path {
pol = append(pol, filepath.Join(policyResourcePath, p))
for _, path := range paths {
pol = filepath.Join(policyResourcePath, path)
pols = append(pols, pol)
}
return pol
return pols
}
return path
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
@ -358,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)
@ -379,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)
@ -426,16 +640,15 @@ 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("failed to print test result:", resultErr)
@ -451,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)
@ -465,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 {
@ -476,26 +708,28 @@ 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++
} else {
res.Result = boldGreen.Sprintf("Pass")
rc.Pass++
}
} else {
fmt.Printf("test failed for policy=%s, rule=%s, resource=%s, expected=%s, received=%s \n",
v.Policy, v.Rule, v.Resource, v.Result, testRes.Result)
fmt.Printf("%s \n", testRes.Message)
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 = "│"
@ -504,8 +738,10 @@ func printTestResult(resps map[string]report.PolicyReportResult, testResults []T
printer.RowLengthTitle = func(rowsLength int) bool {
return rowsLength > 10
}
printer.HeaderBgColor = tablewriter.BgBlackColor
printer.HeaderFgColor = tablewriter.FgGreenColor
fmt.Printf("\n")
printer.Print(table)
return nil
}

View file

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

View file

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

View file

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

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

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

View file

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

View file

@ -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"
@ -177,12 +176,41 @@ func Validate(policy *kyverno.ClusterPolicy, client *dclient.Client, mock bool,
}
// Validate Kind with match resource kinds
for _, kind := range rule.MatchResources.Kinds {
_, k := c.GetKindFromGVK(kind)
if k == p.Kind {
return fmt.Errorf("kind and match resource kind should not be the same.")
match := rule.MatchResources
exclude := rule.ExcludeResources
for _, value := range match.Any {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the any match resource is invalid")
}
}
for _, value := range match.All {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the all match resource is invalid")
}
}
for _, value := range exclude.Any {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the any exclude resource is invalid")
}
}
for _, value := range exclude.All {
err := validateKinds(value.ResourceDescription.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("the kind defined in the all exclude resource is invalid")
}
}
err := validateKinds(rule.MatchResources.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("match resource kind is invalid ")
}
err = validateKinds(rule.ExcludeResources.Kinds, mock, client, p)
if err != nil {
return fmt.Errorf("exclude resource kind is invalid ")
}
// Validate string values in labels
if !isLabelAndAnnotationsString(rule) {
@ -1027,3 +1055,19 @@ func jsonPatchOnPod(rule kyverno.Rule) bool {
return false
}
func validateKinds(kinds []string, mock bool, client *dclient.Client, p kyverno.ClusterPolicy) error {
for _, kind := range kinds {
gv, k := comn.GetKindFromGVK(kind)
if !mock {
_, _, err := client.DiscoveryClient.FindResource(gv, k)
if err != nil || strings.ToLower(k) == k {
return fmt.Errorf("match resource kind %s is invalid ", k)
}
}
if k == p.Kind {
return fmt.Errorf("kind and match resource kind should not be the same")
}
}
return nil
}

View file

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

View file

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

View file

@ -421,7 +421,6 @@ func loadObjects(t *testing.T, path string) []k8sRuntime.Object {
continue
}
t.Log(gvk)
//TODO: add more details
t.Logf("loaded object %s", gvk.Kind)
resources = append(resources, obj)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -74,7 +74,7 @@ func (t *Monitor) SetTime(tm time.Time) {
// Run runs the checker and verify the resource update
func (t *Monitor) Run(register *Register, certRenewer *tls.CertRenewer, eventGen event.Interface, stopCh <-chan struct{}) {
logger := t.log
logger := t.log.WithName("webhookMonitor")
logger.V(4).Info("starting webhook monitor", "interval", idleCheckInterval.String())
status := newStatusControl(register, eventGen, t.log.WithName("WebhookStatusControl"))
@ -82,8 +82,23 @@ func (t *Monitor) Run(register *Register, certRenewer *tls.CertRenewer, eventGen
ticker := time.NewTicker(tickerInterval)
defer ticker.Stop()
createDefaultWebhook := register.createDefaultWebhook
for {
select {
case webhookKind := <-createDefaultWebhook:
logger.Info("received recreation request for resource webhook")
if webhookKind == kindMutating {
err := register.createResourceMutatingWebhookConfiguration(register.readCaData())
if err != nil {
logger.Error(err, "failed to create default MutatingWebhookConfiguration for resources, the webhook will be reconciled", "interval", tickerInterval)
}
} else if webhookKind == kindValidating {
err := register.createResourceValidatingWebhookConfiguration(register.readCaData())
if err != nil {
logger.Error(err, "failed to create default ValidatingWebhookConfiguration for resources, the webhook will be reconciled", "interval", tickerInterval)
}
}
case <-ticker.C:
err := registerWebhookIfNotPresent(register, t.log.WithName("registerWebhookIfNotPresent"))

View file

@ -4,11 +4,11 @@ import (
"fmt"
"github.com/kyverno/kyverno/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1"
admregapi "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (wrc *Register) contructPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
func (wrc *Register) constructPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
@ -24,16 +24,19 @@ func (wrc *Register) contructPolicyValidatingWebhookConfig(caData []byte) *admre
caData,
true,
wrc.timeoutSeconds,
[]string{"clusterpolicies/*", "policies/*"},
"kyverno.io",
"v1",
admregapi.Rule{
Resources: []string{"clusterpolicies/*", "policies/*"},
APIGroups: []string{"kyverno.io"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
},
}
}
func (wrc *Register) contructDebugPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
func (wrc *Register) constructDebugPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
logger := wrc.log
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.PolicyValidatingWebhookServicePath)
logger.V(4).Info("Debug PolicyValidatingWebhookConfig is registered with url ", "url", url)
@ -49,16 +52,19 @@ func (wrc *Register) contructDebugPolicyValidatingWebhookConfig(caData []byte) *
caData,
true,
wrc.timeoutSeconds,
[]string{"clusterpolicies/*", "policies/*"},
"kyverno.io",
"v1",
admregapi.Rule{
Resources: []string{"clusterpolicies/*", "policies/*"},
APIGroups: []string{"kyverno.io"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
},
}
}
func (wrc *Register) contructPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
func (wrc *Register) constructPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyMutatingWebhookConfigurationName,
@ -73,16 +79,19 @@ func (wrc *Register) contructPolicyMutatingWebhookConfig(caData []byte) *admrega
caData,
true,
wrc.timeoutSeconds,
[]string{"clusterpolicies/*", "policies/*"},
"kyverno.io",
"v1",
admregapi.Rule{
Resources: []string{"clusterpolicies/*", "policies/*"},
APIGroups: []string{"kyverno.io"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
},
}
}
func (wrc *Register) contructDebugPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
func (wrc *Register) constructDebugPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
logger := wrc.log
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.PolicyMutatingWebhookServicePath)
logger.V(4).Info("Debug PolicyMutatingWebhookConfig is registered with url ", "url", url)
@ -98,10 +107,13 @@ func (wrc *Register) contructDebugPolicyMutatingWebhookConfig(caData []byte) *ad
caData,
true,
wrc.timeoutSeconds,
[]string{"clusterpolicies/*", "policies/*"},
"kyverno.io",
"v1",
admregapi.Rule{
Resources: []string{"clusterpolicies/*", "policies/*"},
APIGroups: []string{"kyverno.io"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
},
}

View file

@ -8,12 +8,14 @@ import (
"time"
"github.com/go-logr/logr"
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/resourcecache"
"github.com/kyverno/kyverno/pkg/tls"
"github.com/pkg/errors"
admregapi "k8s.io/api/admissionregistration/v1beta1"
admregapi "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
errorsapi "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -34,36 +36,54 @@ const (
// 4. Resource Mutation
// 5. Webhook Status Mutation
type Register struct {
client *client.Client
clientConfig *rest.Config
resCache resourcecache.ResourceCache
serverIP string // when running outside a cluster
timeoutSeconds int32
log logr.Logger
debug bool
client *client.Client
clientConfig *rest.Config
resCache resourcecache.ResourceCache
serverIP string // when running outside a cluster
timeoutSeconds int32
log logr.Logger
debug bool
autoUpdateWebhooks bool
UpdateWebhookChan chan bool
UpdateWebhookChan chan bool
createDefaultWebhook chan string
// manage implements methods to manage webhook configurations
manage
}
// NewRegister creates new Register instance
func NewRegister(
clientConfig *rest.Config,
client *client.Client,
kyvernoClient *kyvernoclient.Clientset,
resCache resourcecache.ResourceCache,
pInformer kyvernoinformer.ClusterPolicyInformer,
npInformer kyvernoinformer.PolicyInformer,
serverIP string,
webhookTimeout int32,
debug bool,
autoUpdateWebhooks bool,
stopCh <-chan struct{},
log logr.Logger) *Register {
return &Register{
clientConfig: clientConfig,
client: client,
resCache: resCache,
serverIP: serverIP,
timeoutSeconds: webhookTimeout,
log: log.WithName("Register"),
debug: debug,
UpdateWebhookChan: make(chan bool),
register := &Register{
clientConfig: clientConfig,
client: client,
resCache: resCache,
serverIP: serverIP,
timeoutSeconds: webhookTimeout,
log: log.WithName("Register"),
debug: debug,
autoUpdateWebhooks: autoUpdateWebhooks,
UpdateWebhookChan: make(chan bool),
createDefaultWebhook: make(chan string),
}
if register.autoUpdateWebhooks {
register.manage = newWebhookConfigManager(client, kyvernoClient, pInformer, npInformer, resCache, register.createDefaultWebhook, stopCh, log.WithName("WebhookConfigManager"))
}
return register
}
// Register clean up the old webhooks and re-creates admission webhooks configs on cluster
@ -109,6 +129,9 @@ func (wrc *Register) Register() error {
return fmt.Errorf("%s", strings.Join(errors, ","))
}
if wrc.autoUpdateWebhooks {
go wrc.manage.start()
}
return nil
}
@ -121,19 +144,19 @@ func (wrc *Register) Check() error {
return err
}
if _, err := mutatingCache.Lister().Get(wrc.getResourceMutatingWebhookConfigName()); err != nil {
if _, err := mutatingCache.Lister().Get(getResourceMutatingWebhookConfigName(wrc.serverIP)); err != nil {
return err
}
if _, err := validatingCache.Lister().Get(wrc.getResourceValidatingWebhookConfigName()); err != nil {
if _, err := validatingCache.Lister().Get(getResourceValidatingWebhookConfigName(wrc.serverIP)); err != nil {
return err
}
if _, err := mutatingCache.Lister().Get(wrc.getPolicyMutatingWebhookConfigurationName()); err != nil {
if _, err := mutatingCache.Lister().Get(getPolicyMutatingWebhookConfigurationName(wrc.serverIP)); err != nil {
return err
}
if _, err := validatingCache.Lister().Get(wrc.getPolicyValidatingWebhookConfigurationName()); err != nil {
if _, err := validatingCache.Lister().Get(getPolicyValidatingWebhookConfigurationName(wrc.serverIP)); err != nil {
return err
}
@ -151,10 +174,11 @@ func (wrc *Register) Remove(cleanUp chan<- struct{}) {
wrc.removeSecrets()
}
// +deprecated
// UpdateWebhookConfigurations updates resource webhook configurations dynamically
// base on the UPDATEs of Kyverno init-config ConfigMap
//
// it currently updates namespaceSelector only, can be extend to update other fieids
// it currently updates namespaceSelector only, can be extend to update other fields
func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Interface) {
logger := wrc.log.WithName("UpdateWebhookConfigurations")
for {
@ -178,17 +202,17 @@ func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Interface)
}
if err := wrc.updateResourceMutatingWebhookConfiguration(nsSelector); err != nil {
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", wrc.getResourceMutatingWebhookConfigName())
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
go func() { wrc.UpdateWebhookChan <- true }()
} else {
logger.Info("successfully updated mutatingWebhookConfigurations", "name", wrc.getResourceMutatingWebhookConfigName())
logger.Info("successfully updated mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
}
if err := wrc.updateResourceValidatingWebhookConfiguration(nsSelector); err != nil {
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", wrc.getResourceValidatingWebhookConfigName())
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
go func() { wrc.UpdateWebhookChan <- true }()
} else {
logger.Info("successfully updated validatingWebhookConfigurations", "name", wrc.getResourceValidatingWebhookConfigName())
logger.Info("successfully updated validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
}
}
}
@ -302,9 +326,9 @@ func (wrc *Register) createPolicyValidatingWebhookConfiguration(caData []byte) e
var config *admregapi.ValidatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.contructDebugPolicyValidatingWebhookConfig(caData)
config = wrc.constructDebugPolicyValidatingWebhookConfig(caData)
} else {
config = wrc.contructPolicyValidatingWebhookConfig(caData)
config = wrc.constructPolicyValidatingWebhookConfig(caData)
}
if _, err := wrc.client.CreateResource("", kindValidating, "", *config, false); err != nil {
@ -324,9 +348,9 @@ func (wrc *Register) createPolicyMutatingWebhookConfiguration(caData []byte) err
var config *admregapi.MutatingWebhookConfiguration
if wrc.serverIP != "" {
config = wrc.contructDebugPolicyMutatingWebhookConfig(caData)
config = wrc.constructDebugPolicyMutatingWebhookConfig(caData)
} else {
config = wrc.contructPolicyMutatingWebhookConfig(caData)
config = wrc.constructPolicyMutatingWebhookConfig(caData)
}
// create mutating webhook configuration resource
@ -387,7 +411,7 @@ func (wrc *Register) removeWebhookConfigurations() {
func (wrc *Register) removePolicyMutatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
mutatingConfig := wrc.getPolicyMutatingWebhookConfigurationName()
mutatingConfig := getPolicyMutatingWebhookConfigurationName(wrc.serverIP)
logger := wrc.log.WithValues("kind", kindMutating, "name", mutatingConfig)
@ -412,9 +436,9 @@ func (wrc *Register) removePolicyMutatingWebhookConfiguration(wg *sync.WaitGroup
logger.Info("webhook configuration deleted")
}
func (wrc *Register) getPolicyMutatingWebhookConfigurationName() string {
func getPolicyMutatingWebhookConfigurationName(serverIP string) string {
var mutatingConfig string
if wrc.serverIP != "" {
if serverIP != "" {
mutatingConfig = config.PolicyMutatingWebhookConfigurationDebugName
} else {
mutatingConfig = config.PolicyMutatingWebhookConfigurationName
@ -425,7 +449,7 @@ func (wrc *Register) getPolicyMutatingWebhookConfigurationName() string {
func (wrc *Register) removePolicyValidatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
validatingConfig := wrc.getPolicyValidatingWebhookConfigurationName()
validatingConfig := getPolicyValidatingWebhookConfigurationName(wrc.serverIP)
logger := wrc.log.WithValues("kind", kindValidating, "name", validatingConfig)
if mutateCache, ok := wrc.resCache.GetGVRCache("ValidatingWebhookConfiguration"); ok {
@ -450,9 +474,9 @@ func (wrc *Register) removePolicyValidatingWebhookConfiguration(wg *sync.WaitGro
logger.Info("webhook configuration deleted")
}
func (wrc *Register) getPolicyValidatingWebhookConfigurationName() string {
func getPolicyValidatingWebhookConfigurationName(serverIP string) string {
var validatingConfig string
if wrc.serverIP != "" {
if serverIP != "" {
validatingConfig = config.PolicyValidatingWebhookConfigurationDebugName
} else {
validatingConfig = config.PolicyValidatingWebhookConfigurationName
@ -475,10 +499,13 @@ func (wrc *Register) constructVerifyMutatingWebhookConfig(caData []byte) *admreg
caData,
true,
wrc.timeoutSeconds,
[]string{"deployments/*"},
"apps",
"v1",
admregapi.Rule{
Resources: []string{"deployments/*"},
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Update},
admregapi.Ignore,
),
},
}
@ -499,10 +526,13 @@ func (wrc *Register) constructDebugVerifyMutatingWebhookConfig(caData []byte) *a
caData,
true,
wrc.timeoutSeconds,
[]string{"deployments/*"},
"apps",
"v1",
admregapi.Rule{
Resources: []string{"deployments/*"},
APIGroups: []string{"apps"},
APIVersions: []string{"v1"},
},
[]admregapi.OperationType{admregapi.Update},
admregapi.Ignore,
),
},
}
@ -597,7 +627,7 @@ func (wrc *Register) checkEndpoint() error {
}
if podIp == "" {
return fmt.Errorf("Pod is not assigned to any node yet")
return fmt.Errorf("pod is not assigned to any node yet")
}
for _, subset := range endpoint.Subsets {
@ -616,7 +646,7 @@ func (wrc *Register) checkEndpoint() error {
// clean up old webhook configurations, if any
wrc.removeWebhookConfigurations()
err = fmt.Errorf("Endpoint not ready")
err = fmt.Errorf("endpoint not ready")
wrc.log.V(3).Info(err.Error(), "ns", config.KyvernoNamespace, "name", config.KyvernoServiceName)
return err
}
@ -624,7 +654,7 @@ func (wrc *Register) checkEndpoint() error {
func (wrc *Register) updateResourceValidatingWebhookConfiguration(nsSelector map[string]interface{}) error {
validatingCache, _ := wrc.resCache.GetGVRCache(kindValidating)
resourceValidating, err := validatingCache.Lister().Get(wrc.getResourceValidatingWebhookConfigName())
resourceValidating, err := validatingCache.Lister().Get(getResourceValidatingWebhookConfigName(wrc.serverIP))
if err != nil {
return errors.Wrapf(err, "unable to get validatingWebhookConfigurations")
}
@ -660,7 +690,7 @@ func (wrc *Register) updateResourceValidatingWebhookConfiguration(nsSelector map
func (wrc *Register) updateResourceMutatingWebhookConfiguration(nsSelector map[string]interface{}) error {
mutatingCache, _ := wrc.resCache.GetGVRCache(kindMutating)
resourceMutating, err := mutatingCache.Lister().Get(wrc.getResourceMutatingWebhookConfigName())
resourceMutating, err := mutatingCache.Lister().Get(getResourceMutatingWebhookConfigName(wrc.serverIP))
if err != nil {
return errors.Wrapf(err, "unable to get mutatingWebhookConfigurations")
}

View file

@ -5,12 +5,23 @@ import (
"sync"
"github.com/kyverno/kyverno/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
admregapi "k8s.io/api/admissionregistration/v1"
errorsapi "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (wrc *Register) defaultResourceWebhookRule() admregapi.Rule {
if wrc.autoUpdateWebhooks {
return admregapi.Rule{}
}
return admregapi.Rule{
Resources: []string{"*/*"},
APIGroups: []string{"*"},
APIVersions: []string{"*"},
}
}
func (wrc *Register) constructDefaultDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
logger := wrc.log
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.MutatingWebhookServicePath)
@ -21,32 +32,30 @@ func (wrc *Register) constructDefaultDebugMutatingWebhookConfig(caData []byte) *
},
Webhooks: []admregapi.MutatingWebhook{
generateDebugMutatingWebhook(
config.MutatingWebhookName,
config.MutatingWebhookName+"-ignore",
url,
caData,
true,
wrc.timeoutSeconds,
[]string{"*/*"},
"*",
"*",
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
generateDebugMutatingWebhook(
config.MutatingWebhookName+"-fail",
url,
caData,
true,
wrc.timeoutSeconds,
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Fail,
),
},
}
}
func (wrc *Register) constructDefaultMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
webhookCfg := generateMutatingWebhook(
config.MutatingWebhookName,
config.MutatingWebhookServicePath,
caData, false, wrc.timeoutSeconds,
[]string{"*/*"}, "*", "*",
[]admregapi.OperationType{admregapi.Create, admregapi.Update})
reinvoke := admregapi.IfNeededReinvocationPolicy
webhookCfg.ReinvocationPolicy = &reinvoke
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationName,
@ -54,13 +63,34 @@ func (wrc *Register) constructDefaultMutatingWebhookConfig(caData []byte) *admre
wrc.constructOwner(),
},
},
Webhooks: []admregapi.MutatingWebhook{webhookCfg},
Webhooks: []admregapi.MutatingWebhook{
generateMutatingWebhook(
config.MutatingWebhookName+"-ignore",
config.MutatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Ignore,
),
generateMutatingWebhook(
config.MutatingWebhookName+"-fail",
config.MutatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
admregapi.Fail,
),
},
}
}
//getResourceMutatingWebhookConfigName returns the webhook configuration name
func (wrc *Register) getResourceMutatingWebhookConfigName() string {
if wrc.serverIP != "" {
func getResourceMutatingWebhookConfigName(serverIP string) string {
if serverIP != "" {
return config.MutatingWebhookConfigurationDebugName
}
return config.MutatingWebhookConfigurationName
@ -69,7 +99,7 @@ func (wrc *Register) getResourceMutatingWebhookConfigName() string {
func (wrc *Register) removeResourceMutatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
configName := wrc.getResourceMutatingWebhookConfigName()
configName := getResourceMutatingWebhookConfigName(wrc.serverIP)
logger := wrc.log.WithValues("kind", kindMutating, "name", configName)
if mutateCache, ok := wrc.resCache.GetGVRCache("MutatingWebhookConfiguration"); ok {
@ -81,7 +111,7 @@ func (wrc *Register) removeResourceMutatingWebhookConfiguration(wg *sync.WaitGro
// delete webhook configuration
err := wrc.client.DeleteResource("", kindMutating, "", configName, false)
if errors.IsNotFound(err) {
if errorsapi.IsNotFound(err) {
logger.V(4).Info("webhook configuration not found")
return
}
@ -103,15 +133,24 @@ func (wrc *Register) constructDefaultDebugValidatingWebhookConfig(caData []byte)
},
Webhooks: []admregapi.ValidatingWebhook{
generateDebugValidatingWebhook(
config.ValidatingWebhookName,
config.ValidatingWebhookName+"-ignore",
url,
caData,
true,
wrc.timeoutSeconds,
[]string{"*/*"},
"*",
"*",
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete, admregapi.Connect},
admregapi.Ignore,
),
generateDebugValidatingWebhook(
config.ValidatingWebhookName+"-fail",
url,
caData,
true,
wrc.timeoutSeconds,
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete, admregapi.Connect},
admregapi.Fail,
),
},
}
@ -127,23 +166,32 @@ func (wrc *Register) constructDefaultValidatingWebhookConfig(caData []byte) *adm
},
Webhooks: []admregapi.ValidatingWebhook{
generateValidatingWebhook(
config.ValidatingWebhookName,
config.ValidatingWebhookName+"-ignore",
config.ValidatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
[]string{"*/*"},
"*",
"*",
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete, admregapi.Connect},
admregapi.Ignore,
),
generateValidatingWebhook(
config.ValidatingWebhookName+"-fail",
config.ValidatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
wrc.defaultResourceWebhookRule(),
[]admregapi.OperationType{admregapi.Create, admregapi.Update, admregapi.Delete, admregapi.Connect},
admregapi.Fail,
),
},
}
}
// getResourceValidatingWebhookConfigName returns the webhook configuration name
func (wrc *Register) getResourceValidatingWebhookConfigName() string {
if wrc.serverIP != "" {
func getResourceValidatingWebhookConfigName(serverIP string) string {
if serverIP != "" {
return config.ValidatingWebhookConfigurationDebugName
}
@ -153,7 +201,7 @@ func (wrc *Register) getResourceValidatingWebhookConfigName() string {
func (wrc *Register) removeResourceValidatingWebhookConfiguration(wg *sync.WaitGroup) {
defer wg.Done()
configName := wrc.getResourceValidatingWebhookConfigName()
configName := getResourceValidatingWebhookConfigName(wrc.serverIP)
logger := wrc.log.WithValues("kind", kindValidating, "name", configName)
if mutateCache, ok := wrc.resCache.GetGVRCache("ValidatingWebhookConfiguration"); ok {
@ -164,7 +212,7 @@ func (wrc *Register) removeResourceValidatingWebhookConfiguration(wg *sync.WaitG
}
err := wrc.client.DeleteResource("", kindValidating, "", configName, false)
if errors.IsNotFound(err) {
if errorsapi.IsNotFound(err) {
logger.V(5).Info("webhook configuration not found")
return
}
@ -175,5 +223,4 @@ func (wrc *Register) removeResourceValidatingWebhookConfiguration(wg *sync.WaitG
}
logger.Info("webhook configuration deleted")
return
}

View file

@ -116,8 +116,6 @@ func retryApplyResource(client *kyvernoclient.Clientset, grSpec kyverno.Generate
gr.SetNamespace(config.KyvernoNamespace)
// Initial state "Pending"
// TODO: status is not updated
// gr.Status.State = kyverno.Pending
// generate requests created in kyverno namespace
isExist := false
if action == v1beta1.Create || action == v1beta1.Update {

View file

@ -7,4 +7,5 @@ results:
- policy: missing
rule: validate-image-tag
resource: test
kind: Pod
result: pass

View file

@ -7,4 +7,5 @@ results:
- policy: disallow-latest-tag
rule: validate-image-tag
resource: missing
kind: Pod
result: pass

View file

@ -7,4 +7,5 @@ results:
- policy: disallow-latest-tag
rule: missing
resource: test
kind: Pod
status: pass

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeploy
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
color: orange
namespace: testing
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
color: orange
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: mydeploy
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
color: orange
spec:
containers:
- image: nginx:1.14.2
name: nginx
ports:
- containerPort: 80

View file

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: same-name-but-diff-kind
spec:
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30007
type: NodePort

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
color: orange
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
name: same-name-but-diff-namespace
namespace: testing
spec:
containers:
- image: nginx:latest
name: nginx
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,62 @@
# Below there are both type of olicies: ClusterPolicy and Policy(Namespaced-Policy)
#ClusterPolicy
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-label
annotations:
policies.kyverno.io/title: Add nodeSelector
policies.kyverno.io/category: Sample
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Labels are used as an important source of metadata describing objects in various ways
or triggering other functionality. Labels are also a very basic concept and should be
used throughout Kubernetes. This policy performs a simple mutation which adds a label
`color=orange` to Pods, Services, ConfigMaps, and Secrets.
spec:
background: false
validationFailureAction:
rules:
- name: add-label
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
metadata:
labels:
color: orange
---
# Policy ( In testing namespace )
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: add-ndots
namespace: testing
annotations:
policies.kyverno.io/title: Add ndots
policies.kyverno.io/category: Sample
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
The ndots value controls where DNS lookups are first performed in a cluster
and needs to be set to a lower value than the default of 5 in some cases.
This policy mutates all Pods to add the ndots option with a value of 1.
spec:
background: false
rules:
- name: add-ndots
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
dnsConfig:
options:
- name: ndots
value: "1"

View file

@ -0,0 +1,100 @@
# resource == patchedResource
apiVersion: v1
kind: Pod
metadata:
labels:
foo: bar
color: orange
name: resource-equal-to-patch-res-for-cp
namespace: practice
spec:
containers:
- image: nginx:latest
name: nginx
---
# Resource with same name and diff. namespace
# Same namespace as namespaced-policy
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: testing
spec:
containers:
- name: nginx
image: nginx:latest
---
# Resource with same name and diff. namespace
# Namespace differ from namespaced-policy
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-namespace
labels:
foo: bar
namespace: production
spec:
containers:
- name: nginx
image: nginx:latest
---
# Deployment in default namespace
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeploy
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
---
# Resource (Service) with same name but different kind
apiVersion: v1
kind: Service
metadata:
name: same-name-but-diff-kind
spec:
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30007
type: NodePort
---
# Resource (Pod) with same name but different kind
apiVersion: v1
kind: Pod
metadata:
name: same-name-but-diff-kind
labels:
foo: bar
spec:
containers:
- name: nginx
image: nginx:latest

View file

@ -0,0 +1,86 @@
name: add-nodeselector
policies:
- policy.yaml
resources:
- resource.yaml
results:
- policy: add-label
rule: add-label
resource: resource-equal-to-patch-res-for-cp
patchedResource: patchedResource1.yaml
kind: Pod
namespace: practice
result: skip
- policy: add-label
rule: add-label
resource: same-name-but-diff-namespace
patchedResource: patchedResource2.yaml
kind: Pod
namespace: testing
result: pass
- policy: add-label
rule: add-label
resource: same-name-but-diff-namespace
patchedResource: patchedResource3.yaml
kind: Pod
namespace: production
result: pass
- policy: add-label
rule: add-label
resource: mydeploy
patchedResource: patchedResource4.yaml
kind: Deployment
result: pass
- policy: add-label
rule: add-label
resource: same-name-but-diff-kind
patchedResource: patchedResource5.yaml
kind: Service
result: skip
- policy: add-label
rule: add-label
resource: same-name-but-diff-kind
patchedResource: patchedResource6.yaml
kind: Pod
result: pass
- policy: testing/add-ndots
rule: add-ndots
resource: resource-equal-to-patch-res-for-cp
namespace: practice
patchedResource: patchedResource7.yaml
kind: Pod
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-namespace
patchedResource: patchedResource8.yaml
namespace: testing
kind: Pod
result: pass
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-namespace
patchedResource: patchedResource9.yaml
kind: Pod
namespace: production
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: mydeploy
patchedResource: patchedResource10.yaml
kind: Deployment
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-kind
patchedResource: patchedResource5.yaml
kind: Service
result: skip
- policy: testing/add-ndots
rule: add-ndots
resource: same-name-but-diff-kind
patchedResource: patchedResource11.yaml
kind: Pod
result: skip

View file

@ -7,46 +7,54 @@ results:
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: Pod
resource: pod-with-labels
# TEST: Pod Missing Labels Should Fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: Pod
resource: pod-missing-labels
# TEST: Deployment with Labels Should Pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: Deployment
resource: deployment-with-labels
# TEST: Deployment with Labels Should Fail
- policy: require-common-labels
rule: check-for-labels
result: skip
kind: Deployment
resource: deployment-missing-labels
# TEST: StatefulSet with Labels Should Pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: StatefulSet
resource: StatefulSet-with-labels
# TEST: StatefulSet with Labels Should fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: StatefulSet
resource: StatefulSet-without-labels
# TEST: Cronjob with Labels Should pass
- policy: require-common-labels
rule: check-for-labels
result: pass
kind: CronJob
resource: cronjob-with-labels
# TEST: Cronjob without Labels Should fail
- policy: require-common-labels
rule: check-for-labels
result: fail
kind: CronJob
resource: cronjob-without-labels

View file

@ -7,20 +7,25 @@ results:
- policy: disallow-latest-tag
rule: require-image-tag
resource: test-require-image-tag-pass
kind: Pod
status: pass
- policy: disallow-latest-tag
rule: require-image-tag
resource: test-require-image-tag-fail
kind: Pod
status: fail
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-ignore
kind: Pod
status: skip
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-fail
kind: Pod
status: fail
- policy: disallow-latest-tag
rule: validate-image-tag
resource: test-validate-image-tag-pass
kind: Pod
status: pass

View file

@ -11,32 +11,40 @@ results:
- policy: cm-variable-example
rule: example-configmap-lookup
resource: test-env-test
kind: Pod
result: pass
- policy: cm-variable-example
rule: example-configmap-lookup
resource: test-env-dev
kind: Pod
result: fail
- policy: cm-array-example
rule: validate-role-annotation
resource: test-web
kind: Pod
result: fail
- policy: cm-array-example
rule: validate-role-annotation
resource: test-app
kind: Pod
result: pass
- policy: cm-blk-scalar-example
rule: validate-blk-role-annotation
resource: test-blk-web
kind: Pod
result: fail
- policy: cm-blk-scalar-example
rule: validate-blk-role-annotation
resource: test-blk-app
kind: Pod
result: pass
- policy: cm-globalval-example
rule: validate-mode
resource: test-global-dev
kind: Pod
result: pass
- policy: cm-globalval-example
rule: validate-mode
resource: test-global-prod
kind: Pod
result: fail

View file

@ -34,7 +34,7 @@ func CallMetrics() (string, error) {
func ProcessMetrics(newStr, e2ePolicyName string) error {
splitByNewLine := strings.Split(newStr, "\n")
for _, lineSplitByNewLine := range splitByNewLine {
// kyverno_policy_rule_info_total{policy_background_mode=\"false\",policy_name=\"gen-cluster-policy\",policy_namespace=\"-\",policy_type=\"cluster\",policy_validation_mode=\"audit\",rule_name=\"gen-cluster-role\",rule_type=\"generate\"} 1
// kyverno_policy_rule_info_total{policy_background_mode=\"false\",policy_name=\"gen-cluster-policy\",policy_namespace=\"-\",policy_type=\"cluster\",policy_validation_mode=\"audit\",rule_name=\"gen-cluster-role\",rule_type=\"generate\",status_ready="false"} 1
if !strings.HasPrefix(lineSplitByNewLine, "kyverno_policy_rule_info_total{") {
continue
}
@ -48,10 +48,18 @@ func ProcessMetrics(newStr, e2ePolicyName string) error {
if strings.HasPrefix(lineSplitByComma, "policy_name=") {
splitByQuote := strings.Split(lineSplitByComma, "\"")
policyName := splitByQuote[1]
if policyName == e2ePolicyName {
if policyName != e2ePolicyName {
continue
}
}
if strings.HasPrefix(lineSplitByComma, "status_ready=") {
splitByQuote := strings.Split(lineSplitByComma, "\"")
status := splitByQuote[1]
if status == "true" {
return nil
}
}
}
}