From 3a798452fe2014c66038446f60d31b6518ef900b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Tue, 27 Aug 2024 17:04:51 +0200 Subject: [PATCH] Add e2e test --- .../features.yaml | 6 - test/e2e_new/broker_test.go | 20 ++ test/reconciler-tests.sh | 6 +- .../authz/addressable_authz_conformance.go | 237 ++++++++++++++++++ .../rekt/resources/eventpolicy/eventpolicy.go | 192 ++++++++++++++ .../resources/eventpolicy/eventpolicy.yaml | 74 ++++++ vendor/modules.txt | 2 + 7 files changed, 528 insertions(+), 9 deletions(-) rename test/{config-oidc-authentication => config-auth}/features.yaml (81%) create mode 100644 vendor/knative.dev/eventing/test/rekt/features/authz/addressable_authz_conformance.go create mode 100644 vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.go create mode 100644 vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.yaml diff --git a/test/config-oidc-authentication/features.yaml b/test/config-auth/features.yaml similarity index 81% rename from test/config-oidc-authentication/features.yaml rename to test/config-auth/features.yaml index ae227d1072..a49fe6eef4 100644 --- a/test/config-oidc-authentication/features.yaml +++ b/test/config-auth/features.yaml @@ -21,11 +21,5 @@ metadata: knative.dev/config-propagation: original knative.dev/config-category: eventing data: - kreference-group: "disabled" - delivery-retryafter: "disabled" - delivery-timeout: "enabled" - kreference-mapping: "disabled" - new-trigger-filters: "enabled" transport-encryption: "strict" - eventtype-auto-create: "disabled" authentication-oidc: "enabled" diff --git a/test/e2e_new/broker_test.go b/test/e2e_new/broker_test.go index cc8f3b5dc2..36aa891e1a 100644 --- a/test/e2e_new/broker_test.go +++ b/test/e2e_new/broker_test.go @@ -34,6 +34,7 @@ import ( "knative.dev/eventing-kafka-broker/control-plane/pkg/kafka" "knative.dev/eventing-kafka-broker/test/e2e_new/single_partition_config" "knative.dev/eventing-kafka-broker/test/rekt/features" + "knative.dev/eventing/test/rekt/features/authz" "knative.dev/eventing/test/rekt/features/broker" brokereventingfeatures "knative.dev/eventing/test/rekt/features/broker" "knative.dev/eventing/test/rekt/features/oidc" @@ -323,6 +324,25 @@ func TestBrokerSendsEventsWithOIDCSupport(t *testing.T) { env.TestSet(ctx, t, brokereventingfeatures.BrokerSendEventWithOIDC()) } +func TestBrokerSupportsAuthZ(t *testing.T) { + t.Parallel() + + ctx, env := global.Environment( + knative.WithKnativeNamespace(system.Namespace()), + knative.WithLoggingConfig, + knative.WithTracingConfig, + k8s.WithEventListener, + environment.WithPollTimings(4*time.Second, 12*time.Minute), + environment.Managed(t), + eventshub.WithTLS(t), + ) + + name := feature.MakeRandomK8sName("broker") + env.Prerequisite(ctx, t, broker.GoesReady(name, brokerresources.WithEnvConfig()...)) + + env.TestSet(ctx, t, authz.AddressableAuthZConformance(brokerresources.GVR(), "Broker", name)) +} + func TestBrokerDispatcherKedaScaling(t *testing.T) { t.Parallel() diff --git a/test/reconciler-tests.sh b/test/reconciler-tests.sh index 5a35ba123e..e1fc6050e4 100755 --- a/test/reconciler-tests.sh +++ b/test/reconciler-tests.sh @@ -64,11 +64,11 @@ kubectl apply -Rf "$(dirname "$0")/config-transport-encryption" go_test_e2e -timeout=1h ./test/e2e_new -run TLS || fail_test -echo "Running E2E Reconciler Tests with OIDC authentication enabled" +echo "Running E2E Reconciler OIDC and AuthZ Tests" -kubectl apply -Rf "$(dirname "$0")/config-oidc-authentication" +kubectl apply -Rf "$(dirname "$0")/config-auth" -go_test_e2e -timeout=1h ./test/e2e_new -run OIDC || fail_test +go_test_e2e -timeout=1h ./test/e2e_new -run "OIDC|AuthZ" || fail_test if ! ${LOCAL_DEVELOPMENT}; then go_test_e2e -tags=sacura -timeout=40m ./test/e2e/... || fail_test "E2E (sacura) suite failed" diff --git a/vendor/knative.dev/eventing/test/rekt/features/authz/addressable_authz_conformance.go b/vendor/knative.dev/eventing/test/rekt/features/authz/addressable_authz_conformance.go new file mode 100644 index 0000000000..7043e86b08 --- /dev/null +++ b/vendor/knative.dev/eventing/test/rekt/features/authz/addressable_authz_conformance.go @@ -0,0 +1,237 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package authz + +import ( + "context" + "fmt" + "time" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" + "knative.dev/eventing/test/rekt/resources/eventpolicy" + "knative.dev/eventing/test/rekt/resources/pingsource" + "knative.dev/reconciler-test/pkg/environment" + + "knative.dev/eventing/test/rekt/features/featureflags" + + "github.com/cloudevents/sdk-go/v2/test" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/reconciler-test/pkg/eventshub" + eventassert "knative.dev/reconciler-test/pkg/eventshub/assert" + "knative.dev/reconciler-test/pkg/feature" + "knative.dev/reconciler-test/pkg/k8s" +) + +// AddressableAuthZConformance returns a feature set to test all Authorization features for an addressable. +func AddressableAuthZConformance(gvr schema.GroupVersionResource, kind, name string) *feature.FeatureSet { + fs := feature.FeatureSet{ + Name: fmt.Sprintf("%s handles authorization features correctly", kind), + Features: []*feature.Feature{ + addressableRespectsEventPolicyFilters(gvr, kind, name), + }, + } + + fs.Features = append(fs.Features, AddressableAuthZConformanceRequestHandling(gvr, kind, name).Features...) + + return &fs +} + +// AddressableAuthZConformanceRequestHandling returns a FeatureSet to test the basic authorization features. +// This basic feature set contains to allow authorized and reject unauthorized requests. In addition it also +// tests, that the addressable becomes unready in case of a NotReady assigned EventPolicy. +func AddressableAuthZConformanceRequestHandling(gvr schema.GroupVersionResource, kind, name string) *feature.FeatureSet { + fs := feature.FeatureSet{ + Name: fmt.Sprintf("%s handles authorization in requests correctly", kind), + Features: []*feature.Feature{ + addressableAllowsAuthorizedRequest(gvr, kind, name), + addressableRejectsUnauthorizedRequest(gvr, kind, name), + addressableBecomesUnreadyOnUnreadyEventPolicy(gvr, kind, name), + }, + } + return &fs +} + +func addressableAllowsAuthorizedRequest(gvr schema.GroupVersionResource, kind, name string) *feature.Feature { + f := feature.NewFeatureNamed(fmt.Sprintf("%s accepts authorized request", kind)) + + f.Prerequisite("OIDC authentication is enabled", featureflags.AuthenticationOIDCEnabled()) + f.Prerequisite("transport encryption is strict", featureflags.TransportEncryptionStrict()) + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + source := feature.MakeRandomK8sName("source") + eventPolicy := feature.MakeRandomK8sName("eventpolicy") + sourceSubject := feature.MakeRandomK8sName("source-oidc-identity") + + event := test.FullEvent() + + // Install event policy + f.Setup("Install the EventPolicy", func(ctx context.Context, t feature.T) { + namespace := environment.FromContext(ctx).Namespace() + eventpolicy.Install( + eventPolicy, + eventpolicy.WithToRef( + gvr.GroupVersion().WithKind(kind), + name), + eventpolicy.WithFromSubject(fmt.Sprintf("system:serviceaccount:%s:%s", namespace, sourceSubject)), + )(ctx, t) + }) + f.Setup(fmt.Sprintf("EventPolicy for %s %s is ready", kind, name), k8s.IsReady(eventpolicy.GVR(), eventPolicy)) + + // Install source + f.Requirement("install source", eventshub.Install( + source, + eventshub.StartSenderToResourceTLS(gvr, name, nil), + eventshub.InputEvent(event), + eventshub.OIDCSubject(sourceSubject), + )) + + f.Alpha(kind). + Must("event sent", eventassert.OnStore(source).MatchSentEvent(test.HasId(event.ID())).Exact(1)). + Must("get 202 on response", eventassert.OnStore(source).Match(eventassert.MatchStatusCode(202)).AtLeast(1)) + + return f +} + +func addressableRejectsUnauthorizedRequest(gvr schema.GroupVersionResource, kind, name string) *feature.Feature { + f := feature.NewFeatureNamed(fmt.Sprintf("%s rejects unauthorized request", kind)) + + f.Prerequisite("OIDC authentication is enabled", featureflags.AuthenticationOIDCEnabled()) + f.Prerequisite("transport encryption is strict", featureflags.TransportEncryptionStrict()) + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + source := feature.MakeRandomK8sName("source") + eventPolicy := feature.MakeRandomK8sName("eventpolicy") + + event := test.FullEvent() + + // Install event policy + f.Setup("Install the EventPolicy with from subject that does not match", eventpolicy.Install( + eventPolicy, + eventpolicy.WithToRef( + gvr.GroupVersion().WithKind(kind), + name), + eventpolicy.WithFromSubject("system:serviceaccount:default:unknown-identity"), + )) + f.Setup(fmt.Sprintf("EventPolicy for %s %s is ready", kind, name), k8s.IsReady(eventpolicy.GVR(), eventPolicy)) + + // Install source + f.Requirement("install source", eventshub.Install( + source, + eventshub.StartSenderToResourceTLS(gvr, name, nil), + eventshub.InputEvent(event), + eventshub.InitialSenderDelay(10*time.Second), + )) + + f.Alpha(kind). + Must("event sent", eventassert.OnStore(source).MatchSentEvent(test.HasId(event.ID())).Exact(1)). + Must("get 403 on response", eventassert.OnStore(source).Match(eventassert.MatchStatusCode(403)).AtLeast(1)) + + return f +} + +func addressableRespectsEventPolicyFilters(gvr schema.GroupVersionResource, kind, name string) *feature.Feature { + f := feature.NewFeatureNamed(fmt.Sprintf("%s only admits events that pass the event policy filter", kind)) + + f.Prerequisite("OIDC authentication is enabled", featureflags.AuthenticationOIDCEnabled()) + f.Prerequisite("transport encryption is strict", featureflags.TransportEncryptionStrict()) + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + eventPolicy := feature.MakeRandomK8sName("eventpolicy") + source1 := feature.MakeRandomK8sName("source") + sourceSubject1 := feature.MakeRandomK8sName("source-oidc-identity") + source2 := feature.MakeRandomK8sName("source") + sourceSubject2 := feature.MakeRandomK8sName("source-oidc-identity") + + event1 := test.FullEvent() + event1.SetType("valid.event.type") + event1.SetID("1") + event2 := test.FullEvent() + event2.SetType("invalid.event.type") + event2.SetID("2") + + // Install event policy + f.Setup("Install the EventPolicy", func(ctx context.Context, t feature.T) { + namespace := environment.FromContext(ctx).Namespace() + eventpolicy.Install( + eventPolicy, + eventpolicy.WithToRef( + gvr.GroupVersion().WithKind(kind), + name), + eventpolicy.WithFromSubject(fmt.Sprintf("system:serviceaccount:%s:%s", namespace, sourceSubject1)), + eventpolicy.WithFromSubject(fmt.Sprintf("system:serviceaccount:%s:%s", namespace, sourceSubject2)), + eventpolicy.WithFilters([]eventingv1.SubscriptionsAPIFilter{ + { + Prefix: map[string]string{ + "type": "valid", + }, + }, + }), + )(ctx, t) + }) + f.Setup(fmt.Sprintf("EventPolicy for %s %s is ready", kind, name), k8s.IsReady(eventpolicy.GVR(), eventPolicy)) + + // Install source + f.Requirement("install source 1", eventshub.Install( + source1, + eventshub.StartSenderToResourceTLS(gvr, name, nil), + eventshub.InputEvent(event1), + eventshub.OIDCSubject(sourceSubject1), + )) + + f.Requirement("install source 2", eventshub.Install( + source2, + eventshub.StartSenderToResourceTLS(gvr, name, nil), + eventshub.InputEvent(event2), + eventshub.OIDCSubject(sourceSubject2), + )) + + f.Alpha(kind). + Must("valid event sent", eventassert.OnStore(source1).MatchSentEvent(test.HasId(event1.ID())).Exact(1)). + Must("get 202 on response", eventassert.OnStore(source1).Match(eventassert.MatchStatusCode(202)).AtLeast(1)) + + f.Alpha(kind). + Must("invalid event sent", eventassert.OnStore(source2).MatchSentEvent(test.HasId(event2.ID())).Exact(1)). + Must("get 403 on response", eventassert.OnStore(source2).Match(eventassert.MatchStatusCode(403)).AtLeast(1)) + + return f +} + +func addressableBecomesUnreadyOnUnreadyEventPolicy(gvr schema.GroupVersionResource, kind, name string) *feature.Feature { + f := feature.NewFeatureNamed(fmt.Sprintf("%s becomes NotReady when EventPolicy is NotReady", kind)) + + f.Prerequisite("OIDC authentication is enabled", featureflags.AuthenticationOIDCEnabled()) + f.Prerequisite("transport encryption is strict", featureflags.TransportEncryptionStrict()) + f.Prerequisite("should not run when Istio is enabled", featureflags.IstioDisabled()) + + eventPolicy := feature.MakeRandomK8sName("eventpolicy") + + f.Setup(fmt.Sprintf("%s is ready initially", kind), k8s.IsReady(gvr, name)) + + // Install event policy + f.Requirement("Install the EventPolicy", eventpolicy.Install( + eventPolicy, + eventpolicy.WithToRef( + gvr.GroupVersion().WithKind(kind), + name), + eventpolicy.WithFromRef(pingsource.Gvr().GroupVersion().WithKind("PingSource"), "doesnt-exist", "doesnt-exist"), + )) + f.Requirement(fmt.Sprintf("EventPolicy for %s %s is NotReady", kind, name), k8s.IsNotReady(eventpolicy.GVR(), eventPolicy)) + + f.Alpha(kind).Must("become NotReady with NotReady EventPolicy ", k8s.IsNotReady(gvr, name)) + + return f +} diff --git a/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.go b/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.go new file mode 100644 index 0000000000..6d44293bd1 --- /dev/null +++ b/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.go @@ -0,0 +1,192 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package eventpolicy + +import ( + "context" + "embed" + "encoding/json" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/reconciler-test/pkg/feature" + "knative.dev/reconciler-test/pkg/k8s" + "knative.dev/reconciler-test/pkg/manifest" + "sigs.k8s.io/yaml" + + eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1" +) + +//go:embed *.yaml +var yamlEmbed embed.FS + +func GVR() schema.GroupVersionResource { + return schema.GroupVersionResource{Group: "eventing.knative.dev", Version: "v1alpha1", Resource: "eventpolicies"} +} + +// Install will create an EventPolicy resource, augmented with the config fn options. +func Install(name string, opts ...manifest.CfgFn) feature.StepFn { + cfg := map[string]interface{}{ + "name": name, + } + for _, fn := range opts { + fn(cfg) + } + return func(ctx context.Context, t feature.T) { + if _, err := manifest.InstallYamlFS(ctx, yamlEmbed, cfg); err != nil { + t.Fatal(err) + } + } +} + +func WithToRef(gvk schema.GroupVersionKind, name string) manifest.CfgFn { + return func(cfg map[string]interface{}) { + if _, set := cfg["to"]; !set { + cfg["to"] = []map[string]interface{}{} + } + + res := cfg["to"].([]map[string]interface{}) + + to := map[string]interface{}{ + "ref": map[string]interface{}{ + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + "name": name, + }} + + res = append(res, to) + + cfg["to"] = res + } +} + +func WithToSelector(gvk schema.GroupVersionKind, labelSelector *metav1.LabelSelector) manifest.CfgFn { + return func(cfg map[string]interface{}) { + if _, set := cfg["to"]; !set { + cfg["to"] = []map[string]interface{}{} + } + + res := cfg["to"].([]map[string]interface{}) + + selector := labelSelectorToStringMap(labelSelector) + selector["apiVersion"] = gvk.GroupVersion().String() + selector["kind"] = gvk.Kind + + to := map[string]interface{}{ + "selector": selector, + } + + res = append(res, to) + + cfg["to"] = res + } +} + +func WithFromRef(gvk schema.GroupVersionKind, name, namespace string) manifest.CfgFn { + return func(cfg map[string]interface{}) { + if _, set := cfg["from"]; !set { + cfg["from"] = []map[string]interface{}{} + } + + res := cfg["from"].([]map[string]interface{}) + + from := map[string]interface{}{ + "ref": map[string]interface{}{ + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + "name": name, + "namespace": namespace, + }} + + res = append(res, from) + + cfg["from"] = res + } +} + +func WithFromSubject(subject string) manifest.CfgFn { + return func(cfg map[string]interface{}) { + if _, set := cfg["from"]; !set { + cfg["from"] = []map[string]interface{}{} + } + + res := cfg["from"].([]map[string]interface{}) + + from := map[string]interface{}{ + "sub": subject, + } + + res = append(res, from) + + cfg["from"] = res + } +} + +func WithFilters(filters []eventingv1.SubscriptionsAPIFilter) manifest.CfgFn { + jsonBytes, err := json.Marshal(filters) + if err != nil { + panic(err) + } + + yamlBytes, err := yaml.JSONToYAML(jsonBytes) + if err != nil { + panic(err) + } + + filtersYaml := string(yamlBytes) + + lines := strings.Split(filtersYaml, "\n") + out := make([]string, 0, len(lines)) + for i := range lines { + out = append(out, " "+lines[i]) + } + + return func(m map[string]interface{}) { + m["filters"] = strings.Join(out, "\n") + } +} + +// IsReady tests to see if an EventPolicy becomes ready within the time given. +func IsReady(name string, timing ...time.Duration) feature.StepFn { + return k8s.IsReady(GVR(), name, timing...) +} + +func labelSelectorToStringMap(selector *metav1.LabelSelector) map[string]interface{} { + if selector == nil { + return map[string]interface{}{} + } + + r := map[string]interface{}{} + + r["matchLabels"] = selector.MatchLabels + + if selector.MatchExpressions != nil { + me := []map[string]interface{}{} + for _, ml := range selector.MatchExpressions { + me = append(me, map[string]interface{}{ + "key": ml.Key, + "operator": ml.Operator, + "values": ml.Values, + }) + } + r["matchExpressions"] = me + } + + return r +} diff --git a/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.yaml b/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.yaml new file mode 100644 index 0000000000..84925531e1 --- /dev/null +++ b/vendor/knative.dev/eventing/test/rekt/resources/eventpolicy/eventpolicy.yaml @@ -0,0 +1,74 @@ +# Copyright 2020 The Knative Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: eventing.knative.dev/v1alpha1 +kind: EventPolicy +metadata: + name: {{ .name }} + namespace: {{ .namespace }} +spec: + {{ if .to }} + to: + {{ range $to := .to }} + {{ if $to.ref }} + - ref: + apiVersion: {{ $to.ref.apiVersion }} + kind: {{ $to.ref.kind }} + name: {{ $to.ref.name }} + {{ end }} #end if $to.ref + + {{ if $to.selector }} + - selector: + apiVersion: {{ $to.selector.apiVersion }} + kind: {{ $to.selector.kind }} + {{ if $to.selector.matchLabels }} + matchLabels: + {{ range $key, $value := $to.selector.matchLabels }} + {{ $key }}: {{ $value }} + {{ end }} + {{ end }} #end if to.matchLabels + + {{ if $to.selector.matchExpressions }} + matchExpressions: + {{ range $expr := $to.selector.matchExpressions }} + - key: {{ $expr.key }} + operator: {{ $expr.operator }} + values: + {{ range $exprValue := $expr.values }} + - {{ $exprValue }} + {{ end }} + {{ end }} #end matchExpressions range + {{ end }} # end if matchExpressions + {{ end }} #end if $to.selector + {{ end }} #end "range $to" + {{ end }} #end "if .to" + + from: + {{ range $from := .from }} + {{ if $from.ref }} + - ref: + apiVersion: {{ $from.ref.apiVersion }} + kind: {{ $from.ref.kind }} + name: {{ $from.ref.name }} + namespace: {{ $from.ref.namespace }} + {{ end }} + {{ if $from.sub }} + - sub: {{ $from.sub }} + {{ end }} + {{ end }} + + {{ if .filters }} + filters: +{{ .filters }} + {{ end }} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4e3f79705b..6312c623e6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1302,6 +1302,7 @@ knative.dev/eventing/test/lib/recordevents/sender knative.dev/eventing/test/lib/resources knative.dev/eventing/test/lib/sender knative.dev/eventing/test/rekt/features +knative.dev/eventing/test/rekt/features/authz knative.dev/eventing/test/rekt/features/broker knative.dev/eventing/test/rekt/features/channel knative.dev/eventing/test/rekt/features/featureflags @@ -1317,6 +1318,7 @@ knative.dev/eventing/test/rekt/resources/channel knative.dev/eventing/test/rekt/resources/channel_impl knative.dev/eventing/test/rekt/resources/containersource knative.dev/eventing/test/rekt/resources/delivery +knative.dev/eventing/test/rekt/resources/eventpolicy knative.dev/eventing/test/rekt/resources/eventtype knative.dev/eventing/test/rekt/resources/pingsource knative.dev/eventing/test/rekt/resources/subscription