1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +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/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
gitutils "github.com/kyverno/kyverno/pkg/utils/git"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/spf13/cobra"
@ -142,10 +141,6 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
if err != nil {
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)
if err != nil {
return rc, resources1, skipInvalidPolicies, responses1, err
@ -171,7 +166,6 @@ func (c *ApplyCommandConfig) applyCommandHelper(out io.Writer) (*processor.Resul
variables,
policies,
resources,
openApiManager,
&skipInvalidPolicies,
dClient,
userInfo,
@ -228,7 +222,6 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
vars *variables.Variables,
policies []kyvernov1.PolicyInterface,
resources []*unstructured.Unstructured,
openApiManager openapi.Manager,
skipInvalidPolicies *SkippedInvalidPolicies,
dClient dclient.Interface,
userInfo *v1beta1.RequestInfo,
@ -241,7 +234,7 @@ func (c *ApplyCommandConfig) applyPolicytoResource(
var validPolicies []kyvernov1.PolicyInterface
for _, pol := range policies {
// 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 {
log.Log.Error(err, "policy validation error")
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/types"
"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/pkg/config"
"github.com/kyverno/kyverno/pkg/openapi"
policyutils "github.com/kyverno/kyverno/pkg/utils/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 {
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 {
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)
}
}

View file

@ -8,14 +8,12 @@ import (
"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/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/table"
"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/test/filter"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/cache"
)
@ -75,11 +73,6 @@ func testCommandExecute(
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
tests, err := loadTests(dirPath, fileName, gitBranch)
if err != nil {
@ -122,7 +115,7 @@ func testCommandExecute(
continue
}
resourcePath := filepath.Dir(test.Path)
responses, err := runTest(out, openApiManager, test, false)
responses, err := runTest(out, test, false)
if err != nil {
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/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/openapi"
policyvalidation "github.com/kyverno/kyverno/pkg/validation/policy"
"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
if testCase.Err != nil {
return nil, testCase.Err
@ -116,7 +115,7 @@ func runTest(out io.Writer, openApiManager openapi.Manager, testCase test.TestCa
var validPolicies []kyvernov1.PolicyInterface
for _, pol := range policies {
// 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 {
log.Log.Error(err, "skipping invalid policy", "name", pol.GetName())
continue

View file

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

5
go.mod
View file

@ -36,7 +36,6 @@ require (
github.com/onsi/gomega v1.27.10
github.com/opencontainers/go-digest v1.0.0
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/prometheus/client_golang v1.16.0
github.com/robfig/cron v1.2.0
@ -70,7 +69,6 @@ require (
google.golang.org/grpc v1.58.2
gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools v2.2.0+incompatible
k8s.io/api v0.28.2
k8s.io/apiextensions-apiserver v0.28.1
@ -80,7 +78,6 @@ require (
k8s.io/client-go v0.28.2
k8s.io/klog/v2 v2.100.1
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/utils v0.0.0-20230726121419-3b25d923346b
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // 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
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
oras.land/oras-go/v2 v2.3.0 // 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.2.1/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.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
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 (
"testing"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/openapi"
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) {
f.Fuzz(func(t *testing.T, data []byte) {
ff := fuzz.NewConsumer(data)
p := &kyverno.ClusterPolicy{}
ff.GenerateStruct(p)
Validate(p, nil, nil, true, fuzzOpenApiManager, "admin")
Validate(p, nil, nil, true, "admin")
})
}

View file

@ -1,7 +1,6 @@
package policy
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -19,18 +18,15 @@ import (
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/autogen"
"github.com/kyverno/kyverno/pkg/clients/dclient"
openapicontroller "github.com/kyverno/kyverno/pkg/controllers/openapi"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/openapi"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
"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/validation/field"
"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
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
spec := policy.GetSpec()
background := spec.BackgroundProcessingEnabled()
mutateExistingOnPolicyUpdate := spec.GetMutateExistingOnPolicyUpdate()
if !mock {
openapicontroller.NewController(client, openApiManager).CheckSync(context.TODO())
}
warnings = append(warnings, checkValidationFailureAction(spec)...)
var errs field.ErrorList
@ -145,11 +138,14 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
}
}
var res []*metav1.APIResourceList
clusterResources := sets.New[string]()
if !mock {
getClusteredResources := func(invalidate bool) (sets.Set[string], error) {
clusterResources := sets.New[string]()
// 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 discovery.IsGroupDiscoveryFailedError(err) {
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)
}
} else {
return warnings, err
return nil, err
}
}
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 {
return warnings, errs.ToAggregate()
// if not using a mock, we first try to validate and if it fails we retry with cache invalidation in between
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() {
@ -364,11 +379,6 @@ func Validate(policy, oldPolicy kyvernov1.PolicyInterface, client dclient.Interf
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
}

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,6 @@ import (
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/openapi"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
policyvalidate "github.com/kyverno/kyverno/pkg/validation/policy"
"github.com/kyverno/kyverno/pkg/webhooks"
@ -15,14 +14,12 @@ import (
type policyHandlers struct {
client dclient.Interface
openApiManager openapi.Manager
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{
client: client,
openApiManager: openApiManager,
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")
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 {
logger.Error(err, "policy validation errors")
}

View file

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

View file

@ -17,7 +17,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/policycache"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
@ -52,10 +51,9 @@ type resourceHandlers struct {
cpolLister kyvernov1listers.ClusterPolicyLister
polLister kyvernov1listers.PolicyLister
urGenerator webhookgenerate.Generator
eventGen event.Interface
openApiManager openapi.ValidateInterface
pcBuilder webhookutils.PolicyContextBuilder
urGenerator webhookgenerate.Generator
eventGen event.Interface
pcBuilder webhookutils.PolicyContextBuilder
admissionReports bool
backgroungServiceAccountName string
@ -74,7 +72,6 @@ func NewHandlers(
polInformer kyvernov1informers.PolicyInformer,
urGenerator webhookgenerate.Generator,
eventGen event.Interface,
openApiManager openapi.ValidateInterface,
admissionReports bool,
backgroungServiceAccountName string,
jp jmespath.Interface,
@ -92,7 +89,6 @@ func NewHandlers(
polLister: polInformer.Lister(),
urGenerator: urGenerator,
eventGen: eventGen,
openApiManager: openApiManager,
pcBuilder: webhookutils.NewPolicyContextBuilder(configuration, jp),
admissionReports: admissionReports,
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")
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)
if err != nil {
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/event"
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/openapi"
"github.com/kyverno/kyverno/pkg/tracing"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
@ -34,27 +33,24 @@ func NewMutationHandler(
log logr.Logger,
engine engineapi.Engine,
eventGen event.Interface,
openApiManager openapi.ValidateInterface,
nsLister corev1listers.NamespaceLister,
metrics metrics.MetricsConfigManager,
) MutationHandler {
return &mutationHandler{
log: log,
engine: engine,
eventGen: eventGen,
openApiManager: openApiManager,
nsLister: nsLister,
metrics: metrics,
log: log,
engine: engine,
eventGen: eventGen,
nsLister: nsLister,
metrics: metrics,
}
}
type mutationHandler struct {
log logr.Logger
engine engineapi.Engine
eventGen event.Interface
openApiManager openapi.ValidateInterface
nsLister corev1listers.NamespaceLister
metrics metrics.MetricsConfigManager
log logr.Logger
engine engineapi.Engine
eventGen event.Interface
nsLister corev1listers.NamespaceLister
metrics metrics.MetricsConfigManager
}
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())
}
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
}

View file

@ -1,234 +1,6 @@
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
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- crd.yaml
assert:
- crd-assert.yaml

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