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

refactor: remove openapi package (#8538)

* refactor: openapi package

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* kubectl validate

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* rm

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* go mod

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix vscode

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-09-27 18:21:47 +02:00 committed by GitHub
parent eedc993ed9
commit 482c243517
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 3665 additions and 4851 deletions

View file

@ -28,7 +28,6 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
gitutils "github.com/kyverno/kyverno/pkg/utils/git" gitutils "github.com/kyverno/kyverno/pkg/utils/git"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy" policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -142,10 +141,6 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
if err != nil { if err != nil {
return nil, nil, skipInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err) return nil, nil, skipInvalidPolicies, nil, fmt.Errorf("failed to decode yaml (%w)", err)
} }
openApiManager, err := openapi.NewManager(log.Log)
if err != nil {
return nil, nil, skipInvalidPolicies, nil, fmt.Errorf("failed to initialize openAPIController (%w)", err)
}
rc, resources1, skipInvalidPolicies, responses1, err, dClient := c.initStoreAndClusterClient(skipInvalidPolicies) rc, resources1, skipInvalidPolicies, responses1, err, dClient := c.initStoreAndClusterClient(skipInvalidPolicies)
if err != nil { if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err return rc, resources1, skipInvalidPolicies, responses1, err
@ -171,7 +166,6 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
variables, variables,
policies, policies,
resources, resources,
openApiManager,
&skipInvalidPolicies, &skipInvalidPolicies,
dClient, dClient,
userInfo, userInfo,
@ -228,7 +222,6 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
vars *variables.Variables, vars *variables.Variables,
policies []kyvernov1.PolicyInterface, policies []kyvernov1.PolicyInterface,
resources []*unstructured.Unstructured, resources []*unstructured.Unstructured,
openApiManager openapi.Manager,
skipInvalidPolicies *SkippedInvalidPolicies, skipInvalidPolicies *SkippedInvalidPolicies,
dClient dclient.Interface, dClient dclient.Interface,
userInfo *v1beta1.RequestInfo, userInfo *v1beta1.RequestInfo,
@ -241,7 +234,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
var validPolicies []kyvernov1.PolicyInterface var validPolicies []kyvernov1.PolicyInterface
for _, pol := range policies { for _, pol := range policies {
// TODO we should return this info to the caller // TODO we should return this info to the caller
_, err := policyvalidation.Validate(pol, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())) _, err := policyvalidation.Validate(pol, nil, nil, true, config.KyvernoUserName(config.KyvernoServiceAccountName()))
if err != nil { if err != nil {
log.Log.Error(err, "policy validation error") log.Log.Error(err, "policy validation error")
if strings.HasPrefix(err.Error(), "variable 'element.name'") { if strings.HasPrefix(err.Error(), "variable 'element.name'") {

View file

@ -14,10 +14,8 @@ import (
"github.com/google/go-containerregistry/pkg/v1/static" "github.com/google/go-containerregistry/pkg/v1/static"
"github.com/google/go-containerregistry/pkg/v1/types" "github.com/google/go-containerregistry/pkg/v1/types"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/commands/oci/internal" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/commands/oci/internal"
clilog "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/policy"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/openapi"
policyutils "github.com/kyverno/kyverno/pkg/utils/policy" policyutils "github.com/kyverno/kyverno/pkg/utils/policy"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy" policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
) )
@ -41,12 +39,8 @@ func (o options) execute(ctx context.Context, dir string, keychain authn.Keychai
if err != nil { if err != nil {
return fmt.Errorf("unable to read policy file or directory %s (%w)", dir, err) return fmt.Errorf("unable to read policy file or directory %s (%w)", dir, err)
} }
openApiManager, err := openapi.NewManager(clilog.Log)
if err != nil {
return fmt.Errorf("creating openapi manager: %v", err)
}
for _, policy := range policies { for _, policy := range policies {
if _, err := policyvalidation.Validate(policy, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())); err != nil { if _, err := policyvalidation.Validate(policy, nil, nil, true, config.KyvernoUserName(config.KyvernoServiceAccountName())); err != nil {
return fmt.Errorf("validating policy %s: %v", policy.GetName(), err) return fmt.Errorf("validating policy %s: %v", policy.GetName(), err)
} }
} }

View file

@ -8,14 +8,12 @@ import (
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/v1alpha1" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/apis/v1alpha1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/command"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/log"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/table" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/output/table"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/report" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/report"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/store"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/filter" "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/filter"
engineapi "github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
) )
@ -75,11 +73,6 @@ func testCommandExecute(
fmt.Fprintln(out, " Error:", e) fmt.Fprintln(out, " Error:", e)
} }
} }
// init openapi manager
openApiManager, err := openapi.NewManager(log.Log)
if err != nil {
return fmt.Errorf("unable to create open api controller, %w", err)
}
// load tests // load tests
tests, err := loadTests(dirPath, fileName, gitBranch) tests, err := loadTests(dirPath, fileName, gitBranch)
if err != nil { if err != nil {
@ -122,7 +115,7 @@ func testCommandExecute(
continue continue
} }
resourcePath := filepath.Dir(test.Path) resourcePath := filepath.Dir(test.Path)
responses, err := runTest(out, openApiManager, test, false) responses, err := runTest(out, test, false)
if err != nil { if err != nil {
return fmt.Errorf("failed to run test (%w)", err) return fmt.Errorf("failed to run test (%w)", err)
} }

View file

@ -22,12 +22,11 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config" "github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api" engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy" policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
) )
func runTest(out io.Writer, openApiManager openapi.Manager, testCase test.TestCase, auditWarn bool) ([]engineapi.EngineResponse, error) { func runTest(out io.Writer, testCase test.TestCase, auditWarn bool) ([]engineapi.EngineResponse, error) {
// don't process test case with errors // don't process test case with errors
if testCase.Err != nil { if testCase.Err != nil {
return nil, testCase.Err return nil, testCase.Err
@ -116,7 +115,7 @@ func runTest(out io.Writer, openApiManager openapi.Manager, testCase test.TestCa
var validPolicies []kyvernov1.PolicyInterface var validPolicies []kyvernov1.PolicyInterface
for _, pol := range policies { for _, pol := range policies {
// TODO we should return this info to the caller // TODO we should return this info to the caller
_, err := policyvalidation.Validate(pol, nil, nil, true, openApiManager, config.KyvernoUserName(config.KyvernoServiceAccountName())) _, err := policyvalidation.Validate(pol, nil, nil, true, config.KyvernoUserName(config.KyvernoServiceAccountName()))
if err != nil { if err != nil {
log.Log.Error(err, "skipping invalid policy", "name", pol.GetName()) log.Log.Error(err, "skipping invalid policy", "name", pol.GetName())
continue continue

View file

@ -20,7 +20,6 @@ import (
genericloggingcontroller "github.com/kyverno/kyverno/pkg/controllers/generic/logging" genericloggingcontroller "github.com/kyverno/kyverno/pkg/controllers/generic/logging"
genericwebhookcontroller "github.com/kyverno/kyverno/pkg/controllers/generic/webhook" genericwebhookcontroller "github.com/kyverno/kyverno/pkg/controllers/generic/webhook"
policymetricscontroller "github.com/kyverno/kyverno/pkg/controllers/metrics/policy" 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" policycachecontroller "github.com/kyverno/kyverno/pkg/controllers/policycache"
vapcontroller "github.com/kyverno/kyverno/pkg/controllers/validatingadmissionpolicy-generate" vapcontroller "github.com/kyverno/kyverno/pkg/controllers/validatingadmissionpolicy-generate"
webhookcontroller "github.com/kyverno/kyverno/pkg/controllers/webhook" webhookcontroller "github.com/kyverno/kyverno/pkg/controllers/webhook"
@ -29,7 +28,6 @@ import (
"github.com/kyverno/kyverno/pkg/informers" "github.com/kyverno/kyverno/pkg/informers"
"github.com/kyverno/kyverno/pkg/leaderelection" "github.com/kyverno/kyverno/pkg/leaderelection"
"github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache" "github.com/kyverno/kyverno/pkg/policycache"
"github.com/kyverno/kyverno/pkg/tls" "github.com/kyverno/kyverno/pkg/tls"
"github.com/kyverno/kyverno/pkg/toggle" "github.com/kyverno/kyverno/pkg/toggle"
@ -82,7 +80,6 @@ func createNonLeaderControllers(
dynamicClient dclient.Interface, dynamicClient dclient.Interface,
configuration config.Configuration, configuration config.Configuration,
policyCache policycache.Cache, policyCache policycache.Cache,
manager openapi.Manager,
) ([]internal.Controller, func(context.Context) error) { ) ([]internal.Controller, func(context.Context) error) {
policyCacheController := policycachecontroller.NewController( policyCacheController := policycachecontroller.NewController(
dynamicClient, dynamicClient,
@ -90,13 +87,8 @@ func createNonLeaderControllers(
kyvernoInformer.Kyverno().V1().ClusterPolicies(), kyvernoInformer.Kyverno().V1().ClusterPolicies(),
kyvernoInformer.Kyverno().V1().Policies(), kyvernoInformer.Kyverno().V1().Policies(),
) )
openApiController := openapicontroller.NewController(
dynamicClient,
manager,
)
return []internal.Controller{ return []internal.Controller{
internal.NewController(policycachecontroller.ControllerName, policyCacheController, policycachecontroller.Workers), internal.NewController(policycachecontroller.ControllerName, policyCacheController, policycachecontroller.Workers),
internal.NewController(openapicontroller.ControllerName, openApiController, openapicontroller.Workers),
}, },
func(ctx context.Context) error { func(ctx context.Context) error {
if err := policyCacheController.WarmUp(); err != nil { if err := policyCacheController.WarmUp(); err != nil {
@ -294,11 +286,6 @@ func main() {
kubeInformer := kubeinformers.NewSharedInformerFactory(setup.KubeClient, resyncPeriod) kubeInformer := kubeinformers.NewSharedInformerFactory(setup.KubeClient, resyncPeriod)
kubeKyvernoInformer := kubeinformers.NewSharedInformerFactoryWithOptions(setup.KubeClient, resyncPeriod, kubeinformers.WithNamespace(config.KyvernoNamespace())) kubeKyvernoInformer := kubeinformers.NewSharedInformerFactoryWithOptions(setup.KubeClient, resyncPeriod, kubeinformers.WithNamespace(config.KyvernoNamespace()))
kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod) kyvernoInformer := kyvernoinformer.NewSharedInformerFactory(setup.KyvernoClient, resyncPeriod)
openApiManager, err := openapi.NewManager(setup.Logger.WithName("openapi"))
if err != nil {
setup.Logger.Error(err, "Failed to create openapi manager")
os.Exit(1)
}
var wg sync.WaitGroup var wg sync.WaitGroup
certRenewer := tls.NewCertRenewer( certRenewer := tls.NewCertRenewer(
setup.KubeClient.CoreV1().Secrets(config.KyvernoNamespace()), setup.KubeClient.CoreV1().Secrets(config.KyvernoNamespace()),
@ -375,7 +362,6 @@ func main() {
setup.KyvernoDynamicClient, setup.KyvernoDynamicClient,
setup.Configuration, setup.Configuration,
policyCache, policyCache,
openApiManager,
) )
// start informers and wait for cache sync // start informers and wait for cache sync
if !internal.StartInformersAndWaitForCacheSync(signalCtx, setup.Logger, kyvernoInformer, kubeInformer, kubeKyvernoInformer) { if !internal.StartInformersAndWaitForCacheSync(signalCtx, setup.Logger, kyvernoInformer, kubeInformer, kubeKyvernoInformer) {
@ -473,7 +459,6 @@ func main() {
) )
policyHandlers := webhookspolicy.NewHandlers( policyHandlers := webhookspolicy.NewHandlers(
setup.KyvernoDynamicClient, setup.KyvernoDynamicClient,
openApiManager,
backgroundServiceAccountName, backgroundServiceAccountName,
) )
resourceHandlers := webhooksresource.NewHandlers( resourceHandlers := webhooksresource.NewHandlers(
@ -489,7 +474,6 @@ func main() {
kyvernoInformer.Kyverno().V1().Policies(), kyvernoInformer.Kyverno().V1().Policies(),
urgen, urgen,
eventGenerator, eventGenerator,
openApiManager,
admissionReports, admissionReports,
backgroundServiceAccountName, backgroundServiceAccountName,
setup.Jp, setup.Jp,

5
go.mod
View file

@ -36,7 +36,6 @@ require (
github.com/onsi/gomega v1.27.10 github.com/onsi/gomega v1.27.10
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc5 github.com/opencontainers/image-spec v1.1.0-rc5
github.com/orcaman/concurrent-map/v2 v2.0.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_golang v1.16.0
github.com/robfig/cron v1.2.0 github.com/robfig/cron v1.2.0
@ -70,7 +69,6 @@ require (
google.golang.org/grpc v1.58.2 google.golang.org/grpc v1.58.2
gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools v2.2.0+incompatible gotest.tools v2.2.0+incompatible
k8s.io/api v0.28.2 k8s.io/api v0.28.2
k8s.io/apiextensions-apiserver v0.28.1 k8s.io/apiextensions-apiserver v0.28.1
@ -80,7 +78,6 @@ require (
k8s.io/client-go v0.28.2 k8s.io/client-go v0.28.2
k8s.io/klog/v2 v2.100.1 k8s.io/klog/v2 v2.100.1
k8s.io/kube-aggregator v0.28.2 k8s.io/kube-aggregator v0.28.2
k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443
k8s.io/pod-security-admission v0.28.2 k8s.io/pod-security-admission v0.28.2
k8s.io/utils v0.0.0-20230726121419-3b25d923346b k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/controller-runtime v0.16.2 sigs.k8s.io/controller-runtime v0.16.2
@ -399,8 +396,10 @@ require (
gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
k8s.io/component-base v0.28.2 // indirect k8s.io/component-base v0.28.2 // indirect
k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443 // indirect
k8s.io/kubectl v0.28.2 // indirect k8s.io/kubectl v0.28.2 // indirect
oras.land/oras-go/v2 v2.3.0 // indirect oras.land/oras-go/v2 v2.3.0 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.4 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.4 // indirect

2
go.sum
View file

@ -1249,8 +1249,6 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
github.com/outcaste-io/ristretto v0.2.1/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/outcaste-io/ristretto v0.2.1/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac=

View file

@ -1,150 +0,0 @@
package openapi
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/controllers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
)
const (
// Workers is the number of workers for this controller
Workers = 1
ControllerName = "openapi-controller"
)
type Controller interface {
controllers.Controller
CheckSync(context.Context)
}
type controller struct {
client dclient.Interface
manager Manager
}
const (
skipErrorMsg = "Got empty response for"
)
// NewController ...
func NewController(client dclient.Interface, mgr Manager) Controller {
if mgr == nil {
panic(fmt.Errorf("nil manager sent into crd sync"))
}
return &controller{
manager: mgr,
client: client,
}
}
func (c *controller) Run(ctx context.Context, workers int) {
if err := c.updateInClusterKindToAPIVersions(); err != nil {
logger.Error(err, "failed to update in-cluster api versions")
}
newDoc, err := c.client.Discovery().OpenAPISchema()
if err != nil {
logger.Error(err, "cannot get OpenAPI schema")
}
err = c.manager.UseOpenAPIDocument(newDoc)
if err != nil {
logger.Error(err, "Could not set custom OpenAPI document")
}
// Sync CRD before kyverno starts
c.sync()
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
wait.UntilWithContext(ctx, c.CheckSync, 15*time.Second)
}()
}
<-ctx.Done()
}
func (c *controller) sync() {
c.manager.Lock()
defer c.manager.Unlock()
c.client.Discovery().CachedDiscoveryInterface().Invalidate()
crds, err := c.client.GetDynamicInterface().Resource(runtimeSchema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
}).List(context.TODO(), metav1.ListOptions{})
if err != nil {
logger.Error(err, "could not fetch crd's from server")
return
}
c.manager.DeleteCRDFromPreviousSync()
for _, crd := range crds.Items {
c.manager.ParseCRD(crd)
}
if err := c.updateInClusterKindToAPIVersions(); err != nil {
logger.Error(err, "sync failed, unable to update in-cluster api versions")
}
newDoc, err := c.client.Discovery().OpenAPISchema()
if err != nil {
logger.Error(err, "cannot get OpenAPI schema")
}
err = c.manager.UseOpenAPIDocument(newDoc)
if err != nil {
logger.Error(err, "Could not set custom OpenAPI document")
}
}
func (c *controller) updateInClusterKindToAPIVersions() error {
overrideRuntimeErrorHandler()
_, apiResourceLists, err := discovery.ServerGroupsAndResources(c.client.Discovery().CachedDiscoveryInterface())
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
err := err.(*discovery.ErrGroupDiscoveryFailed)
for gv, err := range err.Groups {
logger.Error(err, "failed to list api resources", "group", gv)
}
} else if !strings.Contains(err.Error(), skipErrorMsg) {
return err
}
}
preferredAPIResourcesLists, err := discovery.ServerPreferredResources(c.client.Discovery().CachedDiscoveryInterface())
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
err := err.(*discovery.ErrGroupDiscoveryFailed)
for gv, err := range err.Groups {
logger.Error(err, "failed to list api resources", "group", gv)
}
} else if !strings.Contains(err.Error(), skipErrorMsg) {
return err
}
}
c.manager.UpdateKindToAPIVersions(apiResourceLists, preferredAPIResourcesLists)
return nil
}
func (c *controller) CheckSync(ctx context.Context) {
crds, err := c.client.GetDynamicInterface().Resource(runtimeSchema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Version: "v1",
Resource: "customresourcedefinitions",
}).List(ctx, metav1.ListOptions{})
if err != nil {
logger.Error(err, "could not fetch crd's from server")
return
}
if len(c.manager.GetCrdList()) != len(crds.Items) {
c.sync()
}
}

View file

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

View file

@ -1,17 +0,0 @@
package openapi
import (
openapiv2 "github.com/google/gnostic-models/openapiv2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type Manager interface {
Lock()
Unlock()
UseOpenAPIDocument(*openapiv2.Document) error
DeleteCRDFromPreviousSync()
ParseCRD(unstructured.Unstructured)
UpdateKindToAPIVersions([]*metav1.APIResourceList, []*metav1.APIResourceList)
GetCrdList() []string
}

View file

@ -1,17 +0,0 @@
package openapi
import "k8s.io/apimachinery/pkg/util/runtime"
func overrideRuntimeErrorHandler() {
if len(runtime.ErrorHandlers) > 0 {
runtime.ErrorHandlers[0] = func(err error) {
logger.V(6).Info("runtime error", "msg", err.Error())
}
} else {
runtime.ErrorHandlers = []func(err error){
func(err error) {
logger.V(6).Info("runtime error", "msg", err.Error())
},
}
}
}

View file

@ -1,28 +0,0 @@
package openapi
// crdDefinitionPrior represents CRDs version prior to 1.16
var crdDefinitionPrior struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Validation struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"validation"`
} `json:"spec"`
}
// crdDefinitionNew represents CRDs version 1.16+
var crdDefinitionNew struct {
Spec struct {
Names struct {
Kind string `json:"kind"`
} `json:"names"`
Versions []struct {
Schema struct {
OpenAPIV3Schema interface{} `json:"openAPIV3Schema"`
} `json:"schema"`
Storage bool `json:"storage"`
} `json:"versions"`
} `json:"spec"`
}

View file

@ -1,20 +0,0 @@
package openapi
import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func NewFake() ValidateInterface {
return &fakeValidation{}
}
type fakeValidation struct{}
func (f *fakeValidation) ValidateResource(resource unstructured.Unstructured, apiVersion, kind string) error {
return nil
}
func (f *fakeValidation) ValidatePolicyMutation(kyvernov1.PolicyInterface) error {
return nil
}

View file

@ -1,395 +0,0 @@
package openapi
import (
"encoding/json"
"fmt"
"strings"
"sync"
"github.com/go-logr/logr"
"github.com/google/gnostic-models/compiler"
openapiv2 "github.com/google/gnostic-models/openapiv2"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
"github.com/kyverno/kyverno/pkg/engine"
cmap "github.com/orcaman/concurrent-map/v2"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/util/proto/validation"
)
type ValidateInterface interface {
ValidateResource(unstructured.Unstructured, string, string) error
ValidatePolicyMutation(kyvernov1.PolicyInterface) error
}
type Manager interface {
ValidateInterface
openapicontroller.Manager
}
type manager struct {
// definitions holds the map of {definitionName: *openapiv2.Schema}
definitions cmap.ConcurrentMap[string, *openapiv2.Schema]
// kindToDefinitionName holds the map of {(group/version/)kind: definitionName}
// i.e. with k8s 1.20.2
// - Ingress: io.k8s.api.networking.v1.Ingress (preferred version)
// - networking.k8s.io/v1/Ingress: io.k8s.api.networking.v1.Ingress
// - networking.k8s.io/v1beta1/Ingress: io.k8s.api.networking.v1beta1.Ingress
// - extension/v1beta1/Ingress: io.k8s.api.extensions.v1beta1.Ingress
gvkToDefinitionName cmap.ConcurrentMap[string, string]
crdList []string
models proto.Models
// kindToAPIVersions stores the Kind and all its available apiVersions, {kind: apiVersions}
kindToAPIVersions cmap.ConcurrentMap[string, apiVersions]
logger logr.Logger
lock sync.Mutex
}
// apiVersions stores all available gvks for a kind, a gvk is "/" separated string
type apiVersions struct {
serverPreferredGVK string
gvks []string
}
// NewManager initializes a new instance of openapi schema manager
func NewManager(logger logr.Logger) (*manager, error) {
mgr := &manager{
definitions: cmap.New[*openapiv2.Schema](),
gvkToDefinitionName: cmap.New[string](),
kindToAPIVersions: cmap.New[apiVersions](),
logger: logger,
}
apiResourceLists, preferredAPIResourcesLists, err := getAPIResourceLists()
if err != nil {
return nil, err
}
mgr.UpdateKindToAPIVersions(apiResourceLists, preferredAPIResourcesLists)
defaultDoc, err := getSchemaDocument()
if err != nil {
return nil, err
}
err = mgr.UseOpenAPIDocument(defaultDoc)
if err != nil {
return nil, err
}
return mgr, nil
}
func (o *manager) Lock() {
o.lock.Lock()
}
func (o *manager) Unlock() {
o.lock.Unlock()
}
// ValidateResource ...
func (o *manager) ValidateResource(patchedResource unstructured.Unstructured, apiVersion, kind string) error {
o.Lock()
defer o.Unlock()
return o.validateResource(patchedResource, apiVersion, kind)
}
// ValidatePolicyMutation ...
func (o *manager) ValidatePolicyMutation(policy kyvernov1.PolicyInterface) error {
o.Lock()
defer o.Unlock()
kindToRules := make(map[string][]kyvernov1.Rule)
for _, rule := range autogen.ComputeRules(policy) {
if rule.HasMutate() {
if rule.IsMutateExisting() {
for _, target := range rule.Mutation.Targets {
kindToRules[target.Kind] = append(kindToRules[target.Kind], rule)
}
} else {
for _, kind := range rule.MatchResources.Kinds {
kindToRules[kind] = append(kindToRules[kind], rule)
}
for _, resourceFilter := range rule.MatchResources.Any {
for _, kind := range resourceFilter.Kinds {
kindToRules[kind] = append(kindToRules[kind], rule)
}
}
for _, resourceFilter := range rule.MatchResources.All {
for _, kind := range resourceFilter.Kinds {
kindToRules[kind] = append(kindToRules[kind], rule)
}
}
}
}
}
for kind, rules := range kindToRules {
if kind == "CustomResourceDefinition" {
continue
}
newPolicy := policy.CreateDeepCopy()
spec := newPolicy.GetSpec()
spec.SetRules(rules)
k, ok := o.gvkToDefinitionName.Get(kind)
if !ok {
continue
}
d, ok := o.definitions.Get(k)
if !ok {
continue
}
resource, _ := o.generateEmptyResource(d).(map[string]interface{})
if len(resource) == 0 {
o.logger.V(2).Info("unable to validate resource. OpenApi definition not found", "kind", kind)
return nil
}
newResource := unstructured.Unstructured{Object: resource}
newResource.SetKind(kind)
patchedResource, err := engine.ForceMutate(nil, o.logger, newPolicy, newResource)
if err != nil {
return err
}
if kind != "*" {
err = o.validateResource(*patchedResource.DeepCopy(), "", kind)
if err != nil {
return fmt.Errorf("mutate result violates resource schema: %w", err)
}
}
}
return nil
}
// ValidateResource ...
func (o *manager) validateResource(patchedResource unstructured.Unstructured, apiVersion, kind string) error {
var err error
gvk := kind
if apiVersion != "" {
gvk = apiVersion + "/" + kind
}
kind, _ = o.gvkToDefinitionName.Get(gvk)
schema := o.models.LookupModel(kind)
if schema == nil {
// Check if kind is a CRD
schema, err = o.getCRDSchema(kind)
if err != nil || schema == nil {
return fmt.Errorf("pre-validation: couldn't find model %s, err: %v", kind, err)
}
delete(patchedResource.Object, "kind")
}
if errs := validation.ValidateModel(patchedResource.UnstructuredContent(), schema, kind); len(errs) > 0 {
var errorMessages []string
for i := range errs {
errorMessages = append(errorMessages, errs[i].Error())
}
return fmt.Errorf(strings.Join(errorMessages, "\n\n"))
}
return nil
}
func (o *manager) UseOpenAPIDocument(doc *openapiv2.Document) error {
for _, definition := range doc.GetDefinitions().AdditionalProperties {
definitionName := definition.GetName()
o.definitions.Set(definitionName, definition.GetValue())
gvk, preferredGVK, err := o.getGVKByDefinitionName(definitionName)
if err != nil {
o.logger.V(5).Info("unable to cache OpenAPISchema", "definitionName", definitionName, "reason", err.Error())
continue
}
if preferredGVK {
paths := strings.Split(definitionName, ".")
kind := paths[len(paths)-1]
o.gvkToDefinitionName.Set(kind, definitionName)
}
if gvk != "" {
o.gvkToDefinitionName.Set(gvk, definitionName)
}
}
var err error
o.models, err = proto.NewOpenAPIData(doc)
if err != nil {
return err
}
return nil
}
func (o *manager) getGVKByDefinitionName(definitionName string) (gvk string, preferredGVK bool, err error) {
paths := strings.Split(definitionName, ".")
kind := paths[len(paths)-1]
versions, ok := o.kindToAPIVersions.Get(kind)
if !ok {
// the kind here is the sub-resource of a K8s Kind, i.e. CronJobStatus
// such cases are skipped in schema validation
return
}
if matchGVK(definitionName, versions.serverPreferredGVK) {
preferredGVK = true
}
for _, gvk := range versions.gvks {
if matchGVK(definitionName, gvk) {
return gvk, preferredGVK, nil
}
}
return "", preferredGVK, fmt.Errorf("gvk not found by the given definition name %s, %v", definitionName, versions.gvks)
}
func (c *manager) GetCrdList() []string {
return c.crdList
}
// UpdateKindToAPIVersions sets kindToAPIVersions with static manifests
func (c *manager) UpdateKindToAPIVersions(apiResourceLists, preferredAPIResourcesLists []*metav1.APIResourceList) {
tempKindToAPIVersions := getAllAPIVersions(apiResourceLists)
tempKindToAPIVersions = setPreferredVersions(tempKindToAPIVersions, preferredAPIResourcesLists)
c.kindToAPIVersions = cmap.New[apiVersions]()
for key, value := range tempKindToAPIVersions {
c.kindToAPIVersions.Set(key, value)
}
}
// For crd, we do not store definition in document
func (o *manager) getCRDSchema(kind string) (proto.Schema, error) {
if kind == "" {
return nil, fmt.Errorf("invalid kind")
}
path := proto.NewPath(kind)
definition, _ := o.definitions.Get(kind)
if definition == nil {
return nil, fmt.Errorf("could not find definition")
}
// This was added so crd's can access
// normal definitions from existing schema such as
// `metadata` - this maybe a breaking change.
// Removing this may cause policy validate to stop working
existingDefinitions, _ := o.models.(*proto.Definitions)
return (existingDefinitions).ParseSchema(definition, &path)
}
func (o *manager) generateEmptyResource(kindSchema *openapiv2.Schema) interface{} {
types := kindSchema.GetType().GetValue()
if kindSchema.GetXRef() != "" {
d, _ := o.definitions.Get(strings.TrimPrefix(kindSchema.GetXRef(), "#/definitions/"))
return o.generateEmptyResource(d)
}
if len(types) != 1 {
if len(kindSchema.GetProperties().GetAdditionalProperties()) > 0 {
types = []string{"object"}
} else {
return nil
}
}
switch types[0] {
case "object":
return getObjectValue(kindSchema, o)
case "array":
return getArrayValue(kindSchema, o)
case "string":
return getStringValue(kindSchema)
case "integer":
return getNumericValue(kindSchema)
case "number":
return getNumericValue(kindSchema)
case "boolean":
return getBoolValue(kindSchema)
}
o.logger.V(2).Info("unknown type", types[0])
return nil
}
func (o *manager) DeleteCRDFromPreviousSync() {
for _, crd := range o.crdList {
o.gvkToDefinitionName.Remove(crd)
o.definitions.Remove(crd)
}
o.crdList = make([]string, 0)
}
// ParseCRD loads CRD to the cache
func (o *manager) ParseCRD(crd unstructured.Unstructured) {
var err error
crdRaw, _ := json.Marshal(crd.Object)
_ = json.Unmarshal(crdRaw, &crdDefinitionPrior)
openV3schema := crdDefinitionPrior.Spec.Validation.OpenAPIV3Schema
crdName := crdDefinitionPrior.Spec.Names.Kind
if openV3schema == nil {
_ = json.Unmarshal(crdRaw, &crdDefinitionNew)
for _, crdVersion := range crdDefinitionNew.Spec.Versions {
if crdVersion.Storage {
openV3schema = crdVersion.Schema.OpenAPIV3Schema
crdName = crdDefinitionNew.Spec.Names.Kind
break
}
}
}
if openV3schema == nil {
o.logger.V(4).Info("skip adding schema, CRD has no properties", "name", crdName)
return
}
schemaRaw, _ := json.Marshal(openV3schema)
if len(schemaRaw) < 1 {
o.logger.V(4).Info("failed to parse crd schema", "name", crdName)
return
}
schemaRaw, err = addingDefaultFieldsToSchema(crdName, schemaRaw)
if err != nil {
o.logger.Error(err, "failed to parse crd schema", "name", crdName)
return
}
var schema yaml.Node
_ = yaml.Unmarshal(schemaRaw, &schema)
parsedSchema, err := openapiv2.NewSchema(&schema, compiler.NewContext("schema", &schema, nil))
if err != nil {
v3valueFound := isOpenV3Error(err)
if !v3valueFound {
o.logger.Error(err, "failed to parse crd schema", "name", crdName)
}
return
}
o.crdList = append(o.crdList, crdName)
o.gvkToDefinitionName.Set(crdName, crdName)
o.definitions.Set(crdName, parsedSchema)
}

View file

@ -1,215 +0,0 @@
package openapi
import (
"encoding/json"
"testing"
"github.com/go-logr/logr"
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
"gotest.tools/assert"
)
func Test_ValidateMutationPolicy(t *testing.T) {
tcs := []struct {
description string
policy []byte
mustSucceed bool
}{
{
description: "Policy with mutating imagePullPolicy Overlay",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-image-pull-policy-2"},"spec":{"rules":[{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchStrategicMerge":{"spec":{"containers":[{"(name)":"*","imagePullPolicy":"Always"}]}}}}]}}`),
mustSucceed: true,
},
{
description: "Policy with mutating imagePullPolicy Overlay, type of value is different (does not throw error since all numbers are also strings according to swagger)",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"set-image-pull-policy-2"},"spec":{"rules":[{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod"]}},"mutate":{"patchStrategicMerge":{"spec":{"containers":[{"(image)":"*","imagePullPolicy":80}]}}}}]}}`),
mustSucceed: true,
},
{
description: "Policy with patches",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"policy-endpoints"},"spec":{"rules":[{"name":"pEP","match":{"resources":{"kinds":["Endpoints"],"selector":{"matchLabels":{"label":"test"}}}},"mutate":{"patches": "[{\"path\":\"/subsets/0/ports/0/port\",\"op\":\"replace\",\"value\":9663},{\"path\":\"/metadata/labels/isMutated\",\"op\":\"add\",\"value\":\"true\"}]}}]" }}`),
mustSucceed: true,
},
{
description: "Dealing with nested variables",
policy: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"add-ns-access-controls","annotations":{"policies.kyverno.io/category":"Workload Isolation","policies.kyverno.io/description":"Create roles and role bindings for a new namespace"}},"spec":{"background":false,"rules":[{"name":"add-sa-annotation","match":{"resources":{"kinds":["Namespace"]}},"mutate":{"overlay":{"metadata":{"annotations":{"nirmata.io/ns-creator":"{{serviceAccountName-{{something}}}}"}}}}},{"name":"generate-owner-role","match":{"resources":{"kinds":["Namespace"]}},"preconditions":[{"key":"{{request.userInfo.username}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountName}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountNamespace}}","operator":"NotEqual","value":""}],"generate":{"kind":"ClusterRole","name":"ns-owner-{{request.object.metadata.name{{something}}}}-{{request.userInfo.username}}","data":{"metadata":{"annotations":{"nirmata.io/ns-creator":"{{serviceAccountName}}"}},"rules":[{"apiGroups":[""],"resources":["namespaces"],"verbs":["delete"],"resourceNames":["{{request.object.metadata.name}}"]}]}}},{"name":"generate-owner-role-binding","match":{"resources":{"kinds":["Namespace"]}},"preconditions":[{"key":"{{request.userInfo.username}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountName}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountNamespace}}","operator":"NotEqual","value":""}],"generate":{"kind":"ClusterRoleBinding","name":"ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding","data":{"metadata":{"annotations":{"nirmata.io/ns-creator":"{{serviceAccountName}}"}},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"ns-owner-{{request.object.metadata.name}}-{{request.userInfo.username}}"},"subjects":[{"kind":"ServiceAccount","name":"{{serviceAccountName}}","namespace":"{{serviceAccountNamespace}}"}]}}},{"name":"generate-admin-role-binding","match":{"resources":{"kinds":["Namespace"]}},"preconditions":[{"key":"{{request.userInfo.username}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountName}}","operator":"NotEqual","value":""},{"key":"{{serviceAccountNamespace}}","operator":"NotEqual","value":""}],"generate":{"kind":"RoleBinding","name":"ns-admin-{{request.object.metadata.name}}-{{request.userInfo.username}}-binding","namespace":"{{request.object.metadata.name}}","data":{"metadata":{"annotations":{"nirmata.io/ns-creator":"{{serviceAccountName}}"}},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"admin"},"subjects":[{"kind":"ServiceAccount","name":"{{serviceAccountName}}","namespace":"{{serviceAccountNamespace}}"}]}}}]}}`),
mustSucceed: true,
},
{
description: "Policy with patchesJson6902 and added element at the beginning of a list",
policy: []byte(`{"apiVersion": "kyverno.io/v1","kind": "ClusterPolicy","metadata": {"name": "pe"},"spec": {"rules": [{"name": "pe","match": {"resources": {"kinds": ["Endpoints"]}},"mutate": {"patchesJson6902": "- path: \"/subsets/0/addresses/0\"\n op: add\n value: {\"ip\":\"123\"}\n- path: \"/subsets/1/addresses/0\"\n op: add\n value: {\"ip\":\"123\"}"}}]}}`),
mustSucceed: true,
},
{
description: "Policy with patchesJson6902 and added element at the end of a list",
policy: []byte(`{"apiVersion": "kyverno.io/v1","kind": "ClusterPolicy","metadata": {"name": "pe"},"spec": {"rules": [{"name": "pe","match": {"resources": {"kinds": ["Endpoints"]}},"mutate": {"patchesJson6902": "- path: \"/subsets/0/addresses/-\"\n op: add\n value: {\"ip\":\"123\"}\n- path: \"/subsets/1/addresses/-\"\n op: add\n value: {\"ip\":\"123\"}"}}]}}`),
mustSucceed: true,
},
{
description: "Invalid policy with patchStrategicMerge and new match schema(any)",
policy: []byte(`{"apiVersion":"kyverno.io\/v1","kind":"ClusterPolicy","metadata":{"name":"mutate-pod"},"spec":{"rules":[{"name":"mutate-pod","match":{"any":[{"resources":{"kinds":["Pod"]}}]},"mutate":{"patchStrategicMerge":{"spec":{"pod":"incorrect"}}}}]}}`),
mustSucceed: false,
},
{
description: "Invalid policy with patchStrategicMerge and new match schema(all)",
policy: []byte(`{"apiVersion":"kyverno.io\/v1","kind":"ClusterPolicy","metadata":{"name":"mutate-pod"},"spec":{"rules":[{"name":"mutate-pod","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"mutate":{"patchStrategicMerge":{"spec":{"pod":"incorrect"}}}}]}}`),
mustSucceed: false,
},
{
description: "Valid policy with patchStrategicMerge and new match schema(any)",
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"}]}}}}]}}`),
mustSucceed: true,
},
{
description: "Valid policy with patchStrategicMerge and new match schema(all)",
policy: []byte(`{"apiVersion":"kyverno.io\/v1","kind":"ClusterPolicy","metadata":{"name":"set-image-pull-policy"},"spec":{"rules":[{"name":"set-image-pull-policy","match":{"all":[{"resources":{"kinds":["Pod"]}}]},"mutate":{"patchStrategicMerge":{"spec":{"containers":[{"(image)":"*:latest","imagePullPolicy":"IfNotPresent"}]}}}}]}}`),
mustSucceed: true,
},
{
description: "Policy with nested foreach and patchesJson6902",
policy: []byte(`{"apiVersion":"kyverno.io/v2beta1","kind":"ClusterPolicy","metadata":{"name":"replace-image-registry"},"spec":{"background":false,"validationFailureAction":"Enforce","rules":[{"name":"replace-dns-suffix","match":{"any":[{"resources":{"kinds":["Ingress"]}}]},"mutate":{"foreach":[{"list":"request.object.spec.tls","foreach":[{"list":"element.hosts","patchesJson6902":"- path: \"/spec/tls/{{elementIndex0}}/hosts/{{elementIndex1}}\"\n op: replace\n value: \"{{replace_all('{{element}}', '.foo.com', '.newfoo.com')}}\""}]}]}}]}}`),
mustSucceed: true,
},
}
o, _ := NewManager(logr.Discard())
for i, tc := range tcs {
t.Run(tc.description, func(t *testing.T) {
policy := v1.ClusterPolicy{}
_ = json.Unmarshal(tc.policy, &policy)
var errMessage string
err := o.ValidatePolicyMutation(&policy)
if err != nil {
errMessage = err.Error()
}
if tc.mustSucceed {
assert.NilError(t, err, "\nTestcase [%v] failed: Expected no error, Got error: %v", i+1, errMessage)
} else {
assert.Assert(t, err != nil, "\nTestcase [%v] failed: Expected error to have occurred", i+1)
}
})
}
}
func Test_addDefaultFieldsToSchema(t *testing.T) {
addingDefaultFieldsToSchema("", []byte(`null`))
addingDefaultFieldsToSchema("", nil)
}
func Test_matchGVK(t *testing.T) {
testCases := []struct {
definitionName string
gvk string
match bool
}{
{
"io.k8s.api.networking.v1.Ingress",
"networking.k8s.io/v1/Ingress",
true,
},
{
"io.k8s.api.extensions.v1beta1.Ingress",
"extensions/v1beta1/Ingress",
true,
},
{
"io.crossplane.gcp.iam.v1.ServiceAccount",
"v1/ServiceAccount",
false,
},
{
"io.k8s.api.core.v1.Secret",
"v1/Secret",
true,
},
{
"io.wgpolicyk8s.v1alpha1.PolicyReport",
"wgpolicyk8s.io/v1alpha1/PolicyReport",
true,
},
{
"io.k8s.api.rbac.v1.RoleBinding",
"rbac.authorization.k8s.io/v1/RoleBinding",
true,
},
{
"io.k8s.api.rbac.v1beta1.ClusterRoleBinding",
"rbac.authorization.k8s.io/v1beta1/ClusterRoleBinding",
true,
},
{
"io.crossplane.gcp.iam.v1alpha1.ServiceAccount",
"iam.gcp.crossplane.io/v1alpha1/ServiceAccount",
true,
},
{
"io.crossplane.gcp.iam.v1alpha1.ServiceAccount",
"v1/ServiceAccount",
false,
},
{
"v1.ServiceAccount",
"iam.gcp.crossplane.io/v1alpha1/ServiceAccount",
false,
},
{
"io.k8s.api.rbac.v1.Role",
"rbac.authorization.k8s.io/v1/Role",
true,
},
{
"io.k8s.api.rbac.v1.ClusterRole",
"rbac.authorization.k8s.io/v1/ClusterRole",
true,
},
{
"io.k8s.api.flowcontrol.v1beta1.FlowSchema",
"flowcontrol.apiserver.k8s.io/v1beta1/FlowSchema",
true,
},
{
"io.k8s.api.policy.v1beta1.Eviction",
"v1/Eviction",
false,
},
{
"io.k8s.api.rbac.v1beta1.ClusterRole",
"rbac.authorization.k8s.io/v1beta1/ClusterRole",
true,
},
{
"io.k8s.api.policy.v1.Eviction",
"v1/Eviction",
false,
},
}
for i, test := range testCases {
t.Run(test.definitionName, func(t *testing.T) {
res := matchGVK(test.definitionName, test.gvk)
assert.Equal(t, res, test.match, "test #%d failed", i)
})
}
}
// this test covers all supported Ingress
func Test_Ingress(t *testing.T) {
o, err := NewManager(logr.Discard())
assert.NilError(t, err)
versions, ok := o.kindToAPIVersions.Get("Ingress")
assert.Equal(t, true, ok)
assert.Equal(t, versions.serverPreferredGVK, "networking.k8s.io/v1/Ingress")
assert.Equal(t, len(versions.gvks), 1)
definitionName, _ := o.gvkToDefinitionName.Get("Ingress")
assert.Equal(t, definitionName, "io.k8s.api.networking.v1.Ingress")
definitionName, _ = o.gvkToDefinitionName.Get("networking.k8s.io/v1/Ingress")
assert.Equal(t, definitionName, "io.k8s.api.networking.v1.Ingress")
}

View file

@ -1,292 +0,0 @@
package openapi
import (
"encoding/json"
"fmt"
"slices"
"strconv"
"strings"
"sync"
"github.com/google/gnostic-models/compiler"
openapiv2 "github.com/google/gnostic-models/openapiv2"
"github.com/kyverno/kyverno/data"
"github.com/kyverno/kyverno/pkg/logging"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func parseGVK(str string) (group, apiVersion, kind string) {
if strings.Count(str, "/") == 0 {
return "", "", str
}
splitString := strings.Split(str, "/")
if strings.Count(str, "/") == 1 {
return "", splitString[0], splitString[1]
}
return splitString[0], splitString[1], splitString[2]
}
func groupMatches(gvkMap map[string]bool, group, kind string) bool {
if group == "" {
ok := gvkMap["core"]
if ok {
return true
}
} else {
elements := strings.Split(group, ".")
ok := gvkMap[elements[0]]
if ok {
return true
}
}
return false
}
// matchGVK is a helper function that checks if the given GVK matches the definition name
func matchGVK(definitionName, gvk string) bool {
paths := strings.Split(definitionName, ".")
gvkMap := make(map[string]bool)
for _, p := range paths {
gvkMap[p] = true
}
group, version, kind := parseGVK(gvk)
ok := gvkMap[kind]
if !ok {
return false
}
ok = gvkMap[version]
if !ok {
return false
}
if !groupMatches(gvkMap, group, kind) {
return false
}
return true
}
func getSchemaDocument() (*openapiv2.Document, error) {
var spec yaml.Node
err := yaml.Unmarshal([]byte(data.SwaggerDoc), &spec)
if err != nil {
return nil, err
}
root := spec.Content[0]
return openapiv2.NewDocument(root, compiler.NewContext("$root", root, nil))
}
func getArrayValue(kindSchema *openapiv2.Schema, o *manager) interface{} {
var array []interface{}
for _, schema := range kindSchema.GetItems().GetSchema() {
array = append(array, o.generateEmptyResource(schema))
}
return array
}
func getObjectValue(kindSchema *openapiv2.Schema, o *manager) interface{} {
props := make(map[string]interface{})
properties := kindSchema.GetProperties().GetAdditionalProperties()
if len(properties) == 0 {
return props
}
var wg sync.WaitGroup
var mutex sync.Mutex
wg.Add(len(properties))
for _, property := range properties {
go func(property *openapiv2.NamedSchema) {
prop := o.generateEmptyResource(property.GetValue())
mutex.Lock()
props[property.GetName()] = prop
mutex.Unlock()
wg.Done()
}(property)
}
wg.Wait()
return props
}
func getBoolValue(kindSchema *openapiv2.Schema) bool {
if d := kindSchema.GetDefault(); d != nil {
v := getAnyValue(d)
return string(v) == "true"
}
if e := kindSchema.GetExample(); e != nil {
v := getAnyValue(e)
return string(v) == "true"
}
return false
}
func getNumericValue(kindSchema *openapiv2.Schema) int64 {
if d := kindSchema.GetDefault(); d != nil {
v := getAnyValue(d)
val, _ := strconv.Atoi(string(v))
return int64(val)
}
if e := kindSchema.GetExample(); e != nil {
v := getAnyValue(e)
val, _ := strconv.Atoi(string(v))
return int64(val)
}
return int64(0)
}
func getStringValue(kindSchema *openapiv2.Schema) string {
if d := kindSchema.GetDefault(); d != nil {
v := getAnyValue(d)
return string(v)
}
if e := kindSchema.GetExample(); e != nil {
v := getAnyValue(e)
return string(v)
}
return ""
}
func getAnyValue(any *openapiv2.Any) []byte {
if any != nil {
if val := any.GetValue(); val != nil {
return val.GetValue()
}
}
return nil
}
// getAllAPIVersions gets all available versions for a kind
// returns a map which stores all kinds with its versions
func getAllAPIVersions(apiResourceLists []*metav1.APIResourceList) map[string]apiVersions {
tempKindToAPIVersions := make(map[string]apiVersions)
for _, apiResourceList := range apiResourceLists {
lastKind := ""
for _, apiResource := range apiResourceList.APIResources {
if apiResource.Kind == lastKind {
continue
}
version, ok := tempKindToAPIVersions[apiResource.Kind]
if !ok {
tempKindToAPIVersions[apiResource.Kind] = apiVersions{}
}
gvk := strings.Join([]string{apiResourceList.GroupVersion, apiResource.Kind}, "/")
version.gvks = append(version.gvks, gvk)
tempKindToAPIVersions[apiResource.Kind] = version
lastKind = apiResource.Kind
}
}
return tempKindToAPIVersions
}
// setPreferredVersions sets the serverPreferredGVK of the given apiVersions map
func setPreferredVersions(kindToAPIVersions map[string]apiVersions, preferredAPIResourcesLists []*metav1.APIResourceList) map[string]apiVersions {
tempKindToAPIVersionsCopied := copyKindToAPIVersions(kindToAPIVersions)
for kind, versions := range tempKindToAPIVersionsCopied {
for _, preferredAPIResourcesList := range preferredAPIResourcesLists {
for _, resource := range preferredAPIResourcesList.APIResources {
preferredGV := preferredAPIResourcesList.GroupVersion
preferredGVK := preferredGV + "/" + resource.Kind
if slices.Contains(versions.gvks, preferredGVK) {
v := kindToAPIVersions[kind]
// if a Kind belongs to multiple groups, the first group/version
// returned from discovery docs is used as preferred version
// https://github.com/kubernetes/kubernetes/issues/94761#issuecomment-691982480
if v.serverPreferredGVK != "" {
continue
}
v.serverPreferredGVK = strings.Join([]string{preferredGV, kind}, "/")
kindToAPIVersions[kind] = v
}
}
}
}
return kindToAPIVersions
}
func copyKindToAPIVersions(old map[string]apiVersions) map[string]apiVersions {
new := make(map[string]apiVersions, len(old))
for key, value := range old {
new[key] = value
}
return new
}
func getAPIResourceLists() ([]*metav1.APIResourceList, []*metav1.APIResourceList, error) {
var apiResourceLists []*metav1.APIResourceList
err := json.Unmarshal([]byte(data.APIResourceLists), &apiResourceLists)
if err != nil {
return nil, nil, fmt.Errorf("unable to load apiResourceLists: %v", err)
}
var preferredAPIResourcesLists []*metav1.APIResourceList
err = json.Unmarshal([]byte(data.APIResourceLists), &preferredAPIResourcesLists)
if err != nil {
return nil, nil, fmt.Errorf("unable to load preferredAPIResourcesLists: %v", err)
}
return apiResourceLists, preferredAPIResourcesLists, nil
}
func isOpenV3Error(err error) bool {
unsupportedValues := []string{"anyOf", "allOf", "not"}
v3valueFound := false
for _, value := range unsupportedValues {
if !strings.Contains(err.Error(), fmt.Sprintf("has invalid property: %s", value)) {
v3valueFound = true
break
}
}
return v3valueFound
}
// addingDefaultFieldsToSchema will add any default missing fields like apiVersion, metadata
func addingDefaultFieldsToSchema(crdName string, schemaRaw []byte) ([]byte, error) {
var schema struct {
Properties map[string]interface{} `json:"properties"`
}
_ = json.Unmarshal(schemaRaw, &schema)
if len(schema.Properties) < 1 {
logging.V(6).Info("crd schema has no properties", "name", crdName)
return schemaRaw, nil
}
if schema.Properties["apiVersion"] == nil {
apiVersionDefRaw := `{"description":"APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources","type":"string"}`
apiVersionDef := make(map[string]interface{})
_ = json.Unmarshal([]byte(apiVersionDefRaw), &apiVersionDef)
schema.Properties["apiVersion"] = apiVersionDef
}
if schema.Properties["metadata"] == nil {
metadataDefRaw := `{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta","description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"}`
metadataDef := make(map[string]interface{})
_ = json.Unmarshal([]byte(metadataDefRaw), &metadataDef)
schema.Properties["metadata"] = metadataDef
}
schemaWithDefaultFields, _ := json.Marshal(schema)
return schemaWithDefaultFields, nil
}

View file

@ -3,30 +3,17 @@ package policy
import ( import (
"testing" "testing"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1" kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/openapi"
fuzz "github.com/AdaLogics/go-fuzz-headers" fuzz "github.com/AdaLogics/go-fuzz-headers"
) )
var fuzzOpenApiManager openapi.Manager
func init() {
var err error
fuzzOpenApiManager, err = openapi.NewManager(logr.Discard())
if err != nil {
panic(err)
}
}
func FuzzValidatePolicy(f *testing.F) { func FuzzValidatePolicy(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { f.Fuzz(func(t *testing.T, data []byte) {
ff := fuzz.NewConsumer(data) ff := fuzz.NewConsumer(data)
p := &kyverno.ClusterPolicy{} p := &kyverno.ClusterPolicy{}
ff.GenerateStruct(p) ff.GenerateStruct(p)
Validate(p, nil, nil, true, fuzzOpenApiManager, "admin") Validate(p, nil, nil, true, "admin")
}) })
} }

View file

@ -1,7 +1,6 @@
package policy package policy
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -19,18 +18,15 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1" kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen" "github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context" enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables" "github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/engine/variables/regex" "github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/logging" "github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/openapi"
apiutils "github.com/kyverno/kyverno/pkg/utils/api" apiutils "github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data" datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube" kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/wildcard" "github.com/kyverno/kyverno/pkg/utils/wildcard"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
@ -120,14 +116,11 @@ func checkValidationFailureAction(spec *kyvernov1.Spec) []string {
} }
// Validate checks the policy and rules declarations for required configurations // Validate checks the policy and rules declarations for required configurations
func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interface, mock bool, openApiManager openapi.Manager, username string) ([]string, error) { func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interface, mock bool, username string) ([]string, error) {
var warnings []string var warnings []string
spec := policy.GetSpec() spec := policy.GetSpec()
background := spec.BackgroundProcessingEnabled() background := spec.BackgroundProcessingEnabled()
mutateExistingOnPolicyUpdate := spec.GetMutateExistingOnPolicyUpdate() mutateExistingOnPolicyUpdate := spec.GetMutateExistingOnPolicyUpdate()
if !mock {
openapicontroller.NewController(client, openApiManager).CheckSync(context.TODO())
}
warnings = append(warnings, checkValidationFailureAction(spec)...) warnings = append(warnings, checkValidationFailureAction(spec)...)
var errs field.ErrorList var errs field.ErrorList
@ -145,11 +138,14 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
} }
} }
var res []*metav1.APIResourceList getClusteredResources := func(invalidate bool) (sets.Set[string], error) {
clusterResources := sets.New[string]() clusterResources := sets.New[string]()
if !mock {
// Get all the cluster type kind supported by cluster // Get all the cluster type kind supported by cluster
res, err = discovery.ServerPreferredResources(client.Discovery().CachedDiscoveryInterface()) d := client.Discovery().CachedDiscoveryInterface()
if invalidate {
d.Invalidate()
}
res, err := discovery.ServerPreferredResources(d)
if err != nil { if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) { if discovery.IsGroupDiscoveryFailedError(err) {
err := err.(*discovery.ErrGroupDiscoveryFailed) err := err.(*discovery.ErrGroupDiscoveryFailed)
@ -157,7 +153,7 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
logging.Error(err, "failed to list api resources", "group", gv) logging.Error(err, "failed to list api resources", "group", gv)
} }
} else { } else {
return warnings, err return nil, err
} }
} }
for _, resList := range res { for _, resList := range res {
@ -168,10 +164,29 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
} }
} }
} }
return clusterResources, nil
} }
clusterResources := sets.New[string]()
if errs := policy.Validate(clusterResources); len(errs) != 0 { // if not using a mock, we first try to validate and if it fails we retry with cache invalidation in between
return warnings, errs.ToAggregate() if !mock {
clusterResources, err = getClusteredResources(false)
if err != nil {
return warnings, err
}
if errs := policy.Validate(clusterResources); len(errs) != 0 {
clusterResources, err = getClusteredResources(true)
if err != nil {
return warnings, err
}
if errs := policy.Validate(clusterResources); len(errs) != 0 {
return warnings, errs.ToAggregate()
}
}
} else {
if errs := policy.Validate(clusterResources); len(errs) != 0 {
return warnings, errs.ToAggregate()
}
} }
if !policy.IsNamespaced() { if !policy.IsNamespaced() {
@ -364,11 +379,6 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
checkForDeprecatedFieldsInVerifyImages(rule, &warnings) checkForDeprecatedFieldsInVerifyImages(rule, &warnings)
} }
} }
if !mock && (spec.SchemaValidation == nil || *spec.SchemaValidation) {
if err := openApiManager.ValidatePolicyMutation(policy); err != nil {
return warnings, fmt.Errorf("%s (you can bypass schema validation by setting `spec.schemaValidation: false`)", err)
}
}
return warnings, nil return warnings, nil
} }

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,6 @@ import (
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/clients/dclient" "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/openapi"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
policyvalidate "github.com/kyverno/kyverno/pkg/validation/policy" policyvalidate "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/kyverno/kyverno/pkg/webhooks" "github.com/kyverno/kyverno/pkg/webhooks"
@ -15,14 +14,12 @@ import (
type policyHandlers struct { type policyHandlers struct {
client dclient.Interface client dclient.Interface
openApiManager openapi.Manager
backgroungServiceAccountName string backgroungServiceAccountName string
} }
func NewHandlers(client dclient.Interface, openApiManager openapi.Manager, serviceaccount string) webhooks.PolicyHandlers { func NewHandlers(client dclient.Interface, serviceaccount string) webhooks.PolicyHandlers {
return &policyHandlers{ return &policyHandlers{
client: client, client: client,
openApiManager: openApiManager,
backgroungServiceAccountName: serviceaccount, backgroungServiceAccountName: serviceaccount,
} }
} }
@ -33,7 +30,7 @@ func (h *policyHandlers) Validate(ctx context.Context, logger logr.Logger, reque
logger.Error(err, "failed to unmarshal policies from admission request") logger.Error(err, "failed to unmarshal policies from admission request")
return admissionutils.Response(request.UID, err) return admissionutils.Response(request.UID, err)
} }
warnings, err := policyvalidate.Validate(policy, oldPolicy, h.client, false, h.openApiManager, h.backgroungServiceAccountName) warnings, err := policyvalidate.Validate(policy, oldPolicy, h.client, false, h.backgroungServiceAccountName)
if err != nil { if err != nil {
logger.Error(err, "policy validation errors") logger.Error(err, "policy validation errors")
} }

View file

@ -15,7 +15,6 @@ import (
"github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/imageverifycache" "github.com/kyverno/kyverno/pkg/imageverifycache"
"github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache" "github.com/kyverno/kyverno/pkg/policycache"
"github.com/kyverno/kyverno/pkg/registryclient" "github.com/kyverno/kyverno/pkg/registryclient"
"github.com/kyverno/kyverno/pkg/webhooks" "github.com/kyverno/kyverno/pkg/webhooks"
@ -45,16 +44,15 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhook
rclient := registryclient.NewOrDie() rclient := registryclient.NewOrDie()
return &resourceHandlers{ return &resourceHandlers{
client: dclient, client: dclient,
configuration: configuration, configuration: configuration,
metricsConfig: metricsConfig, metricsConfig: metricsConfig,
pCache: policyCache, pCache: policyCache,
nsLister: informers.Core().V1().Namespaces().Lister(), nsLister: informers.Core().V1().Namespaces().Lister(),
urLister: urLister, urLister: urLister,
urGenerator: updaterequest.NewFake(), urGenerator: updaterequest.NewFake(),
eventGen: event.NewFake(), eventGen: event.NewFake(),
openApiManager: openapi.NewFake(), pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp),
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp),
engine: engine.NewEngine( engine: engine.NewEngine(
configuration, configuration,
config.NewDefaultMetricsConfiguration(), config.NewDefaultMetricsConfiguration(),

View file

@ -17,7 +17,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/jmespath" "github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache" "github.com/kyverno/kyverno/pkg/policycache"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission" admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine" engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
@ -52,10 +51,9 @@ type resourceHandlers struct {
cpolLister kyvernov1listers.ClusterPolicyLister cpolLister kyvernov1listers.ClusterPolicyLister
polLister kyvernov1listers.PolicyLister polLister kyvernov1listers.PolicyLister
urGenerator webhookgenerate.Generator urGenerator webhookgenerate.Generator
eventGen event.Interface eventGen event.Interface
openApiManager openapi.ValidateInterface pcBuilder webhookutils.PolicyContextBuilder
pcBuilder webhookutils.PolicyContextBuilder
admissionReports bool admissionReports bool
backgroungServiceAccountName string backgroungServiceAccountName string
@ -74,7 +72,6 @@ func NewHandlers(
polInformer kyvernov1informers.PolicyInformer, polInformer kyvernov1informers.PolicyInformer,
urGenerator webhookgenerate.Generator, urGenerator webhookgenerate.Generator,
eventGen event.Interface, eventGen event.Interface,
openApiManager openapi.ValidateInterface,
admissionReports bool, admissionReports bool,
backgroungServiceAccountName string, backgroungServiceAccountName string,
jp jmespath.Interface, jp jmespath.Interface,
@ -92,7 +89,6 @@ func NewHandlers(
polLister: polInformer.Lister(), polLister: polInformer.Lister(),
urGenerator: urGenerator, urGenerator: urGenerator,
eventGen: eventGen, eventGen: eventGen,
openApiManager: openApiManager,
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp), pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp),
admissionReports: admissionReports, admissionReports: admissionReports,
backgroungServiceAccountName: backgroungServiceAccountName, backgroungServiceAccountName: backgroungServiceAccountName,
@ -158,7 +154,7 @@ func (h *resourceHandlers) Mutate(ctx context.Context, logger logr.Logger, reque
logger.Error(err, "failed to build policy context") logger.Error(err, "failed to build policy context")
return admissionutils.Response(request.UID, err) return admissionutils.Response(request.UID, err)
} }
mh := mutation.NewMutationHandler(logger, h.engine, h.eventGen, h.openApiManager, h.nsLister, h.metricsConfig) mh := mutation.NewMutationHandler(logger, h.engine, h.eventGen, h.nsLister, h.metricsConfig)
mutatePatches, mutateWarnings, err := mh.HandleMutation(ctx, request.AdmissionRequest, mutatePolicies, policyContext, startTime) mutatePatches, mutateWarnings, err := mh.HandleMutation(ctx, request.AdmissionRequest, mutatePolicies, policyContext, startTime)
if err != nil { if err != nil {
logger.Error(err, "mutation failed") logger.Error(err, "mutation failed")

View file

@ -12,7 +12,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/mutate/patch" "github.com/kyverno/kyverno/pkg/engine/mutate/patch"
"github.com/kyverno/kyverno/pkg/event" "github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/metrics" "github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/tracing" "github.com/kyverno/kyverno/pkg/tracing"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine" engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json" jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
@ -34,27 +33,24 @@ func NewMutationHandler(
log logr.Logger, log logr.Logger,
engine engineapi.Engine, engine engineapi.Engine,
eventGen event.Interface, eventGen event.Interface,
openApiManager openapi.ValidateInterface,
nsLister corev1listers.NamespaceLister, nsLister corev1listers.NamespaceLister,
metrics metrics.MetricsConfigManager, metrics metrics.MetricsConfigManager,
) MutationHandler { ) MutationHandler {
return &mutationHandler{ return &mutationHandler{
log: log, log: log,
engine: engine, engine: engine,
eventGen: eventGen, eventGen: eventGen,
openApiManager: openApiManager, nsLister: nsLister,
nsLister: nsLister, metrics: metrics,
metrics: metrics,
} }
} }
type mutationHandler struct { type mutationHandler struct {
log logr.Logger log logr.Logger
engine engineapi.Engine engine engineapi.Engine
eventGen event.Interface eventGen event.Interface
openApiManager openapi.ValidateInterface nsLister corev1listers.NamespaceLister
nsLister corev1listers.NamespaceLister metrics metrics.MetricsConfigManager
metrics metrics.MetricsConfigManager
} }
func (h *mutationHandler) HandleMutation( func (h *mutationHandler) HandleMutation(
@ -147,13 +143,6 @@ func (h *mutationHandler) applyMutation(ctx context.Context, request admissionv1
return nil, nil, fmt.Errorf("failed to apply policy %s rules %v", policyContext.Policy().GetName(), engineResponse.GetFailedRulesWithErrors()) return nil, nil, fmt.Errorf("failed to apply policy %s rules %v", policyContext.Policy().GetName(), engineResponse.GetFailedRulesWithErrors())
} }
if policyContext.Policy().ValidateSchema() && engineResponse.PatchedResource.GetKind() != "*" {
err := h.openApiManager.ValidateResource(*engineResponse.PatchedResource.DeepCopy(), engineResponse.PatchedResource.GetAPIVersion(), engineResponse.PatchedResource.GetKind())
if err != nil {
return nil, nil, fmt.Errorf("failed to validate resource mutated by policy %s: %w", policyContext.Policy().GetName(), err)
}
}
return &engineResponse, policyPatches, nil return &engineResponse, policyPatches, nil
} }

View file

@ -1,234 +1,6 @@
apiVersion: apiextensions.k8s.io/v1 apiVersion: kuttl.dev/v1beta1
kind: CustomResourceDefinition kind: TestStep
metadata: apply:
creationTimestamp: null - crd.yaml
name: roles.iam.aws.crossplane.io assert:
spec: - crd-assert.yaml
group: iam.aws.crossplane.io
names:
categories:
- crossplane
- managed
- aws
kind: Role
listKind: RoleList
plural: roles
shortNames:
- iamrole
singular: role
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=='Ready')].status
name: READY
type: string
- jsonPath: .status.conditions[?(@.type=='Synced')].status
name: SYNCED
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
description: An Role is a managed resource that represents an AWS IAM Role.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: An RoleSpec defines the desired state of an Role.
properties:
deletionPolicy:
default: Delete
description: DeletionPolicy specifies what will happen to the underlying
external when this managed resource is deleted - either "Delete"
or "Orphan" the external resource.
enum:
- Orphan
- Delete
type: string
forProvider:
description: RoleParameters define the desired state of an AWS IAM
Role.
properties:
assumeRolePolicyDocument:
description: AssumeRolePolicyDocument is the the trust relationship
policy document that grants an entity permission to assume the
role.
type: string
description:
description: Description is a description of the role.
type: string
maxSessionDuration:
description: 'MaxSessionDuration is the duration (in seconds)
that you want to set for the specified role. The default maximum
of one hour is applied. This setting can have a value from 1
hour to 12 hours. Default: 3600'
format: int32
type: integer
path:
description: 'Path is the path to the role. Default: /'
type: string
permissionsBoundary:
description: PermissionsBoundary is the ARN of the policy that
is used to set the permissions boundary for the role.
type: string
tags:
description: Tags. For more information about tagging, see Tagging
IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
items:
description: Tag represents user-provided metadata that can
be associated with a IAM role. For more information about
tagging, see Tagging IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
properties:
key:
description: The key name that can be used to look up or
retrieve the associated value. For example, Department
or Cost Center are common choices.
type: string
value:
description: "The value associated with this tag. For example,
tags with a key name of Department could have values such
as Human Resources, Accounting, and Support. Tags with
a key name of Cost Center might have values that consist
of the number associated with the different cost centers
in your company. Typically, many resources have tags with
the same key name but with different values. \n AWS always
interprets the tag Value as a single string. If you need
to store an array, you can store comma-separated values
in the string. However, you must interpret the value in
your code."
type: string
required:
- key
type: object
type: array
required:
- assumeRolePolicyDocument
type: object
providerConfigRef:
default:
name: default
description: ProviderConfigReference specifies how the provider that
will be used to create, observe, update, and delete this managed
resource should be configured.
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
providerRef:
description: 'ProviderReference specifies the provider that will be
used to create, observe, update, and delete this managed resource.
Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`'
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
- namespace
type: object
required:
- forProvider
type: object
status:
description: An RoleStatus represents the observed state of an Role.
properties:
atProvider:
description: RoleExternalStatus keeps the state for the external resource
properties:
arn:
description: ARN is the Amazon Resource Name (ARN) specifying
the role. For more information about ARNs and how to use them
in policies, see IAM Identifiers (http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the IAM User Guide guide.
type: string
roleID:
description: RoleID is the stable and unique string identifying
the role. For more information about IDs, see IAM Identifiers
(http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the Using IAM guide.
type: string
required:
- arn
- roleID
type: object
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions:
- v1beta1

View file

@ -0,0 +1,10 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: roles.iam.aws.crossplane.io
status:
acceptedNames:
kind: Role
listKind: RoleList
plural: roles
singular: role

View file

@ -0,0 +1,234 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
name: roles.iam.aws.crossplane.io
spec:
group: iam.aws.crossplane.io
names:
categories:
- crossplane
- managed
- aws
kind: Role
listKind: RoleList
plural: roles
shortNames:
- iamrole
singular: role
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=='Ready')].status
name: READY
type: string
- jsonPath: .status.conditions[?(@.type=='Synced')].status
name: SYNCED
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1beta1
schema:
openAPIV3Schema:
description: An Role is a managed resource that represents an AWS IAM Role.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: An RoleSpec defines the desired state of an Role.
properties:
deletionPolicy:
default: Delete
description: DeletionPolicy specifies what will happen to the underlying
external when this managed resource is deleted - either "Delete"
or "Orphan" the external resource.
enum:
- Orphan
- Delete
type: string
forProvider:
description: RoleParameters define the desired state of an AWS IAM
Role.
properties:
assumeRolePolicyDocument:
description: AssumeRolePolicyDocument is the the trust relationship
policy document that grants an entity permission to assume the
role.
type: string
description:
description: Description is a description of the role.
type: string
maxSessionDuration:
description: 'MaxSessionDuration is the duration (in seconds)
that you want to set for the specified role. The default maximum
of one hour is applied. This setting can have a value from 1
hour to 12 hours. Default: 3600'
format: int32
type: integer
path:
description: 'Path is the path to the role. Default: /'
type: string
permissionsBoundary:
description: PermissionsBoundary is the ARN of the policy that
is used to set the permissions boundary for the role.
type: string
tags:
description: Tags. For more information about tagging, see Tagging
IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
items:
description: Tag represents user-provided metadata that can
be associated with a IAM role. For more information about
tagging, see Tagging IAM Identities (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html)
in the IAM User Guide.
properties:
key:
description: The key name that can be used to look up or
retrieve the associated value. For example, Department
or Cost Center are common choices.
type: string
value:
description: "The value associated with this tag. For example,
tags with a key name of Department could have values such
as Human Resources, Accounting, and Support. Tags with
a key name of Cost Center might have values that consist
of the number associated with the different cost centers
in your company. Typically, many resources have tags with
the same key name but with different values. \n AWS always
interprets the tag Value as a single string. If you need
to store an array, you can store comma-separated values
in the string. However, you must interpret the value in
your code."
type: string
required:
- key
type: object
type: array
required:
- assumeRolePolicyDocument
type: object
providerConfigRef:
default:
name: default
description: ProviderConfigReference specifies how the provider that
will be used to create, observe, update, and delete this managed
resource should be configured.
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
providerRef:
description: 'ProviderReference specifies the provider that will be
used to create, observe, update, and delete this managed resource.
Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`'
properties:
name:
description: Name of the referenced object.
type: string
required:
- name
type: object
writeConnectionSecretToRef:
description: WriteConnectionSecretToReference specifies the namespace
and name of a Secret to which any connection details for this managed
resource should be written. Connection details frequently include
the endpoint, username, and password required to connect to the
managed resource.
properties:
name:
description: Name of the secret.
type: string
namespace:
description: Namespace of the secret.
type: string
required:
- name
- namespace
type: object
required:
- forProvider
type: object
status:
description: An RoleStatus represents the observed state of an Role.
properties:
atProvider:
description: RoleExternalStatus keeps the state for the external resource
properties:
arn:
description: ARN is the Amazon Resource Name (ARN) specifying
the role. For more information about ARNs and how to use them
in policies, see IAM Identifiers (http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the IAM User Guide guide.
type: string
roleID:
description: RoleID is the stable and unique string identifying
the role. For more information about IDs, see IAM Identifiers
(http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html)
in the Using IAM guide.
type: string
required:
- arn
- roleID
type: object
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions:
- v1beta1