diff --git a/.github/.dependabot.yaml b/.github/.dependabot.yaml new file mode 100644 index 00000000000..453228e1c2e --- /dev/null +++ b/.github/.dependabot.yaml @@ -0,0 +1,8 @@ +# Set update schedule for GitHub Actions +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/workflows/kind-e2e.yaml b/.github/workflows/kind-e2e.yaml index ceca537041b..d57058bbbd4 100644 --- a/.github/workflows/kind-e2e.yaml +++ b/.github/workflows/kind-e2e.yaml @@ -59,16 +59,14 @@ jobs: steps: - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.21.x + uses: knative/actions/setup-go@main # Install the latest release of ko - name: Install ko uses: ko-build/setup-ko@v0.6 - name: Check out code onto GOPATH - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install KinD run: | @@ -112,7 +110,7 @@ jobs: - name: Wait for things to be up run: | set -e - source ./vendor/knative.dev/hack/infra-library.sh + source "$(go run knative.dev/hack/cmd/script infra-library.sh)" wait_until_pods_running ${SYSTEM_NAMESPACE} - name: Run e2e Tests diff --git a/.github/workflows/knative-downstream.yaml b/.github/workflows/knative-downstream.yaml index 7895b357037..de9897c5f17 100644 --- a/.github/workflows/knative-downstream.yaml +++ b/.github/workflows/knative-downstream.yaml @@ -44,9 +44,7 @@ jobs: GOPATH: ${{ github.workspace }} steps: - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.21.x + uses: knative/actions/setup-go@main - name: Install Dependencies run: | go install github.com/google/go-licenses@latest diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 79fd5588fed..5b01605a5da 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -34,6 +34,7 @@ import ( "knative.dev/eventing/pkg/apis/sinks" "knative.dev/eventing/pkg/auth" "knative.dev/eventing/pkg/eventingtls" + "knative.dev/eventing/pkg/reconciler/eventpolicy" "knative.dev/eventing/pkg/reconciler/jobsink" "knative.dev/eventing/pkg/reconciler/apiserversource" @@ -93,6 +94,7 @@ func main() { // Eventing eventtype.NewController, + eventpolicy.NewController, // Flows parallel.NewController, diff --git a/go.mod b/go.mod index d2a8bb38e68..e2b1b79f9d5 100644 --- a/go.mod +++ b/go.mod @@ -45,12 +45,12 @@ require ( k8s.io/utils v0.0.0-20240102154912-e7106e64919e knative.dev/hack v0.0.0-20240607132042-09143140a254 knative.dev/hack/schema v0.0.0-20240607132042-09143140a254 - knative.dev/pkg v0.0.0-20240621201938-fc0720b7a660 + knative.dev/pkg v0.0.0-20240626134149-3f6a546ac3a4 knative.dev/reconciler-test v0.0.0-20240618170853-5bf0b86114f8 sigs.k8s.io/yaml v1.4.0 ) -replace knative.dev/hack => github.com/cardil/knative-hack v0.0.0-20240627155243-4b5dad135546 +replace knative.dev/hack => github.com/cardil/knative-hack v0.0.0-20240702091202-6535957027e0 require ( contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect @@ -123,8 +123,3 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) - -replace ( - github.com/dgrijalva/jwt-go => github.com/form3tech-oss/jwt-go v0.0.0-20210511163231-5b2d2b5f6c34 - github.com/miekg/dns v1.0.14 => github.com/miekg/dns v1.1.25 -) diff --git a/go.sum b/go.sum index a0b8a52e1c9..06efb6abc2f 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/cardil/knative-hack v0.0.0-20240627155243-4b5dad135546 h1:sbjR3wfWYfIEfJx1Th2rxdBDhUyzlHRDjFCkpnSP86M= -github.com/cardil/knative-hack v0.0.0-20240627155243-4b5dad135546/go.mod h1:yk2OjGDsbEnQjfxdm0/HJKS2WqTLEFg/N6nUs6Rqx3Q= +github.com/cardil/knative-hack v0.0.0-20240702091202-6535957027e0 h1:V9QHY1AQIUQnKPobPrVO7OZ49s69ocP40VpkHx57MCc= +github.com/cardil/knative-hack v0.0.0-20240702091202-6535957027e0/go.mod h1:yk2OjGDsbEnQjfxdm0/HJKS2WqTLEFg/N6nUs6Rqx3Q= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -842,8 +842,8 @@ k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCf k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/hack/schema v0.0.0-20240607132042-09143140a254 h1:b9hFHGtxx0Kpm4EEjSD72lL0jms91To3OEVBTbqfOYI= knative.dev/hack/schema v0.0.0-20240607132042-09143140a254/go.mod h1:3pWwBLnTZSM9psSgCAvhKOHIPTzqfEMlWRpDu6IYhK0= -knative.dev/pkg v0.0.0-20240621201938-fc0720b7a660 h1:SYPUGNPA/egS+u6oa6q1kN3Ec+z+we/hPrWie7qhLUg= -knative.dev/pkg v0.0.0-20240621201938-fc0720b7a660/go.mod h1:Wikg4u73T6vk9TctrxZt60VXzqmGEQIx0iKfk1+9o4c= +knative.dev/pkg v0.0.0-20240626134149-3f6a546ac3a4 h1:slPKf3UKdBFZlz+hFy+KXzTgY9yOePLzRuEhKzgc5a4= +knative.dev/pkg v0.0.0-20240626134149-3f6a546ac3a4/go.mod h1:Wikg4u73T6vk9TctrxZt60VXzqmGEQIx0iKfk1+9o4c= knative.dev/reconciler-test v0.0.0-20240618170853-5bf0b86114f8 h1:A+rsitEiTX3GudM51g7zUMza+Ripj+boncmlJ2jZp50= knative.dev/reconciler-test v0.0.0-20240618170853-5bf0b86114f8/go.mod h1:2uUx3U6kdIzgJgMGgrGmdDdcFrFiex/DjuI2gM7Tte8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle.go b/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle.go index 30c8575eac9..a74431f0d78 100644 --- a/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle.go +++ b/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Knative Authors +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. @@ -20,10 +20,12 @@ import ( "knative.dev/pkg/apis" ) -var eventPolicyCondSet = apis.NewLivingConditionSet() +var eventPolicyCondSet = apis.NewLivingConditionSet(EventPolicyConditionAuthenticationEnabled, EventPolicyConditionSubjectsResolved) const ( - EventPolicyConditionReady = apis.ConditionReady + EventPolicyConditionReady = apis.ConditionReady + EventPolicyConditionAuthenticationEnabled apis.ConditionType = "AuthenticationEnabled" + EventPolicyConditionSubjectsResolved apis.ConditionType = "SubjectsResolved" ) // GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface. @@ -32,21 +34,41 @@ func (*EventPolicy) GetConditionSet() apis.ConditionSet { } // GetCondition returns the condition currently associated with the given type, or nil. -func (et *EventPolicyStatus) GetCondition(t apis.ConditionType) *apis.Condition { - return eventPolicyCondSet.Manage(et).GetCondition(t) +func (ep *EventPolicyStatus) GetCondition(t apis.ConditionType) *apis.Condition { + return eventPolicyCondSet.Manage(ep).GetCondition(t) } // IsReady returns true if the resource is ready overall. -func (et *EventPolicyStatus) IsReady() bool { - return et.GetTopLevelCondition().IsTrue() +func (ep *EventPolicyStatus) IsReady() bool { + return ep.GetTopLevelCondition().IsTrue() } // GetTopLevelCondition returns the top level Condition. -func (et *EventPolicyStatus) GetTopLevelCondition() *apis.Condition { - return eventPolicyCondSet.Manage(et).GetTopLevelCondition() +func (ep *EventPolicyStatus) GetTopLevelCondition() *apis.Condition { + return eventPolicyCondSet.Manage(ep).GetTopLevelCondition() } // InitializeConditions sets relevant unset conditions to Unknown state. -func (et *EventPolicyStatus) InitializeConditions() { - eventPolicyCondSet.Manage(et).InitializeConditions() +func (ep *EventPolicyStatus) InitializeConditions() { + eventPolicyCondSet.Manage(ep).InitializeConditions() +} + +// MarkOIDCAuthenticationEnabled sets EventPolicyConditionAuthenticationEnabled condition to true. +func (ep *EventPolicyStatus) MarkOIDCAuthenticationEnabled() { + eventPolicyCondSet.Manage(ep).MarkTrue(EventPolicyConditionAuthenticationEnabled) +} + +// MarkOIDCAuthenticationDisabled sets EventPolicyConditionAuthenticationEnabled condition to false. +func (ep *EventPolicyStatus) MarkOIDCAuthenticationDisabled(reason, messageFormat string, messageA ...interface{}) { + eventPolicyCondSet.Manage(ep).MarkFalse(EventPolicyConditionAuthenticationEnabled, reason, messageFormat, messageA...) +} + +// MarkSubjectsResolved sets EventPolicyConditionSubjectsResolved condition to true. +func (ep *EventPolicyStatus) MarkSubjectsResolvedSucceeded() { + eventPolicyCondSet.Manage(ep).MarkTrue(EventPolicyConditionSubjectsResolved) +} + +// MarkSubjectsNotResolved sets EventPolicyConditionSubjectsResolved condition to false. +func (ep *EventPolicyStatus) MarkSubjectsResolvedFailed(reason, messageFormat string, messageA ...interface{}) { + eventPolicyCondSet.Manage(ep).MarkFalse(EventPolicyConditionSubjectsResolved, reason, messageFormat, messageA...) } diff --git a/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle_test.go b/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle_test.go index 1f18f054a1f..620c2b595df 100644 --- a/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle_test.go +++ b/pkg/apis/eventing/v1alpha1/eventpolicy_lifecycle_test.go @@ -49,12 +49,12 @@ func TestEventPolicyGetConditionSet(t *testing.T) { func TestEventPolicyGetCondition(t *testing.T) { tests := []struct { name string - ets *EventPolicyStatus + eps *EventPolicyStatus condQuery apis.ConditionType want *apis.Condition }{{ name: "single condition", - ets: &EventPolicyStatus{ + eps: &EventPolicyStatus{ Status: duckv1.Status{ Conditions: []apis.Condition{ eventPolicyConditionReady, @@ -67,7 +67,7 @@ func TestEventPolicyGetCondition(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := test.ets.GetCondition(test.condQuery) + got := test.eps.GetCondition(test.condQuery) if diff := cmp.Diff(test.want, got); diff != "" { t.Error("unexpected condition (-want, +got) =", diff) } @@ -78,18 +78,27 @@ func TestEventPolicyGetCondition(t *testing.T) { func TestEventPolicyInitializeConditions(t *testing.T) { tests := []struct { name string - ets *EventPolicyStatus + eps *EventPolicyStatus want *EventPolicyStatus }{ { name: "empty", - ets: &EventPolicyStatus{}, + eps: &EventPolicyStatus{}, want: &EventPolicyStatus{ Status: duckv1.Status{ - Conditions: []apis.Condition{{ - Type: EventPolicyConditionReady, - Status: corev1.ConditionUnknown, - }, + Conditions: []apis.Condition{ + { + Type: EventPolicyConditionAuthenticationEnabled, + Status: corev1.ConditionUnknown, + }, + { + Type: EventPolicyConditionReady, + Status: corev1.ConditionUnknown, + }, + { + Type: EventPolicyConditionSubjectsResolved, + Status: corev1.ConditionUnknown, + }, }, }, }, @@ -98,10 +107,100 @@ func TestEventPolicyInitializeConditions(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - test.ets.InitializeConditions() - if diff := cmp.Diff(test.want, test.ets, ignoreAllButTypeAndStatus); diff != "" { + test.eps.InitializeConditions() + if diff := cmp.Diff(test.want, test.eps, ignoreAllButTypeAndStatus); diff != "" { t.Error("unexpected conditions (-want, +got) =", diff) } }) } } + +func TestEventPolicyReadyCondition(t *testing.T) { + tests := []struct { + name string + eps *EventPolicyStatus + markOIDCAuthenticationEnabled bool + markSubjectsResolvedSucceeded bool + wantReady bool + }{ + { + name: "Initially everything is Unknown, Auth&SubjectsResolved marked as true, EP should become Ready", + eps: &EventPolicyStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + {Type: EventPolicyConditionReady, Status: corev1.ConditionUnknown}, + {Type: EventPolicyConditionAuthenticationEnabled, Status: corev1.ConditionUnknown}, + {Type: EventPolicyConditionSubjectsResolved, Status: corev1.ConditionUnknown}, + }, + }, + }, + markOIDCAuthenticationEnabled: true, + markSubjectsResolvedSucceeded: true, + wantReady: true, + }, + { + name: "Initially everything is True, Auth&SubjectsResolved stay true, EP should stay Ready", + eps: &EventPolicyStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + {Type: EventPolicyConditionReady, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionAuthenticationEnabled, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionSubjectsResolved, Status: corev1.ConditionTrue}, + }, + }, + }, + markOIDCAuthenticationEnabled: true, + markSubjectsResolvedSucceeded: true, + wantReady: true, + }, + { + name: "Initially everything is True, then AuthenticationEnabled marked as False, EP should become NotReady", + eps: &EventPolicyStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + {Type: EventPolicyConditionReady, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionAuthenticationEnabled, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionSubjectsResolved, Status: corev1.ConditionTrue}, + }, + }, + }, + markOIDCAuthenticationEnabled: false, + markSubjectsResolvedSucceeded: true, + wantReady: false, + }, + { + name: "Initially everything is True, then SubjectsResolved marked as False, EP should become NotReady", + eps: &EventPolicyStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{ + {Type: EventPolicyConditionReady, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionAuthenticationEnabled, Status: corev1.ConditionTrue}, + {Type: EventPolicyConditionSubjectsResolved, Status: corev1.ConditionTrue}, + }, + }, + }, + markOIDCAuthenticationEnabled: true, + markSubjectsResolvedSucceeded: false, + wantReady: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.markOIDCAuthenticationEnabled { + test.eps.MarkOIDCAuthenticationEnabled() + } else { + test.eps.MarkOIDCAuthenticationDisabled("OIDCAuthenticationDisabled", "") + } + if test.markSubjectsResolvedSucceeded { + test.eps.MarkSubjectsResolvedSucceeded() + } else { + test.eps.MarkSubjectsResolvedFailed("SubjectsNotResolved", "") + } + ep := EventPolicy{Status: *test.eps} + got := ep.GetConditionSet().Manage(test.eps).IsHappy() + if test.wantReady != got { + t.Errorf("unexpected readiness: want %v, got %v", test.wantReady, got) + } + }) + } +} diff --git a/pkg/apis/eventing/v1beta2/eventtype_defaults_test.go b/pkg/apis/eventing/v1beta2/eventtype_defaults_test.go index 5909350f688..1337884b095 100644 --- a/pkg/apis/eventing/v1beta2/eventtype_defaults_test.go +++ b/pkg/apis/eventing/v1beta2/eventtype_defaults_test.go @@ -40,7 +40,7 @@ func TestEventTypeDefaults(t *testing.T) { Spec: EventTypeSpec{}, }, }, - "broker empty": { + "default broker reference": { initial: EventType{ Spec: EventTypeSpec{ Type: "test-type", @@ -76,9 +76,10 @@ func TestEventTypeDefaults(t *testing.T) { }, expected: EventType{ Spec: EventTypeSpec{ - Type: "test-type", - Source: testSource, - Schema: testSchema, + Reference: nil, + Type: "test-type", + Source: testSource, + Schema: testSchema, }, }, }, diff --git a/pkg/apis/eventing/v1beta3/eventtype_defaults_test.go b/pkg/apis/eventing/v1beta3/eventtype_defaults_test.go index 29f6c8adb34..4f4d41aaa79 100644 --- a/pkg/apis/eventing/v1beta3/eventtype_defaults_test.go +++ b/pkg/apis/eventing/v1beta3/eventtype_defaults_test.go @@ -40,7 +40,7 @@ func TestEventTypeDefaults(t *testing.T) { Spec: EventTypeSpec{}, }, }, - "broker empty": { + "default broker reference": { initial: EventType{ Spec: EventTypeSpec{ Reference: &duckv1.KReference{ @@ -115,6 +115,7 @@ func TestEventTypeDefaults(t *testing.T) { }, expected: EventType{ Spec: EventTypeSpec{ + Reference: nil, Attributes: []EventAttributeDefinition{ { Value: "test-type", diff --git a/pkg/apis/feature/features.go b/pkg/apis/feature/features.go index 982ca8c67e0..4d6c235df15 100644 --- a/pkg/apis/feature/features.go +++ b/pkg/apis/feature/features.go @@ -18,6 +18,7 @@ package feature import ( "fmt" + "log" "strings" corev1 "k8s.io/api/core/v1" @@ -186,7 +187,8 @@ func NewFlagsConfigFromMap(data map[string]string) (Flags, error) { } else if strings.Contains(k, NodeSelectorLabel) { flags[sanitizedKey] = Flag(v) } else { - return flags, fmt.Errorf("cannot parse the feature flag '%s' = '%s'", k, v) + flags[k] = Flag(v) + log.Printf("Warning: unknown feature flag value %q=%q\n", k, v) } } diff --git a/pkg/reconciler/channel/channel_test.go b/pkg/reconciler/channel/channel_test.go index 783abe359a2..c1796379bc1 100644 --- a/pkg/reconciler/channel/channel_test.go +++ b/pkg/reconciler/channel/channel_test.go @@ -400,7 +400,7 @@ func TestReconcile(t *testing.T) { WithInMemoryChannelDLSUnknown(), WithInMemoryChannelEventPoliciesReady()), NewEventPolicy(unreadyEventPolicyName, testNS, - WithUnreadyEventPolicyCondition, + WithUnreadyEventPolicyCondition("", ""), WithEventPolicyToRef(channelV1GVK, channelName), ), NewEventPolicy(fmt.Sprintf("%s-%s", unreadyEventPolicyName, channelName), testNS, @@ -453,7 +453,7 @@ func TestReconcile(t *testing.T) { WithEventPolicyToRef(channelV1GVK, channelName), ), NewEventPolicy(unreadyEventPolicyName, testNS, - WithUnreadyEventPolicyCondition, + WithUnreadyEventPolicyCondition("", ""), WithEventPolicyToRef(channelV1GVK, channelName), ), NewEventPolicy(fmt.Sprintf("%s-%s", readyEventPolicyName, channelName), testNS, diff --git a/pkg/reconciler/eventpolicy/controller.go b/pkg/reconciler/eventpolicy/controller.go new file mode 100644 index 00000000000..58b66b06022 --- /dev/null +++ b/pkg/reconciler/eventpolicy/controller.go @@ -0,0 +1,47 @@ +/* +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" + + eventpolicyinformer "knative.dev/eventing/pkg/client/injection/informers/eventing/v1alpha1/eventpolicy" + eventpolicyreconciler "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1alpha1/eventpolicy" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/resolver" +) + +// NewController initializes the controller and is called by the generated code +// Registers event handlers to enqueue events +func NewController( + ctx context.Context, + cmw configmap.Watcher, +) *controller.Impl { + // Access informers + eventPolicyInformer := eventpolicyinformer.Get(ctx) + + r := &Reconciler{} + impl := eventpolicyreconciler.NewImpl(ctx, r) + + r.authResolver = resolver.NewAuthenticatableResolverFromTracker(ctx, impl.Tracker) + + // Set up event handlers + eventPolicyInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + + return impl +} diff --git a/pkg/reconciler/eventpolicy/controller_test.go b/pkg/reconciler/eventpolicy/controller_test.go new file mode 100644 index 00000000000..2ddea353dc3 --- /dev/null +++ b/pkg/reconciler/eventpolicy/controller_test.go @@ -0,0 +1,39 @@ +/* +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 ( + "testing" + + "knative.dev/pkg/configmap" + + . "knative.dev/pkg/reconciler/testing" + + // Fake injection informers + _ "knative.dev/eventing/pkg/client/injection/informers/eventing/v1alpha1/eventpolicy/fake" + _ "knative.dev/pkg/client/injection/ducks/duck/v1/authstatus/fake" +) + +func TestNew(t *testing.T) { + ctx, _ := SetupFakeContext(t) + + c := NewController(ctx, configmap.NewStaticWatcher()) + + if c == nil { + t.Fatal("Expected NewController to return a non-nil value") + } +} diff --git a/pkg/reconciler/eventpolicy/eventpolicy.go b/pkg/reconciler/eventpolicy/eventpolicy.go new file mode 100644 index 00000000000..959181e1943 --- /dev/null +++ b/pkg/reconciler/eventpolicy/eventpolicy.go @@ -0,0 +1,55 @@ +/* +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" + "fmt" + + "knative.dev/eventing/pkg/apis/eventing/v1alpha1" + "knative.dev/eventing/pkg/apis/feature" + "knative.dev/eventing/pkg/auth" + pkgreconciler "knative.dev/pkg/reconciler" + "knative.dev/pkg/resolver" +) + +type Reconciler struct { + authResolver *resolver.AuthenticatableResolver +} + +// ReconcileKind implements Interface.ReconcileKind. +// 1. Verify the Reference exists. +func (r *Reconciler) ReconcileKind(ctx context.Context, ep *v1alpha1.EventPolicy) pkgreconciler.Event { + featureFlags := feature.FromContext(ctx) + if featureFlags.IsOIDCAuthentication() { + ep.Status.MarkOIDCAuthenticationEnabled() + } else { + ep.Status.MarkOIDCAuthenticationDisabled("OIDCAuthenticationDisabled", "") + return nil + } + // We reconcile the status of the EventPolicy + // by looking at all .spec.from[].refs have subjects + // and accordingly set the eventpolicy status + subjects, err := auth.ResolveSubjects(r.authResolver, ep) + if err != nil { + ep.Status.MarkSubjectsResolvedFailed("SubjectsNotResolved", err.Error()) + return fmt.Errorf("failed to resolve .spec.from[].ref: %w", err) + } + ep.Status.MarkSubjectsResolvedSucceeded() + ep.Status.From = subjects + return nil +} diff --git a/pkg/reconciler/eventpolicy/eventpolicy_test.go b/pkg/reconciler/eventpolicy/eventpolicy_test.go new file mode 100644 index 00000000000..dcc7ab0ec44 --- /dev/null +++ b/pkg/reconciler/eventpolicy/eventpolicy_test.go @@ -0,0 +1,239 @@ +/* +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" + "fmt" + "testing" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgotesting "k8s.io/client-go/testing" + "knative.dev/eventing/pkg/apis/feature" + sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1" + fakeeventingclient "knative.dev/eventing/pkg/client/injection/client/fake" + "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1alpha1/eventpolicy" + . "knative.dev/eventing/pkg/reconciler/testing/v1" + duckv1authstatus "knative.dev/pkg/client/injection/ducks/duck/v1/authstatus" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + logtesting "knative.dev/pkg/logging/testing" + . "knative.dev/pkg/reconciler/testing" + "knative.dev/pkg/resolver" + "knative.dev/pkg/tracker" +) + +const ( + testNS = "test-namespace" + eventPolicyName = "test-eventpolicy" + pingSourceName = "test-pingsource" + apiServerSourceName = "test-apiserversource" + serviceAccountname = "test-sa" +) + +var ( + pingSourceWithServiceAccount = NewPingSource(pingSourceName, testNS, WithPingSourceOIDCServiceAccountName(serviceAccountname)) + apiServerSourceWithServiceAccount = NewApiServerSource(apiServerSourceName, testNS, WithApiServerSourceOIDCServiceAccountName((serviceAccountname))) + pingSourceGVK = v1.GroupVersionKind(sourcesv1.SchemeGroupVersion.WithKind("PingSource")) + apiServerSourceGVK = v1.GroupVersionKind(sourcesv1.SchemeGroupVersion.WithKind("APIServerSource")) + SubjectsNotResolvedErrorMessage = fmt.Sprintf("could not resolve subjects from reference: could not resolve auth status: failed to get authenticatable %s/%s: failed to get object %s/%s: pingsources.sources.knative.dev \"%s\" not found", testNS, pingSourceName, testNS, pingSourceName, pingSourceName) + SubjectsNotResolvedEventMessage = fmt.Sprintf("Warning InternalError failed to resolve .spec.from[].ref: could not resolve subjects from reference: could not resolve auth status: failed to get authenticatable %s/%s: failed to get object %s/%s: pingsources.sources.knative.dev \"%s\" not found", testNS, pingSourceName, testNS, pingSourceName, pingSourceName) +) + +func TestReconcile(t *testing.T) { + table := TableTest{ + { + Name: "bad workqueue key", + // Make sure Reconcile handles bad keys. + Key: "too/many/parts", + }, + // test cases for authentication-oidc feature disabled + { + Name: "with oidc disabled, status set to NotReady", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Disabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + NewEventPolicy(eventPolicyName, testNS, + WithInitEventPolicyConditions, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + ), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyAuthenticationDisabledCondition, + WithUnreadyEventPolicyCondition("OIDCAuthenticationDisabled", ""), + WithEventPolicySubjectsResolvedUnknown, + ), + }, + }, + WantErr: false, + }, + + // test cases for authentication-oidc feature enabled + { + Name: "subject not found, status set to NotReady", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Enabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + NewEventPolicy(eventPolicyName, testNS, + WithInitEventPolicyConditions, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + ), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyAuthenticationEnabledCondition, + WithUnreadyEventPolicyCondition("SubjectsNotResolved", SubjectsNotResolvedErrorMessage), + WithEventPolicySubjectsResolvedFailed("SubjectsNotResolved", SubjectsNotResolvedErrorMessage), + ), + }, + }, + WantEvents: []string{SubjectsNotResolvedEventMessage}, + WantErr: true, + }, + { + Name: "subject found for pingsource, status set to Ready", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Enabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + pingSourceWithServiceAccount, + NewEventPolicy(eventPolicyName, testNS, + WithInitEventPolicyConditions, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS)), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyStatusFromSub([]string{fmt.Sprintf("system:serviceaccount:%s:%s", testNS, serviceAccountname)}), + WithEventPolicyAuthenticationEnabledCondition, + WithReadyEventPolicyCondition, + WithEventPolicySubjectsResolvedSucceeded, + ), + }, + }, + WantErr: false, + }, + { + Name: "subject found for apiserversource, status set to Ready", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Enabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + apiServerSourceWithServiceAccount, + NewEventPolicy(eventPolicyName, testNS, + WithInitEventPolicyConditions, WithEventPolicyFrom(apiServerSourceGVK, apiServerSourceName, testNS)), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(apiServerSourceGVK, apiServerSourceName, testNS), + WithEventPolicyStatusFromSub([]string{fmt.Sprintf("system:serviceaccount:%s:%s", testNS, serviceAccountname)}), + WithEventPolicyAuthenticationEnabledCondition, + WithReadyEventPolicyCondition, + WithEventPolicySubjectsResolvedSucceeded, + ), + }, + }, + WantErr: false, + }, + { + Name: "Multiple subjects found, status set to Ready", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Enabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + apiServerSourceWithServiceAccount, + pingSourceWithServiceAccount, + NewEventPolicy(eventPolicyName, testNS, + WithInitEventPolicyConditions, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyFrom(apiServerSourceGVK, apiServerSourceName, testNS)), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyFrom(apiServerSourceGVK, apiServerSourceName, testNS), + WithEventPolicyStatusFromSub([]string{ + fmt.Sprintf("system:serviceaccount:%s:%s", testNS, serviceAccountname), + fmt.Sprintf("system:serviceaccount:%s:%s", testNS, serviceAccountname), + }), + WithEventPolicyAuthenticationEnabledCondition, + WithReadyEventPolicyCondition, + WithEventPolicySubjectsResolvedSucceeded, + ), + }, + }, + WantErr: false, + }, + + // test cases for authentication-oidc feature disabled afterwards + { + Name: "Ready status EventPolicy updated to NotReady", + Ctx: feature.ToContext(context.TODO(), feature.Flags{ + feature.OIDCAuthentication: feature.Disabled, + }), + Key: testNS + "/" + eventPolicyName, + Objects: []runtime.Object{ + pingSourceWithServiceAccount, + NewEventPolicy(eventPolicyName, testNS, + WithReadyEventPolicyCondition, + WithEventPolicyAuthenticationEnabledCondition, + WithEventPolicySubjectsResolvedSucceeded, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS)), + }, + WantStatusUpdates: []clientgotesting.UpdateActionImpl{ + { + Object: NewEventPolicy(eventPolicyName, testNS, + WithEventPolicyFrom(pingSourceGVK, pingSourceName, testNS), + WithEventPolicyAuthenticationDisabledCondition, + WithUnreadyEventPolicyCondition("OIDCAuthenticationDisabled", ""), + WithEventPolicySubjectsResolvedSucceeded, + ), + }, + }, + WantErr: false, + }, + } + logger := logtesting.TestLogger(t) + table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { + ctx = duckv1authstatus.WithDuck(ctx) + r := &Reconciler{ + authResolver: resolver.NewAuthenticatableResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0))} + return eventpolicy.NewReconciler(ctx, logger, + fakeeventingclient.Get(ctx), listers.GetEventPolicyLister(), + controller.GetEventRecorder(ctx), r) + }, + false, + logger, + )) +} diff --git a/pkg/reconciler/inmemorychannel/controller/inmemorychannel_test.go b/pkg/reconciler/inmemorychannel/controller/inmemorychannel_test.go index f9f859444ba..79e5fa0e012 100644 --- a/pkg/reconciler/inmemorychannel/controller/inmemorychannel_test.go +++ b/pkg/reconciler/inmemorychannel/controller/inmemorychannel_test.go @@ -697,7 +697,7 @@ func TestAllCases(t *testing.T) { ), makeChannelService(NewInMemoryChannel(imcName, testNS)), NewEventPolicy(unreadyEventPolicyName, testNS, - WithUnreadyEventPolicyCondition, + WithUnreadyEventPolicyCondition("", ""), WithEventPolicyToRef(imcV1GVK, imcName), ), }, @@ -735,7 +735,7 @@ func TestAllCases(t *testing.T) { WithEventPolicyToRef(imcV1GVK, imcName), ), NewEventPolicy(unreadyEventPolicyName, testNS, - WithUnreadyEventPolicyCondition, + WithUnreadyEventPolicyCondition("", ""), WithEventPolicyToRef(imcV1GVK, imcName), ), }, diff --git a/pkg/reconciler/testing/v1/eventpolicy.go b/pkg/reconciler/testing/v1/eventpolicy.go index 0d2934ac6ce..442fd397483 100644 --- a/pkg/reconciler/testing/v1/eventpolicy.go +++ b/pkg/reconciler/testing/v1/eventpolicy.go @@ -44,25 +44,72 @@ func NewEventPolicy(name, namespace string, o ...EventPolicyOption) *v1alpha1.Ev return ep } -func WithInitEventPolicyConditions(et *v1alpha1.EventPolicy) { - et.Status.InitializeConditions() +func WithInitEventPolicyConditions(ep *v1alpha1.EventPolicy) { + ep.Status.InitializeConditions() } -func WithReadyEventPolicyCondition(ep *v1alpha1.EventPolicy) { - ep.Status.Conditions = []apis.Condition{ - { - Type: v1alpha1.EventPolicyConditionReady, +func WithEventPolicyAuthenticationEnabledCondition(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionAuthenticationEnabled, Status: corev1.ConditionTrue, - }, + }) +} + +func WithEventPolicyAuthenticationDisabledCondition(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionAuthenticationEnabled, + Status: corev1.ConditionFalse, + Reason: "OIDCAuthenticationDisabled", + }) +} + +func WithEventPolicySubjectsResolvedSucceeded(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionSubjectsResolved, + Status: corev1.ConditionTrue, + }) +} + +func WithEventPolicySubjectsResolvedFailed(reason, message string) EventPolicyOption { + return func(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionSubjectsResolved, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) } } -func WithUnreadyEventPolicyCondition(ep *v1alpha1.EventPolicy) { - ep.Status.Conditions = []apis.Condition{ - { +func WithEventPolicySubjectsResolvedUnknown(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionSubjectsResolved, + Status: corev1.ConditionUnknown, + }) +} + +func WithReadyEventPolicyCondition(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ Type: v1alpha1.EventPolicyConditionReady, - Status: corev1.ConditionFalse, - }, + Status: corev1.ConditionTrue, + }) +} + +func WithUnreadyEventPolicyCondition(reason, message string) EventPolicyOption { + return func(ep *v1alpha1.EventPolicy) { + ep.Status.Conditions = append(ep.Status.Conditions, + apis.Condition{ + Type: v1alpha1.EventPolicyConditionReady, + Status: corev1.ConditionFalse, + Reason: reason, + Message: message, + }) } } @@ -102,3 +149,9 @@ func WithEventPolicyOwnerReferences(ownerRefs ...metav1.OwnerReference) EventPol ep.ObjectMeta.OwnerReferences = append(ep.ObjectMeta.OwnerReferences, ownerRefs...) } } + +func WithEventPolicyStatusFromSub(subs []string) EventPolicyOption { + return func(ep *v1alpha1.EventPolicy) { + ep.Status.From = append(ep.Status.From, subs...) + } +}