Repository: istio/istio (Go)

Issue:
RequestAuthentication: outputClaimToHeaders with underscores in header names breaks after upgrade to Istio 1.25.2
Hi , We're currently running Istio `1.21.5 `in our clusters, and are planning to upgrade to `1.25.2`.
During this process, we noticed a breaking change related to **RequestAuthentication** when using outputClaimToHeaders with **underscores** in header names.

In `1.21.5`, this worked fine:
```
outputClaimToHeaders:
 - claim: "sub"
  header: "x-user_id"
```
But in `1.25.2`, it fails with:
```
admission webhook "validation.istio.io" denied the request:
configuration is invalid:
* header name x-user_id is not a valid header name
...
```

Example manifest that now fails in `1.25.2`:

```
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: test-758-jwt
  namespace: developer-tooling
  labels:
    app: test
    version: "758"
spec:
  selector:
    matchLabels:
      app: test
      version: "758"
  jwtRules:
    - issuer: https://www.test.com
      jwksUri: http://keycloak-http.identity-management/realms/users/protocol/openid-connect/certs
      fromHeaders:
        - name: authorization
          prefix: 'Bearer '
      fromCookies:
        - _dct
      fromParams:
        - jwt
      forwardOriginalToken: true
      outputClaimToHeaders:
        - claim: user_id
          header: x-user-id
    - issuer: http://keycloak-http.identity-management/realms/users
      jwksUri: http://keycloak-http.identity-management/realms/users/protocol/openid-connect/certs
      fromHeaders:
        - name: authorization
          prefix: 'Bearer '
      fromCookies:
        - _dct
      fromParams:
        - jwt
      forwardOriginalToken: true
      outputClaimToHeaders:
        - claim: user_id
          header: x-user-id
    - issuer: https://auth.test.com/realms/users
      jwksUri: https://auth.test.com/realms/users/protocol/openid-connect/certs
      fromHeaders:
        - name: authorization
          prefix: 'Bearer '
      fromCookies:
        - _dct
      fromParams:
        - jwt
      forwardOriginalToken: true
      outputClaimToHeaders:
        - claim: user_id
          header: x-user_id   # breaks in 1.25.2
```

We traced this to ValidateStrictHTTPHeaderName()
Ref:
- [validation.go#L244](https://github.com/istio/istio/blob/master/pkg/config/validation/validation.go#L244)
- [validation.go#L285](https://github.com/istio/istio/blob/master/pkg/config/validation/validation.go#L285)
However, in the test suite,` x_user_id` seems to still be considered valid:
 - [validation_test.go#L1041](https://github.com/istio/istio/blob/master/pkg/config/validation/validation_test.go#L1041)

Is there any compatibility flag or workaround to keep using underscores in headers (at least temporarily)?


Discussion:
Thnaks for the issue! I agree this is a bug and sent a fix to revert to the old regex to allow underscore



Patch:
diff --git a/pkg/config/validation/validation.go b/pkg/config/validation/validation.go
index 45d57e04d4b6..b64969aabeb8 100644
--- a/pkg/config/validation/validation.go
+++ b/pkg/config/validation/validation.go
@@ -75,8 +75,9 @@ var (
 	tchars               = "!#$%&'*+-.^_`|~" + "A-Z" + "a-z" + "0-9"
 	validHeaderNameRegex = regexp.MustCompile("^[" + tchars + "]+$")
 
-	validProbeHeaderNameRegex  = regexp.MustCompile("^[-A-Za-z0-9]+$")
-	validStrictHeaderNameRegex = validProbeHeaderNameRegex
+	validProbeHeaderNameRegex = regexp.MustCompile("^[-A-Za-z0-9]+$")
+	// This is kept as a semi-arbitrary set of allowed characters for backwards compatibility.
+	validStrictHeaderNameRegex = regexp.MustCompile("^[-_A-Za-z0-9]+$")
 )
 
 const (


Test patch:
diff --git a/pkg/config/validation/validation_test.go b/pkg/config/validation/validation_test.go
index 1a13894816fc..5ed22f31aae5 100644
--- a/pkg/config/validation/validation_test.go
+++ b/pkg/config/validation/validation_test.go
@@ -1087,6 +1087,7 @@ func TestStrictValidateHTTPHeaderName(t *testing.T) {
 		{name: "X-Requested-With", valid: true},
 		{name: ":authority", valid: false},
 		{name: "", valid: false},
+		{name: "foo_bar", valid: true},
 	}
 
 	for _, tc := range testCases {