Skip to content
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

Merged
merged 9 commits into from
Mar 26, 2024

Conversation

pgellert
Copy link
Contributor

@pgellert pgellert commented Mar 18, 2024

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

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v23.3.x
  • v23.2.x

Release Notes

  • none

@pgellert pgellert self-assigned this Mar 18, 2024
@pgellert
Copy link
Contributor Author

The first two commits are from #17089

@vbotbuildovich
Copy link
Collaborator

vbotbuildovich commented Mar 18, 2024

new failures in https://buildkite.com/redpanda/redpanda/builds/46368#018e5264-d54c-4705-9a88-a55a4fa32a93:

"rptest.tests.rbac_test.RBACTest.test_get_role"

new failures in https://buildkite.com/redpanda/redpanda/builds/46507#018e5d69-c189-4636-b8f9-46746d3589f2:

"rptest.tests.offset_for_leader_epoch_archival_test.OffsetForLeaderEpochArchivalTest.test_querying_remote_partitions.remote_reads=.False.True"

Copy link
Member

@dotnwat dotnwat left a comment

Choose a reason for hiding this comment

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

🔥

Copy link
Member

@oleiman oleiman left a 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

tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
src/v/redpanda/admin/security.cc Show resolved Hide resolved
@pgellert pgellert force-pushed the rbac/admin-api-crud branch 3 times, most recently from 78a1928 to 7fdf042 Compare March 19, 2024 09:33
@pgellert
Copy link
Contributor Author

Force-pushed 3 times to:

  1. Address code review feedback
  2. Fix flaky ducktape tests
  3. Rebase to dev to resolve conflicts

oleiman
oleiman previously approved these changes Mar 20, 2024
Copy link
Member

@oleiman oleiman left a 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!

tests/rptest/tests/rbac_test.py Outdated Show resolved Hide resolved
@pgellert
Copy link
Contributor Author

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.

@pgellert pgellert requested a review from oleiman March 20, 2024 16:34
oleiman
oleiman previously approved these changes Mar 20, 2024
Copy link
Member

@oleiman oleiman left a 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!

src/v/redpanda/admin/security.cc Show resolved Hide resolved
src/v/redpanda/admin/security.cc Outdated Show resolved Hide resolved
@pgellert
Copy link
Contributor Author

Force-pushed to refactor with Oren's recommendation: #17166 (comment)

oleiman
oleiman previously approved these changes Mar 20, 2024
Copy link
Member

@oleiman oleiman left a comment

Choose a reason for hiding this comment

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

lgtm

@pgellert
Copy link
Contributor Author

Rebased to dev to resolve conflicts

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");
Copy link
Member

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?

Copy link
Member

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 🙂

src/v/redpanda/admin/security.cc Show resolved Hide resolved
Comment on lines +745 to +806
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);
Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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.

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)) {
Copy link
Member

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?

Copy link
Contributor Author

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.

src/v/redpanda/admin/security.cc Outdated Show resolved Hide resolved
Comment on lines +787 to +830
// In order that we can do a reliably ordered validation of
// the request (and drop no-op requests), run on controller leader;
Copy link
Member

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?

Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

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/*

Copy link
Member

@BenPope BenPope Mar 25, 2024

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.

src/v/redpanda/admin/security.cc Show resolved Hide resolved
Comment on lines 255 to 256
assert self._set_of_user_roles() in [{self.role_name0},
{self.role_name1}]
Copy link
Member

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.

Copy link
Contributor Author

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.

src/v/redpanda/admin/security.cc Outdated Show resolved Hide resolved
backoff_sec=0.5)

# Assert idempotency
self.superuser_admin.delete_role(role=self.role_name0)
Copy link
Member

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?

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.
@pgellert
Copy link
Contributor Author

Force-pushed twice, first to address code review comments and then to rebase to dev to resolve conflicts.

role_entry,
security::role_member{
security::role_member_type::user, user});
return name_prefix_matches && role_has_user;
Copy link
Member

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 &&

Suggested change
return name_prefix_matches && role_has_user;
return name_prefix_matches && role_has_user();

Copy link
Contributor Author

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

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.

Suggested change
wait_until(lambda: self._set_of_user_roles() == {self.role_name0},
wait_until(lambda: self.role_name0 in self._set_of_user_roles()

Copy link
Contributor Author

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")
Copy link
Member

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)

Copy link
Contributor Author

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.

tests/rptest/tests/rbac_test.py Show resolved Hide resolved
tests/rptest/tests/rbac_test.py Show resolved Hide resolved
Copy link
Member

@oleiman oleiman left a comment

Choose a reason for hiding this comment

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

lgtm

Comment on lines +754 to +762
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));
}
Copy link
Member

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

Copy link
Member

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.

Copy link
Contributor Author

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

Comment on lines +847 to +851
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);
}
Copy link
Member

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

Suggested change
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);

Copy link
Contributor Author

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.

@pgellert pgellert merged commit 9dae9fe into redpanda-data:dev Mar 26, 2024
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants