-
Notifications
You must be signed in to change notification settings - Fork 682
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
Conversation
} | ||
|
||
@Override | ||
public boolean authorize(ResourcePermission permissionRequested) { |
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.
why would I not pass in the Principal and requested permissions? Then I can have 1 authorizer and not 1 per user.
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'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; |
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 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
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.
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); |
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.
Why do we need a ProtobufSimpleAuthorizer
per principal? Could we not have a single Authorizer
that will authenticate all principals?
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.
The Principal is part of the implementation of this authentication & authorization algorithm and is encapsulated by this class.
Implementation of authorization checks for the new protocol.
404eeca
to
ea1d8c1
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.
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); |
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.
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, |
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.
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 { |
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.
+1 thorough and clear tests.
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. |
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.
Okay, if we've got no-op on the locator then ship it!
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.
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. |
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.
Could we indicate this by using a Optional? That way we also force the consumer to deal with that case.
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 kind of prefer a comment or even annotation saying nullable -- optionals themselves can be null, and have some cost.
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.
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.
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.
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?
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.
We'll change this to throw AuthenticationRequiredException if authentication has not yet been performed.
*/ | ||
package org.apache.geode.security; | ||
|
||
public interface StreamAuthorizer { |
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.
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 { |
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.
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; |
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.
this would be nicer as well if Optional
import org.apache.geode.test.junit.categories.IntegrationTest; | ||
|
||
@Category(IntegrationTest.class) | ||
public class AuthorizationIntegrationTest { |
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.
Hugs for not adding this to the existing Roundtrip 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.
Are we introducing new "Authorizer" interface which app needs to implement?
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'm confused why we need both SecurityManager
and StreamAuthenticator
. Are we bleeding communication/protocol ideas into the role-based access control domain?
@metatype We need the I wonder if it would be better to send a hash that gets put into the |
I'm not suggesting changing the protobuf message. That seems ok to me. Since the Am I missing something? |
@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. |
@metatype After reviewing StreamAuthenticator I get your point. I think these new interfaces and classes need to be in a different package. |
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.