Skip to content

Commit ff8caa6

Browse files
committed
Make auth-proxy query EventPolicies dynamically
IntegrationSink was baking EventPolicies into the AUTH_POLICIES env var, requiring deployment rollouts whenever policies changed. This caused test failures because old pods with stale policies continued serving traffic during RollingUpdate. Change auth-proxy to query EventPolicies dynamically using a namespace-scoped informer, similar to how Broker and Channel work. This eliminates deployment rollouts when EventPolicies change. - Add knative-eventing-eventpolicy-reader ClusterRole - Create namespace-scoped EventPolicy informer in auth-proxy - Add parent resource env vars to identify which resource to query policies for - Create RoleBinding in sink's namespace for EventPolicy access - Remove AUTH_POLICIES env var from deployment spec - Add test coverage for OIDC-enabled deployments with RoleBindings
1 parent 0ef7bc1 commit ff8caa6

7 files changed

Lines changed: 423 additions & 83 deletions

File tree

cmd/auth_proxy/main.go

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package main
2222

2323
import (
2424
"context"
25-
"encoding/json"
2625
"net"
2726
"os"
2827

@@ -36,11 +35,16 @@ import (
3635

3736
"github.com/kelseyhightower/envconfig"
3837
"go.uber.org/zap"
38+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39+
"k8s.io/apimachinery/pkg/runtime/schema"
3940
"k8s.io/client-go/kubernetes"
4041
"k8s.io/utils/ptr"
4142
cmdbroker "knative.dev/eventing/cmd/broker"
4243
"knative.dev/eventing/pkg/apis/feature"
4344
"knative.dev/eventing/pkg/auth"
45+
eventingclient "knative.dev/eventing/pkg/client/clientset/versioned"
46+
eventinginformers "knative.dev/eventing/pkg/client/informers/externalversions"
47+
eventingv1alpha1listers "knative.dev/eventing/pkg/client/listers/eventing/v1alpha1"
4448
"knative.dev/eventing/pkg/eventingtls"
4549
"knative.dev/eventing/pkg/kncloudevents"
4650
"knative.dev/pkg/apis"
@@ -69,7 +73,11 @@ type envConfig struct {
6973
SinkNamespace string `envconfig:"SINK_NAMESPACE" required:"true"`
7074
SinkAudience *string `envconfig:"SINK_AUDIENCE"`
7175

72-
AuthPolicies string `envconfig:"AUTH_POLICIES"`
76+
// Parent resource information for dynamic EventPolicy lookup
77+
ParentAPIVersion string `envconfig:"PARENT_API_VERSION" required:"true"`
78+
ParentKind string `envconfig:"PARENT_KIND" required:"true"`
79+
ParentName string `envconfig:"PARENT_NAME" required:"true"`
80+
ParentNamespace string `envconfig:"PARENT_NAMESPACE" required:"true"`
7381

7482
SinkTLSCertPath *string `envconfig:"SINK_TLS_CERT_FILE"`
7583
SinkTLSKeyPath *string `envconfig:"SINK_TLS_KEY_FILE"`
@@ -79,13 +87,14 @@ type envConfig struct {
7987
// ProxyHandler handles HTTP requests and performs authentication/authorization
8088
// before forwarding to the target service
8189
type ProxyHandler struct {
82-
kubeClient kubernetes.Interface
83-
withContext func(ctx context.Context) context.Context
84-
authVerifier *auth.Verifier
85-
httpProxy *httputil.ReverseProxy
86-
httpsProxy *httputil.ReverseProxy
87-
config envConfig
88-
authSubjects []auth.SubjectsWithFilters
90+
kubeClient kubernetes.Interface
91+
withContext func(ctx context.Context) context.Context
92+
authVerifier *auth.Verifier
93+
httpProxy *httputil.ReverseProxy
94+
httpsProxy *httputil.ReverseProxy
95+
config envConfig
96+
eventPolicyLister eventingv1alpha1listers.EventPolicyLister
97+
parentResourceGVK schema.GroupVersionKind
8998
}
9099

91100
func main() {
@@ -104,7 +113,14 @@ func main() {
104113

105114
featureStore := setupFeatureStore(ctx, logger, configMapWatcher)
106115

107-
handler, err := createProxyHandler(ctx, config, logger, featureStore, configMapWatcher)
116+
// Create namespace-scoped EventPolicy informer
117+
eventPolicyLister, eventPolicyInformer, err := setupEventPolicyInformer(ctx, config, logger)
118+
if err != nil {
119+
logger.Fatalw("Failed to setup EventPolicy informer", zap.Error(err))
120+
}
121+
informers = append(informers, eventPolicyInformer)
122+
123+
handler, err := createProxyHandler(ctx, config, logger, featureStore, configMapWatcher, eventPolicyLister)
108124
if err != nil {
109125
logger.Fatalw("Failed to create proxy handler", zap.Error(err))
110126
}
@@ -166,21 +182,45 @@ func setupFeatureStore(_ context.Context, logger *zap.SugaredLogger, configMapWa
166182
return featureStore
167183
}
168184

169-
// createProxyHandler creates and configures the proxy handler
170-
func createProxyHandler(ctx context.Context, config envConfig, logger *zap.SugaredLogger, featureStore *feature.Store, configMapWatcher *configmap.InformedWatcher) (*ProxyHandler, error) {
171-
var authSubjects []auth.SubjectsWithFilters
185+
// setupEventPolicyInformer creates a namespace-scoped EventPolicy informer
186+
func setupEventPolicyInformer(ctx context.Context, config envConfig, logger *zap.SugaredLogger) (eventingv1alpha1listers.EventPolicyLister, controller.Informer, error) {
187+
cfg := injection.GetConfig(ctx)
172188

173-
if len(config.AuthPolicies) > 0 {
174-
if err := json.Unmarshal([]byte(config.AuthPolicies), &authSubjects); err != nil {
175-
return nil, fmt.Errorf("failed to parse policies: %w", err)
176-
}
189+
// Create eventing client
190+
eventingClient, err := eventingclient.NewForConfig(cfg)
191+
if err != nil {
192+
return nil, nil, fmt.Errorf("failed to create eventing client: %w", err)
177193
}
178194

195+
// Create namespace-scoped informer factory
196+
eventingInformerFactory := eventinginformers.NewSharedInformerFactoryWithOptions(
197+
eventingClient,
198+
0, // resyncPeriod - 0 means no resync
199+
eventinginformers.WithNamespace(config.ParentNamespace),
200+
)
201+
202+
eventPolicyInformer := eventingInformerFactory.Eventing().V1alpha1().EventPolicies()
203+
204+
logger.Infof("Created namespace-scoped EventPolicy informer for namespace %s", config.ParentNamespace)
205+
206+
return eventPolicyInformer.Lister(), eventPolicyInformer.Informer(), nil
207+
}
208+
209+
// createProxyHandler creates and configures the proxy handler
210+
func createProxyHandler(ctx context.Context, config envConfig, logger *zap.SugaredLogger, featureStore *feature.Store, configMapWatcher *configmap.InformedWatcher, eventPolicyLister eventingv1alpha1listers.EventPolicyLister) (*ProxyHandler, error) {
211+
// Parse parent resource GVK
212+
gv, err := schema.ParseGroupVersion(config.ParentAPIVersion)
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to parse parent API version %q: %w", config.ParentAPIVersion, err)
215+
}
216+
parentGVK := gv.WithKind(config.ParentKind)
217+
179218
handler := &ProxyHandler{
180-
kubeClient: kubeclient.Get(ctx),
181-
authVerifier: auth.NewVerifier(ctx, nil, nil, configMapWatcher),
182-
config: config,
183-
authSubjects: authSubjects,
219+
kubeClient: kubeclient.Get(ctx),
220+
authVerifier: auth.NewVerifier(ctx, nil, nil, configMapWatcher),
221+
config: config,
222+
eventPolicyLister: eventPolicyLister,
223+
parentResourceGVK: parentGVK,
184224
}
185225

186226
handler.withContext = func(ctx context.Context) context.Context {
@@ -251,7 +291,15 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
251291

252292
logger.Debugf("Handling request to %s", r.RequestURI)
253293

254-
err := h.authVerifier.VerifyRequestFromSubjectsWithFilters(ctx, features, h.config.SinkAudience, h.authSubjects, h.config.SinkNamespace, r, w)
294+
// Get EventPolicies dynamically for the parent resource
295+
authSubjects, err := h.getAuthSubjects(logger)
296+
if err != nil {
297+
logger.Errorw("Failed to get EventPolicies", zap.Error(err))
298+
http.Error(w, "Internal server error", http.StatusInternalServerError)
299+
return
300+
}
301+
302+
err = h.authVerifier.VerifyRequestFromSubjectsWithFilters(ctx, features, h.config.SinkAudience, authSubjects, h.config.SinkNamespace, r, w)
255303
if err != nil {
256304
logger.Debugw("Failed to verify AuthN and AuthZ", zap.Error(err))
257305
return
@@ -266,6 +314,35 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
266314
}
267315
}
268316

317+
// getAuthSubjects retrieves EventPolicies for the parent resource and converts them to SubjectsWithFilters
318+
func (h *ProxyHandler) getAuthSubjects(logger *zap.SugaredLogger) ([]auth.SubjectsWithFilters, error) {
319+
// Get EventPolicies applying to this resource
320+
policies, err := auth.GetEventPoliciesForResource(
321+
h.eventPolicyLister,
322+
h.parentResourceGVK,
323+
metav1.ObjectMeta{
324+
Name: h.config.ParentName,
325+
Namespace: h.config.ParentNamespace,
326+
},
327+
)
328+
if err != nil {
329+
return nil, fmt.Errorf("failed to get EventPolicies: %w", err)
330+
}
331+
332+
logger.Debugf("Found %d EventPolicies for %s/%s", len(policies), h.config.ParentNamespace, h.config.ParentName)
333+
334+
// Convert EventPolicies to SubjectsWithFilters
335+
subjectsWithFilters := make([]auth.SubjectsWithFilters, 0, len(policies))
336+
for _, policy := range policies {
337+
subjectsWithFilters = append(subjectsWithFilters, auth.SubjectsWithFilters{
338+
Subjects: policy.Status.From,
339+
Filters: policy.Spec.Filters,
340+
})
341+
}
342+
343+
return subjectsWithFilters, nil
344+
}
345+
269346
// httpReverseProxy creates a reverse proxy for HTTP traffic to the target service
270347
func httpReverseProxy(config envConfig) (*httputil.ReverseProxy, error) {
271348
httpTarget := fmt.Sprintf("http://%s:%d", config.TargetHost, config.TargetHTTPPort)

config/core/roles/clusterrole-namespaced.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,16 @@ rules:
115115
- apiGroups: ["eventing.knative.dev", "messaging.knative.dev", "sources.knative.dev", "flows.knative.dev", "bindings.knative.dev", "sinks.knative.dev"]
116116
resources: ["*"]
117117
verbs: ["get", "list", "watch"]
118+
---
119+
kind: ClusterRole
120+
apiVersion: rbac.authorization.k8s.io/v1
121+
metadata:
122+
name: knative-eventing-eventpolicy-reader
123+
labels:
124+
rbac.authorization.k8s.io/aggregate-to-view: "true"
125+
app.kubernetes.io/version: devel
126+
app.kubernetes.io/name: knative-eventing
127+
rules:
128+
- apiGroups: ["eventing.knative.dev"]
129+
resources: ["eventpolicies"]
130+
verbs: ["get", "list", "watch"]

pkg/reconciler/integration/sink/integrationsink.go

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, sink *sinks.IntegrationS
114114
}
115115

116116
logger.Debugw("Reconciling IntegrationSink auth-proxy RBAC")
117-
_, err = r.reconcileAuthProxyRBAC(ctx, sink)
117+
err = r.reconcileAuthProxyRBAC(ctx, sink)
118118
if err != nil {
119119
logging.FromContext(ctx).Errorw("Error reconciling auth-proxy RBAC", zap.Error(err))
120120
return err
@@ -149,7 +149,7 @@ func (r *Reconciler) ReconcileKind(ctx context.Context, sink *sinks.IntegrationS
149149
}
150150

151151
func (r *Reconciler) reconcileDeployment(ctx context.Context, sink *sinks.IntegrationSink, authProxyImage string, featureFlags feature.Flags) (*v1.Deployment, error) {
152-
expected, err := resources.MakeDeploymentSpec(sink, authProxyImage, featureFlags, r.trustBundleConfigMapLister, r.eventPolicyLister)
152+
expected, err := resources.MakeDeploymentSpec(sink, authProxyImage, featureFlags, r.trustBundleConfigMapLister)
153153
if err != nil {
154154
return nil, fmt.Errorf("failed to create deployment template: %w", err)
155155
}
@@ -434,41 +434,103 @@ func (r *Reconciler) serviceChanged(existingSvc corev1.ServiceSpec, wantSvc core
434434
return !equality.Semantic.DeepDerivative(wantSvc, existingSvc)
435435
}
436436

437-
func (r *Reconciler) reconcileAuthProxyRBAC(ctx context.Context, sink *sinks.IntegrationSink) (*rbacv1.RoleBinding, error) {
437+
func (r *Reconciler) reconcileAuthProxyRBAC(ctx context.Context, sink *sinks.IntegrationSink) error {
438438
features := feature.FromContext(ctx)
439439

440+
// 1. Reconcile EventPolicy access RBAC (RoleBinding in sink's namespace)
441+
// This allows auth-proxy to read EventPolicies to enforce authorization
442+
if err := r.reconcileEventPolicyRBAC(ctx, sink, features); err != nil {
443+
return fmt.Errorf("failed to reconcile EventPolicy RBAC: %w", err)
444+
}
445+
446+
// 2. Reconcile ConfigMap access RBAC (RoleBinding in knative-eventing namespace)
447+
// This allows auth-proxy to read logging configuration from the knative-eventing namespace
448+
err := r.reconcileConfigMapAccessRBAC(ctx, sink, features)
449+
if err != nil {
450+
return fmt.Errorf("failed to reconcile ConfigMap access RBAC: %w", err)
451+
}
452+
453+
return nil
454+
}
455+
456+
// reconcileEventPolicyRBAC reconciles the namespace-scoped RoleBinding
457+
// that allows auth-proxy to read EventPolicies in the sink's namespace.
458+
// The RoleBinding references the knative-eventing-eventpolicy-reader ClusterRole.
459+
func (r *Reconciler) reconcileEventPolicyRBAC(ctx context.Context, sink *sinks.IntegrationSink, features feature.Flags) error {
460+
if !features.IsOIDCAuthentication() {
461+
// EventPolicy RoleBinding has OwnerReferences, so it'll be garbage collected
462+
// when the sink is deleted. No explicit cleanup needed when OIDC is disabled.
463+
return nil
464+
}
465+
466+
if err := r.reconcileEventPolicyRoleBinding(ctx, sink); err != nil {
467+
return err
468+
}
469+
470+
return nil
471+
}
472+
473+
// reconcileConfigMapAccessRBAC reconciles the RoleBinding in knative-eventing namespace
474+
// that allows auth-proxy to read ConfigMaps (for logging configuration).
475+
func (r *Reconciler) reconcileConfigMapAccessRBAC(ctx context.Context, sink *sinks.IntegrationSink, features feature.Flags) error {
440476
expected, err := resources.MakeAuthProxyRoleBindings(sink, r.integrationSinkLister, features)
441477
if err != nil {
442-
return nil, fmt.Errorf("creating auth proxy rolebinding: %w", err)
478+
return fmt.Errorf("creating auth proxy rolebinding: %w", err)
443479
}
444480

445481
if !features.IsOIDCAuthentication() {
446-
return nil, r.deleteIntegrationSinkRBAC(ctx, expected)
482+
return r.deleteIntegrationSinkRBAC(ctx, expected)
447483
}
448484

449485
rolebinding, err := r.rolebindingLister.RoleBindings(expected.Namespace).Get(expected.Name)
450486
if apierrors.IsNotFound(err) {
451-
created, err := r.kubeClientSet.RbacV1().RoleBindings(system.Namespace()).Create(ctx, expected, metav1.CreateOptions{})
487+
_, err = r.kubeClientSet.RbacV1().RoleBindings(system.Namespace()).Create(ctx, expected, metav1.CreateOptions{})
452488
if err != nil {
453-
return nil, err
489+
return err
454490
}
455-
456-
return created, nil
491+
return nil
457492
}
458493
if err != nil {
459-
return nil, fmt.Errorf("failed to get rolebinding %s/%s: %w", expected.GetNamespace(), expected.GetName(), err)
494+
return fmt.Errorf("failed to get rolebinding %s/%s: %w", expected.GetNamespace(), expected.GetName(), err)
460495
}
461496

462497
if !r.rolebindingChanged(rolebinding, expected) {
463-
return rolebinding, nil
498+
return nil
464499
}
465500

466501
expected.ResourceVersion = rolebinding.ResourceVersion
467-
updated, err := r.kubeClientSet.RbacV1().RoleBindings(expected.Namespace).Update(ctx, expected, metav1.UpdateOptions{})
502+
_, err = r.kubeClientSet.RbacV1().RoleBindings(expected.Namespace).Update(ctx, expected, metav1.UpdateOptions{})
468503
if err != nil {
469-
return nil, err
504+
return err
470505
}
471-
return updated, nil
506+
return nil
507+
}
508+
509+
func (r *Reconciler) reconcileEventPolicyRoleBinding(ctx context.Context, sink *sinks.IntegrationSink) error {
510+
expected := resources.MakeEventPolicyRoleBinding(sink)
511+
512+
rb, err := r.kubeClientSet.RbacV1().RoleBindings(expected.Namespace).Get(ctx, expected.Name, metav1.GetOptions{})
513+
if apierrors.IsNotFound(err) {
514+
_, err := r.kubeClientSet.RbacV1().RoleBindings(expected.Namespace).Create(ctx, expected, metav1.CreateOptions{})
515+
if err != nil {
516+
return fmt.Errorf("creating EventPolicy RoleBinding: %w", err)
517+
}
518+
return nil
519+
}
520+
if err != nil {
521+
return fmt.Errorf("getting EventPolicy RoleBinding: %w", err)
522+
}
523+
524+
// Update if subjects changed
525+
if !equality.Semantic.DeepEqual(rb.Subjects, expected.Subjects) {
526+
rb.Subjects = expected.Subjects
527+
_, err = r.kubeClientSet.RbacV1().RoleBindings(expected.Namespace).Update(ctx, rb, metav1.UpdateOptions{})
528+
if err != nil {
529+
return fmt.Errorf("updating EventPolicy RoleBinding: %w", err)
530+
}
531+
}
532+
533+
return nil
472534
}
473535

474536
func (r *Reconciler) deleteIntegrationSinkRBAC(ctx context.Context, rolebinding *rbacv1.RoleBinding) error {
@@ -488,6 +550,10 @@ func (r *Reconciler) deleteIntegrationSinkRBAC(ctx context.Context, rolebinding
488550
return fmt.Errorf("failed to delete auth-proxy rolebinding %s/%s: %w", rolebinding.Namespace, rolebinding.Name, err)
489551
}
490552

553+
// Note: EventPolicy Role and RoleBinding have OwnerReferences set to the IntegrationSink,
554+
// so they will be automatically garbage collected when the sink is deleted.
555+
// We don't need to explicitly delete them here.
556+
491557
return nil
492558
}
493559

0 commit comments

Comments
 (0)