Skip to content

authz: RBAC HTTP filter + utilities#3455

Merged
mattklein123 merged 4 commits intoenvoyproxy:masterfrom
rodaine:rbac-filter
May 28, 2018
Merged

authz: RBAC HTTP filter + utilities#3455
mattklein123 merged 4 commits intoenvoyproxy:masterfrom
rodaine:rbac-filter

Conversation

@rodaine
Copy link
Member

@rodaine rodaine commented May 22, 2018

This patch adds a new RBAC authz HTTP filter. The engine component is independent of the filter itself, so it can be shared with the network-level filter (coming in a future PR). This patch also reworks the proto configs for the RBAC rules a bit. The previous structure made it ambiguous if rules should be applied with a logical AND or OR. As well, rules of different types could not previously be mixed.

Risk Level: Medium (new filter + minor touchups to existing shared code)
Testing: unit + integration
Docs: updated with new landing page for the filter
Release Notes: updated

Signed-off-by: Chris Roche croche@lyft.com

@mattklein123 mattklein123 self-assigned this May 22, 2018
@mattklein123
Copy link
Member

@rodaine can you take a look at CI issues then I will take a first pass?

@rodaine rodaine force-pushed the rbac-filter branch 2 times, most recently from 38552be to 614d1dd Compare May 22, 2018 23:07
@rodaine
Copy link
Member Author

rodaine commented May 23, 2018

Ready to go, @mattklein123.

Copy link
Member

Choose a reason for hiding this comment

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

While I review this rest of this change, the indent here is funny, and I'm almost certain we used to run clang-format over the proto files which would have fixed this. I also would have thought that #3450 would have fixed not running clang-format, so I suspect there is something else going on here. Do you mind taking a quick look to see if you can figure out if/whether clang-format is running on these files? cc @htuch @alyssawilk

Copy link
Member

Choose a reason for hiding this comment

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

Oh. hmm. I think the issue is because .proto is in the doc suffix list: c5d715a#diff-2e4b5720c93b5c27bb0c8c7c4312aaa8R17

I think we want to remove it from there and treat it as code?

Copy link
Contributor

Choose a reason for hiding this comment

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

That'd be fine, but it requires some workarounds (e.g. to avoid the C++ namespace checks in .proto) which I didn't have time for. It shouldn't be a ton of work though.

Copy link
Member

Choose a reason for hiding this comment

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

OK no problem I can fix this later today.

Copy link
Contributor

@liminw liminw left a comment

Choose a reason for hiding this comment

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

@rodaine Thanks for the change! My comments are mostly on RBAC proto changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

An RBAC role is a set of permissions. Each permission defines an allowed action. So by definition, it is "OR" relationship between permissions. And within a single permission, the rules are "AND"ed together. Hence,

  1. We do not need top-level "or_rules" in a permission, because if you define "A OR B" within a permission, it is better to create two permissions "A" and "B".
  2. We do not have to specify "and_rules" explicitly because within a permission, the rules are always ANDed.
  3. I see that you have changed the semantics of "attribute-->a list of allowed values" to specific or_rules (attribute==value1 or attribute==value2). So the example you showed (removed top-level "and_rules" based on the above comment).
permissions:
- header: { name: ":method", exact_match: "GET" }
  header: { name: ":path", regex_match: "/products(/.*)?" }
  or_rules:
    rules:
      - destination_port: 80
      - destination_port: 443

vs. attribute->a list of allowed values model.

permissions:
- header: { name: ":method", exact_match: "GET" }
  header: { name: ":path", regex_match: "/products(/.*)?" }
  destination_port: [80, 443]

Regarding 3, just from user experience perspective, I feel that the second (attribute->a list of allowed value) presentation is simpler. What do you think? But if it creates difficulty/confusion in implementation, I am fine either way.

Same comments apply to "principals". By definition, it is "OR" relationship between different principals. Within a single "principal", the rules are "AND"ed together. Hence, the three points I mentioned above apply to "principals" fields too.

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe it's been discussed before, but the ergonomics of the configs here are for the Envoy's implementation itself, not so much the user. User-friendlier structures can be easily morphed to this model.

As for why there is support for AND/OR similar to the original implementation of the auth protos, it's to make it very explicit of what the intent is of how the rules within a permission/principal apply. While I agree from a user's perspective that the top-level and_rules are redundant, it makes it clear how each sub-rule is evaluated and leaves nothing up to convention. The existence of the and/or_rules is not mutually exclusive with your approach of splitting out each individual permission/principal within a policy, but it does have the potential to eliminate an explosion of rules which could be more succinctly described with composed ANDs/ORs.

