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

feat: Add REST and Websocket authorization hooks and interface #3000

Merged
merged 7 commits into from
Jun 27, 2019

Conversation

spena
Copy link
Member

@spena spena commented Jun 21, 2019

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?

  • Look first at the 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. The WSQueryEndpodpoint is the Websocket part that calls the authorization provider.
  • Then look at the KsqlSecurityExtension and KsqlAuthorizationProvider interfaces. Easy to understand. These will be the interfaces to implement by external authorization applications.
  • Go to KsqlRestApplication to see how the KsqlAuthorizationFilter is registered, and the security extension is initialized.
  • Finally, look at the 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

  • I plan to add better user context impersonation hooks in a follow-up PR.
  • I plan to remove the KsqlDefaultSecurityExtension and make it Optional

Testing done

Added united tests
Added integration tests on AuthorizationFunctionalTest
Run mvn test

Reviewer checklist

  • Ensure docs are updated if necessary. (eg. if a user visible feature is being added or changed).
  • Ensure relevant issues are linked (description should include text like "Fixes #")

@spena spena added the security label Jun 21, 2019
@spena spena added this to the 5.4 milestone Jun 21, 2019
@spena spena requested a review from a team June 21, 2019 14:19
@spena spena self-assigned this Jun 21, 2019
Copy link
Contributor

@vcrfxia vcrfxia left a 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);
Copy link
Contributor

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.

Copy link
Member Author

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

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.

Copy link
Member Author

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()
Copy link
Contributor

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.

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

@vcrfxia vcrfxia requested a review from a team June 22, 2019 00:38
Copy link
Contributor

@vcrfxia vcrfxia left a 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();
Copy link
Contributor

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.

@vcrfxia vcrfxia requested a review from a team June 24, 2019 15:37
@spena spena force-pushed the ksql_authorization branch 2 times, most recently from c5cb2d3 to 7ff9dff Compare June 24, 2019 21:29
@spena spena changed the title Add REST and Websocket authorization hooks and interface feat: Add REST and Websocket authorization hooks and interface Jun 25, 2019
Copy link
Contributor

@rodesai rodesai left a 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)
Copy link
Contributor

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)

Copy link
Member Author

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;
Copy link
Contributor

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.

Copy link
Member Author

@spena spena Jun 25, 2019

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.

@rodesai
Copy link
Contributor

rodesai commented Jun 25, 2019

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.

@spena
Copy link
Member Author

spena commented Jun 25, 2019

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.

@spena spena merged commit 39af991 into confluentinc:master Jun 27, 2019
@spena spena deleted the ksql_authorization branch June 27, 2019 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants