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

GEODE-3447 Implement client authorization for the new protocol #719

Closed
wants to merge 4 commits into from

Conversation

bschuchardt
Copy link
Contributor

@bschuchardt bschuchardt commented Aug 17, 2017

Implementation of authorization checks for the new protocol. This will have to be merged with the Locator Protobuf communications work that is in progress on another branch before we check it in.

@kohlmu-pivotal @hiteshk25 @galen-pivotal @pivotal-amurmann @WireBaron

Thank you for submitting a contribution to Apache Geode.

In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:

For all changes:

  • [ x] Is there a JIRA ticket associated with this PR? Is it referenced in the commit message?

  • [ x] Has your PR been rebased against the latest commit within the target branch (typically develop)?

  • [x ] Is your initial contribution a single, squashed commit?

  • Does gradlew build run cleanly?
    rat failed on a new StreamAuthorizer interface file. This will be corrected.

  • [ x] Have you written or updated unit tests to verify your changes?

  • If adding new dependencies to the code, are these dependencies licensed in a way that is compatible for inclusion under ASF 2.0?

Note:

Please ensure that once the PR is submitted, you check travis-ci for build issues and
submit an update to your PR as soon as possible. If you need help, please send an
email to dev@geode.apache.org.

}

@Override
public boolean authorize(ResourcePermission permissionRequested) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why would I not pass in the Principal and requested permissions? Then I can have 1 authorizer and not 1 per 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'd rather not have other code deal with the Principal. Other implementations may not have a Principal. I.e., a no-op implementation.

.build().writeDelimitedTo(outputStream);
}