This form, IMO, strikes a balance between the original intent as you proposed, plus makes it easier and clearer how it should be interpreted by the engine, as well as provides flexibility for use cases that go beyond a typical RBAC design.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation. Per our offline discussion, I am fine with the proto changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

If "any" is unset or set to false, and there is no permission defined, what is the behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

The oneof has a required validation rule. You would not be able to emit a permission/principal without a rule set. This was intended to make the intent explicit (instead of by convention).

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here. If "any" is unset or set to false, and no principal is defined, what is the behavior?

Copy link
Member Author

Choose a reason for hiding this comment

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

"any" cannot be set to false, the validation rule precludes it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for clarification.

Copy link
Contributor

@yangminzhu yangminzhu May 23, 2018

Choose a reason for hiding this comment

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

The paths, methods and conditions (destination_ports, destination_ips) in the old design are all repeated field, is this still supported in the new design?
Also the same to the header in principal.

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, just not as repeated fields to make the relationship explicit. It's certainly more verbose, but still supports all the same behaviors as the previous design while being more flexible.

Copy link
Contributor

@yangminzhu yangminzhu May 23, 2018

Choose a reason for hiding this comment

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

To confirm my understanding, the top level policy match is always OR?
and if the action is ALLOW but the request is not matched (which means all the policies are not matched), then the request will be rejected, and vice-versa?
Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

that's correct:

A request is authorized IFF:

  • Action=ALLOWED && a policy matches
  • Action=DENY && no policy matches

A policy matches IFF:

  • One (or more) permission matches the action
  • One (or more) principal matches the downstream

Copy link
Contributor

Choose a reason for hiding this comment

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

where is the service field in the new design?

Copy link
Member Author

@rodaine rodaine May 23, 2018

Choose a reason for hiding this comment

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

it was removed because it's source is unclear from Envoy's perspective. To get the same effect as what was previously described in the comment, use a header matcher on the "x-envoy-downstream-cluster" header

@yangminzhu
Copy link
Contributor

Thanks for the change, Chris!
I'm just thinking it would be great if we can split the RBAC proto change in a different PR. It would be a much smaller PR and likely to be merged soon, so I can work on the Istio side with the latest proto at the same time.

@rodaine
Copy link
Member Author

rodaine commented May 23, 2018

Going to split out the proto changes into a separate PR to facilitate review and unblock @yangminzhu /Istio folks!

#3477

@rodaine
Copy link
Member Author

rodaine commented May 24, 2018

@mattklein123 rebased with changes from #3477

Copy link
Contributor

Choose a reason for hiding this comment

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

How would this support other authentication method, like JWT-based authentication?
@diemtvu

Copy link
Member Author

Choose a reason for hiding this comment

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

currently it does not but we can extend this in a later PR to other mechanisms. I am already iffy on this existing behavior (which I ported from the ext authz filter), and it can most certainly be improved.

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Overall, really great work @rodaine. Well done. I will nominate you to be on the Go++ steering committee. 😉.

One substantial high level comment and some nits, once we sort out the high level comment I will look at the tests. Thank you!

Copy link
Member

Choose a reason for hiding this comment

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

nit: del

Copy link
Member

Choose a reason for hiding this comment

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

This comment is not going to make you happy, but I'm wondering what you think about maintaining the bifurcated variants of allowed/matches through this code. If you look at what we did in ratelimit, we basically use the same call points whether it's HTTP or not (similar for access logging). There are two ways to do this:

  1. Allow headers to be nullptr (switch to const raw pointer)
  2. Synthetically pass a static const empty http headers for calls from the eventual network filter. This makes the matching code even simpler since there needs to be no null checking anywhere in the engine.

I would vote that we get rid of the split functions everywhere, and do number 2 above. Given this PR just touches HTTP, it will always be populated with a real header block. The eventual network filter can just pass a static empty head block as I already mentioned. I think this will delete a bunch of code. Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

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

Going with (2). Easy enough!

Copy link
Member

Choose a reason for hiding this comment

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

