jwt_authn: write JWT payload to dynamic metadata#4707
jwt_authn: write JWT payload to dynamic metadata#4707htuch merged 10 commits intoenvoyproxy:masterfrom
Conversation
lizan
left a comment
There was a problem hiding this comment.
Can you update the PR description to the original one? It will be in the commit message when we merge.
There was a problem hiding this comment.
Left over from #4698 (comment)
Parse JSON payload into a Struct and use that as value.
ef71dd5 to
a105167
Compare
|
It was #4698 |
There was a problem hiding this comment.
Can payload_pairs_ just be a struct and you insert to it here. So setPayload doesn't have to iterate and copy them once again?
There was a problem hiding this comment.
In a second thought, should we make payload_in_metadata as a string, so the key will be specified there? It is more consistent with forward_header configuration. And issuer is already in the payload iss. It will be easier to RBAC rules that can do StringMatcher over issuer rather than use PathSegment (exact match only).
There was a problem hiding this comment.
Hi @lizan I did not understand. Could you clarify your proposal? What format should the payload be stored in dynamic_metadata?
If we just store the json string payload, how RBAC rules can use its individual field, such as "iss", "aud"?
There was a problem hiding this comment.
I meant make payload_in_meatadata config as a string instead of a bool, like forward_payload_header. Then use that string as a key.
The benefit of that is the key will be specified by config, then when you configure MetadataMatcher you can do StringMatch against value of iss, but if issuer is struct key it couldn't.
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>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
13515a3 to
cd0e317
Compare
|
@diemtvu, @yangminzhu and me discussed if we want to use recent introduced FilterState to pass payload and decided to continue to use dynamicMetadata with following reasons:
If RBAC filter decides not supporting dynamicMetadata, this fitler will switch to use FilterState. |
|
Using dynamic metadata is totally fine and they exists along with FilterState. You cannot add a FilterState without modifying RBAC filter to support it so in this case it should be dynamic metadata. |
|
Sorry, I was out last week. |
| @@ -19,6 +19,8 @@ typedef std::unique_ptr<Authenticator> AuthenticatorPtr; | |||
|
|
|||
| typedef std::function<void(const ::google::jwt_verify::Status& status)> AuthenticatorCallback; | |||
There was a problem hiding this comment.
Not sure about this func callback now. It made sense when it is just the status that needs to be returned, but now every new thing that needs to be returned will get a new func callback?
There was a problem hiding this comment.
Agreed that it is not ideal. The best solution is to define a callback interface that has addPayload() and onComplete() call, and later can add call for new data. I did try to explore this route, it is not easy; which object should implement such interface, maybe ContextImpl, it is good for addPayload, but not good for onComplete() since OnComplete can be called per Verifier which there could be many, not just one.
So I settled down with two call_back functions for now, it is easier to pass in two lambda functions.
|
@lizan could you help to merge this? |
|
@htuch can we merge this? I think we talked about potential deprecation of metadata but I don't think that should block this? |
htuch
left a comment
There was a problem hiding this comment.
Yeah, it should be reasonable to accept this PR assuming the authors are comfortable that we might change the dynamic metadata interface soon.
| MessageUtil::loadFromJson(jwt_->payload_str_, payload_pb); | ||
| set_payload_cb_(provider.payload_in_metadata(), payload_pb); | ||
| } catch (EnvoyException ex) { | ||
| ENVOY_LOG(warn, "Error in converting payload for issuer {}, error: {}", jwt_->iss_, |
| ProtobufWkt::Struct payload_pb; | ||
| MessageUtil::loadFromJson(jwt_->payload_str_, payload_pb); | ||
| set_payload_cb_(provider.payload_in_metadata(), payload_pb); | ||
| } catch (EnvoyException ex) { |
There was a problem hiding this comment.
In general, you can't throw exceptions on the data path..
There was a problem hiding this comment.
I don't think we have policy/style guid not throw exception on data path. It's true in general envoy core doesn't catch data path exceptions, but as long as it is caught by filter itself it should be OK? I know some extensions does this a lot (e.g thrift proxy).
There was a problem hiding this comment.
If the filter catches it's OK, but it does make reasoning about behavior harder. It would significantly simplify the understandability of exception safety to readers to avoid this.
There was a problem hiding this comment.
Use RELEASE_ASSERT to replace this try and catch. The error should not happen.
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
htuch
left a comment
There was a problem hiding this comment.
Looks good, some last nits and a question on the interface.
| // The value is the *protobuf::Struct*. The value of this field will be the key for its *fields* | ||
| // and the value is the *protobuf::Struct* converted from JWT JSON payload. | ||
| // | ||
| // For example, if palyload_in_metadata is *my_payload*: |
|
|
||
| // If non empty, successfully verified JWT payloads will be written to StreamInfo DynamicMetadata | ||
| // in the format as: *namespace* is the jwt_authn filter name as **envoy.filters.http.jwt_authn** | ||
| // The value is the *protobuf::Struct*. The value of this field will be the key for its *fields* |
There was a problem hiding this comment.
This is a pretty confusing interface. Why not put this under a fixed key like payload_metadata?
There was a problem hiding this comment.
You may have multiple JWT entries in a single request and they go to different scope.
| const auto status = | ||
| Protobuf::util::JsonStringToMessage(jwt_->payload_str_, &payload_pb, options); | ||
| // payload_str_ have been verified as valid JSON already. | ||
| // All valid JSON should be able to parse into a portobuf Struct. |
There was a problem hiding this comment.
Nit: typo "portobuf" (although I like it).
| // which set an empty payload struct for each issuer. | ||
| static ProtobufWkt::Struct getExpectedPayload(const std::vector<std::string>& issuers) { | ||
| ProtobufWkt::Struct struct_obj; | ||
| auto fields = struct_obj.mutable_fields(); |
Description: Use dynamicMetadata in the StreamInfo to pass all successfully verified JWT payloads to other HTTP filters.
Risk Level: Low
Testing: Add unit-tests