@Override
public boolean isAuthenticated() {
return authenticated;
return authorizer != null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I must disagree with this logic. Something is NOT authenticated just because there is an authorizer populated. A authorizer should be constructed BECAUSE something was authenticated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A authorizer IS only constructed BECAUSE something was authenticated. Therefore this is a valid implementation of isAuthenticated().

try {
Object principal = securityManager.authenticate(properties);
authenticated = principal != null;
if (principal != null) {
authorizer = new ProtobufSimpleAuthorizer(principal, securityManager);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need a ProtobufSimpleAuthorizer per principal? Could we not have a single Authorizer that will authenticate all principals?

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 Principal is part of the implementation of this authentication & authorization algorithm and is encapsulated by this class.

Copy link
Contributor

@galen-pivotal galen-pivotal left a comment

Choose a reason for hiding this comment

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

If we ever need to do permissions per region or other more fine-grained permissions we'll need to refactor this. that's fine, we don't need it now.

The one thing that does worry me is that clients (say, producers) may be authorized for write and not read, and GetAvailableServers might not work for them. Does GetAvailableServers need any permissions? I don't think the current client authorizes... perhaps we could simply not authorize? I'm a bit on the fence, what do you think?

package org.apache.geode.security;

public interface StreamAuthorizer {
boolean authorize(ResourcePermission permissionRequested);
Copy link
Contributor

Choose a reason for hiding this comment

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

We may need to refactor this at some point when operations start needing more than one permission (which depends on whether write authorization implies read authorization), but I think that's more than a failing of the security API than ours, and it should be possible to do everything we need for the alpha anyways (no getAndSet yet).

ClientProtocol.Request::getGetAvailableServersRequest,
new GetAvailableServersOperationHandler(),
opsResp -> ClientProtocol.Response.newBuilder().setGetAvailableServersResponse(opsResp)));
operationContexts.put(RequestAPICase.GETAVAILABLESERVERSREQUEST,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is one that should be authorized for either read or write... is read a subset of write? I don't know.

verifyOperations(true, true);
}

private void verifyOperations(boolean readAllowed, boolean writeAllowed) throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 thorough and clear tests.

@bschuchardt
Copy link
Contributor Author

GetAvailableServers will be executed on a Locator which installs a no-op authorizer in the execution "context". That can change if we decide later on that we want to perform authentication and authorization on locator requests.

Copy link
Contributor

@galen-pivotal galen-pivotal left a comment

Choose a reason for hiding this comment

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

Okay, if we've got no-op on the locator then ship it!

Copy link

@pivotal-amurmann pivotal-amurmann left a comment

Choose a reason for hiding this comment

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

A few opportunities for minor improvements but generally looks great.

* Return an authorization object which can be used to determine which permissions this stream has
* according to the provided securityManager.
*
* Calling this before authentication has succeeded may result in a null return object.

Choose a reason for hiding this comment

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

Could we indicate this by using a Optional? That way we also force the consumer to deal with that case.

Copy link
Contributor

Choose a reason for hiding this comment

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

I kind of prefer a comment or even annotation saying nullable -- optionals themselves can be null, and have some cost.

Copy link

Choose a reason for hiding this comment

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

How am I gonna see the comment as a user of this? I think it's unreasonable to assume that every consumer of the class is gonna look at its code.
I agree that it's exceedingly unfortunate that Java fails completely and allows null Optionals. However, if we expose a optional we are telling the consumer explicitly that they need to concern themselves with the empty case. We are also telling them that they shouldn't worry about the optional itself being null, because that's the concern of our method and not theirs. If we function as promised it's not gonna be null. I'd rather expose that contract explicitly than throw some text into the file that is only meaningful to humans who happen to look at the implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Optional doesn't really fix the problem, just introduces more GC pressure :-)

Wouldn't it be better to throw IllegalStateException when the precondition is not met?

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'll change this to throw AuthenticationRequiredException if authentication has not yet been performed.

*/
package org.apache.geode.security;

public interface StreamAuthorizer {

Choose a reason for hiding this comment

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

Does it make sense to remove the Stream part of this name? There seems to be nothing about this interface that is related to streams at all.

if (context.getAuthorizer().authorize(operationContext.getAccessPermissionRequired())) {
result = operationContext.getOperationHandler().process(serializationService,
operationContext.getFromRequest().apply(request), context);
} else {

Choose a reason for hiding this comment

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

We might want to log this. Logging all security related events can be a life saver during or after a security incident.

.build().writeDelimitedTo(outputStream);
}

@Override
public boolean isAuthenticated() {
return authenticated;
// note: an authorizer is only created if the user has been authenticated
return authorizer != null;

Choose a reason for hiding this comment

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

this would be nicer as well if Optional

import org.apache.geode.test.junit.categories.IntegrationTest;

@Category(IntegrationTest.class)
public class AuthorizationIntegrationTest {

Choose a reason for hiding this comment

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

Hugs for not adding this to the existing Roundtrip test! 🤗

Copy link
Contributor

@hiteshk25 hiteshk25 left a comment

Choose a reason for hiding this comment

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

Are we introducing new "Authorizer" interface which app needs to implement?

Copy link
Contributor

@metatype metatype left a comment

Choose a reason for hiding this comment

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

I'm confused why we need both SecurityManager and StreamAuthenticator. Are we bleeding communication/protocol ideas into the role-based access control domain?

@galen-pivotal
Copy link
Contributor

galen-pivotal commented Aug 25, 2017

@metatype We need the StreamAuthenticator to receive and send (Protobuf-encoded) messages containing the credentials that get passed to the SecurityManager. I would think that ideally it's nothing more than this.

I wonder if it would be better to send a hash that gets put into the Properties that SecurityManager uses, rather than having a message that explicitly contains username and password?

@metatype
Copy link
Contributor

I'm not suggesting changing the protobuf message. That seems ok to me.

Since the StreamAuthenticator is part of protobuf messaging I think it should remain in the geode-protobuf module. Putting it in a public API package for RBAC seems like we're leaking abstractions into other parts of the codebase.

Am I missing something?

@bschuchardt
Copy link
Contributor Author

@metatype StreamAuthenticator wasn't added in this change set. We added a StreamAuthorizer. Both of these are needed in geode-core because SecurityManager doesn't know about communication streams and we need these interfaces to be able to plug in checks in the geode-protobuf module.

@bschuchardt bschuchardt reopened this Aug 28, 2017
@bschuchardt
Copy link
Contributor Author

@metatype After reviewing StreamAuthenticator I get your point. I think these new interfaces and classes need to be in a different package.

@PurelyApplied PurelyApplied deleted the feature/GEODE-3447 branch November 16, 2018 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants