-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Security: fix joining cluster with production license #31341
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,12 +9,14 @@ | |||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.Version; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.action.ActionListener; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.action.support.DestructiveOperations; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.cluster.service.ClusterService; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.CheckedConsumer; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.component.AbstractComponent; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.settings.Setting; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.settings.Settings; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.util.concurrent.AbstractRunnable; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.common.util.concurrent.ThreadContext; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.gateway.GatewayService; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.license.XPackLicenseState; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.tasks.Task; | ||||||||||||||||||||||||||||||||||||||||||||||
import org.elasticsearch.threadpool.ThreadPool; | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -72,14 +74,17 @@ public class SecurityServerTransportInterceptor extends AbstractComponent implem | |||||||||||||||||||||||||||||||||||||||||||||
private final SecurityContext securityContext; | ||||||||||||||||||||||||||||||||||||||||||||||
private final boolean reservedRealmEnabled; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
private volatile boolean isStateNotRecovered = true; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
public SecurityServerTransportInterceptor(Settings settings, | ||||||||||||||||||||||||||||||||||||||||||||||
ThreadPool threadPool, | ||||||||||||||||||||||||||||||||||||||||||||||
AuthenticationService authcService, | ||||||||||||||||||||||||||||||||||||||||||||||
AuthorizationService authzService, | ||||||||||||||||||||||||||||||||||||||||||||||
XPackLicenseState licenseState, | ||||||||||||||||||||||||||||||||||||||||||||||
SSLService sslService, | ||||||||||||||||||||||||||||||||||||||||||||||
SecurityContext securityContext, | ||||||||||||||||||||||||||||||||||||||||||||||
DestructiveOperations destructiveOperations) { | ||||||||||||||||||||||||||||||||||||||||||||||
DestructiveOperations destructiveOperations, | ||||||||||||||||||||||||||||||||||||||||||||||
ClusterService clusterService) { | ||||||||||||||||||||||||||||||||||||||||||||||
super(settings); | ||||||||||||||||||||||||||||||||||||||||||||||
this.settings = settings; | ||||||||||||||||||||||||||||||||||||||||||||||
this.threadPool = threadPool; | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -90,6 +95,7 @@ public SecurityServerTransportInterceptor(Settings settings, | |||||||||||||||||||||||||||||||||||||||||||||
this.securityContext = securityContext; | ||||||||||||||||||||||||||||||||||||||||||||||
this.profileFilters = initializeProfileFilters(destructiveOperations); | ||||||||||||||||||||||||||||||||||||||||||||||
this.reservedRealmEnabled = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings); | ||||||||||||||||||||||||||||||||||||||||||||||
clusterService.addListener(e -> isStateNotRecovered = e.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -98,7 +104,9 @@ public AsyncSender interceptSender(AsyncSender sender) { | |||||||||||||||||||||||||||||||||||||||||||||
@Override | ||||||||||||||||||||||||||||||||||||||||||||||
public <T extends TransportResponse> void sendRequest(Transport.Connection connection, String action, TransportRequest request, | ||||||||||||||||||||||||||||||||||||||||||||||
TransportRequestOptions options, TransportResponseHandler<T> handler) { | ||||||||||||||||||||||||||||||||||||||||||||||
if (licenseState.isSecurityEnabled() && licenseState.isAuthAllowed()) { | ||||||||||||||||||||||||||||||||||||||||||||||
final boolean stateNotRecovered = isStateNotRecovered; | ||||||||||||||||||||||||||||||||||||||||||||||
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. The purposes of this assignment (from volatile to local) initially confused me, and I'm worried someone might think it is redundant and remove it in the future. Can we add a comment to try and discourage that from happening? |
||||||||||||||||||||||||||||||||||||||||||||||
final boolean sendWithAuth = (licenseState.isSecurityEnabled() && licenseState.isAuthAllowed()) || stateNotRecovered; | ||||||||||||||||||||||||||||||||||||||||||||||
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. It seems that if the state is not recovered, then we're going to 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. Same thought as @tvernum here. 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. The interceptor is not registered at all if security is disabled. elasticsearch/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java Lines 808 to 829 in 3b70e94
This change limits to only sending auth when security could possibly be enabled. Once we've recovered the state then the check is based upon the license. By having the check we maintain the ability to assert that all outgoing requests have authentication after the state has been recovered. |
||||||||||||||||||||||||||||||||||||||||||||||
if (sendWithAuth) { | ||||||||||||||||||||||||||||||||||||||||||||||
// the transport in core normally does this check, BUT since we are serializing to a string header we need to do it | ||||||||||||||||||||||||||||||||||||||||||||||
// ourselves otherwise we wind up using a version newer than what we can actually send | ||||||||||||||||||||||||||||||||||||||||||||||
final Version minVersion = Version.min(connection.getVersion(), Version.CURRENT); | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -108,20 +116,20 @@ public <T extends TransportResponse> void sendRequest(Transport.Connection conne | |||||||||||||||||||||||||||||||||||||||||||||
if (AuthorizationUtils.shouldReplaceUserWithSystem(threadPool.getThreadContext(), action)) { | ||||||||||||||||||||||||||||||||||||||||||||||
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> sendWithUser(connection, action, request, options, | ||||||||||||||||||||||||||||||||||||||||||||||
new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original) | ||||||||||||||||||||||||||||||||||||||||||||||
, handler), sender), minVersion); | ||||||||||||||||||||||||||||||||||||||||||||||
, handler), sender, stateNotRecovered), minVersion); | ||||||||||||||||||||||||||||||||||||||||||||||
} else if (AuthorizationUtils.shouldSetUserBasedOnActionOrigin(threadPool.getThreadContext())) { | ||||||||||||||||||||||||||||||||||||||||||||||
AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadPool.getThreadContext(), securityContext, | ||||||||||||||||||||||||||||||||||||||||||||||
(original) -> sendWithUser(connection, action, request, options, | ||||||||||||||||||||||||||||||||||||||||||||||
new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original) | ||||||||||||||||||||||||||||||||||||||||||||||
, handler), sender)); | ||||||||||||||||||||||||||||||||||||||||||||||
, handler), sender, stateNotRecovered)); | ||||||||||||||||||||||||||||||||||||||||||||||
} else if (securityContext.getAuthentication() != null && | ||||||||||||||||||||||||||||||||||||||||||||||
securityContext.getAuthentication().getVersion().equals(minVersion) == false) { | ||||||||||||||||||||||||||||||||||||||||||||||
// re-write the authentication since we want the authentication version to match the version of the connection | ||||||||||||||||||||||||||||||||||||||||||||||
securityContext.executeAfterRewritingAuthentication(original -> sendWithUser(connection, action, request, options, | ||||||||||||||||||||||||||||||||||||||||||||||
new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original), handler), sender), | ||||||||||||||||||||||||||||||||||||||||||||||
minVersion); | ||||||||||||||||||||||||||||||||||||||||||||||
new ContextRestoreResponseHandler<>(threadPool.getThreadContext().wrapRestorable(original), handler), sender, | ||||||||||||||||||||||||||||||||||||||||||||||
stateNotRecovered), minVersion); | ||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||
sendWithUser(connection, action, request, options, handler, sender); | ||||||||||||||||||||||||||||||||||||||||||||||
sendWithUser(connection, action, request, options, handler, sender, stateNotRecovered); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||
sender.sendRequest(connection, action, request, options, handler); | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -132,9 +140,10 @@ public <T extends TransportResponse> void sendRequest(Transport.Connection conne | |||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
private <T extends TransportResponse> void sendWithUser(Transport.Connection connection, String action, TransportRequest request, | ||||||||||||||||||||||||||||||||||||||||||||||
TransportRequestOptions options, TransportResponseHandler<T> handler, | ||||||||||||||||||||||||||||||||||||||||||||||
AsyncSender sender) { | ||||||||||||||||||||||||||||||||||||||||||||||
// There cannot be a request outgoing from this node that is not associated with a user. | ||||||||||||||||||||||||||||||||||||||||||||||
if (securityContext.getAuthentication() == null) { | ||||||||||||||||||||||||||||||||||||||||||||||
AsyncSender sender, final boolean stateNotRecovered) { | ||||||||||||||||||||||||||||||||||||||||||||||
// There cannot be a request outgoing from this node that is not associated with a user | ||||||||||||||||||||||||||||||||||||||||||||||
// unless we do not know the actual license of the cluster | ||||||||||||||||||||||||||||||||||||||||||||||
if (securityContext.getAuthentication() == null && stateNotRecovered == false) { | ||||||||||||||||||||||||||||||||||||||||||||||
// we use an assertion here to ensure we catch this in our testing infrastructure, but leave the ISE for cases we do not catch | ||||||||||||||||||||||||||||||||||||||||||||||
// in tests and may be hit by a user | ||||||||||||||||||||||||||||||||||||||||||||||
assertNoAuthentication(action); | ||||||||||||||||||||||||||||||||||||||||||||||
|
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 was a suggestion from @bleskes that I went ahead and adopted as well
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.
Can you explain the reasoning behind doing this?
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 for cases where a user with a production license on 6.0 to 6.2.x upgrades to 6.3.1+. We require TLS for production mode, so if TLS is enabled we can take that as security being explicitly enabled.