mirror of
https://github.com/kyverno/kyverno.git
synced 2025-04-18 02:06:52 +00:00
feat: add global context entry validation webhook (#9619)
* feat: add global context entry validation webhook Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: use `k8s.io/apimachinery/pkg/util/json` instead of `encoding/json` Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: lint Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> --------- Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
parent
2b712107d2
commit
3142af64a0
17 changed files with 420 additions and 14 deletions
cmd/kyverno
pkg
config
controllers/globalcontext
utils/admission
cleanup.gocleanup_test.goexception.goexception_test.goglobalcontext.goglobalcontext_test.gometadata.gometadata_test.gopolicy.gopolicy_test.go
validation/globalcontext
webhooks
|
@ -38,8 +38,10 @@ import (
|
|||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
|
||||
"github.com/kyverno/kyverno/pkg/validation/exception"
|
||||
"github.com/kyverno/kyverno/pkg/validation/globalcontext"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks"
|
||||
webhooksexception "github.com/kyverno/kyverno/pkg/webhooks/exception"
|
||||
webhooksglobalcontext "github.com/kyverno/kyverno/pkg/webhooks/globalcontext"
|
||||
webhookspolicy "github.com/kyverno/kyverno/pkg/webhooks/policy"
|
||||
webhooksresource "github.com/kyverno/kyverno/pkg/webhooks/resource"
|
||||
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/updaterequest"
|
||||
|
@ -56,6 +58,7 @@ import (
|
|||
const (
|
||||
resyncPeriod = 15 * time.Minute
|
||||
exceptionWebhookControllerName = "exception-webhook-controller"
|
||||
gctxWebhookControllerName = "global-context-webhook-controller"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -183,9 +186,37 @@ func createrLeaderControllers(
|
|||
configuration,
|
||||
caSecretName,
|
||||
)
|
||||
gctxWebhookController := genericwebhookcontroller.NewController(
|
||||
gctxWebhookControllerName,
|
||||
kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations(),
|
||||
kubeInformer.Admissionregistration().V1().ValidatingWebhookConfigurations(),
|
||||
caInformer,
|
||||
config.GlobalContextValidatingWebhookConfigurationName,
|
||||
config.GlobalContextValidatingWebhookServicePath,
|
||||
serverIP,
|
||||
servicePort,
|
||||
webhookServerPort,
|
||||
nil,
|
||||
[]admissionregistrationv1.RuleWithOperations{{
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"kyverno.io"},
|
||||
APIVersions: []string{"v2alpha1"},
|
||||
Resources: []string{"globalcontextentries"},
|
||||
},
|
||||
Operations: []admissionregistrationv1.OperationType{
|
||||
admissionregistrationv1.Create,
|
||||
admissionregistrationv1.Update,
|
||||
},
|
||||
}},
|
||||
genericwebhookcontroller.Fail,
|
||||
genericwebhookcontroller.None,
|
||||
configuration,
|
||||
caSecretName,
|
||||
)
|
||||
leaderControllers = append(leaderControllers, internal.NewController(certmanager.ControllerName, certManager, certmanager.Workers))
|
||||
leaderControllers = append(leaderControllers, internal.NewController(webhookcontroller.ControllerName, webhookController, webhookcontroller.Workers))
|
||||
leaderControllers = append(leaderControllers, internal.NewController(exceptionWebhookControllerName, exceptionWebhookController, 1))
|
||||
leaderControllers = append(leaderControllers, internal.NewController(gctxWebhookControllerName, gctxWebhookController, 1))
|
||||
|
||||
if generateVAPs {
|
||||
checker := checker.NewSelfChecker(kubeClient.AuthorizationV1().SelfSubjectAccessReviews())
|
||||
|
@ -498,11 +529,15 @@ func main() {
|
|||
Enabled: internal.PolicyExceptionEnabled(),
|
||||
Namespace: internal.ExceptionNamespace(),
|
||||
})
|
||||
globalContextHandlers := webhooksglobalcontext.NewHandlers(globalcontext.ValidationOptions{
|
||||
Enabled: internal.PolicyExceptionEnabled(),
|
||||
})
|
||||
server := webhooks.NewServer(
|
||||
signalCtx,
|
||||
policyHandlers,
|
||||
resourceHandlers,
|
||||
exceptionHandlers,
|
||||
globalContextHandlers,
|
||||
setup.Configuration,
|
||||
setup.MetricsManager,
|
||||
webhooks.DebugModeOptions{
|
||||
|
|
|
@ -24,6 +24,8 @@ const (
|
|||
ValidatingWebhookConfigurationName = "kyverno-resource-validating-webhook-cfg"
|
||||
// ExceptionValidatingWebhookConfigurationName ...
|
||||
ExceptionValidatingWebhookConfigurationName = "kyverno-exception-validating-webhook-cfg"
|
||||
// GlobalContextValidatingWebhookConfigurationName ...
|
||||
GlobalContextValidatingWebhookConfigurationName = "kyverno-global-context-validating-webhook-cfg"
|
||||
// CleanupValidatingWebhookConfigurationName ...
|
||||
CleanupValidatingWebhookConfigurationName = "kyverno-cleanup-validating-webhook-cfg"
|
||||
// PolicyMutatingWebhookConfigurationName default policy mutating webhook configuration name
|
||||
|
@ -58,6 +60,8 @@ const (
|
|||
ValidatingWebhookServicePath = "/validate"
|
||||
// ExceptionValidatingWebhookServicePath is the path for policy exception validation webhook(used to validate policy exception resource)
|
||||
ExceptionValidatingWebhookServicePath = "/exceptionvalidate"
|
||||
// GlobalContextValidatingWebhookServicePath is the path for global context validation webhook(used to validate global context entries)
|
||||
GlobalContextValidatingWebhookServicePath = "/globalcontextvalidate"
|
||||
// CleanupValidatingWebhookServicePath is the path for cleanup policy validation webhook(used to validate cleanup policy resource)
|
||||
CleanupValidatingWebhookServicePath = "/validate"
|
||||
// TtlValidatingWebhookServicePath is the path for validation of cleanup.kyverno.io/ttl label value
|
||||
|
|
|
@ -89,10 +89,6 @@ func (c *controller) getEntry(name string) (*kyvernov2alpha1.GlobalContextEntry,
|
|||
}
|
||||
|
||||
func (c *controller) makeStoreEntry(ctx context.Context, gce *kyvernov2alpha1.GlobalContextEntry) (store.Entry, error) {
|
||||
// TODO: should be done at validation time
|
||||
if err := gce.Validate(); err != nil {
|
||||
return nil, err.ToAggregate()
|
||||
}
|
||||
if gce.Spec.KubernetesResource != nil {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: gce.Spec.KubernetesResource.Group,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func UnmarshalCleanupPolicy(kind string, raw []byte) (kyvernov2alpha1.CleanupPolicyInterface, error) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -9,6 +8,7 @@ import (
|
|||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func TestUnmarshalCleanupPolicy(t *testing.T) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func UnmarshalPolicyException(raw []byte) (*kyvernov2beta1.PolicyException, error) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
|
||||
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
28
pkg/utils/admission/globalcontext.go
Normal file
28
pkg/utils/admission/globalcontext.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func UnmarshalGlobalContextEntry(raw []byte) (*kyvernov2alpha1.GlobalContextEntry, error) {
|
||||
var exception *kyvernov2alpha1.GlobalContextEntry
|
||||
if err := json.Unmarshal(raw, &exception); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exception, nil
|
||||
}
|
||||
|
||||
func GetGlobalContextEntry(request admissionv1.AdmissionRequest) (*kyvernov2alpha1.GlobalContextEntry, *kyvernov2alpha1.GlobalContextEntry, error) {
|
||||
var empty *kyvernov2alpha1.GlobalContextEntry
|
||||
gctx, err := UnmarshalGlobalContextEntry(request.Object.Raw)
|
||||
if err != nil {
|
||||
return gctx, empty, err
|
||||
}
|
||||
if request.Operation == admissionv1.Update {
|
||||
old, err := UnmarshalGlobalContextEntry(request.OldObject.Raw)
|
||||
return gctx, old, err
|
||||
}
|
||||
return gctx, empty, nil
|
||||
}
|
183
pkg/utils/admission/globalcontext_test.go
Normal file
183
pkg/utils/admission/globalcontext_test.go
Normal file
|
@ -0,0 +1,183 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func TestUnmarshalGlobalContext(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
raw []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid JSON",
|
||||
raw: []byte(`{"field": "value"}`),
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON data",
|
||||
raw: []byte(`invalid JSON data`),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty JSON",
|
||||
raw: []byte(`{}`),
|
||||
},
|
||||
{
|
||||
name: "Missing Field",
|
||||
raw: []byte(``),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Nested Array",
|
||||
raw: []byte(`{"nested": [{"field": "value"}]}`),
|
||||
},
|
||||
{
|
||||
name: "Invalid Type",
|
||||
raw: []byte(`123`),
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := UnmarshalGlobalContextEntry(test.raw)
|
||||
if test.expectErr {
|
||||
if err == nil {
|
||||
t.Error("Expected an error, but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
var gctx *kyvernov2alpha1.GlobalContextEntry
|
||||
json.Unmarshal(test.raw, &gctx)
|
||||
if !reflect.DeepEqual(result, gctx) {
|
||||
t.Errorf("Expected %+v, got %+v", gctx, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGlobalContext(t *testing.T) {
|
||||
type args struct {
|
||||
request admissionv1.AdmissionRequest
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
args args
|
||||
}{{
|
||||
name: "Valid JSON",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "CREATE",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Invalid JSON data",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`invalid JSON data`),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "UPDATE",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Empty JSON",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`{}`),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "DELETE",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Missing Field",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(``),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "CONNECT",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Nested Array",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`{"nested": [{"field": "value"}]}`),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "DELETE",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Invalid Type",
|
||||
args: args{
|
||||
request: admissionv1.AdmissionRequest{
|
||||
Object: runtime.RawExtension{
|
||||
Raw: []byte(`123`),
|
||||
},
|
||||
OldObject: runtime.RawExtension{
|
||||
Raw: []byte(`{"field":"value"}`),
|
||||
},
|
||||
Operation: "UPDATE",
|
||||
},
|
||||
},
|
||||
}}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
g1, g2, _ := GetGlobalContextEntry(test.args.request)
|
||||
var empty *kyvernov2alpha1.GlobalContextEntry
|
||||
expectedG1, err := UnmarshalGlobalContextEntry(test.args.request.Object.Raw)
|
||||
if err != nil {
|
||||
expectedG2 := empty
|
||||
if !reflect.DeepEqual(expectedG1, g1) || !reflect.DeepEqual(expectedG2, g2) {
|
||||
t.Errorf("Expected policies %+v and %+v , got %+v and %+v ", expectedG1, expectedG2, g1, g2)
|
||||
}
|
||||
} else if test.args.request.Operation == admissionv1.Update {
|
||||
expectedG2, err := UnmarshalGlobalContextEntry(test.args.request.OldObject.Raw)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedG1, g1) || !reflect.DeepEqual(expectedG2, g2) {
|
||||
t.Errorf("Expected policies %+v and %+v , got %+v and %+v ", expectedG1, expectedG2, g1, g2)
|
||||
}
|
||||
} else {
|
||||
expectedG2 := empty
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedG1, g1) || !reflect.DeepEqual(expectedG2, g2) {
|
||||
t.Errorf("Expected policies %+v and %+v , got %+v and %+v ", expectedG1, expectedG2, g1, g2)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func UnmarshalPartialObjectMetadata(raw []byte) (*metav1.PartialObjectMetadata, error) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func UnmarshalPolicy(kind string, raw []byte) (kyvernov1.PolicyInterface, error) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package admission
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -9,6 +8,7 @@ import (
|
|||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func TestUnmarshalPolicy(t *testing.T) {
|
||||
|
|
26
pkg/validation/globalcontext/validate.go
Normal file
26
pkg/validation/globalcontext/validate.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
kyvernov2alpha1 "github.com/kyverno/kyverno/api/kyverno/v2alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
disabledGctx = "Global context entry would not be processed until it is enabled."
|
||||
)
|
||||
|
||||
type ValidationOptions struct {
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Validate checks global context entry is valid
|
||||
func Validate(ctx context.Context, logger logr.Logger, gctx *kyvernov2alpha1.GlobalContextEntry, opts ValidationOptions) ([]string, error) {
|
||||
var warnings []string
|
||||
if !opts.Enabled {
|
||||
warnings = append(warnings, disabledGctx)
|
||||
}
|
||||
errs := gctx.Validate()
|
||||
return warnings, errs.ToAggregate()
|
||||
}
|
81
pkg/validation/globalcontext/validate_test.go
Normal file
81
pkg/validation/globalcontext/validate_test.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/kyverno/kyverno/pkg/logging"
|
||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func Test_Validate(t *testing.T) {
|
||||
type args struct {
|
||||
opts ValidationOptions
|
||||
resource []byte
|
||||
}
|
||||
tc := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "GlobalContextEntry disabled.",
|
||||
args: args{
|
||||
opts: ValidationOptions{
|
||||
Enabled: false,
|
||||
},
|
||||
resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"GlobalContextEntry","metadata":{"name":"ingress"},"spec":{"apiCall":{"service":{"url":"https://svc.kyverno/example","caBundle":"-----BEGIN CERTIFICATE-----\n-----REDACTED-----\n-----END CERTIFICATE-----"},"refreshInterval":"10ns"}}}`),
|
||||
},
|
||||
want: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "GlobalContextEntry enabled, both KubernetesResource and APICall present",
|
||||
args: args{
|
||||
opts: ValidationOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"GlobalContextEntry","metadata":{"name":"ingress"},"spec":{"apiCall":{"service":{"url":"https://svc.kyverno/example","caBundle":"-----BEGIN CERTIFICATE-----\n-----REDACTED-----\n-----END CERTIFICATE-----"},"refreshInterval":"10ns"},"kubernetesResource":{"group":"apis/networking.k8s.io","version":"v1","resource":"ingresses","namespace":"apps"}}}`),
|
||||
},
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "GlobalContextEntry enabled, neither KubernetesResource nor APICall present",
|
||||
args: args{
|
||||
opts: ValidationOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"GlobalContextEntry","metadata":{"name":"ingress"},"spec":{}}`),
|
||||
},
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "GlobalContextEntry enabled.",
|
||||
args: args{
|
||||
opts: ValidationOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
resource: []byte(`{"apiVersion":"kyverno.io/v2alpha1","kind":"GlobalContextEntry","metadata":{"name":"ingress"},"spec":{"apiCall":{"service":{"url":"https://svc.kyverno/example","caBundle":"-----BEGIN CERTIFICATE-----\n-----REDACTED-----\n-----END CERTIFICATE-----"},"refreshInterval":"10ns"}}}`),
|
||||
},
|
||||
want: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, c := range tc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
gctx, err := admissionutils.UnmarshalGlobalContextEntry(c.args.resource)
|
||||
assert.NilError(t, err)
|
||||
warnings, err := Validate(context.Background(), logging.GlobalLogger(), gctx, c.args.opts)
|
||||
if c.wantErr {
|
||||
assert.Assert(t, err != nil)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
assert.Assert(t, len(warnings) == c.want)
|
||||
})
|
||||
}
|
||||
}
|
36
pkg/webhooks/globalcontext/validate.go
Normal file
36
pkg/webhooks/globalcontext/validate.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
|
||||
validation "github.com/kyverno/kyverno/pkg/validation/globalcontext"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks"
|
||||
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
|
||||
)
|
||||
|
||||
type gctxHandlers struct {
|
||||
validationOptions validation.ValidationOptions
|
||||
}
|
||||
|
||||
func NewHandlers(validationOptions validation.ValidationOptions) webhooks.GlobalContextHandlers {
|
||||
return &gctxHandlers{
|
||||
validationOptions: validationOptions,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs the validation check on global context entries
|
||||
func (h *gctxHandlers) Validate(ctx context.Context, logger logr.Logger, request handlers.AdmissionRequest, startTime time.Time) handlers.AdmissionResponse {
|
||||
gctx, _, err := admissionutils.GetGlobalContextEntry(request.AdmissionRequest)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to unmarshal global context entry from admission request")
|
||||
return admissionutils.Response(request.UID, err)
|
||||
}
|
||||
warnings, err := validation.Validate(ctx, logger, gctx, h.validationOptions)
|
||||
if err != nil {
|
||||
logger.Error(err, "global context entry validation errors")
|
||||
}
|
||||
return admissionutils.Response(request.UID, err, warnings...)
|
||||
}
|
|
@ -42,6 +42,11 @@ type ExceptionHandlers interface {
|
|||
Validate(context.Context, logr.Logger, handlers.AdmissionRequest, time.Time) admissionv1.AdmissionResponse
|
||||
}
|
||||
|
||||
type GlobalContextHandlers interface {
|
||||
// Validate performs the validation check on global context entries
|
||||
Validate(context.Context, logr.Logger, handlers.AdmissionRequest, time.Time) admissionv1.AdmissionResponse
|
||||
}
|
||||
|
||||
type PolicyHandlers interface {
|
||||
// Mutate performs the mutation of policy resources
|
||||
Mutate(context.Context, logr.Logger, handlers.AdmissionRequest, time.Time) admissionv1.AdmissionResponse
|
||||
|
@ -72,6 +77,7 @@ func NewServer(
|
|||
policyHandlers PolicyHandlers,
|
||||
resourceHandlers ResourceHandlers,
|
||||
exceptionHandlers ExceptionHandlers,
|
||||
globalContextHandlers GlobalContextHandlers,
|
||||
configuration config.Configuration,
|
||||
metricsConfig metrics.MetricsConfigManager,
|
||||
debugModeOpts DebugModeOptions,
|
||||
|
@ -89,6 +95,7 @@ func NewServer(
|
|||
resourceLogger := logger.WithName("resource")
|
||||
policyLogger := logger.WithName("policy")
|
||||
exceptionLogger := logger.WithName("exception")
|
||||
globalContextLogger := logger.WithName("globalcontext")
|
||||
verifyLogger := logger.WithName("verify")
|
||||
registerWebhookHandlers(
|
||||
mux,
|
||||
|
@ -152,6 +159,16 @@ func NewServer(
|
|||
WithAdmission(exceptionLogger.WithName("validate")).
|
||||
ToHandlerFunc("VALIDATE"),
|
||||
)
|
||||
mux.HandlerFunc(
|
||||
"POST",
|
||||
config.GlobalContextValidatingWebhookServicePath,
|
||||
handlers.FromAdmissionFunc("VALIDATE", globalContextHandlers.Validate).
|
||||
WithDump(debugModeOpts.DumpPayload).
|
||||
WithSubResourceFilter().
|
||||
WithMetrics(globalContextLogger, metricsConfig.Config(), metrics.WebhookValidating).
|
||||
WithAdmission(globalContextLogger.WithName("validate")).
|
||||
ToHandlerFunc("VALIDATE"),
|
||||
)
|
||||
mux.HandlerFunc(
|
||||
"POST",
|
||||
config.VerifyMutatingWebhookServicePath,
|
||||
|
|
Loading…
Add table
Reference in a new issue