-
Notifications
You must be signed in to change notification settings - Fork 551
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
Role based authorization #17025
Role based authorization #17025
Conversation
73d6cc8
to
789bfab
Compare
/dt |
/dt |
This comment was marked as outdated.
This comment was marked as outdated.
/dt |
7df47a5
to
8f68758
Compare
force push review comments and minor commit history cleanups |
CI Failure:
|
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.
those changes lgtm
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.
Looking good.
I wonder if it's worth reducing allocs by changing the interface away from:
auto acls = _store.find(type, resource_name());
src/v/security/acl.cc
Outdated
@@ -185,19 +220,31 @@ std::vector<std::vector<acl_binding>> acl_store::remove_bindings( | |||
continue; | |||
} | |||
|
|||
// NOTE(oren): deleted bindings are held in the enclosing scope, so | |||
// we can use a non-owning reference here to avoid copying. | |||
ss::chunked_fifo<acl_principal_view> maybe_roles; |
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.
No preference here, more of a question of why not?
ss::chunked_fifo<acl_principal_view> maybe_roles; | |
chunked_vector<acl_principal_view> maybe_roles; |
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.
The ambient guidance I've heard is "use chunked_vector
where you would use std::vector
"; I don't need random access here, so I chose the fifo. That might be an overly legalistic interpretation of the heuristic though. Will peek inside and/or ask around.
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.
I think either is fine. chunked_fifo
has items_per_chunk = 128
regardless of sizeof(item)
. I don't think it will matter much here.
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.
Actually I'm not sure why we're collecting maybe_role
inside of erase_if
instead of returning true, is it to deal with dry_run
?
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.
is it to deal with
dry_run
Partly. Also the erase_if operates on the internal acl_entry
container (not the acl_entry_set
itself), so there's no obvious way to plug in the role_cache accounting. I agree it's yucky.
Maybe the right thing is to do a little refactoring here. There are plenty of tests to support that effort.
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.
still looking at a refactor, but I did switch to chunked_vector
force push code review comments, notably:
|
Taking a look this afternoon 👍 |
Actually I'm confused - @BenPope are you referring to the |
The return of the vector of matches |
force push moved some new code to a follow-on PR: #17152 |
src/v/security/authorizer.h
Outdated
|
||
auto member_roles = | ||
[this, &principal]() -> security::role_store::roles_range { | ||
if (principal.type() == security::principal_type::user) { |
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.
I think it makes sense to allow ephemeral users to have role, although other parts of the code don't allow this.
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.
I'd like to talk through this a bit. Ticket to follow up: https://github.com/redpanda-data/core-internal/issues/1175
force push to address CR comments. Shifting code around and collapsing a few lambdas in |
force push correct move semantics to save a copy |
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.
I only got part way through, but wanted to leave my feedback. I am mostly asking for comments on new methods/classes that are added, I know this code is pretty lightly doc'd at the moment, but we can at least improve the new stuff.
Helper functions for needed conversions between role_members and acl_principal-like things. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Non-allocating string_view-based principal-like-thing. Also introduces acl_principal_base, which is needed for polymorphic usage at various internal interfaces, and corresponding changes to those interfaces. A side effect of this is that `acl_principal::name` now returns a string_view, so we also introduce `acl_principal::adl_name` to preserve the original behavior for ADL-based serialization. Note that both acl_principal_view and (now) acl_principal are declared final, which should allow devirtualization to take place in release mode. As a result, this change should (and based on subsequent benchmarking does) not carry any appreciable performance penalty. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
force push modest header documentation improvements |
Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Non-allocating role-member-like thing for transparent role_store lookups. Saves an allocation in the authorizer. Adjusts role_store::roles_for_member to accept either one of these or the usual role_member struct. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Cache of role principal names on acl_entry_set to avoid linear-time lookups for role principals that don't have any bindings. We can do this because role bindings are strict (no name wildcards). Also adds a special case so acl_principal.wildcard() returns false for role type principals. This avoids matching wildcard users against wildcard roles (which shouldn't exist). Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
role_store::roles_for_member returns a boost::iterator_range into the members store. For convenience in security::authorizer, expose that type via an alias in role_store's public interface. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
This does come at a cost, since we have to search the role_store to confidently assert "no match". However, if the role_store is empty (or if the authN'ed principal is not found in the store), this cost should be on the order of a node_hash_map lookup (internal to role_store::roles_for_member). See subsequent benchmarks for detail. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
For easier filtering. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Single invocations of various role_store calls are more interesting than many in a loop. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
Notably, the cost of a single authorized operation increases super- logarithmically but sub-linearly with the number of roles in the store. Performance degradation in the presence of many member entries is marginal. Signed-off-by: Oren Leiman <oren.leiman@redpanda.com>
force push authorizer takes a pointer rather than a reference. |
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.
LGTM
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.
A few questions, LGTM
} | ||
// ensure that elements won't outlive the corresponding bindings | ||
// in enclosing scope. | ||
maybe_roles.erase_to_end(maybe_roles.begin()); |
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.
Is this clear
?
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.
Ah, my intention was to avoid releasing the memory, but it does look like this decays to fragmented_vector::clear
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.
maybe a std::vector
is actually a better choice here?
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.
I don't have a good mental model of the limits here, but it feels safer to alloc a bit more other than crash (if this could grow to an oversized alloc).
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.
Oh, good spot - I hadn't noticed that it took the path of clear.
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.
don't have a good mental model of the limits here
Neither do I, but I'd expect the number of iterations to be quite small in the common case. So potential savings is marginal compared to risk of the unknown. Probably just leave it as is for now.
"Role principal should be non-null and have 'role' type if " | ||
"present"); |
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.
Someday we should either upgrade absl to use the nullability helpers or just create a similar abstraction: https://github.com/abseil/abseil-cpp/blob/master/absl/base/nullability.h
Nothing to do here, just food for thought.
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.
Yes, I'll have all the contracts and monads please and thanks. Anyway, good call out, I wasn't aware of that stuff at all. Very cool.
// NOTE(oren): We know there isn't a match at this point, but I've left | ||
// this as an opt_acl_match to preserve semantics, namely that this | ||
// will return a non-authorized result irrespective of the | ||
// allow_empty_matches flag. On the other hand, switching to an | ||
// empty_match_result _should_ alter semantics but doesn't break any | ||
// tests. Not clear whether this is a bug, intended behavior, a gap | ||
// in test coverage, or a combination. |
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.
Is there a followup ticket to track this down?
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.
Not yet, but I'll make one now. Left the comment there on the off-chance somebody would drop into the PR with ad hoc knowledge, but that didn't happen
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.
auto result = _role_store->roles_for_member( | ||
security::role_member_view::from_principal( | ||
principal)) | ||
| std::views::transform([](const auto& e) { | ||
return role::to_principal_view(e); | ||
}) | ||
| std::views::transform( | ||
[&user, &check_access, perm](const auto& e) { | ||
return check_access(perm, user, &e); | ||
}) | ||
| std::views::filter( | ||
[](const std::optional<auth_result>& r) { | ||
return r.has_value(); | ||
}) | ||
| std::views::take(1); |
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.
🔥
@@ -17,17 +17,52 @@ | |||
#include <seastar/coroutine/maybe_yield.hh> | |||
|
|||
#include <absl/container/flat_hash_map.h> | |||
#include <container/fragmented_vector.h> |
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.
@oleiman use "..."
for internal includes
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.
Good spot, thanks. Not sure how that slipped in.
This PR wires roles into the authorization flow in redpanda.
closes https://github.com/redpanda-data/core-internal/issues/1101
Preliminary benchmarks:
Backports Required
Release Notes