Copying JWT claims to header

I’m trying to implement adding JWT claims as request headers, using the undocumented DYNAMIC_METADATA feature as mentioned in this github issue comment and explained in more detail as an ‘existing solution’ in this google doc feature proposal. By the way, is there a place where these feature proposals are tracked? I would like to get more information about the state of this proposal, is it anywhere on the roadmap?

The specific part I’m talking about this this:

Solution 3: Use an undocumented feature in VirtualService
Istio VirtualService supports setting headers from dynamic attributes using the DYNAMIC_METADATA key. This can be combined with the JWT metadata to copy claims to headers.
See example below

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews-route
spec:
  hosts:
  - reviews.prod.svc.cluster.local
  http:
  - headers:
      request:
        set:
          # Copy the group claim to the x-istio-jwt-group header
          x-istio-jwt-group:
            '%DYNAMIC_METADATA(["istio_authn", "request.auth.claims", "group"])%'

As a POC, I have deployed an app which listens on /echo-header and will echo back the value of the whatever-id header of any request it receives. The app is called web and has an accompanying kind: Service called web-svc. I have configured several AuthorizationPolicy resources and one RequestAuthentication resource to make it so that this service can only be accessed with a valid JWT. This part works as I get a 401 when I make a request without JWT or with expired JWT. A VirtualService and Gateway resource make the service available for external requests. So far so good.

My from from here on out is to copy the value of the whatever-id claim which exists on my JWT (verified on jwt.io) into the whatever-id header.
I Have then modified the VirtualService as mentioned in the feature proposal like so:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: web
spec:
  hosts:
    - example.com
  gateways:
    - web-gateway
  http:
    - match:
        - uri:
            prefix: /
      route:
        - destination:
            host: web-svc
            port:
              number: 80
      headers:
        request:
          set:
            whatever-id: '%DYNAMIC_METADATA(["istio_authn", "request.auth.claims", "whatever-id"])%'
            # whatever-id: "testing"

This however does not work. I verified the header transformation part, by commenting/uncommenting the last 2 lines, after which I did the testing string back from my POC application. But when I use the config with the line that should copy the claim value into the header, the header is not set on the request.

When I set logging to rbac:debug I can see the following log when making a request:

2022-02-23T14:04:05.708454Z     debug   envoy rbac      checking request: requestedServerName: outbound_.80_._.web-svc.route-echo.svc.cluster.local, sourceIP: 172.16.36.22:50722, directRemoteIP: 172.16.36.22:50722, remoteIP: 172.16.36.35:0,localAddress: 172.16.36.57:80, ssl: uriSanPeerCertificate: spiffe://cluster.local/ns/istio-ingress/sa/istio-ingress, dnsSanPeerCertificate: , subjectPeerCertificate: , headers: ':authority', 'example.com'
':path', '/echo-header'
':method', 'GET'
':scheme', 'https'
... <removed irrelevant headers for brevity>
, dynamicMetadata: filter_metadata {
  key: "envoy.filters.http.jwt_authn"
  value {
    fields {
      key: "<redacted issuer url>"
      value {
        struct_value {
          ... <removed all irrelevant claims for brevity>
          fields {
            key: "whatever-id"
            value {
              string_value: "98a5b3ea-f5e7-4012-8cf5-9cfb5cf9e65f"
            }
          }
        }
      }
    }
  }
}
filter_metadata {
  key: "istio_authn"
  value {
    fields {
      key: "request.auth.audiences"
      value {
        string_value: "account"
      }
    }
    fields {
      key: "request.auth.claims"
      value {
        struct_value {
          ... <removed all irrelevant claims for brevity>
          fields {
            key: "whatever-id"
            value {
              list_value {
                values {
                  string_value: "98a5b3ea-f5e7-4012-8cf5-9cfb5cf9e65f"
                }
              }
            }
          }
        }
      }
    }
    fields {
      key: "request.auth.presenter"
      value {
        string_value: "poc"
      }
    }
    fields {
      key: "request.auth.principal"
      value {
        string_value: "<redacted>"
      }
    }
    fields {
      key: "request.auth.raw_claims"
      value {
        string_value: "<redacted>"
      }
    }
  }
}

First thing I noticed when looking at this, is that the dynamicMetadata (which I assumed can be accessed by using the %DYNAMIC_METADATA()% synxtax as mentioned in the solution) does not contain an istio_authn field, the only field it contains is the envoy.filters.http.jwt_auth field, which has the key value equal to the issuer URL of my JWT, and under the value belonging to that key, there is a whatever-id field, which has the value which I want to add to my header, so I tried changing the header value in my VirtualService to:

whatever-id: '%DYNAMIC_METADATA(["envoy.filters.http.jwt_authn", "<issuer url>", "whatever-id"])%'

Sadly this did not work either.

What I also noticed, is that the keys used in the solution ("istio_authn", "request.auth.claims") are available under the filter_metadata property of the log (as opposed to the dynamicMetadata property).
So maybe it is possible to use another syntax to access those filter_metadata properties?