-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Authn uses protobuf.Struct to store claims and add list support for RBAC #1925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
|
|
||
| #include "authn_utils.h" | ||
| #include "common/json/json_loader.h" | ||
| #include "google/protobuf/struct.pb.h" | ||
| #include "src/envoy/http/jwt_auth/jwt.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
@@ -25,51 +26,23 @@ namespace { | |
| // The JWT audience key name | ||
| static const std::string kJwtAudienceKey = "aud"; | ||
|
|
||
| // The JWT groups key name | ||
| static const std::string kJwtGroupsKey = "groups"; | ||
|
|
||
| // Extract JWT audience into the JwtPayload. | ||
| // This function should to be called after the claims are extracted. | ||
| void ExtractJwtAudience( | ||
| const Envoy::Json::Object& obj, | ||
| const ::google::protobuf::Map< ::std::string, ::std::string>& claims, | ||
| istio::authn::JwtPayload* payload) { | ||
| const std::string& key = kJwtAudienceKey; | ||
| // "aud" can be either string array or string. | ||
| // Extract JWT claim as a string list. | ||
| // This function only extracts string and string list claims. | ||
| // A string claim is extracted as a string list of 1 item. | ||
| void ExtractStringList(const std::string& key, const Envoy::Json::Object& obj, | ||
| std::vector<std::string>* list) { | ||
| // First, try as string | ||
| if (claims.count(key) > 0) { | ||
| payload->add_audiences(claims.at(key)); | ||
| return; | ||
| } | ||
| // Next, try as string array | ||
| try { | ||
| std::vector<std::string> aud_vector = obj.getStringArray(key); | ||
| for (const std::string aud : aud_vector) { | ||
| payload->add_audiences(aud); | ||
| } | ||
| // Try as string, will throw execption if object type is not string. | ||
| list->push_back(obj.getString(key)); | ||
| } catch (Json::Exception& e) { | ||
| // Not convertable to string array | ||
| } | ||
| } | ||
|
|
||
| // Extract JWT groups into the JwtPayload. | ||
| // This function should to be called after the claims are extracted. | ||
| void ExtractJwtGroups( | ||
| const Envoy::Json::Object& obj, | ||
| const ::google::protobuf::Map< ::std::string, ::std::string>& claims, | ||
| istio::authn::JwtPayload* payload) { | ||
| const std::string& key = kJwtGroupsKey; | ||
| // "groups" can be either string array or string. | ||
| // First, try as string | ||
| if (claims.count(key) > 0) { | ||
| payload->add_groups(claims.at(key)); | ||
| return; | ||
| // Not convertable to string | ||
| } | ||
| // Next, try as string array | ||
| try { | ||
| std::vector<std::string> group_vector = obj.getStringArray(key); | ||
| for (const std::string group : group_vector) { | ||
| payload->add_groups(group); | ||
| std::vector<std::string> vector = obj.getStringArray(key); | ||
| for (const std::string v : vector) { | ||
| list->push_back(v); | ||
| } | ||
| } catch (Json::Exception& e) { | ||
| // Not convertable to string array | ||
|
|
@@ -89,37 +62,39 @@ bool AuthnUtils::ProcessJwtPayload(const std::string& payload_str, | |
| } | ||
|
|
||
| *payload->mutable_raw_claims() = payload_str; | ||
| ::google::protobuf::Map< ::std::string, ::std::string>* claims = | ||
| payload->mutable_claims(); | ||
|
|
||
| // Extract claims | ||
| json_obj->iterate( | ||
| [payload](const std::string& key, const Json::Object& obj) -> bool { | ||
| ::google::protobuf::Map< ::std::string, ::std::string>* claims = | ||
| payload->mutable_claims(); | ||
| // In current implementation, only string objects are extracted into | ||
| // claims. If call obj.asJsonString(), will get "panic: not reached" | ||
| // from json_loader.cc. | ||
| try { | ||
| // Try as string, will throw execption if object type is not string. | ||
| (*claims)[key] = obj.asString(); | ||
| } catch (Json::Exception& e) { | ||
| } | ||
| return true; | ||
| }); | ||
| // Extract audience | ||
| // ExtractJwtAudience() should be called after claims are extracted. | ||
| ExtractJwtAudience(*json_obj, payload->claims(), payload); | ||
| // ExtractJwtGroups() should be called after claims are extracted. | ||
| ExtractJwtGroups(*json_obj, payload->claims(), payload); | ||
| auto claims = payload->mutable_claims()->mutable_fields(); | ||
| // Extract claims as string lists | ||
| json_obj->iterate([json_obj, claims](const std::string& key, | ||
| const Json::Object&) -> bool { | ||
| // In current implementation, only string/string list objects are extracted | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of looping over the JSON object manually, why not use ParseJsonMessage() to parse the whole json string to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yangminzhu The output of ParseJsonMessage() is of the type Message:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm curious where are we using the @diemtvu WDYT?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The consumers of the claims protobuf.Struct include the producers of dynamic metadata and tests in the Istio proxy repo. If the claims become a map from string key to protobuf.Struct value, every consumer of the claims needs to add some code to verify the type of the claim value before accessing a claim. In contrast, with current implementation, the contract between the producer of the claims and the consumers is that a claim value must be of the string list type and a consumer directly reads a claim value as a string list without extra code to verify the type of a claim.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can explore, but I do think ParseJsonMessage, even if it works with Struct, may has some limitation. For example, in our approach, we want to have consistent representation for string + string array. I'm ok with this custom conversion for now, and find a better utils later (or refactor them to a util function)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you point me to one of the consumer side code that needs to change if you use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The following is a few examples on the consumer side code that need to change if use ParseJsonMessage():
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another example on the consumer side code that need to change if use ParseJsonMessage():
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are just right in this function, I'm trying to see if there are any consumer code outside this file and I will be supersized if it is consuming the Anyway, I'm ok if you would like to go with the current approach, we can discuss more and refactor later. |
||
| std::vector<std::string> list; | ||
| ExtractStringList(key, *json_obj, &list); | ||
| for (auto s : list) { | ||
| (*claims)[key].mutable_list_value()->add_values()->set_string_value(s); | ||
| } | ||
| return true; | ||
| }); | ||
| // Copy audience to the audience in context.proto | ||
| if (claims->find(kJwtAudienceKey) != claims->end()) { | ||
| for (const auto& v : (*claims)[kJwtAudienceKey].list_value().values()) { | ||
| payload->add_audiences(v.string_value()); | ||
| } | ||
| } | ||
|
|
||
| // Build user | ||
| if (claims->count("iss") > 0 && claims->count("sub") > 0) { | ||
| payload->set_user((*claims)["iss"] + "/" + (*claims)["sub"]); | ||
| if (claims->find("iss") != claims->end() && | ||
| claims->find("sub") != claims->end()) { | ||
| payload->set_user( | ||
| (*claims)["iss"].list_value().values().Get(0).string_value() + "/" + | ||
| (*claims)["sub"].list_value().values().Get(0).string_value()); | ||
| } | ||
| // Build authorized presenter (azp) | ||
| if (claims->count("azp") > 0) { | ||
| payload->set_presenter((*claims)["azp"]); | ||
| if (claims->find("azp") != claims->end()) { | ||
| payload->set_presenter( | ||
| (*claims)["azp"].list_value().values().Get(0).string_value()); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use for
(const auto& value : field.second.list_value().values())?Also, do you want to put the logic of converting a ListValue to comma separated string into a util function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"int i" here is used to avoid adding a comma to the last list item. Since the for loop here of six lines is only used at other places, it is not placed in a separate util function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be using string join utils under https://github.com/envoyproxy/envoy/blob/master/source/common/common/utility.h#L322
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
join() input argument is a vectorstd::string: static std::string join(const std::vectorstd::string& source, const std::string& delimiter). To use join(), field.second needs to be converted to vectorstd::string. I have not found a one-liner to do the conversion.