Before Istio 1.5 with the mixer it was easy to set headers related to values included in a JWT.
How can I do this in Istio 1.5?
Example:
- Bearer token in a request includes the user id as [sub]
- UserId should be included as header “X-user-id”
Before Istio 1.5 with the mixer it was easy to set headers related to values included in a JWT.
How can I do this in Istio 1.5?
Example:
Do you want to inject request headers before JWT is forwarded to the application? One way you can do is to inject an EnvoyFilter after Istio authentication filter, and add your logic of settings headers there.
I found this yesterday looking for something else
This works for me.
Envoy jwt auth adds the claims to the dynamic metadata. ISTIO by default uses the issuer as the key in the dynamic metadata.
step 1:
Update the access log so that you can see what values you get in the dynamic metadata.
add this to the access logs: “auth_jwt”:"%DYNAMIC_METADATA(envoy.filters.http.jwt_authn)%"
In this step you should be able to find out the key for which you can access your claims.
step2:
Create an envoy filter that uses the dynamic metadata to add headers to the request.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: jwt-to-header-filter
namespace: istio-system
spec:
workloadSelector:
labels:
app: istio-ingressgateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: "envoy.http_connection_manager"
subFilter:
name: "envoy.router"
patch:
operation: INSERT_BEFORE
value:
name: envoy.lua
config:
inlineCode: |
function envoy_on_request(request_handle)
local meta = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
local claims = meta["{add the key here from step1. This is most likely your jwt issuer url.}"]
local user = claims.iss.."/"..claims.username
request_handle:logInfo("username"..user)
request_handle:headers():add("x-jwt-user", user)
end
You can use outputPayloadToHeader to have istio base64 encode the jwt payload and pass it along as a a header
Wow!! This seems the best option!
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-echoserver"
namespace: foo
spec:
selector:
matchLabels:
app: echoserver
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.7/security/tools/jwt/samples/jwks.json"
outputPayloadToHeader: x-jwt
Thank you.
@inaiat How does this helps?
its indeed forward the JWT token as an header but it does not decode it on the Envoy part.
@Thet_Ko how shouldnt your EnvoyFilter includes also section for the jwt_authn filter?
10x
@chen I receive a payload in base64. Like this:
{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}
So, my app can handle this jwt payload.
Ho, yes my app also, but i thought the main idea for this thread was to be able to decrypt the jwt on the Envoy and use specific claims as request headers.
i fail to get the decryption part properly.
Sure… I cant handle this with latest istio (1.7.x) versions. So I use this approach (I think this easiest way)
Hi Chen,
You can add just add request authentication similar to what inaiat has done. The envoy filter will use this jwt token to do its parsing and processing.
Note this line inside the envoy metafilter:
local claims = meta["{add the key here from step1. This is most likely your jwt issuer url.}"]
Thanks, i’ve already got this working.
my problem was that i was trying to use the ‘outputPayLoadToHeader’ on the EnvoyFilter and basically do the decryption myself and that was the wrong direction.
i’ve removed the outputPayloadToHeader from the requestAuthentication as start decoding it myself on the lua script was waste of time.
the magic was indeed on the jwt_authn filter.
meta=request_handle:streamInfo():dynamicMetadata():get(“envoy.filters.http.jwt_authn”)
it did the decryption part and i only needed to read the claims from the metadata based on my issuer.
thanks:)
Hey guys, great discussion; i’ve been scouring documentation and google searching for hours trying to find out to do exactly this. I am unfortunately not able to get it working: I am using this example:
function envoy_on_request(request_handle)
local meta = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
local claims = meta["{add the key here from step1. This is most likely your jwt issuer url.}"]
local user = claims.iss.."/"..claims.client_id
request_handle:logInfo("username"..user)
request_handle:headers():add("x-jwt-user", user)
end
Will this not take a header of “Authorization: Bearer XXXXXXX”, decode the XXXX part, take out the client_id, and pass it on as a value on the header of x-jwt-user? This is my payload decoded:
{
"scope": [],
"client_id": "jasonselftest-Test-999999",
"iss": "https://xxxxxxxxxxx",
"aud": "https://xxxxx.xxxx.xxxxx.xxxxx/",
"exp": 1610507389
}
What I am really aiming for is I want to take the “client_id” from my jwt token and store it as a metric which i can then show in Grafana; this way, our developers could see exactly what client_id is hitting them on their dashboards. Any tips or ideas? Any help would be HUGELY appreciated, this would be a big win for us. Thanks!
HI @jbilliau-rcd ,
in short it should work, but can you change it to the following:
function envoy_on_request(request_handle)
local meta = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
print(meta) //do you see an object here or just nill?
local claims = metadata["{add the key here from step1. This is most likely your jwt issuer url.}"]
print(claims) //do you see an object here or just nill?
local user = claims.iss.."/"..claims.client_id
request_handle:logInfo("username"..user)
request_handle:headers():add("x-jwt-user", user)
end
also, are you sure you are using the right metadata name?
if you are using JWT, it should be the full url of the issuer.
the prints i’ve added will help you understand if you were able to fetch the right content.
also, i didnt understand from your message if you do get antyhing on the headers or not.
Chen
@chen I got it working! Did this:
patch:
operation: INSERT_BEFORE
filterClass: STATS
value:
name: envoy.lua
typed_config:
"@type": "type.googleapis.com/envoy.config.filter.http.lua.v2.Lua"
inlineCode: |
function envoy_on_request(request_handle)
local meta = request_handle:streamInfo():dynamicMetadata():get("envoy.filters.http.jwt_authn")
local claims = meta["https://api.test.xxxx.xxxx.zone"]
local user = claims.client_id
request_handle:logInfo("x-jwt-user"..user)
request_handle:headers():add("x-jwt-user", user)
end
I now see the x-jwt-user as a header. I then used this to write it to envoy access logs:
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: "envoy.http_connection_manager"
patch:
operation: MERGE
value:
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
access_log:
- name: envoy.file_access_log
typed_config:
"@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog"
path: /dev/stdout
format: "%REQ(x-jwt-user)%"
Lastly, I edited my stats-1.7 envoyfilter and did this to inject it as a prom metric:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.http_connection_manager
subFilter:
name: envoy.router
proxy:
proxyVersion: ^1\.7.*
patch:
operation: INSERT_BEFORE
value:
name: istio.stats
typed_config:
'@type': type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
configuration:
'@type': type.googleapis.com/google.protobuf.StringValue
value: |
{"debug":false,"metrics":[{"dimensions":{"client_id":"string(request.headers[\"x-jwt-user\"])"}}],"stat_prefix":"istio"}
root_id: stats_inbound
vm_config:
code:
local:
inline_string: envoy.wasm.stats
runtime: envoy.wasm.runtime.null
vm_id: stats_inbound
Now, I can display istio_request_total by client_id and response code, which is pretty awesome. Thanks for all the examples, this was a very helpful thread.
Cool glad it works:)
Can you please share your envoy filter? I am stuck on this for so long.
you have the envoy filter on this thread. where are you stuck? share what you did and will try to help.
I am getting nil value in dynamicMetadata()