-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Make Authentication/Authorization Stacks Shallower/Simpler #75662
Merged
original-brownbear
merged 4 commits into
elastic:master
from
original-brownbear:shallower-stacks-authentication
Jul 29, 2021
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d7f1113
Make Authentication/Authorization Stacks Shallower/Simpler
original-brownbear 5840c2f
more needless runnable
original-brownbear 4d00441
Merge remote-tracking branch 'elastic/master' into shallower-stacks-a…
original-brownbear ad52af6
NIT: cast cleaner
original-brownbear File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -193,7 +193,7 @@ public void authenticate(String action, TransportRequest transportRequest, boole | |
*/ | ||
public void authenticate(String action, TransportRequest transportRequest, | ||
AuthenticationToken token, ActionListener<Authentication> listener) { | ||
new Authenticator(action, transportRequest, shouldFallbackToAnonymous(true), listener).authenticateToken(token); | ||
new Authenticator(action, transportRequest, shouldFallbackToAnonymous(true), listener).consumeToken(token); | ||
} | ||
|
||
public void expire(String principal) { | ||
|
@@ -314,9 +314,9 @@ private Authenticator(AuditableRequest auditableRequest, User fallbackUser, bool | |
* these operations are: | ||
* | ||
* <ol> | ||
* <li>look for existing authentication {@link #lookForExistingAuthentication(Consumer)}</li> | ||
* <li>look for existing authentication {@link #lookForExistingAuthentication()}</li> | ||
* <li>look for a user token</li> | ||
* <li>token extraction {@link #extractToken(Consumer)}</li> | ||
* <li>token extraction {@link #extractToken()}</li> | ||
* <li>token authentication {@link #consumeToken(AuthenticationToken)}</li> | ||
* <li>user lookup for run as if necessary {@link #consumeUser(User, Map)} and | ||
* {@link #lookupRunAsUser(User, String, Consumer)}</li> | ||
|
@@ -330,14 +330,19 @@ private void authenticateAsync() { | |
logger.debug("No realms available, failing authentication"); | ||
listener.onResponse(null); | ||
} else { | ||
lookForExistingAuthentication((authentication) -> { | ||
if (authentication != null) { | ||
logger.trace("Found existing authentication [{}] in request [{}]", authentication, request); | ||
listener.onResponse(authentication); | ||
} else { | ||
checkForBearerToken(); | ||
} | ||
}); | ||
final Authentication authentication; | ||
try { | ||
authentication = lookForExistingAuthentication(); | ||
} catch (Exception e) { | ||
listener.onFailure(e); | ||
return; | ||
} | ||
if (authentication != null) { | ||
logger.trace("Found existing authentication [{}] in request [{}]", authentication, request); | ||
listener.onResponse(authentication); | ||
} else { | ||
checkForBearerToken(); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -393,7 +398,14 @@ private void checkForApiKey() { | |
logger.warn("Authentication using apikey failed - {}", authResult.getMessage()); | ||
} | ||
} | ||
extractToken(this::consumeToken); | ||
final AuthenticationToken token; | ||
try { | ||
token = extractToken(); | ||
} catch (Exception e) { | ||
listener.onFailure(e); | ||
return; | ||
} | ||
consumeToken(token); | ||
} | ||
}, | ||
e -> listener.onFailure(request.exceptionProcessingRequest(e, null)))); | ||
|
@@ -404,25 +416,20 @@ private void checkForApiKey() { | |
* consumer is called if no exception was thrown while trying to read the authentication and may be called with a {@code null} | ||
* value | ||
*/ | ||
private void lookForExistingAuthentication(Consumer<Authentication> authenticationConsumer) { | ||
Runnable action; | ||
private Authentication lookForExistingAuthentication() { | ||
final Authentication authentication; | ||
try { | ||
final Authentication authentication = authenticationSerializer.readFromContext(threadContext); | ||
if (authentication != null && request instanceof AuditableRestRequest) { | ||
action = () -> listener.onFailure(request.tamperedRequest()); | ||
} else { | ||
action = () -> authenticationConsumer.accept(authentication); | ||
} | ||
authentication = authenticationSerializer.readFromContext(threadContext); | ||
} catch (Exception e) { | ||
logger.error((Supplier<?>) | ||
() -> new ParameterizedMessage("caught exception while trying to read authentication from request [{}]", request), | ||
e); | ||
action = () -> listener.onFailure(request.tamperedRequest()); | ||
throw request.tamperedRequest(); | ||
} | ||
|
||
// While we could place this call in the try block, the issue is that we catch all exceptions and could catch exceptions that | ||
// have nothing to do with a tampered request. | ||
action.run(); | ||
if (authentication != null && request instanceof AuditableRestRequest) { | ||
throw request.tamperedRequest(); | ||
} | ||
return authentication; | ||
} | ||
|
||
/** | ||
|
@@ -431,28 +438,26 @@ private void lookForExistingAuthentication(Consumer<Authentication> authenticati | |
* no exception was caught during the extraction process and may be called with a {@code null} token. | ||
*/ | ||
// pkg-private accessor testing token extraction with a consumer | ||
void extractToken(Consumer<AuthenticationToken> consumer) { | ||
Runnable action = () -> consumer.accept(null); | ||
AuthenticationToken extractToken() { | ||
try { | ||
if (authenticationToken != null) { | ||
action = () -> consumer.accept(authenticationToken); | ||
return authenticationToken; | ||
} else { | ||
for (Realm realm : defaultOrderedRealmList) { | ||
final AuthenticationToken token = realm.token(threadContext); | ||
if (token != null) { | ||
logger.trace("Found authentication credentials [{}] for principal [{}] in request [{}]", | ||
token.getClass().getName(), token.principal(), request); | ||
action = () -> consumer.accept(token); | ||
break; | ||
return token; | ||
} | ||
} | ||
} | ||
} catch (Exception e) { | ||
logger.warn("An exception occurred while attempting to find authentication credentials", e); | ||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, null)); | ||
throw request.exceptionProcessingRequest(e, null); | ||
} | ||
|
||
action.run(); | ||
return null; | ||
} | ||
|
||
/** | ||
|
@@ -606,19 +611,12 @@ void handleNullToken() { | |
authentication = null; | ||
} | ||
|
||
Runnable action; | ||
if (authentication != null) { | ||
action = () -> writeAuthToContext(authentication); | ||
writeAuthToContext(authentication); | ||
} else { | ||
action = () -> { | ||
logger.debug("No valid credentials found in request [{}], rejecting", request); | ||
listener.onFailure(request.anonymousAccessDenied()); | ||
}; | ||
logger.debug("No valid credentials found in request [{}], rejecting", request); | ||
listener.onFailure(request.anonymousAccessDenied()); | ||
} | ||
|
||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing when | ||
// an exception bubbles up even after successful authentication | ||
action.run(); | ||
} | ||
|
||
/** | ||
|
@@ -712,32 +710,23 @@ void finishAuthentication(User finalUser) { | |
* successful | ||
*/ | ||
void writeAuthToContext(Authentication authentication) { | ||
Runnable action = () -> { | ||
logger.trace("Established authentication [{}] for request [{}]", authentication, request); | ||
listener.onResponse(authentication); | ||
}; | ||
try { | ||
authenticationSerializer.writeToContext(authentication, threadContext); | ||
request.authenticationSuccess(authentication); | ||
// Header for operator privileges will only be written if authentication actually happens, | ||
// i.e. not read from either header or transient header | ||
operatorPrivilegesService.maybeMarkOperatorUser(authentication, threadContext); | ||
} catch (Exception e) { | ||
action = () -> { | ||
logger.debug( | ||
logger.debug( | ||
new ParameterizedMessage("Failed to store authentication [{}] for request [{}]", authentication, request), e); | ||
listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken)); | ||
}; | ||
listener.onFailure(request.exceptionProcessingRequest(e, authenticationToken)); | ||
return; | ||
} | ||
|
||
// we assign the listener call to an action to avoid calling the listener within a try block and auditing the wrong thing | ||
// when an exception bubbles up even after successful authentication | ||
action.run(); | ||
Comment on lines
-733
to
-735
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok the same comment makes more sense here (because it has a try/catch block). As I suspected, it is no longer applicable even before this PR. |
||
logger.trace("Established authentication [{}] for request [{}]", authentication, request); | ||
listener.onResponse(authentication); | ||
} | ||
|
||
private void authenticateToken(AuthenticationToken token) { | ||
this.consumeToken(token); | ||
} | ||
} | ||
|
||
abstract static class AuditableRequest { | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Don't quite get this existing comment. But I suspect the original concern may not exist any more.