Adds a granular licensing support to SQL. JDBC now requires a platinum license, everything else work with any non-expired license.

Original commit: elastic/x-pack-elasticsearch@a30470e2c9
This commit is contained in:
Igor Motov 2017-08-10 19:19:09 -04:00 committed by GitHub
parent 9d81805616
commit a0de35d801
9 changed files with 357 additions and 8 deletions

View File

@ -57,6 +57,9 @@ public class XPackLicenseState {
messages.put(XPackPlugin.UPGRADE, new String[] {
"Upgrade API is disabled"
});
messages.put(XPackPlugin.SQL, new String[] {
"SQL support is disabled"
});
EXPIRATION_MESSAGES = Collections.unmodifiableMap(messages);
}
@ -73,6 +76,7 @@ public class XPackLicenseState {
messages.put(XPackPlugin.GRAPH, XPackLicenseState::graphAcknowledgementMessages);
messages.put(XPackPlugin.MACHINE_LEARNING, XPackLicenseState::machineLearningAcknowledgementMessages);
messages.put(XPackPlugin.LOGSTASH, XPackLicenseState::logstashAcknowledgementMessages);
messages.put(XPackPlugin.SQL, XPackLicenseState::sqlAcknowledgementMessages);
ACKNOWLEDGMENT_MESSAGES = Collections.unmodifiableMap(messages);
}
@ -209,6 +213,21 @@ public class XPackLicenseState {
return Strings.EMPTY_ARRAY;
}
private static String[] sqlAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) {
switch (newMode) {
case BASIC:
case STANDARD:
case GOLD:
switch (currentMode) {
case TRIAL:
case PLATINUM:
return new String[] { "JDBC support will be disabled, but you can continue to use SQL CLI and REST endpoint" };
}
break;
}
return Strings.EMPTY_ARRAY;
}
/** A wrapper for the license mode and state, to allow atomically swapping. */
private static class Status {
@ -491,4 +510,29 @@ public class XPackLicenseState {
// Should work on all active licenses
return localStatus.active;
}
/**
* Determine if SQL support should be enabled.
* <p>
* SQL is available for all license types except {@link OperationMode#MISSING}
*/
public boolean isSqlAllowed() {
return status.active;
}
/**
* Determine if JDBC support should be enabled.
* <p>
* JDBC is available only in for {@link OperationMode#PLATINUM} and {@link OperationMode#TRIAL} licences
*/
public boolean isJdbcAllowed() {
// status is volatile
Status localStatus = status;
OperationMode operationMode = localStatus.mode;
boolean licensed = operationMode == OperationMode.TRIAL || operationMode == OperationMode.PLATINUM;
return licensed && localStatus.active;
}
}

View File

