1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

feat: generate validating admission policies and their bindings from Kyverno policies (#7840)

* feat: generate validating admission policies and their bindings from Kyverno policies

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add generate VAPs feature flag

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: use container flags instead of feature flags

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: limit VAP generation to cluster policies

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add policy checks for generating VAPs

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* chore: rename package

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: translate match/exclude resources in Kyverno policies to their alternatives in validating admission policies

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add vap info in kyverno policy status

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: delete the translation of

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add kuttl tests

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add generateValidatingAdmissionPolicy feature flag in the helm chart

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* chore: update codegen

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* feat: add validating admission policy kuttl tests in the workflow

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: check K8s server version

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix lint issue

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

* fix: remove the kind config of VAPs

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>

---------

Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
This commit is contained in:
Mariam Fahmy 2023-08-31 13:25:21 +03:00 committed by GitHub
parent b4a4302a60
commit c583b64120
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 2629 additions and 22 deletions

View file

@ -232,6 +232,62 @@ jobs:
if: failure()
uses: ./.github/actions/kyverno-logs
# runs conformance test suites with configuration:
validating-admission-policies:
runs-on: ubuntu-latest
permissions:
packages: read
strategy:
fail-fast: false
matrix:
config:
- name: validating-admission-policies
values:
- standard
- generate-validating-admission-policy
k8s-version:
- name: v1.26
version: v1.26.6
- name: v1.27
version: v1.27.3
tests:
- generate-validating-admission-policy
needs: prepare-images
name: ${{ matrix.k8s-version.name }} - ${{ matrix.config.name }} - ${{ matrix.tests }}
steps:
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup build env
uses: ./.github/actions/setup-build-env
with:
build-cache-key: run-conformance
- name: Create kind cluster
run: |
export KIND_IMAGE=kindest/node:${{ matrix.k8s-version.version }}
export KIND_CONFIG=vap-v1alpha1
make kind-create-cluster
- name: Download kyverno images archive
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: kyverno.tar
- name: Load kyverno images archive in kind cluster
run: make kind-load-image-archive
- name: Install kyverno
run: |
export USE_CONFIG=${{ join(matrix.config.values, ',') }}
make kind-install-kyverno
- name: Wait for kyverno ready
uses: ./.github/actions/kyverno-wait-ready
- name: Test with kuttl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./.tools/kubectl-kuttl test ./test/conformance/kuttl/${{ matrix.tests }} \
--config ./test/conformance/kuttl/_config/common.yaml
- name: Debug failure
if: failure()
uses: ./.github/actions/kyverno-logs
# runs conformance test suites with configuration:
default:
runs-on: ubuntu-latest

View file

@ -33,6 +33,9 @@ type PolicyStatus struct {
// RuleCount describes total number of rules in a policy
// +optional
RuleCount RuleCountStatus `json:"rulecount" yaml:"rulecount"`
// ValidatingAdmissionPolicy contains status information
// +optional
ValidatingAdmissionPolicy ValidatingAdmissionPolicyStatus `json:"validatingadmissionpolicy" yaml:"validatingadmissionpolicy"`
}
// RuleCountStatus contains four variables which describes counts for
@ -75,3 +78,12 @@ type AutogenStatus struct {
// Rules is a list of Rule instances. It contains auto generated rules added for pod controllers
Rules []Rule `json:"rules,omitempty" yaml:"rules,omitempty"`
}
// ValidatingAdmissionPolicy contains status information
type ValidatingAdmissionPolicyStatus struct {
// Generated indicates whether a validating admission policy is generated from the policy or not
Generated bool `json:"generated" yaml:"generated"`
// Message is a human readable message indicating details about the generation of validating admission policy
// It is an empty string when validating admission policy is successfully generated.
Message string `json:"message" yaml:"message"`
}

View file

@ -1078,6 +1078,7 @@ func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
}
in.Autogen.DeepCopyInto(&out.Autogen)
out.RuleCount = in.RuleCount
out.ValidatingAdmissionPolicy = in.ValidatingAdmissionPolicy
return
}
@ -1501,6 +1502,22 @@ func (in *UserInfo) DeepCopy() *UserInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ValidatingAdmissionPolicyStatus) DeepCopyInto(out *ValidatingAdmissionPolicyStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyStatus.
func (in *ValidatingAdmissionPolicyStatus) DeepCopy() *ValidatingAdmissionPolicyStatus {
if in == nil {
return nil
}
out := new(ValidatingAdmissionPolicyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Validation) DeepCopyInto(out *Validation) {
*out = *in

View file

@ -303,6 +303,7 @@ The chart values are organised per component.
| features.deferredLoading.enabled | bool | `true` | Enables the feature |
| features.dumpPayload.enabled | bool | `false` | Enables the feature |
| features.forceFailurePolicyIgnore.enabled | bool | `false` | Enables the feature |
| features.generateValidatingAdmissionPolicy.enabled | bool | `false` | Enables the feature |
| features.logging.format | string | `"text"` | Logging format |
| features.logging.verbosity | int | `2` | Logging verbosity |
| features.omitEvents.eventTypes | list | `[]` | Events which should not be emitted (possible values `PolicyViolation`, `PolicyApplied`, `PolicyError`, and `PolicySkipped`) |

View file

@ -40,6 +40,9 @@
{{- with .forceFailurePolicyIgnore -}}
{{- $flags = append $flags (print "--forceFailurePolicyIgnore=" .enabled) -}}
{{- end -}}
{{- with .generateValidatingAdmissionPolicy -}}
{{- $flags = append $flags (print "--generateValidatingAdmissionPolicy=" .enabled) -}}
{{- end -}}
{{- with .logging -}}
{{- $flags = append $flags (print "--loggingFormat=" .format) -}}
{{- $flags = append $flags (print "--v=" (join "," .verbosity)) -}}

View file

@ -160,6 +160,7 @@ spec:
"deferredLoading"
"dumpPayload"
"forceFailurePolicyIgnore"
"generateValidatingAdmissionPolicy"
"logging"
"omitEvents"
"policyExceptions"

View file

@ -12709,6 +12709,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -21305,6 +21322,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -30216,6 +30250,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -38813,6 +38864,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object

View file

@ -405,6 +405,9 @@ features:
forceFailurePolicyIgnore:
# -- Enables the feature
enabled: false
generateValidatingAdmissionPolicy:
# -- Enables the feature
enabled: false
logging:
# -- Logging format
format: text

View file

@ -22,6 +22,7 @@ import (
policymetricscontroller "github.com/kyverno/kyverno/pkg/controllers/metrics/policy"
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
policycachecontroller "github.com/kyverno/kyverno/pkg/controllers/policycache"
vapcontroller "github.com/kyverno/kyverno/pkg/controllers/validatingadmissionpolicy-generate"
webhookcontroller "github.com/kyverno/kyverno/pkg/controllers/webhook"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/event"
@ -105,6 +106,7 @@ func createNonLeaderControllers(
}
func createrLeaderControllers(
generateVAPs bool,
admissionReports bool,
serverIP string,
webhookTimeout int,
@ -122,6 +124,8 @@ func createrLeaderControllers(
servicePort int32,
configuration config.Configuration,
) ([]internal.Controller, func(context.Context) error, error) {
var leaderControllers []internal.Controller
certManager := certmanager.NewController(
caInformer,
tlsInformer,
@ -178,29 +182,40 @@ func createrLeaderControllers(
configuration,
caSecretName,
)
return []internal.Controller{
internal.NewController(certmanager.ControllerName, certManager, certmanager.Workers),
internal.NewController(webhookcontroller.ControllerName, webhookController, webhookcontroller.Workers),
internal.NewController(exceptionWebhookControllerName, exceptionWebhookController, 1),
},
nil,
nil
leaderControllers = append(leaderControllers, internal.NewController(certmanager.ControllerName, certManager, certmanager.Workers))
leaderControllers = append(leaderControllers, internal.NewController(webhookcontroller.ControllerName, webhookController, webhookcontroller.Workers))
leaderControllers = append(leaderControllers, internal.NewController(exceptionWebhookControllerName, exceptionWebhookController, 1))
if generateVAPs {
vapController := vapcontroller.NewController(
kubeClient,
kyvernoClient,
dynamicClient.Discovery(),
kyvernoInformer.Kyverno().V1().Policies(),
kyvernoInformer.Kyverno().V1().ClusterPolicies(),
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies(),
kubeInformer.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings(),
)
leaderControllers = append(leaderControllers, internal.NewController(vapcontroller.ControllerName, vapController, vapcontroller.Workers))
}
return leaderControllers, nil, nil
}
func main() {
var (
// 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
serverIP string
webhookTimeout int
maxQueuedEvents int
omitEvents string
autoUpdateWebhooks bool
webhookRegistrationTimeout time.Duration
admissionReports bool
dumpPayload bool
servicePort int
backgroundServiceAccountName string
serverIP string
webhookTimeout int
maxQueuedEvents int
omitEvents string
autoUpdateWebhooks bool
webhookRegistrationTimeout time.Duration
admissionReports bool
dumpPayload bool
servicePort int
backgroundServiceAccountName string
generateValidatingAdmissionPolicy bool
)
flagset := flag.NewFlagSet("kyverno", flag.ExitOnError)
flagset.BoolVar(&dumpPayload, "dumpPayload", false, "Set this flag to activate/deactivate debug mode.")
@ -213,6 +228,7 @@ func main() {
flagset.Func(toggle.ProtectManagedResourcesFlagName, toggle.ProtectManagedResourcesDescription, toggle.ProtectManagedResources.Parse)
flagset.Func(toggle.ForceFailurePolicyIgnoreFlagName, toggle.ForceFailurePolicyIgnoreDescription, toggle.ForceFailurePolicyIgnore.Parse)
flagset.BoolVar(&admissionReports, "admissionReports", true, "Enable or disable admission reports.")
flagset.BoolVar(&generateValidatingAdmissionPolicy, "generateValidatingAdmissionPolicy", false, "Set this flag 'true' to generate validating admission policies.")
flagset.IntVar(&servicePort, "servicePort", 443, "Port used by the Kyverno Service resource and for webhook configurations.")
flagset.StringVar(&backgroundServiceAccountName, "backgroundServiceAccountName", "", "Background service account name.")
flagset.StringVar(&caSecretName, "caSecretName", "", "Name of the secret containing CA.")
@ -249,6 +265,13 @@ func main() {
setup.Logger.Error(errors.New("exiting... tlsSecretName is a required flag"), "exiting... tlsSecretName is a required flag")
os.Exit(1)
}
// check if server version is supported for validating admission policy generation
if generateValidatingAdmissionPolicy {
if !kubeutils.HigherThanKubernetesVersion(setup.KubeClient.Discovery(), setup.Logger, 1, 26, 0) {
setup.Logger.Error(errors.New("validating admission policy aren't supported"), "validating admission policy aren't supported")
os.Exit(1)
}
}
caSecret := informers.NewSecretInformer(setup.KubeClient, config.KyvernoNamespace(), caSecretName, resyncPeriod)
tlsSecret := informers.NewSecretInformer(setup.KubeClient, config.KyvernoNamespace(), tlsSecretName, resyncPeriod)
if !informers.StartInformersAndWaitForCacheSync(signalCtx, setup.Logger, caSecret, tlsSecret) {
@ -381,6 +404,7 @@ func main() {
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod)
// create leader controllers
leaderControllers, warmup, err := createrLeaderControllers(
generateValidatingAdmissionPolicy,
admissionReports,
serverIP,
webhookTimeout,

View file

@ -8892,6 +8892,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -17488,6 +17505,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object

View file

@ -8894,6 +8894,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -17491,6 +17508,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object

View file

@ -12912,6 +12912,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -21508,6 +21525,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -30419,6 +30453,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -39016,6 +39067,23 @@ spec:
- validate
- verifyimages
type: object
validatingadmissionpolicy:
description: ValidatingAdmissionPolicy contains status information
properties:
generated:
description: Generated indicates whether a validating admission
policy is generated from the policy or not
type: boolean
message:
description: Message is a human readable message indicating details
about the generation of validating admission policy It is an
empty string when validating admission policy is successfully
generated.
type: string
required:
- generated
- message
type: object
required:
- ready
type: object
@ -41872,6 +41940,7 @@ spec:
- --enableDeferredLoading=true
- --dumpPayload=false
- --forceFailurePolicyIgnore=false
- --generateValidatingAdmissionPolicy=false
- --loggingFormat=text
- --v=2
- --enablePolicyException=false

View file

@ -2943,6 +2943,20 @@ RuleCountStatus
<p>RuleCount describes total number of rules in a policy</p>
</td>
</tr>
<tr>
<td>
<code>validatingadmissionpolicy</code><br/>
<em>
<a href="#kyverno.io/v1.ValidatingAdmissionPolicyStatus">
ValidatingAdmissionPolicyStatus
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ValidatingAdmissionPolicy contains status information</p>
</td>
</tr>
</tbody>
</table>
<hr />
@ -4069,6 +4083,49 @@ See: <a href="https://kyverno.io/docs/writing-policies/preconditions/">https://k
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ValidatingAdmissionPolicyStatus">ValidatingAdmissionPolicyStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.PolicyStatus">PolicyStatus</a>)
</p>
<p>
<p>ValidatingAdmissionPolicy contains status information</p>
</p>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>generated</code><br/>
<em>
bool
</em>
</td>
<td>
<p>Generated indicates whether a validating admission policy is generated from the policy or not</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string
</em>
</td>
<td>
<p>Message is a human readable message indicating details about the generation of validating admission policy
It is an empty string when validating admission policy is successfully generated.</p>
</td>
</tr>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.Validation">Validation
</h3>
<p>

View file

@ -25,10 +25,11 @@ import (
// PolicyStatusApplyConfiguration represents an declarative configuration of the PolicyStatus type for use
// with apply.
type PolicyStatusApplyConfiguration struct {
Ready *bool `json:"ready,omitempty"`
Conditions []v1.Condition `json:"conditions,omitempty"`
Autogen *AutogenStatusApplyConfiguration `json:"autogen,omitempty"`
RuleCount *RuleCountStatusApplyConfiguration `json:"rulecount,omitempty"`
Ready *bool `json:"ready,omitempty"`
Conditions []v1.Condition `json:"conditions,omitempty"`
Autogen *AutogenStatusApplyConfiguration `json:"autogen,omitempty"`
RuleCount *RuleCountStatusApplyConfiguration `json:"rulecount,omitempty"`
ValidatingAdmissionPolicy *ValidatingAdmissionPolicyStatusApplyConfiguration `json:"validatingadmissionpolicy,omitempty"`
}
// PolicyStatusApplyConfiguration constructs an declarative configuration of the PolicyStatus type for use with
@ -70,3 +71,11 @@ func (b *PolicyStatusApplyConfiguration) WithRuleCount(value *RuleCountStatusApp
b.RuleCount = value
return b
}
// WithValidatingAdmissionPolicy sets the ValidatingAdmissionPolicy field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ValidatingAdmissionPolicy field is set to the value of the last call.
func (b *PolicyStatusApplyConfiguration) WithValidatingAdmissionPolicy(value *ValidatingAdmissionPolicyStatusApplyConfiguration) *PolicyStatusApplyConfiguration {
b.ValidatingAdmissionPolicy = value
return b
}

View file

@ -0,0 +1,48 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
// ValidatingAdmissionPolicyStatusApplyConfiguration represents an declarative configuration of the ValidatingAdmissionPolicyStatus type for use
// with apply.
type ValidatingAdmissionPolicyStatusApplyConfiguration struct {
Generated *bool `json:"generated,omitempty"`
Message *string `json:"message,omitempty"`
}
// ValidatingAdmissionPolicyStatusApplyConfiguration constructs an declarative configuration of the ValidatingAdmissionPolicyStatus type for use with
// apply.
func ValidatingAdmissionPolicyStatus() *ValidatingAdmissionPolicyStatusApplyConfiguration {
return &ValidatingAdmissionPolicyStatusApplyConfiguration{}
}
// WithGenerated sets the Generated field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Generated field is set to the value of the last call.
func (b *ValidatingAdmissionPolicyStatusApplyConfiguration) WithGenerated(value bool) *ValidatingAdmissionPolicyStatusApplyConfiguration {
b.Generated = &value
return b
}
// WithMessage sets the Message field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Message field is set to the value of the last call.
func (b *ValidatingAdmissionPolicyStatusApplyConfiguration) WithMessage(value string) *ValidatingAdmissionPolicyStatusApplyConfiguration {
b.Message = &value
return b
}

View file

@ -131,6 +131,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &kyvernov1.TargetResourceSpecApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("UserInfo"):
return &kyvernov1.UserInfoApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicyStatus"):
return &kyvernov1.ValidatingAdmissionPolicyStatusApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("Validation"):
return &kyvernov1.ValidationApplyConfiguration{}
case v1.SchemeGroupVersion.WithKind("ValidationFailureActionOverride"):

View file

@ -0,0 +1,390 @@
package validatingadmissionpolicygenerate
import (
"context"
"time"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1informers "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/controllers"
"github.com/kyverno/kyverno/pkg/logging"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"k8s.io/api/admissionregistration/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
admissionregistrationv1alpha1informers "k8s.io/client-go/informers/admissionregistration/v1alpha1"
"k8s.io/client-go/kubernetes"
admissionregistrationv1alpha1listers "k8s.io/client-go/listers/admissionregistration/v1alpha1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
)
const (
// Workers is the number of workers for this controller
Workers = 2
ControllerName = "validatingadmissionpolicy-generate-controller"
maxRetries = 10
)
type controller struct {
// clients
client kubernetes.Interface
kyvernoClient versioned.Interface
discoveryClient dclient.IDiscovery
// listers
cpolLister kyvernov1listers.ClusterPolicyLister
vapLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyLister
vapbindingLister admissionregistrationv1alpha1listers.ValidatingAdmissionPolicyBindingLister
// queue
queue workqueue.RateLimitingInterface
}
func NewController(
client kubernetes.Interface,
kyvernoClient versioned.Interface,
discoveryClient dclient.IDiscovery,
polInformer kyvernov1informers.PolicyInformer,
cpolInformer kyvernov1informers.ClusterPolicyInformer,
vapInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyInformer,
vapbindingInformer admissionregistrationv1alpha1informers.ValidatingAdmissionPolicyBindingInformer,
) controllers.Controller {
queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), ControllerName)
c := &controller{
client: client,
kyvernoClient: kyvernoClient,
discoveryClient: discoveryClient,
cpolLister: cpolInformer.Lister(),
vapLister: vapInformer.Lister(),
vapbindingLister: vapbindingInformer.Lister(),
queue: queue,
}
// Set up an event handler for when Kyverno policies change
controllerutils.AddEventHandlersT(cpolInformer.Informer(), c.addPolicy, c.updatePolicy, c.deletePolicy)
// Set up an event handler for when validating admission policies change
controllerutils.AddEventHandlersT(vapInformer.Informer(), c.addVAP, c.updateVAP, c.deleteVAP)
// Set up an event handler for when validating admission policy bindings change
controllerutils.AddEventHandlersT(vapbindingInformer.Informer(), c.addVAPbinding, c.updateVAPbinding, c.deleteVAPbinding)
return c
}
func (c *controller) Run(ctx context.Context, workers int) {
controllerutils.Run(ctx, logger, ControllerName, time.Second, c.queue, workers, maxRetries, c.reconcile)
}
func (c *controller) addPolicy(obj kyvernov1.PolicyInterface) {
logger.Info("policy created", "uid", obj.GetUID(), "kind", obj.GetKind(), "name", obj.GetName())
c.enqueuePolicy(obj)
}
func (c *controller) updatePolicy(old, obj kyvernov1.PolicyInterface) {
if datautils.DeepEqual(old.GetSpec(), obj.GetSpec()) {
return
}
logger.Info("policy updated", "uid", obj.GetUID(), "kind", obj.GetKind(), "name", obj.GetName())
c.enqueuePolicy(obj)
}
func (c *controller) deletePolicy(obj kyvernov1.PolicyInterface) {
var p kyvernov1.PolicyInterface
switch kubeutils.GetObjectWithTombstone(obj).(type) {
case *kyvernov1.ClusterPolicy:
p = kubeutils.GetObjectWithTombstone(obj).(*kyvernov1.ClusterPolicy)
default:
logger.Info("Failed to get deleted object", "obj", obj)
return
}
logger.Info("policy deleted", "uid", p.GetUID(), "kind", p.GetKind(), "name", p.GetName())
c.enqueuePolicy(obj)
}
func (c *controller) enqueuePolicy(obj kyvernov1.PolicyInterface) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err != nil {
logger.Error(err, "failed to extract policy name")
return
}
c.queue.Add(key)
}
func (c *controller) addVAP(obj *v1alpha1.ValidatingAdmissionPolicy) {
c.enqueueVAP(obj)
}
func (c *controller) updateVAP(old, obj *v1alpha1.ValidatingAdmissionPolicy) {
if datautils.DeepEqual(old.Spec, obj.Spec) {
return
}
c.enqueueVAP(obj)
}
func (c *controller) deleteVAP(obj *v1alpha1.ValidatingAdmissionPolicy) {
c.enqueueVAP(obj)
}
func (c *controller) enqueueVAP(v *v1alpha1.ValidatingAdmissionPolicy) {
if len(v.OwnerReferences) == 1 {
if v.OwnerReferences[0].Kind == "ClusterPolicy" {
cpol, err := c.cpolLister.Get(v.OwnerReferences[0].Name)
if err != nil {
return
}
c.enqueuePolicy(cpol)
}
}
}
func (c *controller) addVAPbinding(obj *v1alpha1.ValidatingAdmissionPolicyBinding) {
c.enqueueVAPbinding(obj)
}
func (c *controller) updateVAPbinding(old, obj *v1alpha1.ValidatingAdmissionPolicyBinding) {
if datautils.DeepEqual(old.Spec, obj.Spec) {
return
}
c.enqueueVAPbinding(obj)
}
func (c *controller) deleteVAPbinding(obj *v1alpha1.ValidatingAdmissionPolicyBinding) {
c.enqueueVAPbinding(obj)
}
func (c *controller) enqueueVAPbinding(vb *v1alpha1.ValidatingAdmissionPolicyBinding) {
if len(vb.OwnerReferences) == 1 {
if vb.OwnerReferences[0].Kind == "ClusterPolicy" {
cpol, err := c.cpolLister.Get(vb.OwnerReferences[0].Name)
if err != nil {
return
}
c.enqueuePolicy(cpol)
}
}
}
func (c *controller) getClusterPolicy(name string) (*kyvernov1.ClusterPolicy, error) {
cpolicy, err := c.cpolLister.Get(name)
if err != nil {
return nil, err
}
return cpolicy, nil
}
func (c *controller) getValidatingAdmissionPolicy(name string) (*v1alpha1.ValidatingAdmissionPolicy, error) {
vap, err := c.vapLister.Get(name)
if err != nil {
return nil, err
}
return vap, nil
}
func (c *controller) getValidatingAdmissionPolicyBinding(name string) (*v1alpha1.ValidatingAdmissionPolicyBinding, error) {
vapbinding, err := c.vapbindingLister.Get(name)
if err != nil {
return nil, err
}
return vapbinding, nil
}
func (c *controller) buildValidatingAdmissionPolicy(vap *v1alpha1.ValidatingAdmissionPolicy, cpol kyvernov1.PolicyInterface) error {
// set owner reference
vap.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: "kyverno.io/v1",
Kind: cpol.GetKind(),
Name: cpol.GetName(),
UID: cpol.GetUID(),
},
}
// construct validating admission policy resource rules
var matchResources v1alpha1.MatchResources
var matchRules []v1alpha1.NamedRuleWithOperations
rule := cpol.GetSpec().Rules[0]
match := rule.MatchResources
if !match.ResourceDescription.IsEmpty() {
if err := c.translateResource(&matchResources, &matchRules, match.ResourceDescription); err != nil {
return err
}
}
if match.Any != nil {
if err := c.translateResourceFilters(&matchResources, &matchRules, match.Any); err != nil {
return err
}
}
if match.All != nil {
if err := c.translateResourceFilters(&matchResources, &matchRules, match.All); err != nil {
return err
}
}
// set validating admission policy spec
vap.Spec = v1alpha1.ValidatingAdmissionPolicySpec{
ParamKind: rule.Validation.CEL.ParamKind,
MatchConstraints: &matchResources,
Validations: rule.Validation.CEL.Expressions,
AuditAnnotations: rule.Validation.CEL.AuditAnnotations,
}
// set labels
controllerutils.SetManagedByKyvernoLabel(vap)
return nil
}
func (c *controller) buildValidatingAdmissionPolicyBinding(vapbinding *v1alpha1.ValidatingAdmissionPolicyBinding, cpol kyvernov1.PolicyInterface) error {
// set owner reference
vapbinding.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: "kyverno.io/v1",
Kind: cpol.GetKind(),
Name: cpol.GetName(),
UID: cpol.GetUID(),
},
}
// set validation action for vap binding
var validationActions []v1alpha1.ValidationAction
action := cpol.GetSpec().ValidationFailureAction
if action.Enforce() {
validationActions = append(validationActions, v1alpha1.Deny)
} else if action.Audit() {
validationActions = append(validationActions, v1alpha1.Audit)
validationActions = append(validationActions, v1alpha1.Warn)
}
// set validating admission policy binding spec
rule := cpol.GetSpec().Rules[0]
vapbinding.Spec = v1alpha1.ValidatingAdmissionPolicyBindingSpec{
PolicyName: cpol.GetName(),
ParamRef: rule.Validation.CEL.ParamRef,
ValidationActions: validationActions,
}
// set labels
controllerutils.SetManagedByKyvernoLabel(vapbinding)
return nil
}
func (c *controller) reconcile(ctx context.Context, logger logr.Logger, key, namespace, name string) error {
policy, err := c.getClusterPolicy(name)
if err != nil {
if apierrors.IsNotFound(err) {
return nil
}
logger.Error(err, "unable to get the policy from policy informer")
return err
}
spec := policy.GetSpec()
if !spec.HasValidate() {
return nil
}
if ok, msg := canGenerateVAP(spec); !ok {
c.updateClusterPolicyStatus(ctx, *policy, false, msg)
return nil
}
polName := policy.GetName()
observedVAP, err := c.getValidatingAdmissionPolicy(polName)
if err != nil {
if !apierrors.IsNotFound(err) {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
observedVAP = &v1alpha1.ValidatingAdmissionPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: polName,
},
}
}
observedVAPbinding, err := c.getValidatingAdmissionPolicyBinding(polName + "-binding")
if err != nil {
if !apierrors.IsNotFound(err) {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
observedVAPbinding = &v1alpha1.ValidatingAdmissionPolicyBinding{
ObjectMeta: metav1.ObjectMeta{
Name: polName + "-binding",
},
}
}
if observedVAP.ResourceVersion == "" {
err := c.buildValidatingAdmissionPolicy(observedVAP, policy)
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
_, err = c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, observedVAP, metav1.CreateOptions{})
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
} else {
_, err = controllerutils.Update(
ctx,
observedVAP,
c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies(),
func(observed *v1alpha1.ValidatingAdmissionPolicy) error {
return c.buildValidatingAdmissionPolicy(observed, policy)
})
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
}
if observedVAPbinding.ResourceVersion == "" {
err := c.buildValidatingAdmissionPolicyBinding(observedVAPbinding, policy)
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
_, err = c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(ctx, observedVAPbinding, metav1.CreateOptions{})
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
} else {
_, err = controllerutils.Update(
ctx,
observedVAPbinding,
c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings(),
func(observed *v1alpha1.ValidatingAdmissionPolicyBinding) error {
return c.buildValidatingAdmissionPolicyBinding(observed, policy)
})
if err != nil {
c.updateClusterPolicyStatus(ctx, *policy, false, err.Error())
return err
}
}
c.updateClusterPolicyStatus(ctx, *policy, true, "")
return nil
}
func (c *controller) updateClusterPolicyStatus(ctx context.Context, cpol kyvernov1.ClusterPolicy, generated bool, msg string) {
latest := cpol.DeepCopy()
latest.Status.ValidatingAdmissionPolicy.Generated = generated
latest.Status.ValidatingAdmissionPolicy.Message = msg
new, _ := c.kyvernoClient.KyvernoV1().ClusterPolicies().UpdateStatus(ctx, latest, metav1.UpdateOptions{})
logging.V(3).Info("updated kyverno policy status", "name", cpol.GetName(), "status", new.Status)
}

View file

@ -0,0 +1,5 @@
package validatingadmissionpolicygenerate
import "github.com/kyverno/kyverno/pkg/logging"
var logger = logging.WithName(ControllerName)

View file

@ -0,0 +1,148 @@
package validatingadmissionpolicygenerate
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
)
func checkResources(resource kyvernov1.ResourceDescription) (bool, string) {
var msg string
if len(resource.Namespaces) != 0 || len(resource.Annotations) != 0 {
msg = "skip generating validating admission policy: Namespaces / Annotations in resource description isn't applicable."
return false, msg
}
return true, msg
}
func checkUserInfo(info kyvernov1.UserInfo) (bool, string) {
var msg string
if !info.IsEmpty() {
msg = "skip generating validating admission policy: Roles / ClusterRoles / Subjects in `any/all` isn't applicable."
return false, msg
}
return true, msg
}
func canGenerateVAP(spec *kyvernov1.Spec) (bool, string) {
var msg string
if len(spec.Rules) > 1 {
msg = "skip generating validating admission policy: multiple rules aren't applicable."
return false, msg
}
rule := spec.Rules[0]
if !rule.HasValidateCEL() {
msg = "skip generating validating admission policy for non CEL rules."
return false, msg
}
if len(spec.ValidationFailureActionOverrides) > 1 {
msg = "skip generating validating admission policy: multiple validationFailureActionOverrides aren't applicable."
return false, msg
}
if len(spec.ValidationFailureActionOverrides) != 0 && len(spec.ValidationFailureActionOverrides[0].Namespaces) != 0 {
msg = "skip generating validating admission policy: Namespaces in validationFailureActionOverrides isn't applicable."
return false, msg
}
// check the matched/excluded resources of the CEL rule.
match, exclude := rule.MatchResources, rule.ExcludeResources
if !exclude.UserInfo.IsEmpty() || !exclude.ResourceDescription.IsEmpty() || exclude.All != nil || exclude.Any != nil {
msg = "skip generating validating admission policy: Exclude isn't applicable."
return false, msg
}
if ok, msg := checkUserInfo(match.UserInfo); !ok {
return false, msg
}
if ok, msg := checkResources(match.ResourceDescription); !ok {
return false, msg
}
var (
containsNamespaceSelector = false
containsObjectSelector = false
)
// since 'any' specify resources which will be ORed, it can be converted into multiple NamedRuleWithOperations in validating admission policy
for _, value := range match.Any {
if ok, msg := checkUserInfo(value.UserInfo); !ok {
return false, msg
}
if ok, msg := checkResources(value.ResourceDescription); !ok {
return false, msg
}
// since namespace/object selectors are applied to all NamedRuleWithOperations in validating admission policy, then
// multiple namespace/object selectors aren't applicable across the `any` clause.
if value.NamespaceSelector != nil {
if containsNamespaceSelector {
msg = "skip generating validating admission policy: multiple NamespaceSelector across 'any' aren't applicable."
return false, msg
}
containsNamespaceSelector = true
}
if value.Selector != nil {
if containsObjectSelector {
msg = "skip generating validating admission policy: multiple ObjectSelector across 'any' aren't applicable."
return false, msg
}
containsObjectSelector = true
}
}
// since 'all' specify resources which will be ANDed, we can't have more than one resource.
if match.All != nil {
if len(match.All) > 1 {
msg = "skip generating validating admission policy: multiple 'all' isn't applicable."
return false, msg
} else {
if ok, msg := checkUserInfo(match.All[0].UserInfo); !ok {
return false, msg
}
if ok, msg := checkResources(match.All[0].ResourceDescription); !ok {
return false, msg
}
}
}
// since 'any' specify resources which will be ORed, it can be converted into multiple NamedRuleWithOperations in validating admission policy
for _, value := range exclude.Any {
if ok, msg := checkUserInfo(value.UserInfo); !ok {
return false, msg
}
if ok, msg := checkResources(value.ResourceDescription); !ok {
return false, msg
}
// since namespace/object selectors are applied to all NamedRuleWithOperations in validating admission policy, then
// multiple namespace/object selectors aren't applicable across the `any` clause.
if value.NamespaceSelector != nil {
if containsNamespaceSelector {
msg = "skip generating validating admission policy: multiple NamespaceSelector across 'any' aren't applicable."
return false, msg
}
containsNamespaceSelector = true
}
if value.Selector != nil {
if containsObjectSelector {
msg = "skip generating validating admission policy: multiple ObjectSelector across 'any' aren't applicable."
return false, msg
}
containsObjectSelector = true
}
}
// since 'all' specify resources which will be ANDed, we can't have more than one resource.
if exclude.All != nil {
if len(exclude.All) > 1 {
msg = "skip generating validating admission policy: multiple 'all' isn't applicable."
return false, msg
} else {
if ok, msg := checkUserInfo(exclude.All[0].UserInfo); !ok {
return false, msg
}
if ok, msg := checkResources(exclude.All[0].ResourceDescription); !ok {
return false, msg
}
}
}
return true, msg
}

View file

@ -0,0 +1,487 @@
package validatingadmissionpolicygenerate
import (
"encoding/json"
"testing"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"gotest.tools/assert"
)
func Test_Check_Resources(t *testing.T) {
testCases := []struct {
name string
resource []byte
expected bool
}{
{
name: "resource-with-namespaces",
resource: []byte(`
{
"kinds": [
"Service"
],
"namespaces": [
"prod"
],
"operations": [
"CREATE"
]
}
`),
expected: false,
},
{
name: "resource-with-annotations",
resource: []byte(`
{
"annotations": {
"imageregistry": "https://hub.docker.com/"
},
"kinds": [
"Pod"
],
"operations": [
"CREATE",
"UPDATE"
]
}
`),
expected: false,
},
{
name: "resource-with-object-selector",
resource: []byte(`
{
"kinds": [
"Pod"
],
"operations": [
"CREATE",
"UPDATE"
],
"selector": {
"matchLabels": {
"app": "critical"
}
}
}
`),
expected: true,
},
{
name: "resource-with-namespace-selector",
resource: []byte(`
{
"kinds": [
"Pod"
],
"operations": [
"CREATE",
"UPDATE"
],
"namespaceSelector": {
"matchLabels": {
"app": "critical"
}
}
}
`),
expected: true,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
var res kyvernov1.ResourceDescription
err := json.Unmarshal(test.resource, &res)
assert.NilError(t, err)
out, _ := checkResources(res)
assert.Equal(t, out, test.expected)
})
}
}
func Test_Can_Generate_ValidatingAdmissionPolicy(t *testing.T) {
testCases := []struct {
name string
policy []byte
expected bool
}{
{
name: "policy-with-two-rules",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-latest-tag"
},
"spec": {
"rules": [
{
"name": "require-image-tag",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "object.spec.containers.all(container, !container.image.matches('^[a-zA-Z]+:[0-9]*$'))"
}
]
}
}
},
{
"name": "validate-image-tag",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "object.spec.containers.all(container, !container.image.contains('latest'))"
}
]
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-mutate-rule",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "set-image-pull-policy"
},
"spec": {
"rules": [
{
"name": "set-image-pull-policy",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"mutate": {
"patchStrategicMerge": {
"spec": {
"containers": [
{
"(image)": "*:latest",
"imagePullPolicy": "IfNotPresent"
}
]
}
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-non-CEL-validate-rule",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "require-ns-purpose-label"
},
"spec": {
"validationFailureAction": "Enforce",
"rules": [
{
"name": "require-ns-purpose-label",
"match": {
"any": [
{
"resources": {
"kinds": [
"Namespace"
]
}
}
]
},
"validate": {
"pattern": {
"metadata": {
"labels": {
"purpose": "production"
}
}
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-multiple-validationFailureActionOverrides",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-host-path"
},
"spec": {
"validationFailureAction": "Enforce",
"validationFailureActionOverrides": [
{
"action": "Enforce",
"namespaces": [
"default"
]
},
{
"action": "Audit",
"namespaces": [
"test"
]
}
],
"rules": [
{
"name": "host-path",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
}
]
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-namespace-in-validationFailureActionOverrides",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-host-path"
},
"spec": {
"validationFailureAction": "Enforce",
"validationFailureActionOverrides": [
{
"action": "Enforce",
"namespaces": [
"test-ns"
]
}
],
"rules": [
{
"name": "host-path",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
}
]
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-subjects-and-clusterroles",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-host-path"
},
"spec": {
"validationFailureAction": "Enforce",
"rules": [
{
"name": "host-path",
"match": {
"any": [
{
"resources": {
"kinds": [
"Deployment"
],
"operations": [
"CREATE",
"UPDATE"
]
},
"subjects": [
{
"kind": "User",
"name": "mary@somecorp.com"
}
],
"clusterRoles": [
"cluster-admin"
]
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
}
]
}
}
}
]
}
}
`),
expected: false,
},
{
name: "policy-with-object-selector",
policy: []byte(`
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-host-path"
},
"spec": {
"validationFailureAction": "Enforce",
"rules": [
{
"name": "host-path",
"match": {
"any": [
{
"resources": {
"kinds": [
"Deployment"
],
"operations": [
"CREATE",
"UPDATE"
],
"selector": {
"matchLabels": {
"app": "mongodb"
},
"matchExpressions": [
{
"key": "tier",
"operator": "In",
"values": [
"database"
]
}
]
}
}
}
]
},
"validate": {
"cel": {
"expressions": [
{
"expression": "!has(object.spec.volumes) || object.spec.volumes.all(volume, !has(volume.hostPath))"
}
]
}
}
}
]
}
}
`),
expected: true,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
policies, _, err := yamlutils.GetPolicy([]byte(test.policy))
assert.NilError(t, err)
assert.Equal(t, 1, len(policies))
out, _ := canGenerateVAP(policies[0].GetSpec())
assert.Equal(t, out, test.expected)
})
}
}

View file

@ -0,0 +1,108 @@
package validatingadmissionpolicygenerate
import (
"fmt"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"golang.org/x/exp/slices"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/api/admissionregistration/v1alpha1"
)
func (c *controller) translateResourceFilters(matchResources *v1alpha1.MatchResources, rules *[]v1alpha1.NamedRuleWithOperations, resFilters kyvernov1.ResourceFilters) error {
for _, filter := range resFilters {
err := c.translateResource(matchResources, rules, filter.ResourceDescription)
if err != nil {
return err
}
}
return nil
}
func (c *controller) translateResource(matchResources *v1alpha1.MatchResources, rules *[]v1alpha1.NamedRuleWithOperations, res kyvernov1.ResourceDescription) error {
err := c.constructValidatingAdmissionPolicyRules(rules, res.Kinds, res.GetOperations())
if err != nil {
return err
}
matchResources.ResourceRules = *rules
matchResources.NamespaceSelector = res.NamespaceSelector
matchResources.ObjectSelector = res.Selector
return nil
}
func (c *controller) constructValidatingAdmissionPolicyRules(rules *[]v1alpha1.NamedRuleWithOperations, kinds []string, operations []string) error {
// translate operations to their corresponding values in validating admission policy.
ops := c.translateOperations(operations)
// get kinds from kyverno policies and translate them to rules in validating admission policies.
// matched resources in kyverno policies are written in the following format:
// group/version/kind/subresource
// whereas matched resources in validating admission policies are written in the following format:
// apiGroups: ["group"]
// apiVersions: ["version"]
// resources: ["resource"]
for _, kind := range kinds {
group, version, kind, subresource := kubeutils.ParseKindSelector(kind)
gvrss, err := c.discoveryClient.FindResources(group, version, kind, subresource)
if err != nil {
return err
}
if len(gvrss) != 1 {
return fmt.Errorf("no unique match for kind %s", kind)
}
for topLevelApi, apiResource := range gvrss {
isNewRule := true
// If there's a rule that contains both group and version, then the resource is appended to the existing rule instead of creating a new one.
// Example: apiGroups: ["apps"]
// apiVersions: ["v1"]
// resources: ["deployments", "statefulsets"]
// Otherwise, a new rule is created.
for i := range *rules {
if slices.Contains((*rules)[i].APIGroups, topLevelApi.Group) && slices.Contains((*rules)[i].APIVersions, topLevelApi.Version) {
(*rules)[i].Resources = append((*rules)[i].Resources, apiResource.Name)
isNewRule = false
break
}
}
if isNewRule {
r := v1alpha1.NamedRuleWithOperations{
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
Rule: admissionregistrationv1.Rule{
Resources: []string{apiResource.Name},
APIGroups: []string{topLevelApi.Group},
APIVersions: []string{topLevelApi.Version},
},
Operations: ops,
},
}
*rules = append(*rules, r)
}
}
}
return nil
}
func (c *controller) translateOperations(operations []string) []admissionregistrationv1.OperationType {
var vapOperations []admissionregistrationv1.OperationType
for _, op := range operations {
if op == string(kyvernov1.Create) {
vapOperations = append(vapOperations, admissionregistrationv1.Create)
} else if op == string(kyvernov1.Update) {
vapOperations = append(vapOperations, admissionregistrationv1.Update)
} else if op == string(kyvernov1.Connect) {
vapOperations = append(vapOperations, admissionregistrationv1.Connect)
} else if op == string(kyvernov1.Delete) {
vapOperations = append(vapOperations, admissionregistrationv1.Delete)
}
}
// set default values for operations since it's a required field in validating admission policies
if len(vapOperations) == 0 {
vapOperations = append(vapOperations, admissionregistrationv1.Create)
vapOperations = append(vapOperations, admissionregistrationv1.Update)
}
return vapOperations
}

View file

@ -45,13 +45,18 @@ func (h validateCELHandler) Process(
logger.V(3).Info("skipping CEL validation on deleted resource")
return resource, nil
}
// check if a corresponding validating admission policy is generated
vapStatus := policyContext.Policy().GetStatus().ValidatingAdmissionPolicy
if vapStatus.Generated {
logger.V(3).Info("skipping CEL validation due to the generation of its corresponding validating admission policy")
return resource, nil
}
// get resource's name, namespace, GroupVersionResource, and GroupVersionKind
gvr := schema.GroupVersionResource(policyContext.RequestResource())
gvk := resource.GroupVersionKind()
namespaceName := resource.GetNamespace()
resourceName := resource.GetName()
object := resource.DeepCopyObject()
// in case of update request, set the oldObject to the current resource before it gets updated
var oldObject runtime.Object

View file

@ -0,0 +1,18 @@
features:
generateValidatingAdmissionPolicy:
enabled: true
admissionController:
rbac:
clusterRole:
extraResources:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingadmissionpolicies
- validatingadmissionpolicybindings
verbs:
- create
- update
- delete
- list

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t9
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: true

View file

@ -0,0 +1,26 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t9
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
all:
- resources:
kinds:
- Deployment
- StatefulSet
operations:
- CREATE
- UPDATE
selector:
matchLabels:
app: critical
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,32 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t9
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t9
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- deployments
- statefulsets
objectSelector:
matchLabels:
app: critical
validations:
- expression: '!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume,
!has(volume.hostPath))'
message: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath
must be unset.

View file

@ -0,0 +1,12 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t9-binding
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t9
spec:
policyName: disallow-host-path-t9

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t8
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: true

View file

@ -0,0 +1,40 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t8
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
- resources:
kinds:
- StatefulSet
operations:
- CREATE
- UPDATE
- resources:
kinds:
- ReplicaSet
operations:
- CREATE
- UPDATE
- resources:
kinds:
- DaemonSet
operations:
- CREATE
- UPDATE
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,31 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t8
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t8
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- deployments
- statefulsets
- replicasets
- daemonsets
validations:
- expression: '!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume,
!has(volume.hostPath))'
message: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath
must be unset.

View file

@ -0,0 +1,12 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t8-binding
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t8
spec:
policyName: disallow-host-path-t8

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t7
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: true

View file

@ -0,0 +1,31 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t7
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- ReplicaSet
- DaemonSet
operations:
- CREATE
- UPDATE
namespaceSelector:
matchExpressions:
- key: type
operator: In
values:
- connector
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,37 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t7
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t7
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- deployments
- statefulsets
- replicasets
- daemonsets
namespaceSelector:
matchExpressions:
- key: type
operator: In
values:
- connector
validations:
- expression: '!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume,
!has(volume.hostPath))'
message: HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath
must be unset.

View file

@ -0,0 +1,12 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t7-binding
ownerReferences:
- apiVersion: kyverno.io/v1
kind: ClusterPolicy
name: disallow-host-path-t7
spec:
policyName: disallow-host-path-t7

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t1
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,39 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t1
spec:
validationFailureAction: Audit
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
namespaceSelector:
matchExpressions:
- key: type
operator: In
values:
- connector
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
namespaceSelector:
matchExpressions:
- key: type
operator: In
values:
- compute
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t1
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t1-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t2
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t2
spec:
validationFailureAction: Audit
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
selector:
matchLabels:
app: critical
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
selector:
matchLabels:
app: normal
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t2
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t2-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t10
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,26 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t10
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
exclude:
any:
- clusterRoles:
- cluster-admin
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t10
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t10
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t3
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,27 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t3
spec:
validationFailureAction: Audit
background: false
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
subjects:
- kind: User
name: mary@somecorp.com
clusterRoles:
- cluster-admin
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t3
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t3-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t4
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,23 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t4
spec:
validationFailureAction: Audit
rules:
- name: host-path
match:
any:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
namespaces:
- prod
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t4
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t4-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t5
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,23 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t5
spec:
validationFailureAction: Audit
rules:
- name: host-path
match:
any:
- resources:
annotations:
imageregistry: "https://hub.docker.com/"
kinds:
- Deployment
operations:
- CREATE
- UPDATE
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t5
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t5-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t6
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,32 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-path-t6
spec:
validationFailureAction: Audit
rules:
- name: host-path
match:
all:
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
selector:
matchLabels:
app: critical
- resources:
kinds:
- Deployment
operations:
- CREATE
- UPDATE
names:
- app
validate:
cel:
expressions:
- expression: "!has(object.spec.template.spec.volumes) || object.spec.template.spec.volumes.all(volume, !has(volume.hostPath))"
message: "HostPath volumes are forbidden. The field spec.template.spec.volumes[*].hostPath must be unset."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t6
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-host-path-t6-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,28 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: "object.spec.containers.all(container, !container.image.matches('^[a-zA-Z]+:[0-9]*$'))"
message: "An image tag is required."
- name: validate-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
cel:
expressions:
- expression: "object.spec.containers.all(container, !container.image.contains('latest'))"
message: "Using a mutable image tag e.g. 'latest' is not allowed."

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-latest-tag
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: disallow-latest-tag-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-label-app
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,26 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-label-app
spec:
validationFailureAction: Audit
validationFailureActionOverrides:
- action: Enforce
namespaces:
- default
- action: Audit
namespaces:
- test
rules:
- name: check-label-app
match:
any:
- resources:
kinds:
- Pod
validate:
message: "The label `app` is required."
pattern:
metadata:
labels:
app: "?*"

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: check-label-app
spec: {}

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: check-label-app-binding
spec: {}

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
error:
- validatingadmissionpolicy.yaml
- validatingadmissionpolicybinding.yaml

View file

@ -0,0 +1,12 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-ns-purpose-label
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready
validatingadmissionpolicy:
generated: false

View file

@ -0,0 +1,19 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-ns-purpose-label
spec:
validationFailureAction: Enforce
rules:
- name: require-ns-purpose-label
match:
any:
- resources:
kinds:
- Namespace
validate:
message: "You must have label `purpose` with value `production` set on all new namespaces."
pattern:
metadata:
labels:
purpose: production

View file

@ -0,0 +1,7 @@
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
name: require-ns-purpose-label
spec: {}

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