This is an automated email from the ASF dual-hosted git repository.

shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new dc8db3d7 fix: fail loud when enable-csrf is set but csrf-key 
annotation is missing (#2813)
dc8db3d7 is described below

commit dc8db3d714d098fde6cd5eec986287b3c9fd1fad
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Aug 3 10:38:11 2026 +0800

    fix: fail loud when enable-csrf is set but csrf-key annotation is missing 
(#2813)
---
 .../adc/translator/annotations/plugins/csrf.go     |  8 +++-
 .../translator/annotations/plugins/csrf_test.go    | 10 ++++-
 internal/webhook/v1/ingress_webhook.go             | 23 +++++++++++
 internal/webhook/v1/ingress_webhook_test.go        | 45 ++++++++++++++++++++++
 4 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/internal/adc/translator/annotations/plugins/csrf.go 
b/internal/adc/translator/annotations/plugins/csrf.go
index 1bee407c..811f4753 100644
--- a/internal/adc/translator/annotations/plugins/csrf.go
+++ b/internal/adc/translator/annotations/plugins/csrf.go
@@ -16,6 +16,8 @@
 package plugins
 
 import (
+       "fmt"
+
        adctypes "github.com/apache/apisix-ingress-controller/api/adc"
        
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
 )
@@ -39,7 +41,11 @@ func (c *csrf) Handle(e annotations.Extractor) (any, error) {
 
        key := e.GetStringAnnotation(annotations.AnnotationsCsrfKey)
        if key == "" {
-               return nil, nil
+               // csrf requested without a key: the webhook rejects this, but 
log a
+               // breadcrumb for the cases it cannot cover (webhook off, 
pre-upgrade
+               // Ingress). Parse skips only this plugin, the rest of the 
route stands.
+               return nil, fmt.Errorf("annotation %q is enabled but %q is 
missing or empty",
+                       annotations.AnnotationsEnableCsrf, 
annotations.AnnotationsCsrfKey)
        }
 
        return &adctypes.CSRFConfig{
diff --git a/internal/adc/translator/annotations/plugins/csrf_test.go 
b/internal/adc/translator/annotations/plugins/csrf_test.go
index 0f681703..e41f8cf5 100644
--- a/internal/adc/translator/annotations/plugins/csrf_test.go
+++ b/internal/adc/translator/annotations/plugins/csrf_test.go
@@ -43,10 +43,16 @@ func TestCSRFHandler(t *testing.T) {
        assert.Nil(t, err, "checking given error")
        assert.Nil(t, out, "checking given output")
 
-       // Test with enable-csrf true but no key
+       // Test with enable-csrf true but no key: errors so Parse logs a 
breadcrumb
        anno[annotations.AnnotationsEnableCsrf] = "true"
        delete(anno, annotations.AnnotationsCsrfKey)
        out, err = p.Handle(annotations.NewExtractor(anno))
-       assert.Nil(t, err, "checking given error")
+       assert.Error(t, err, "expecting an error when csrf-key is missing")
        assert.Nil(t, out, "checking given output when key is missing")
+
+       // Test with enable-csrf true but empty key: same behavior
+       anno[annotations.AnnotationsCsrfKey] = ""
+       out, err = p.Handle(annotations.NewExtractor(anno))
+       assert.Error(t, err, "expecting an error when csrf-key is empty")
+       assert.Nil(t, out, "checking given output when key is empty")
 }
diff --git a/internal/webhook/v1/ingress_webhook.go 
b/internal/webhook/v1/ingress_webhook.go
index 3d1a46ad..d6d3e4f5 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -27,6 +27,7 @@ import (
        logf "sigs.k8s.io/controller-runtime/pkg/log"
        "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
 
+       
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
        "github.com/apache/apisix-ingress-controller/internal/controller"
        
"github.com/apache/apisix-ingress-controller/internal/webhook/v1/reference"
        sslvalidator 
"github.com/apache/apisix-ingress-controller/internal/webhook/v1/ssl"
@@ -75,6 +76,10 @@ func (v *IngressCustomValidator) ValidateCreate(ctx 
context.Context, obj runtime
                return nil, nil
        }
 
+       if err := validateAnnotations(ingress); err != nil {
+               return nil, err
+       }
+
        detector := sslvalidator.NewConflictDetector(v.Client)
        conflicts := detector.DetectConflicts(ctx, ingress)
        if len(conflicts) > 0 {
@@ -96,6 +101,10 @@ func (v *IngressCustomValidator) ValidateUpdate(ctx 
context.Context, oldObj, new
                return nil, nil
        }
 
+       if err := validateAnnotations(ingress); err != nil {
+               return nil, err
+       }
+
        detector := sslvalidator.NewConflictDetector(v.Client)
        conflicts := detector.DetectConflicts(ctx, ingress)
        if len(conflicts) > 0 {
@@ -106,6 +115,20 @@ func (v *IngressCustomValidator) ValidateUpdate(ctx 
context.Context, oldObj, new
        return warnings, nil
 }
 
+// validateAnnotations rejects annotation combinations that would otherwise be
+// silently dropped, leaving the route without a requested security plugin.
+// enable-csrf with no csrf-key is refused here so kubectl apply fails loudly
+// instead of programming a route the operator believes is protected.
+func validateAnnotations(ingress *networkingv1.Ingress) error {
+       e := annotations.NewExtractor(ingress.Annotations)
+       if e.GetBoolAnnotation(annotations.AnnotationsEnableCsrf) &&
+               e.GetStringAnnotation(annotations.AnnotationsCsrfKey) == "" {
+               return fmt.Errorf("annotation %q is enabled but %q is missing 
or empty",
+                       annotations.AnnotationsEnableCsrf, 
annotations.AnnotationsCsrfKey)
+       }
+       return nil
+}
+
 // ValidateDelete implements webhook.CustomValidator so a webhook will be 
registered for the type Ingress.
 func (v *IngressCustomValidator) ValidateDelete(ctx context.Context, obj 
runtime.Object) (admission.Warnings, error) {
        return nil, nil
diff --git a/internal/webhook/v1/ingress_webhook_test.go 
b/internal/webhook/v1/ingress_webhook_test.go
index d6bce33f..0a5481c4 100644
--- a/internal/webhook/v1/ingress_webhook_test.go
+++ b/internal/webhook/v1/ingress_webhook_test.go
@@ -113,3 +113,48 @@ func 
TestIngressCustomValidator_NoWarningsWhenReferencesExist(t *testing.T) {
        require.NoError(t, err)
        assert.Empty(t, warnings)
 }
+
+func csrfIngress(anno map[string]string) *networkingv1.Ingress {
+       return &networkingv1.Ingress{
+               ObjectMeta: metav1.ObjectMeta{Name: "test-ingress", Namespace: 
"default", Annotations: anno},
+               Spec:       networkingv1.IngressSpec{},
+       }
+}
+
+func TestIngressCustomValidator_RejectsEnableCsrfWithoutKey(t *testing.T) {
+       validator := buildIngressValidator(t)
+
+       // missing csrf-key
+       _, err := validator.ValidateCreate(context.Background(), 
csrfIngress(map[string]string{
+               "k8s.apisix.apache.org/enable-csrf": "true",
+       }))
+       require.Error(t, err)
+
+       // empty csrf-key
+       _, err = validator.ValidateCreate(context.Background(), 
csrfIngress(map[string]string{
+               "k8s.apisix.apache.org/enable-csrf": "true",
+               "k8s.apisix.apache.org/csrf-key":    "",
+       }))
+       require.Error(t, err)
+
+       // the same invalid update must also be rejected, not only create
+       old := csrfIngress(nil)
+       _, err = validator.ValidateUpdate(context.Background(), old, 
csrfIngress(map[string]string{
+               "k8s.apisix.apache.org/enable-csrf": "true",
+       }))
+       require.Error(t, err)
+}
+
+func TestIngressCustomValidator_AllowsEnableCsrfWithKey(t *testing.T) {
+       validator := buildIngressValidator(t)
+
+       _, err := validator.ValidateCreate(context.Background(), 
csrfIngress(map[string]string{
+               "k8s.apisix.apache.org/enable-csrf": "true",
+               "k8s.apisix.apache.org/csrf-key":    "my-secret-key",
+       }))
+       require.NoError(t, err)
+
+       // csrf not enabled: key absence is fine
+       _, err = validator.ValidateCreate(context.Background(), 
csrfIngress(nil))
+       require.NoError(t, err)
+}

Reply via email to