Skip to content

jwt_authn: write JWT payload to dynamic metadata#4707

Merged
htuch merged 10 commits intoenvoyproxy:masterfrom
qiwzhang:jwt_dynamic_metadadta
Oct 23, 2018
Merged

jwt_authn: write JWT payload to dynamic metadata#4707
htuch merged 10 commits intoenvoyproxy:masterfrom
qiwzhang:jwt_dynamic_metadadta

Conversation

@qiwzhang
Copy link
Copy Markdown
Contributor

@qiwzhang qiwzhang commented Oct 12, 2018

Description: Use dynamicMetadata in the StreamInfo to pass all successfully verified JWT payloads to other HTTP filters.

Risk Level: Low
Testing: Add unit-tests

@qiwzhang qiwzhang requested a review from lizan as a code owner October 12, 2018 17:39
Copy link
Copy Markdown
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the PR description to the original one? It will be in the commit message when we merge.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left over from #4698 (comment)

Parse JSON payload into a Struct and use that as value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@qiwzhang qiwzhang force-pushed the jwt_dynamic_metadadta branch from ef71dd5 to a105167 Compare October 12, 2018 22:37
@qiwzhang
Copy link
Copy Markdown
Contributor Author

It was #4698
It was accidentally closed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

@qiwzhang qiwzhang Oct 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@lizan lizan self-assigned this Oct 13, 2018
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>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
@qiwzhang qiwzhang force-pushed the jwt_dynamic_metadadta branch from 13515a3 to cd0e317 Compare October 15, 2018 23:47
@qiwzhang
Copy link
Copy Markdown
Contributor Author

qiwzhang commented Oct 16, 2018

@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:

  • FilterState may not be ready. It is not clear where to put these data object class definitions.
  • dynamicMetadata is not going to go away soon. RBAC filter is still using dynamicMetadata,

If RBAC filter decides not supporting dynamicMetadata, this fitler will switch to use FilterState.

@lizan
Copy link
Copy Markdown
Member

lizan commented Oct 16, 2018

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.

lizan
lizan previously approved these changes Oct 16, 2018
@potatop
Copy link
Copy Markdown
Contributor

potatop commented Oct 16, 2018

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@qiwzhang
Copy link
Copy Markdown
Contributor Author

@lizan could you help to merge this?

@lizan lizan requested a review from htuch October 18, 2018 20:40
@lizan
Copy link
Copy Markdown
Member

lizan commented Oct 18, 2018

@htuch can we merge this? I think we talked about potential deprecation of metadata but I don't think that should block this?

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test for this?

ProtobufWkt::Struct payload_pb;
MessageUtil::loadFromJson(jwt_->payload_str_, payload_pb);
set_payload_cb_(provider.payload_in_metadata(), payload_pb);
} catch (EnvoyException ex) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, you can't throw exceptions on the data path..

Copy link
Copy Markdown
Member

@lizan lizan Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use RELEASE_ASSERT to replace this try and catch. The error should not happen.

Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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*:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo palyload

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks


// 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*
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty confusing interface. Why not put this under a fixed key like payload_metadata?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo "portobuf" (although I like it).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks

// 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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: auto*

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@htuch htuch merged commit 2399402 into envoyproxy:master Oct 23, 2018
@qiwzhang qiwzhang deleted the jwt_dynamic_metadadta branch October 23, 2018 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants