Jwt_authn config update: Support per route Jwt requirements#3381
Jwt_authn config update: Support per route Jwt requirements#3381htuch merged 20 commits intoenvoyproxy:masterfrom
Conversation
There was a problem hiding this comment.
Just delete this instead of DEPRECATED since no one is using this? same below.
There was a problem hiding this comment.
Unfortunately, Istio 0.8 is using this one. I am pretty sure nobody is using the "bypass" field.
There was a problem hiding this comment.
There was a problem hiding this comment.
Can you add comment to clarify if all are required or just one.
There was a problem hiding this comment.
All are required. Updated the comment.
There was a problem hiding this comment.
There was a problem hiding this comment.
It's not quite clear to me why we need this bool? Can we simply put empty required_issuers list?
There was a problem hiding this comment.
Sound likes a good idea. I will remove it.
|
@qiwzhang This works for me, but will you also include the ability to configure at the route level with per-route-filters as discussed on the issue thread? I can see other people wanting that. If we support both it should cover a pretty wide variety of setups. EDIT: maybe spoke too soon, have a few comments that I'll add inline. |
There was a problem hiding this comment.
I would maybe rename this, since they are no longer rules but provider defintions.
There was a problem hiding this comment.
Actually I don't know anymore, maybe rules is okay, you guys can discuss internally what the naming should be.
There was a problem hiding this comment.
Changed to Provider
There was a problem hiding this comment.
What's the plan with other kinds of request matching (e.g. header matching for CORS options requests)?
There was a problem hiding this comment.
envoy.api.v2.route.RouteMatch supports header matching in additional to path matching.
|
Not sure how to comment on parts of the file that aren't part of the diff so I'll do it here. I think the issuer/audience definitions (aka |
7460431 to
d479977
Compare
|
I added "audiences" in JwtRequirement to override the one in the JwtProvider. You can set different audiences in the per-route requirement. |
7dc1cff to
6e25a38
Compare
There was a problem hiding this comment.
I think the YAML examples above are a bit incorrect ("requires" field, dashes should be on issuer lines), could you double check @qiwzhang?
There was a problem hiding this comment.
Same as above, check the YAML, I think "requires" fields are a little off.
There was a problem hiding this comment.
Again, check the YAML, I think both match and requires need some tweaks.
6e25a38 to
ce5106d
Compare
There was a problem hiding this comment.
If I'm reading the comment correctly, this means we're AND-ing all the requirements? Is that more useful than OR-ing the requirements? I'd think that a typical request has one JWT and would thus only match one issuer, so it's unlikely for AND-ed requirements to be satisfied unless they all have the same issuer and only differ in audience + the JWT has multiple audiences defined. Having OR-ed requirements means the route can accept tokens from multiple issuers, which seems like a more common use case. Thoughts?
There was a problem hiding this comment.
I think AND-ing the requirement will be more useful. If you ever want to verify two JWT from different issuers, you most likely want both of them to pass, not just one of them. For example, Google cloud supports IAP and end-user token, some requests want both to pass, it will be: [iap, end_user_jwt], some only want iap, it will be [iap], even the requests may carry end_user_jwt, but it is not required. It is very hard for me to imaging a request only wants either iap or end_user_jwt.
There was a problem hiding this comment.
I see, so the request carries multiple JWTs in different headers/params? If so AND-ing makes sense, because otherwise there isn't a good way to specify it.
There was a problem hiding this comment.
@qiwzhang we do OR-ed requirements in Google Cloud, https://github.com/googleapis/googleapis/blob/master/google/api/auth.proto#L61 . It is useful to support AND-ing when you have multiple JWTs. However, more common cases we've seen are: your service serve both internal service-to-service traffic and ingress traffic, then you need to specify JWT signed by a service account OR End-user JWT issued by A OR End-user JWT issued by B.
There was a problem hiding this comment.
Agree with @lizan , I'd prefer OR too, at least at the top level. Just seems more common to allow multiple issuers to authenticate a user.
However, I could see multi-JWT requests being used, so perhaps we can come up with something that satisfies both? I'd rather start off with a slightly more verbose config vs change it down the road.
A few misc notes that come to mind for multi-JWT configuration:
- We could do a "2-d" list, where the top level iterates over OR-ed requirements and the inner is AND-ed. Or maybe use a union or oneof?
We'd probably want a way to map headers to requirements explicitly, otherwise the implementation would have to try each of the N^2 pairs. So either the requirements need to specify theEDIT: nevermind, this probably isn't necessary: in typical cases the JWT issuer claim should make it easy to look up the correct provider to use to validate.from_headersorfrom_params, or the requirements need ids and there needs to be some mapping elsewhere.
There was a problem hiding this comment.
Let's not make things too complicate, we might be fine with OR here, but if there are multiple JWTs, validate all of them if possible, and let authZ (RBAC) to handle the complex AND/OR rules.
There was a problem hiding this comment.
I vaguely remember someone else commenting on this field and recommending that it be removed (rather than deprecated) since this config is still in the alpha stage. Was there a final decision on that? It seems that this behavior is pretty trivial to specify as a single catch-all requirement.
There was a problem hiding this comment.
Istio is using it (as internal config between its components, not a user facing config). So we could not remove it now, but like to remove it once Istio code is changed to use new "rules" field.
ce5106d to
a9f9f65
Compare
|
OK, I added 2D "or_jwt" and "and_jwt" in the JwtRequiement. PATL |
There was a problem hiding this comment.
add validate rule that this must not be empty.
I feel there should be better name than and_jwt / or_jwt...
There was a problem hiding this comment.
Added validate rule, and renamed to "one_of" and "all_of"
a9f9f65 to
a0484ad
Compare
|
Format failure is from someone else files. ERROR: clang-format check failed for file: ./api/envoy/config/grpc_credentials/v2alpha/file_based_metadata.proto |
a0484ad to
7f3ce03
Compare
|
Overall I think this is reasonable. It feels a bit verbose and seem to be overreaching a bit into authz, but at least it's pretty clear what it's doing. That said, I'm not 100% sold on it, so I'll suggest another option that isn't quite as flexible, but is far more concise for the common use case while supporting simple AND-ed requirements: Compare that to the equivalent one_of/all_of config: This should be doable with a simple FWIW I'm also in favor of what @lizan suggested: drop AND-ed requirement support at this layer and just use a simple OR requirement, but validate all JWTs presented. That would certainly suffice for me, but since I'm not familiar with the multi-JWT use case I'll defer that decision to you guys. EDIT: update to the proposal above, we could also support a single requirement like so, allowing for a very clean rule table for simple use cases. |
7f3ce03 to
4cf80c8
Compare
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
56f0d22 to
4ffb59e
Compare
| // | ||
| // [#not-implemented-hide:] | ||
| message JwtRule { | ||
| message JwtProvider { |
There was a problem hiding this comment.
@qiwzhang From a configuration PoV the only part of this change that doesn't exactly meet my usecase is that the JwtProvider type describes both the verification and forwarding parameters for a JWT. As a result filters that wish to re-use just the verification logic are also required to set the forward rule to true (as false is the default) so that the headers are not mutated. It'd be nice if either forwarding JWT headers was the default behaviour or alternatively and preferentially there was a clear type separation between verification configuration and forwarding configuration. The latter case would allow other filters to expose just the verification configuration as part of their own config. For example:
message VerificationRules {
string issuer = 1 [(validate.rules).string.min_bytes = 1];
...
}
message FrowardingRules {
bool forward = 1;
string forward_payload_header = 2;
...
}
message JwtProvider {
VerificationRules verification_rules = 1;
ForwardingRules forwarding_rules = 2;
}
There was a problem hiding this comment.
Not clear on what is requested. Yes, current Provider has combined VerificationRule and ForwardRule. But not clear the benefits of separating them.
If jwt_authn filter is used alone, it needs both VerificationRule and ForwardRule. If jwt_authn filter is used only for verification by another filter (like your or Istio-authn), you still need to set ForwardRule for jwt_authn filer. For example, istio_authn doesn't need origin token, so its "forward" is false, just use its payload so it will use "forward_payload_header". If your filter needs origin token, you just set "forward" to true.
Maybe you will not use jwt_authn filter as a whole filter, you will just link its code as c++ library into your filter? I don't think it is a good way to go.
There was a problem hiding this comment.
Maybe you will not use jwt_authn filter as a whole filter, you will just link its code as c++ library
into your filter? I don't think it is a good way to go.
Unfortunately I think the only approach for my filter is to include and link the jwt_authn code directly and invoke it in the same way as http/jwt_authn/http_filter.cc:decode_headers. The reason for this is because the JWT on which I need verification performed is obtained by my filter via a filter-initiated external asynchronous HTTP request. As a result the JWT that is acquired does not pass through the filter chain. So using the code as a filter is not really an option. As such I need to make my filter expose the necessary config to perform verification and ideally not expose parameters that are not relevant whilst not redefining similar structures that already exist. In my case the forward field is not applicable. A solution to that is simply to document that setting this field within my filter will have no effect. And that's fine. So disregard my initial request but I will be wanting to directly invoke the Authenticator code.
| } else { | ||
| callback_->onComplete(status); | ||
| } | ||
| callback_->onComplete(status); |
There was a problem hiding this comment.
For use by other filters, is it possible to have an onComplete callback that passes a const reference/pointer to the token as well as the status? In my uses case (OpenID Connect) I need to check some additional JWT claims to validate against XSRF attacks. These claims are potentially use case specific so pushing them into the Authenticator would be wrong.
There was a problem hiding this comment.
Clearly, you were thinking about linking jwt_authn code into your filter.
I will prefer to explore if you can use jwt_authn code as a filter. Your filter can use its verification result. Istio_authn originally linked jwt_authn code into its filter, it makes the filter very complicated since it needs to deal with aync callbacks. We abandoned that idea. Now it will not touch jwt_authn filter, just config it to behave the way istio_authn wanted and uses its result.
Now the result is passed by HTTP headers. We can change it to use requestinfo.dynamidMetadata to pass the result. https://github.com/envoyproxy/envoy/blob/master/include/envoy/request_info/request_info.h#L282
There was a problem hiding this comment.
In (near) future we will make the verified JWT into metadata, that will allow JWT works with RBAC filter, see #3638 too. I think that will address your use case? @nickrmc83
There was a problem hiding this comment.
|
@nickrmc83 . It seems that we agreed that your user case is so special, we should not adjust jwt_authn config for that. |
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
|
@lizan I addressed your comment of moving "audiences" to RequiredProviider by adding a new field in JwtRequirement onof field: "provider_with_audiences". Users can config audiences in JwtProvider, they can also override them in JwtRequirement. |
htuch
left a comment
There was a problem hiding this comment.
LGTM modulo nits. Looks quite neat. Would be good to obtain approval from @lizan and @nickrmc83 as well.
|
|
||
| // This message specifies a list of RequiredProvider. | ||
| // Their results are OR-ed; if any one of them passes, the result is passed | ||
| message JwtRequirementOrList { |
There was a problem hiding this comment.
Out of curiosity, would this work if we just inline it into the above fields? E.g. if we had repeasted JwtRequirement requires_any = 3 [constraints];? I'm not familiar with the proto rules on self-reference.
There was a problem hiding this comment.
Not, it will not work, proto "oneof" doesn't "repeated" field.
| // prefix: "/healthz" | ||
| // | ||
| // In above example, "requires" field is empty for /healthz prefix match, | ||
| // it means that requrests matched the path prefix don't require jwt authentication. |
There was a problem hiding this comment.
Nit: I think what you want is "that requests matching the path prefix don't require JWT authentication."
| // will pass even the verifications fail. A typical usage is: this filter is used to only verify | ||
| // JWTs and pass the verified JWT payloads to another filter, the other filter will make decision. | ||
| // This usage has impact on the process flow. For example, normally for "requires_any" | ||
| // requirements, if the first requirement passes, the rest requiements will not be verified. But |
There was a problem hiding this comment.
Nit: "the rest of the requirements"
| // - provider_name: "provider2" | ||
| // | ||
| // A special handling if rules is empty: all JWTs in a request will be verified, but the request | ||
| // will pass even the verifications fail. A typical usage is: this filter is used to only verify |
There was a problem hiding this comment.
Nit: "even if the verification fails"
| // - provider_name: "provider2" | ||
| // | ||
| // A special handling if rules is empty: all JWTs in a request will be verified, but the request | ||
| // will pass even the verifications fail. A typical usage is: this filter is used to only verify |
There was a problem hiding this comment.
I think this is pretty surprising behavior; is there no way to design this based on principle-of-least-surprise? E.g. add an explicit "continue_request_on_verification_fail" field?
There was a problem hiding this comment.
Thanks for the input. Agreed. I removed it and add "allow_missing_or_failed" as one of "oneof" in the JwtRequirement.
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
htuch
left a comment
There was a problem hiding this comment.
Thanks for the work put into making this rock.
|
Hello guys, I would like to support multi-tenancy on our microservice. Once the user is authenticated, my JWT will contains a field like tenant_id: 12323232 . I would like to route the request based on that jwt field to the specific tenant's microservice. Do you think it will work on what you have done ? |
|
@atali can you open an issue with more details of your use case? Currently it is not possible to route request based on JWT claims (this PR is reverse, route-based JWT configuration). |
|
Is this still being worked on? If not, I would like to help. |
|
@potatop could you be specific on which feature you like to help? |
|
Yes, I was talking about the RequirementRule functionality. |
|
Can I get some clarification on desired behavior? Sorry if this has been answered before.
|
|
|
@potatop can you open a new issue for further questions/feature requests? |
Signed-off-by: Wayne Zhang qiwzhang@google.com
Description:
Based on the requirement discussion from #2514.
Change the Jwt_authn config to support different requirement based on route match.
Risk Level: Low