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:
parent
b4a4302a60
commit
c583b64120
107 changed files with 2629 additions and 22 deletions
56
.github/workflows/conformance.yaml
vendored
56
.github/workflows/conformance.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`) |
|
||||
|
|
|
@ -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)) -}}
|
||||
|
|
|
@ -160,6 +160,7 @@ spec:
|
|||
"deferredLoading"
|
||||
"dumpPayload"
|
||||
"forceFailurePolicyIgnore"
|
||||
"generateValidatingAdmissionPolicy"
|
||||
"logging"
|
||||
"omitEvents"
|
||||
"policyExceptions"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -405,6 +405,9 @@ features:
|
|||
forceFailurePolicyIgnore:
|
||||
# -- Enables the feature
|
||||
enabled: false
|
||||
generateValidatingAdmissionPolicy:
|
||||
# -- Enables the feature
|
||||
enabled: false
|
||||
logging:
|
||||
# -- Logging format
|
||||
format: text
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"):
|
||||
|
|
390
pkg/controllers/validatingadmissionpolicy-generate/controller.go
Normal file
390
pkg/controllers/validatingadmissionpolicy-generate/controller.go
Normal 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)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package validatingadmissionpolicygenerate
|
||||
|
||||
import "github.com/kyverno/kyverno/pkg/logging"
|
||||
|
||||
var logger = logging.WithName(ControllerName)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
features:
|
||||
generateValidatingAdmissionPolicy:
|
||||
enabled: true
|
||||
|
||||
admissionController:
|
||||
rbac:
|
||||
clusterRole:
|
||||
extraResources:
|
||||
- apiGroups:
|
||||
- admissionregistration.k8s.io
|
||||
resources:
|
||||
- validatingadmissionpolicies
|
||||
- validatingadmissionpolicybindings
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- list
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
assert:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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."
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||
kind: ValidatingAdmissionPolicy
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: kyverno
|
||||
name: disallow-latest-tag
|
||||
spec: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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: "?*"
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||
kind: ValidatingAdmissionPolicy
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: kyverno
|
||||
name: check-label-app
|
||||
spec: {}
|
|
@ -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: {}
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
apply:
|
||||
- policy.yaml
|
||||
assert:
|
||||
- policy-assert.yaml
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: kuttl.dev/v1beta1
|
||||
kind: TestStep
|
||||
error:
|
||||
- validatingadmissionpolicy.yaml
|
||||
- validatingadmissionpolicybinding.yaml
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
Loading…
Reference in a new issue