-
Notifications
You must be signed in to change notification settings - Fork 1k
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
feat: Add REST and Websocket authorization hooks and interface #3000
Conversation
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.
Thanks @spena ! LGTM minus my comment about the new integration test inline.
try { | ||
provider.checkEndpointAccess(user, method, path); | ||
} catch (final Throwable t) { | ||
log.warn(String.format("User:%s is denied access \"%s %s\"", user, method, path), t); |
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.
Will this error message be confusing to users of the websocket endpoint? I'm no websocket expert but I think talking about POST-ing to a websocket endpoint is strange.
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.
Right. I changed it.
); | ||
|
||
// When: | ||
makeKsqlRequest(VALID_USER1, "SHOW TOPICS;"); |
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.
nit: can we rename VALID_USER1
to simply USER1
? When reading this test I was expecting the request to succeed at first. Plus there are no invalid users in this test.
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.
Changed it.
} | ||
|
||
private static void assertSuccessful(final List<KsqlEntity> results) { | ||
results.stream() |
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 check doing anything? I don't think SHOW TOPICS;
returns a CommandStatusEntity. By having the check filter for CommandStatusEntities and only perform checks on the results that match, the test could pass without performing any checks. It would be better to directly assert what we expect about the results, for example, that results.size() is 1, and that one entity is of type KafkaTopicsList
, etc.
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 copied this method from another integration test. I didn't notice the results weren't correct.
I changed it to verify a topic was returned.
1b002a3
to
346b9f0
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.
Thanks @spena -- LGTM!
|
||
// Then: | ||
assertSuccessful(results); | ||
final List<KafkaTopicInfo> topics = ((KafkaTopicsList)results.get(0)).getTopics(); |
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.
To be extra thorough we could also assert that results.size()
is 1, but it's not a huge deal.
c5cb2d3
to
7ff9dff
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.
Thanks @spena, left some comments inline
ac -> config.register(new KsqlAuthorizationFilter(ac)) | ||
); | ||
|
||
// Registers any other security filters (i.e. user context impersonation) |
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.
nit: this comment isn't really helpful, and is a bit confusing (since the impersonation stuff is really a detail of the plugin)
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.
Right. I was actually going to remove this call in another PR. My plan is to wrap the impersonation implementation into a call like extension.getUserContext(principal)
, and make that call from the REST and WS endpoints to get the Kafka and SR clients for that user.
This PR is only authorization so I didn't want to add more functionality.
* @param path The endpoint path to access, i.e. "/ksql", "/ksql/terminate", "/query" | ||
* @throws KsqlException for access denied or any other authorization error | ||
*/ | ||
void checkEndpointAccess(Principal user, String method, String path) throws KsqlException; |
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 access denied errors merit a more specific exception 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.
I can use the Kafka AuthorizationException
for it, what do you think? or should I create our own KsqlAuthorizationException
?
Also, I want to let users know that other exceptions can be thrown for other errors, perhaps having both Authorization and KsqlException? I don't want KsqlException because I don't want to link the error to a KSQL error. The exception thrown should be the external library exception.
Not exactly specific to this PR, but we should add some a docs page about how to write a security extension. It's not obvious for example how the impersonation credentials get passed around. |
I will do it in another PR once I complete the hooks for impersonation as well. |
9f7c1a3
to
1d4fe5e
Compare
1d4fe5e
to
8944526
Compare
Description
Add hooks and an interface to provide authorization to KSQL REST and Websocket endpoints. KSQL does not provide any authorization provider by default. An external authorizer provider (returned by the KSQL security extension) is used when checking for endpoint access.
How to review?
KsqlAuthorizationFilter
. This filter is called every time a user wants to access a REST endpoint. The filter does not do any check. It is up to the authorization provider to validate the request parameters. TheWSQueryEndpodpoint
is the Websocket part that calls the authorization provider.KsqlSecurityExtension
andKsqlAuthorizationProvider
interfaces. Easy to understand. These will be the interfaces to implement by external authorization applications.KsqlRestApplication
to see how theKsqlAuthorizationFilter
is registered, and the security extension is initialized.AuthorizationFunctionalTest
which has a couple of validation tests that allow and deny access to a KSQL request. I plan to add more tests in a PR for impersonation.Notes
KsqlDefaultSecurityExtension
and make it OptionalTesting done
Added united tests
Added integration tests on
AuthorizationFunctionalTest
Run mvn test
Reviewer checklist