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

Add basic authentication plugin #1087

Merged
merged 8 commits into from
Feb 2, 2018
Merged

Conversation

yush1ga
Copy link
Contributor

@yush1ga yush1ga commented Jan 19, 2018

Motivation

Some competitors of Pulsar has the basic authentication plugin.

Modifications

Added the basic authentication plugin.

Result

Pulsar can use basic authentication.

@yush1ga yush1ga added this to the 1.22.0-incubating milestone Jan 19, 2018
@yush1ga yush1ga added the type/enhancement The enhancements for the existing features or docs. e.g. reduce memory usage of the delayed messages label Jan 19, 2018
@yush1ga yush1ga self-assigned this Jan 19, 2018
@@ -0,0 +1,6 @@
{
"super": {
"password": "superPassword",
Copy link
Member

Choose a reason for hiding this comment

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

Seriously? You should google "htpasswd".

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, let's use common format for basic user:password file. That would eliminate the needs for a specific script/tool to manage it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it and made userId equivalent to role.

@merlimat merlimat added the type/feature The PR added a new feature or issue requested a new feature label Jan 19, 2018

public static final String AuthenticatedRoleAttributeName = AuthenticationFilter.class.getName() + "-role";

public AuthenticationFilter(PulsarService pulsar) {
this.authenticationService = pulsar.getBrokerService().getAuthenticationService();
if (pulsar.getConfiguration().getAuthenticationProviders()
.contains(AuthenticationProviderBasic.class.getName())) {
isBasic = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than a ad-hoc solution, is it possible to have a general way to trigger the right header in the response?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can modify AuthenticationService::authenticateHttpRequest. Currently we just ignore exceptions in the method to try all auth providers, but probably we should gather information from the providers. Because "WWW-Authenticate" header can contain multiple challenges, we can suggest all supported authentication types at the first 401 response.
https://tools.ietf.org/html/rfc7235#section-4.1

Also, there is (at least) another response header, "Authentication-Info" (https://tools.ietf.org/html/rfc7615), that will carry information when authentication succeeds. So it would be better to design the API for this mechanism so that providers can respond with arbitrary headers and/or body data regardless of authentication results.

https://tools.ietf.org/html/rfc7235#section-2.1

HTTP does not restrict applications to this simple challenge-response
framework for access authentication. Additional mechanisms can be
used, such as authentication at the transport level or via message
encapsulation, and with additional header fields specifying
authentication information. However, such additional mechanisms are
not defined by this specification.

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 think modifying authenticateHttpRequest is not efficient because the information is gathered every time authentication failed.
I fixed AuthentcationFilter class to gather information from providers when it is initialized.

Copy link
Member

Choose a reason for hiding this comment

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

I don’t think we are always be able to gather information on initialization. For example, Digest authentication needs to generate a challenge (nonce) every time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make sense.
I added PulsarHttpAuthenticationException class, which has realm information for WWW-Authentication header and the information is gathered in authenticateHttpRequest.

private String authToken;

public AuthenticationDataBasic(String userId, String password) {
authToken = "Basic " + Base64.getEncoder().encodeToString((userId + ":" + password).getBytes());
Copy link
Member

Choose a reason for hiding this comment

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

If I remember correctly, authentication method name is stored in some field of a command. "Basic " is needed for the HTTP header but it's redundant for the pulsar's command.

Also, base64 encoding is needed because of a limitation of HTTP headers. I don't think pulsar's command has the same limitation.

They both increases data length unnecessarily。

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 made command data not contain encoded params.

return password;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing new line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

@yush1ga yush1ga force-pushed the password-auth branch 2 times, most recently from f5b4fec to 4def43d Compare January 24, 2018 01:50
@@ -81,12 +81,15 @@ public String authenticate(AuthenticationDataSource authData, String authMethodN
}
}

public String authenticateHttpRequest(HttpServletRequest request) throws AuthenticationException {
public String authenticateHttpRequest(HttpServletRequest request) throws PulsarHttpAuthenticationException {
PulsarHttpAuthenticationException exception = new PulsarHttpAuthenticationException("Authentication required");
Copy link
Contributor

Choose a reason for hiding this comment

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

Creating the exception is a very costly operation because it fills up the stack trace, we should only do it in the failure path

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 made it create realmInformation string to avoid creating an exception.

@@ -0,0 +1,2 @@
superUser:mQQQIsyvvKRtU
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure but should we add this file under license exclude list ?

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

HttpServletResponse httpResponse = (HttpServletResponse) response;
if (isNotBlank(e.getRealmInformation())) {
((HttpServletResponse) response).setHeader("WWW-Authenticate", e.getRealmInformation());
Copy link
Member

Choose a reason for hiding this comment

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

As I wrote on the previous comment, there is another standard header that is designed for returning information on authentication success, and authentication methods are also allowed to use other non-standard headers. Requiring auth providers to embed the header information into an exception and assuming the header name is "WWW-Authenticate" would be a limitation in the future.

Honestly speaking, I think using an exception to indicate simple authentication failure is a bad design, at least in Java, because of its cost. Authentication failures happen occasionally, and it can be caused easily by attackers. We shouldn't use exceptions for such a expectable case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@maskit
I roughly agree with you.

using an exception to indicate simple authentication failure is a bad design

I think this should not be fixed in this PR adding authentication plugin.
We should consider good design and make another PR.
That's why, I exclude the code about authentication header from this PR.

Copy link
Member

Choose a reason for hiding this comment

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

Alright.

Copy link
Member

@maskit maskit left a comment

Choose a reason for hiding this comment

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

There are few points need to be improved later (e.g. HTTP support, role management), but seems good. Let's revisit them after we improve API.

Copy link
Contributor

@merlimat merlimat left a comment

Choose a reason for hiding this comment

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

👍

@merlimat merlimat merged commit 71f281a into apache:master Feb 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/enhancement The enhancements for the existing features or docs. e.g. reduce memory usage of the delayed messages type/feature The PR added a new feature or issue requested a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants