Repository: kubernetes/ingress-nginx (Go)

Issue:
auth-tls-error-page does not allow named locations from server snippet
We are trying to upgrade from v1.11.5. However, we are blocked because ingress-nginx now blocks our nginx.ingress.kubernetes.io/auth-tls-error-page annotations.

We have defined some custom nginx locations in a server snippet annotation like so:

```
  server-snippet: |
    location @401 {
      internal;
      return 401;
    }
```

Then we set `nginx.ingress.kubernetes.io/auth-tls-error-page: "@401"` annotation in the Ingress.

 In v1.11.5, this works. In the latest release, this is rejected.

> E0808 05:40:43.123532       7 annotations.go:193] "ingress contains invalid annotation value" err="annotation nginx.ingress.kubernetes.io/auth-tls-error-page contains invalid value"

Please fix the regex to allow named locations.

https://github.com/kubernetes/ingress-nginx/blob/0f185955aedcddb3291afd8899cee2b1a0d99750/internal/ingress/annotations/authtls/main.go#L45


Discussion:
This issue is currently awaiting triage.

If Ingress contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
/assign



Patch:
diff --git a/internal/ingress/annotations/authtls/main.go b/internal/ingress/annotations/authtls/main.go
index de4d1cc1d8..cb1484e716 100644
--- a/internal/ingress/annotations/authtls/main.go
+++ b/internal/ingress/annotations/authtls/main.go
@@ -42,7 +42,7 @@ const (
 
 var (
 	authVerifyClientRegex = regexp.MustCompile(`^(on|off|optional|optional_no_ca)$`)
-	redirectRegex         = regexp.MustCompile(`^((https?://)?[A-Za-z0-9\-.]+(:\d+)?)?(/[A-Za-z0-9\-_.]+)*/?$`)
+	redirectRegex         = regexp.MustCompile(`^(@[A-Za-z0-9_-]+|((https?://)?[A-Za-z0-9\-.]+(:\d+)?)?(/[A-Za-z0-9\-_.]+)*/?)$`)
 )
 
 var authTLSAnnotations = parser.Annotation{
@@ -148,12 +148,12 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
 	var err error
 	config := &Config{}
 
-	tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing, a.annotationConfig.Annotations)
+	authTLSSecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing, a.annotationConfig.Annotations)
 	if err != nil {
 		return &Config{}, err
 	}
 
-	ns, _, err := k8s.ParseNameNS(tlsauthsecret)
+	ns, _, err := k8s.ParseNameNS(authTLSSecret)
 	if err != nil {
 		return &Config{}, ing_errors.NewLocationDenied(err.Error())
 	}
@@ -166,7 +166,7 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
 		return &Config{}, ing_errors.NewLocationDenied("cross namespace secrets are not supported")
 	}
 
-	authCert, err := a.r.GetAuthCertificate(tlsauthsecret)
+	authCert, err := a.r.GetAuthCertificate(authTLSSecret)
 	if err != nil {
 		e := fmt.Errorf("error obtaining certificate: %w", err)
 		return &Config{}, ing_errors.LocationDeniedError{Reason: e}


Test patch:
diff --git a/internal/ingress/annotations/authtls/main_test.go b/internal/ingress/annotations/authtls/main_test.go
index 37342e513a..96ff2898b6 100644
--- a/internal/ingress/annotations/authtls/main_test.go
+++ b/internal/ingress/annotations/authtls/main_test.go
@@ -48,14 +48,7 @@ func buildIngress() *networking.Ingress {
 			Namespace: api.NamespaceDefault,
 		},
 		Spec: networking.IngressSpec{
-			DefaultBackend: &networking.IngressBackend{
-				Service: &networking.IngressServiceBackend{
-					Name: "default-backend",
-					Port: networking.ServiceBackendPort{
-						Number: 80,
-					},
-				},
-			},
+			DefaultBackend: &defaultBackend,
 			Rules: []networking.IngressRule{
 				{
 					Host: "foo.bar.com",
@@ -163,15 +156,38 @@ func TestAnnotations(t *testing.T) {
 	if u.ValidationDepth != 2 {
 		t.Errorf("expected %v but got %v", 2, u.ValidationDepth)
 	}
-	if u.ErrorPage != "ok.com/error" {
-		t.Errorf("expected %v but got %v", "ok.com/error", u.ErrorPage)
-	}
 	if u.PassCertToUpstream != true {
 		t.Errorf("expected %v but got %v", true, u.PassCertToUpstream)
 	}
 	if u.MatchCN != "CN=(hello-app|ok|goodbye)" {
 		t.Errorf("expected %v but got %v", "CN=(hello-app|ok|goodbye)", u.MatchCN)
 	}
+
+	for _, tc := range []struct {
+		name      string
+		errorPage string
+		want      string
+	}{
+		{"url redirect", "ok.com/error", "ok.com/error"},
+		{"named redirect numeric", "@401", "@401"},
+		{"named redirect alphanumeric with underscores", "@four_oh_one", "@four_oh_one"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			data[parser.GetAnnotationWithPrefix(annotationAuthTLSErrorPage)] = tc.errorPage
+			ing.SetAnnotations(data)
+			i, err := NewParser(fakeSecret).Parse(ing)
+			if err != nil {
+				t.Errorf("Unexpected error with ingress: %v", err)
+			}
+			u, ok := i.(*Config)
+			if !ok {
+				t.Errorf("expected *Config but got %v", u)
+			}
+			if u.ErrorPage != tc.want {
+				t.Errorf("expected %v but got %v", tc.want, u.ErrorPage)
+			}
+		})
+	}
 }
 
 func TestInvalidAnnotations(t *testing.T) {