-
Notifications
You must be signed in to change notification settings - Fork 552
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
admin: implement RBAC List/Get/Rename/Delete APIs #17166
Conversation
The first two commits are from #17089 |
new failures in https://buildkite.com/redpanda/redpanda/builds/46368#018e5264-d54c-4705-9a88-a55a4fa32a93:
new failures in https://buildkite.com/redpanda/redpanda/builds/46507#018e5d69-c189-4636-b8f9-46746d3589f2:
|
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.
🔥
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. few questions, mostly focused around ducktape
78a1928
to
7fdf042
Compare
Force-pushed 3 times to:
|
7fdf042
to
669ac7b
Compare
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've been working on similar endpoints, so, as before, it'll be good to get a second set of eyes. But LGTM!
669ac7b
to
5b68ea8
Compare
Force-pushed to also include returning the correct REST status codes (201 Created, 204 No Content) to also close https://github.com/redpanda-data/core-internal/issues/1173. |
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.
nice, thanks for doing that!
5b68ea8
to
c3b258b
Compare
Force-pushed to refactor with Oren's recommendation: #17166 (comment) |
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
ducktape was retried in https://buildkite.com/redpanda/redpanda/builds/46507#018e5d69-c189-4636-b8f9-46746d3589f2 ducktape was retried in https://buildkite.com/redpanda/redpanda/builds/46554#018e6131-24dc-4d16-9f69-6ff00ef3d713 ducktape was retried in https://buildkite.com/redpanda/redpanda/builds/46699#018e74e3-55c7-4992-b73f-2fa16b791169 |
c3b258b
to
f1ed886
Compare
Rebased to dev to resolve conflicts |
src/v/redpanda/admin/security.cc
Outdated
ss::future<ss::json::json_return_type> | ||
admin_server::list_roles_handler(std::unique_ptr<ss::http::request> req) { | ||
auto filter = req->get_query_param("filter"); | ||
auto user = req->get_query_param("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.
This query parameter is registered in the swagger as principal
and set as principal
in the admin.list_roles
?. Why aren't there failing tests?
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.
why aren't there failing tests
Member update endpoints just went in. @pgellert we can resolve those todos now 🙂
ss::httpd::security_json::roles_list j_res{}; | ||
for (const auto& role_name : roles) { | ||
ss::httpd::security_json::role_description j_desc; | ||
j_desc.name = ss::sstring{role_name}; | ||
j_res.roles.push(j_desc); | ||
} | ||
|
||
return ss::make_ready_future<ss::json::json_return_type>(j_res); |
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.
This generated code isn't very nice to work with; it could do with some constructors, and move semantics. and a push_back
, so that you can use it with std::transform
and std::back_inserter
(although we could easily write a json::pusher
that does calls push
on the list), and then not copy the whole thing into the response.
And if this is expected to get large, hopefully the array is streamed?
Probably not worth fixing for this PR, though.
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.
This generated code isn't very nice to work with; it could do with some constructors, and move semantics. and a push_back, so that you can use it with std::transform and std::back_inserter (although we could easily write a json::pusher that does calls push on the list), and then not copy the whole thing into the response.
Do you think it's worth a backlog ticket to make this change to seastar?
And if this is expected to get large, hopefully the array is streamed?
It does look like it is streamed: https://github.com/scylladb/seastar/blob/cd8a9133d2c02f63dbd578d882cf7333a427e194/include/seastar/json/formatter.hh#L103-L118
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.
generated code isn't very nice to work with
fwiw, I've been working on changing this in spare time over the last few days. lots of room for improvement.
src/v/redpanda/admin/security.cc
Outdated
ss::future<ss::json::json_return_type> | ||
admin_server::get_role_handler(std::unique_ptr<ss::http::request> req) { | ||
ss::sstring role_v; | ||
if (!admin::path_decode(req->param["role"], role_v)) { |
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 thought you added a get_path_param
, or did that not get merged?
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 upstreaming of that to seastar got stuck. Let me circle back to that and fix these in bulk.
// In order that we can do a reliably ordered validation of | ||
// the request (and drop no-op requests), run on controller leader; |
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'm not sure why we can't forward this to the controller?
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.
You mean why we can't forward it internally to security_frontend
? Like e.g. describe_acls?
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.
We'd need an internal RPC to forward to the controller leader node, right? How do we decide when to forward internally with an RPC vs relying on client forwarding?
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.
We'd need an internal RPC
Yeah, I believe so.
How do we decide
No clue, honestly. w/in the admin API, the redirect seems more general to me, but if there's a hidden cost to doing things this way, we should make a determination.
Probably not for this PR though (since this usage is fairly prevalent across v1/security/*
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.
We'd need an internal RPC to forward to the controller leader node, right?
Yes.
How do we decide when to forward internally with an RPC vs relying on client forwarding?
Good question. I wasn't actually aware of how this 307 worked. Should be ok.
Probably not for this PR though (since this usage is fairly prevalent across
Fine by me.
tests/rptest/tests/rbac_test.py
Outdated
assert self._set_of_user_roles() in [{self.role_name0}, | ||
{self.role_name1}] |
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.
This check seems redundant give the next check for set equivalence.
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.
This is to guard against seeing the intermediary state of both roles being present. I've updated the comment to make this even clearer.
backoff_sec=0.5) | ||
|
||
# Assert idempotency | ||
self.superuser_admin.delete_role(role=self.role_name0) |
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.
Should the status_code be checked?
f1ed886
to
dfe63cf
Compare
This implements the list_roles_handler which will allow the listing of roles known to the redpanda cluster.
Validate that the list roles API is wokring.
This changes the create and delete role admin API handlers to return 201 and 204 as prescribed by the REST standard.
dfe63cf
to
263346d
Compare
role_entry, | ||
security::role_member{ | ||
security::role_member_type::user, user}); | ||
return name_prefix_matches && role_has_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.
question (non-blocking): Is it worth making role_has_user
lazy in case it can be short-circuited at name_prefix_matches
?
You can make it a named lambda and invoke it after the &&
return name_prefix_matches && role_has_user; | |
return name_prefix_matches && role_has_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'm not sure, I think I prefer the clearer code for now and we can optimize this if it becomes a bottleneck. But happy to take this up if you disagree.
res = self.superuser_admin.create_role(role=self.role_name0) | ||
created_role = res.json()['role'] | ||
assert created_role == self.role_name0, f"Incorrect create role response: {res.json()}" | ||
|
||
# Also verify idempotency | ||
wait_until(lambda: self._set_of_user_roles() == {self.role_name0}, |
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.
nitpick: We only need to check that the role exists, bot that no other role exists.
wait_until(lambda: self._set_of_user_roles() == {self.role_name0}, | |
wait_until(lambda: self.role_name0 in self._set_of_user_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.
Fair, although here both should be true
backoff_sec=2, | ||
err_msg="Role was not created") | ||
|
||
self.logger.debug("Also test idempotency of create_role") |
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.
Should the status_code
be checked? (as well as above)
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.
It's checked above (I added that as the last commit, so I think that's why it's not showing up on the commit you commented on.) I think it's fine to check it only once.
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
auto principal_type = req->get_query_param("principal_type"); | ||
|
||
if (!principal_type.empty() && principal_type != "User") { | ||
throw_role_exception( | ||
role_errc::malformed_def, | ||
fmt::format( | ||
"Role membership reserved for user principals, got {{{}}}", | ||
principal_type)); | ||
} |
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.
nitpick: It would be nice if we could express more directly that principal_type
takes a default value ("User") and feed that value (explicitly set or defaulted) into the predicate. More a limitation of the roles interface than anything I think.
principal=bob.name, | ||
principal_type="User", | ||
expected_roles=[role_two]) | ||
|
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.
suggestion (if-minor): Could add a sanity check that default principal_type works as expected, e.g
test_filter(filter="aaaa",
principal=bob.name,
expected_roles=[role_two])
Should be a well-formed request.
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 have exactly this on line 234 :)
if (err == cluster::errc::role_exists) { | ||
throw_role_exception(role_errc::role_name_conflict); | ||
} else if (err == cluster::errc::role_does_not_exist) { | ||
throw_role_exception(role_errc::role_not_found); | ||
} |
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.
suggestion (non-blocking): here and maybe elsewhere
if (err == cluster::errc::role_exists) { | |
throw_role_exception(role_errc::role_name_conflict); | |
} else if (err == cluster::errc::role_does_not_exist) { | |
throw_role_exception(role_errc::role_not_found); | |
} | |
throw_on_role_command_err(err); |
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 we can do one last round of polish if it's worth it as a follow-up PR for this.
This builds on #17089 to implement more of the RBAC admin API endpoints. It implements listing roles, getting a role (describing the role), updating the role (renaming it), and deleting the role.
Closes https://github.com/redpanda-data/core-internal/issues/1104
Closes https://github.com/redpanda-data/core-internal/issues/1105
Closes https://github.com/redpanda-data/core-internal/issues/1106
Closes https://github.com/redpanda-data/core-internal/issues/1107
Closes https://github.com/redpanda-data/core-internal/issues/1173
Backports Required
Release Notes