nit: for function decls, spell out the parameter name (this will end up getting caught by clang-tidy on google import which we don't run in public CI). (Same elsewhere.)

Copy link
Member

Choose a reason for hiding this comment

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

del empty destructors, same elsewhere.

Copy link
Member

Choose a reason for hiding this comment

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

Please use the new FactoryBase plumbing (see other examples)

Copy link
Member

Choose a reason for hiding this comment

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

not needed

Copy link
Member

Choose a reason for hiding this comment

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

"envoy.filters.http.rbac" (see note above)

Copy link
Member

Choose a reason for hiding this comment

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

nit: const

Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

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

Very cool change!

Copy link
Contributor

Choose a reason for hiding this comment

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

Per https://google.github.io/styleguide/cppguide.html#General_Naming_Rules
I'd be inclined to rename RBAC where possible with AccessControlEngine or RouteBasedAccessControlEngine. For folks not familiar with the acronym they're going to have to look it up where most folks reading "RouteBasedAccessControlEngine" will have a pretty good idea of what it does :-)

This is totally optional - given the config is rbac - you can totally argue that we leave this rbac for consistency :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

certainly don't mind making it clearer!

Copy link
Contributor

Choose a reason for hiding this comment

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

Comments please. Also consider more verbose naming. engine_disabled_ vs allow_if_matched_?

Copy link

Choose a reason for hiding this comment

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

nit: hate to be that person, but can we change the language to be "safe list"/"block list" instead? Parathesis definitely help, but would like to change the language as well to be inclusive. thanks!

Copy link

Choose a reason for hiding this comment

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

"allow list" is another option

@rodaine
Copy link
Member Author

rodaine commented May 25, 2018

Comments addressed, @mattklein123 @alyssawilk @lita!

@mattklein123
Copy link
Member

@rodaine taking another pass now but FYI needs master merge.

@mattklein123
Copy link
Member

@rodaine also please add a release note also. Thank you!

Copy link

Choose a reason for hiding this comment

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

🎉

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Great work, just some random comments, release notes, master merge, then I think ready to ship!

Copy link
Member

Choose a reason for hiding this comment

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

nit: errant comment

Copy link
Member

Choose a reason for hiding this comment

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

You can delete this function, it's implemented by the base class.

Copy link
Member

Choose a reason for hiding this comment

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

nit: given this is only going to be used in an extension integration test, I would not add this to test/config/utility.cc. Just define in your extension integration test and use there.

Copy link
Member

Choose a reason for hiding this comment

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

All of these tests should be defined using envoy_extension_cc_test(). I think I see why you didn't use it here, since there is no clear extension that owns it if it gets used from multiple filters. I would for now have it be owned by the HTTP rbac filter and put in a TODO That we might need to make a test owner a list of multiple owners so that if either the network or HTTP filter gets included we test this stuff.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was being consistent with the other libs in the common namespace. I'll make this change and add the TODO.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also adding a envoy_extension_cc_mock with similar mechanics.

Copy link
Member

Choose a reason for hiding this comment

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

nit: for perf reasons constructor/destructor should go in cc file (see other examples, gmock docs).

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@liminw liminw left a comment

Choose a reason for hiding this comment

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

@rodaine Thanks for this excellent work! The code is really elegant. I just have a small note.

Copy link
Contributor

Choose a reason for hiding this comment

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

This indicates that RBAC filter is actually checking whether or not SSL connection is set up. It might be OK for now. In the long run, our preference is to leave the authentication check to a separate authentication filter. Also, once JWT authentication filter is present, just checking SSL connection won't be enough. I just want to leave some notes here for future consideration.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah we can totally iterate on this going forward, this just matches the existing behavior from ext_authz. I'd like to see the name field deprecated on the Authorization proto and replaced with specific sources for this data.

Chris Roche added 3 commits May 25, 2018 13:24
Signed-off-by: Chris Roche <croche@lyft.com>
Signed-off-by: Chris Roche <croche@lyft.com>
Signed-off-by: Chris Roche <croche@lyft.com>
Signed-off-by: Chris Roche <croche@lyft.com>
Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thank you!

return true;
}

std::string principal = ssl->uriSanPeerCertificate();
Copy link
Contributor

Choose a reason for hiding this comment

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

For Istio, my understanding is local RBAC filter is going to chain with authN filter, which already fished out identiy for both mtls/JWT cases and set principle in request header; so here may need to use principle from header directly.

https://github.com/istio/proxy/blob/master/src/envoy/http/authn/filter_context.cc#L56
https://github.com/istio/proxy/blob/master/src/envoy/http/authn/http_filter.cc#L79

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.

8 participants