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:
parent
eedc993ed9
commit
482c243517
26 changed files with 3665 additions and 4851 deletions
|
@ -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'") {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
5
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package openapi
|
||||
|
||||
import "github.com/kyverno/kyverno/pkg/logging"
|
||||
|
||||
var logger = logging.ControllerLogger(ControllerName)
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue