Prevent 7.x nodes from joining cluster with un-upgraded 6.x .security indices (elastic/x-pack-elasticsearch#2921) (elastic/x-pack-elasticsearch#2940)

This is a forward-port of elastic/x-pack-elasticsearch/pull/2921.

original commit message:

Before this commit, a cluster with security enabled and backed by
native-realm user permissions allowed rolled upgrades to clusters without
upgrading the `.security` index. This resulted in the newly established
6.0 cluster not able to register the native-realm users previously established
in the `.security` index. In order to fix this, one would have to rely on file-based
users to re-configure and upgrade the `.security` index. Since this state is easily
avoidable with an upgrade, this commit rejects the joining of upgraded nodes without
upgrading the security index beforehand.

modifications:

Test with 7.x vs 6.x nodes.

Original commit: elastic/x-pack-elasticsearch@56f81bfb20
This commit is contained in:
Tal Levy 2017-11-09 12:49:59 -08:00 committed by GitHub
parent 0c10d82f78
commit 8c489b1a98
2 changed files with 98 additions and 3 deletions

View File

@ -17,6 +17,7 @@ import org.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
@ -198,8 +199,12 @@ import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
import static org.elasticsearch.xpack.XPackSettings.HTTP_SSL_ENABLED;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME;
import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.INTERNAL_INDEX_FORMAT;
import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.INTERNAL_SECURITY_INDEX;
public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin {
@ -938,7 +943,11 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
@Override
public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
return enabled ? new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings)) : null;
if (enabled) {
return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings))
.andThen(new ValidateUpgradedSecurityIndex());
}
return null;
}
static final class ValidateTLSOnJoin implements BiConsumer<DiscoveryNode, ClusterState> {
@ -956,4 +965,17 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
}
}
}
static final class ValidateUpgradedSecurityIndex implements BiConsumer<DiscoveryNode, ClusterState> {
@Override
public void accept(DiscoveryNode node, ClusterState state) {
if (state.getNodes().getMinNodeVersion().before(Version.V_7_0_0_alpha1)) {
IndexMetaData indexMetaData = state.getMetaData().getIndices().get(SECURITY_INDEX_NAME);
if (indexMetaData != null && INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()) < INTERNAL_INDEX_FORMAT) {
throw new IllegalStateException("Security index is not on the current version [" + INTERNAL_INDEX_FORMAT + "] - " +
"The Upgrade API must be run for 7.x nodes to join the cluster");
}
}
}
}
}

View File

@ -19,8 +19,10 @@ import org.elasticsearch.Version;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.ClusterSettings;
@ -46,7 +48,11 @@ import org.elasticsearch.xpack.security.authc.file.FileRealm;
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
import org.elasticsearch.xpack.ssl.SSLService;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.INTERNAL_INDEX_FORMAT;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -203,7 +209,7 @@ public class SecurityTests extends ESTestCase {
assertThat(filter, hasItem(Security.setting("authc.realms.*.ssl.truststore.algorithm")));
}
public void testTLSJoinValidatorOnDisabledSecurity() throws Exception {
public void testJoinValidatorOnDisabledSecurity() throws Exception {
Settings disabledSettings = Settings.builder().put("xpack.security.enabled", false).build();
createComponents(disabledSettings);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
@ -216,7 +222,6 @@ public class SecurityTests extends ESTestCase {
assertNotNull(joinValidator);
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT);
joinValidator.accept(node, ClusterState.builder(ClusterName.DEFAULT).build());
assertTrue(joinValidator instanceof Security.ValidateTLSOnJoin);
int numIters = randomIntBetween(1,10);
for (int i = 0; i < numIters; i++) {
boolean tlsOn = randomBoolean();
@ -236,4 +241,72 @@ public class SecurityTests extends ESTestCase {
validator.accept(node, ClusterState.builder(ClusterName.DEFAULT).metaData(MetaData.builder().build()).build());
}
}
public void testIndexJoinValidator_Old_And_Rolling() throws Exception {
createComponents(Settings.EMPTY);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNotNull(joinValidator);
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT);
IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME)
.settings(settings(Version.V_6_1_0).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_INDEX_FORMAT - 1))
.numberOfShards(1).numberOfReplicas(0)
.build();
DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), Version.V_6_1_0);
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(existingOtherNode).build();
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
.nodes(discoveryNodes)
.metaData(MetaData.builder().put(indexMetaData, true).build()).build();
IllegalStateException e = expectThrows(IllegalStateException.class,
() -> joinValidator.accept(node, clusterState));
assertThat(e.getMessage(), equalTo("Security index is not on the current version [6] - " +
"The Upgrade API must be run for 7.x nodes to join the cluster"));
}
public void testIndexJoinValidator_FullyCurrentCluster() throws Exception {
createComponents(Settings.EMPTY);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNotNull(joinValidator);
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT);
int indexFormat = randomBoolean() ? INTERNAL_INDEX_FORMAT : INTERNAL_INDEX_FORMAT - 1;
IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME)
.settings(settings(Version.V_6_1_0).put(INDEX_FORMAT_SETTING.getKey(), indexFormat))
.numberOfShards(1).numberOfReplicas(0)
.build();
DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), Version.CURRENT);
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(existingOtherNode).build();
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
.nodes(discoveryNodes)
.metaData(MetaData.builder().put(indexMetaData, true).build()).build();
joinValidator.accept(node, clusterState);
}
public void testIndexUpgradeValidatorWithUpToDateIndex() throws Exception {
createComponents(Settings.EMPTY);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNotNull(joinValidator);
Version version = randomBoolean() ? Version.CURRENT : Version.V_6_1_0;
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT);
IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME)
.settings(settings(version).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_INDEX_FORMAT))
.numberOfShards(1).numberOfReplicas(0)
.build();
DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), version);
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(existingOtherNode).build();
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
.nodes(discoveryNodes)
.metaData(MetaData.builder().put(indexMetaData, true).build()).build();
joinValidator.accept(node, clusterState);
}
public void testIndexUpgradeValidatorWithMissingIndex() throws Exception {
createComponents(Settings.EMPTY);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNotNull(joinValidator);
DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT);
DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), Version.V_6_1_0);
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder().add(existingOtherNode).build();
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT)
.nodes(discoveryNodes).build();
joinValidator.accept(node, clusterState);
}
}