@ -38,6 +38,7 @@ import org.elasticsearch.index.IndexModule;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.license.LicenseService;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.Licensing;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
@ -101,6 +102,7 @@ import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.crypto.CryptoService;
import org.elasticsearch.xpack.sql.analysis.catalog.FilteredCatalog;
import org.elasticsearch.xpack.sql.plugin.SecurityCatalogFilter;
import org.elasticsearch.xpack.sql.plugin.SqlLicenseChecker;
import org.elasticsearch.xpack.sql.plugin.SqlPlugin;
import org.elasticsearch.xpack.ssl.SSLConfigurationReloader;
import org.elasticsearch.xpack.ssl.SSLService;
@ -161,6 +163,9 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
/** Name constant for the upgrade feature. */
public static final String UPGRADE = "upgrade";
/** Name constant for the sql feature. */
public static final String SQL = "sql";
// inside of YAML settings we still use xpack do not having handle issues with dashes
private static final String SETTINGS_NAME = "xpack";
@ -234,7 +239,19 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
this.logstash = new Logstash(settings);
this.deprecation = new Deprecation();
this.upgrade = new Upgrade(settings);
this.sql = new SqlPlugin();
// sql projects don't depend on x-pack and as a result we cannot pass XPackLicenseState object to SqlPlugin directly here
this.sql = new SqlPlugin(new SqlLicenseChecker(
() -> {
if (!licenseState.isSqlAllowed()) {
throw LicenseUtils.newComplianceException(XPackPlugin.SQL);
}
},
() -> {
if (!licenseState.isJdbcAllowed()) {
throw LicenseUtils.newComplianceException("jdbc");
}
})
);
// Check if the node is a transport client.
if (transportClientMode == false) {
this.extensionsService = new XPackExtensionsService(settings, resolveXPackExtensionsFile(env), getExtensions());

View File

@ -53,14 +53,22 @@ public class XPackLicenseStateTests extends ESTestCase {
return randomFrom(OperationMode.values());
}
static OperationMode randomTrialStandardGoldOrPlatinumMode() {
public static OperationMode randomTrialStandardGoldOrPlatinumMode() {
return randomFrom(TRIAL, STANDARD, GOLD, PLATINUM);
}
static OperationMode randomTrialOrPlatinumMode() {
public static OperationMode randomTrialOrPlatinumMode() {
return randomFrom(TRIAL, PLATINUM);
}
public static OperationMode randomTrialBasicStandardGoldOrPlatinumMode() {
return randomFrom(TRIAL, BASIC, STANDARD, GOLD, PLATINUM);
}
public static OperationMode randomBasicStandardOrGold() {
return randomFrom(BASIC, STANDARD, GOLD);
}
public void testSecurityDefaults() {
XPackLicenseState licenseState = new XPackLicenseState();
assertThat(licenseState.isAuthAllowed(), is(true));
@ -310,4 +318,83 @@ public class XPackLicenseStateTests extends ESTestCase {
assertAllowed(PLATINUM, false, XPackLicenseState::isLogstashAllowed, false);
assertAllowed(STANDARD, false, XPackLicenseState::isLogstashAllowed, false);
}
public void testSqlDefaults() {
XPackLicenseState licenseState = new XPackLicenseState();
assertThat(licenseState.isSqlAllowed(), is(true));
assertThat(licenseState.isJdbcAllowed(), is(true));
}
public void testSqlBasic() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(BASIC, true);
assertThat(licenseState.isSqlAllowed(), is(true));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlBasicExpired() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(BASIC, false);
assertThat(licenseState.isSqlAllowed(), is(false));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlStandard() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(STANDARD, true);
assertThat(licenseState.isSqlAllowed(), is(true));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlStandardExpired() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(STANDARD, false);
assertThat(licenseState.isSqlAllowed(), is(false));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlGold() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(GOLD, true);
assertThat(licenseState.isSqlAllowed(), is(true));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlGoldExpired() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(GOLD, false);
assertThat(licenseState.isSqlAllowed(), is(false));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlPlatinum() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(PLATINUM, true);
assertThat(licenseState.isSqlAllowed(), is(true));
assertThat(licenseState.isJdbcAllowed(), is(true));
}
public void testSqlPlatinumExpired() {
XPackLicenseState licenseState = new XPackLicenseState();
licenseState.update(PLATINUM, false);
assertThat(licenseState.isSqlAllowed(), is(false));
assertThat(licenseState.isJdbcAllowed(), is(false));
}
public void testSqlAckAnyToTrialOrPlatinum() {
assertAckMesssages(XPackPlugin.SQL, randomMode(), randomTrialOrPlatinumMode(), 0);
}
public void testSqlAckTrialOrPlatinumToNotTrialOrPlatinum() {
assertAckMesssages(XPackPlugin.SQL, randomTrialOrPlatinumMode(), randomBasicStandardOrGold(), 1);
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.AbstractLicensesIntegrationTestCase;
import org.elasticsearch.license.License;
import org.elasticsearch.license.License.OperationMode;
import org.elasticsearch.xpack.sql.cli.net.protocol.CommandRequest;
import org.elasticsearch.xpack.sql.jdbc.net.protocol.MetaTableRequest;
import org.elasticsearch.xpack.sql.jdbc.net.protocol.MetaTableResponse;
import org.elasticsearch.xpack.sql.plugin.cli.action.CliAction;
import org.elasticsearch.xpack.sql.plugin.cli.action.CliResponse;
import org.elasticsearch.xpack.sql.plugin.jdbc.action.JdbcAction;
import org.elasticsearch.xpack.sql.plugin.jdbc.action.JdbcResponse;
import org.elasticsearch.xpack.sql.plugin.sql.action.SqlAction;
import org.elasticsearch.xpack.sql.plugin.sql.action.SqlResponse;
import org.elasticsearch.xpack.sql.protocol.shared.Request;
import org.elasticsearch.xpack.sql.protocol.shared.Response;
import org.hamcrest.Matchers;
import org.junit.Before;
import java.util.Locale;
import static org.elasticsearch.license.XPackLicenseStateTests.randomBasicStandardOrGold;
import static org.elasticsearch.license.XPackLicenseStateTests.randomTrialBasicStandardGoldOrPlatinumMode;
import static org.elasticsearch.license.XPackLicenseStateTests.randomTrialOrPlatinumMode;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.core.IsEqual.equalTo;
public class SqlLicenseIT extends AbstractLicensesIntegrationTestCase {
@Override
protected boolean ignoreExternalCluster() {
return true;
}
@Before
public void resetLicensing() throws Exception {
enableJdbcLicensing();
}
private static OperationMode randomValidSqlLicenseType() {
return randomTrialBasicStandardGoldOrPlatinumMode();
}
private static OperationMode randomInvalidSqlLicenseType() {
return OperationMode.MISSING;
}
private static OperationMode randomValidJdbcLicenseType() {
return randomTrialOrPlatinumMode();
}
private static OperationMode randomInvalidJdbcLicenseType() {
return randomBasicStandardOrGold();
}
public void enableSqlLicensing() throws Exception {
updateLicensing(randomValidSqlLicenseType());
}
public void disableSqlLicensing() throws Exception {
updateLicensing(randomInvalidSqlLicenseType());
}
public void enableJdbcLicensing() throws Exception {
updateLicensing(randomValidJdbcLicenseType());
}
public void disableJdbcLicensing() throws Exception {
updateLicensing(randomInvalidJdbcLicenseType());
}
public void updateLicensing(OperationMode licenseOperationMode) throws Exception {
String licenseType = licenseOperationMode.name().toLowerCase(Locale.ROOT);
wipeAllLicenses();
if (licenseType.equals("missing")) {
putLicenseTombstone();
} else {
License license = org.elasticsearch.license.TestUtils.generateSignedLicense(licenseType, TimeValue.timeValueMinutes(1));
putLicense(license);
}
}
public void testSqlActionLicense() throws Exception {
setupTestIndex();
disableSqlLicensing();
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class,
() -> client().prepareExecute(SqlAction.INSTANCE).query("SELECT * FROM test").get());
assertThat(e.getMessage(), equalTo("current license is non-compliant for [sql]"));
enableSqlLicensing();
SqlResponse response = client().prepareExecute(SqlAction.INSTANCE).query("SELECT * FROM test").get();
assertThat(response.size(), Matchers.equalTo(2L));
}
public void testCliActionLicense() throws Exception {
setupTestIndex();
disableSqlLicensing();
Request request = new CommandRequest("SELECT * FROM test");
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class,
() -> client().prepareExecute(CliAction.INSTANCE).request(request).get());
assertThat(e.getMessage(), equalTo("current license is non-compliant for [sql]"));
enableSqlLicensing();
CliResponse response = client().prepareExecute(CliAction.INSTANCE).request(request).get();
assertThat(response.response(request).toString(), containsString("bar"));
assertThat(response.response(request).toString(), containsString("baz"));
}
public void testJdbcActionLicense() throws Exception {
setupTestIndex();
disableJdbcLicensing();
Request request = new MetaTableRequest("test");
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class,
() -> client().prepareExecute(JdbcAction.INSTANCE).request(request).get());
assertThat(e.getMessage(), equalTo("current license is non-compliant for [jdbc]"));
enableJdbcLicensing();
JdbcResponse jdbcResponse = client().prepareExecute(JdbcAction.INSTANCE).request(request).get();
Response response = jdbcResponse.response(request);
assertThat(response, instanceOf(MetaTableResponse.class));
}
private void setupTestIndex() {
assertAcked(client().admin().indices().prepareCreate("test").get());
client().prepareBulk()
.add(new IndexRequest("test", "doc", "1").source("data", "bar", "count", 42))
.add(new IndexRequest("test", "doc", "2").source("data", "baz", "count", 43))
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
.get();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.plugin;
/**
* Determines if different features of SQL should be enabled
*/
public class SqlLicenseChecker {
private final Runnable checkIfSqlAllowed;
private final Runnable checkIfJdbcAllowed;
public SqlLicenseChecker(Runnable checkIfSqlAllowed, Runnable checkIfJdbcAllowed) {
this.checkIfSqlAllowed = checkIfSqlAllowed;
this.checkIfJdbcAllowed = checkIfJdbcAllowed;
}
/**
* Throws an ElasticsearchSecurityException if sql is not allowed
*/
public void checkIfSqlAllowed() {
checkIfSqlAllowed.run();
}
/**
* Throws an ElasticsearchSecurityException if jdbc is not allowed
*/
public void checkIfJdbcAllowed() {
checkIfJdbcAllowed.run();
}
}

View File

@ -39,6 +39,13 @@ import java.util.List;
import java.util.function.Supplier;
public class SqlPlugin implements ActionPlugin {
private final SqlLicenseChecker sqlLicenseChecker;
public SqlPlugin(SqlLicenseChecker sqlLicenseChecker) {
this.sqlLicenseChecker = sqlLicenseChecker;
}
/**
* Create components used by the sql plugin.
* @param catalogFilter if non-null it is a filter for the catalog to apply security
@ -49,6 +56,7 @@ public class SqlPlugin implements ActionPlugin {
Catalog catalog = catalogFilter == null ? esCatalog : new FilteredCatalog(esCatalog, catalogFilter);
return Arrays.asList(
esCatalog, // Added as a component so that it can get IndexNameExpressionResolver injected.
sqlLicenseChecker,
new PlanExecutor(client, catalog));
}

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.sql.execution.PlanExecutor;
import org.elasticsearch.xpack.sql.plugin.SqlLicenseChecker;
import org.elasticsearch.xpack.sql.plugin.cli.CliServer;
import org.elasticsearch.xpack.sql.protocol.shared.Request;
@ -26,20 +27,23 @@ import static org.elasticsearch.xpack.sql.util.ActionUtils.chain;
public class TransportCliAction extends HandledTransportAction<CliRequest, CliResponse> {
private final CliServer cliServer;
private final SqlLicenseChecker sqlLicenseChecker;
@Inject
public TransportCliAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
ClusterService clusterService,
PlanExecutor planExecutor) {
PlanExecutor planExecutor,
SqlLicenseChecker sqlLicenseChecker) {
super(settings, CliAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, CliRequest::new);
this.sqlLicenseChecker = sqlLicenseChecker;
this.cliServer = new CliServer(planExecutor, clusterService.getClusterName().value(), () -> clusterService.localNode().getName(), Version.CURRENT, Build.CURRENT);
}
@Override
protected void doExecute(CliRequest cliRequest, ActionListener<CliResponse> listener) {
sqlLicenseChecker.checkIfSqlAllowed();
final Request request;
try {
request = cliRequest.request();

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.sql.execution.PlanExecutor;
import org.elasticsearch.xpack.sql.plugin.SqlLicenseChecker;
import org.elasticsearch.xpack.sql.plugin.jdbc.JdbcServer;
import org.elasticsearch.xpack.sql.protocol.shared.Request;
@ -26,20 +27,23 @@ import static org.elasticsearch.xpack.sql.util.ActionUtils.chain;
public class TransportJdbcAction extends HandledTransportAction<JdbcRequest, JdbcResponse> {
private final JdbcServer jdbcServer;
private final SqlLicenseChecker sqlLicenseChecker;
@Inject
public TransportJdbcAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
ClusterService clusterService,
PlanExecutor planExecutor) {
PlanExecutor planExecutor,
SqlLicenseChecker sqlLicenseChecker) {
super(settings, JdbcAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, JdbcRequest::new);
this.sqlLicenseChecker = sqlLicenseChecker;
this.jdbcServer = new JdbcServer(planExecutor, clusterService.getClusterName().value(), () -> clusterService.localNode().getName(), Version.CURRENT, Build.CURRENT);
}
@Override
protected void doExecute(JdbcRequest jdbcRequest, ActionListener<JdbcResponse> listener) {
sqlLicenseChecker.checkIfJdbcAllowed();
final Request request;
try {
request = jdbcRequest.request();

View File

@ -20,6 +20,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.execution.PlanExecutor;
import org.elasticsearch.xpack.sql.plugin.SqlLicenseChecker;
import org.elasticsearch.xpack.sql.session.RowSetCursor;
import java.util.ArrayList;
@ -41,20 +42,24 @@ public class TransportSqlAction extends HandledTransportAction<SqlRequest, SqlRe
private final Supplier<String> ephemeralId;
private final PlanExecutor planExecutor;
private final SqlLicenseChecker sqlLicenseChecker;
@Inject
public TransportSqlAction(Settings settings, ThreadPool threadPool,
TransportService transportService, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
PlanExecutor planExecutor) {
PlanExecutor planExecutor,
SqlLicenseChecker sqlLicenseChecker) {
super(settings, SqlAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, SqlRequest::new);
this.planExecutor = planExecutor;
this.sqlLicenseChecker = sqlLicenseChecker;
ephemeralId = () -> transportService.getLocalNode().getEphemeralId();
}
@Override
protected void doExecute(SqlRequest request, ActionListener<SqlResponse> listener) {
sqlLicenseChecker.checkIfSqlAllowed();
String sessionId = request.sessionId();
String query = request.query();