security: do not allow security APIs to execute when unlicensed

Although most of the security functionality was disabled when a basic license was applied,
some of the functionality still could be executed such as using the authenticate API or using
a transport client. The issue here is the UI calls the authenticate API and this gives the impression
that security is really in use when it is not.

Original commit: elastic/x-pack-elasticsearch@881453fc4c
This commit is contained in:
jaymode 2016-08-26 13:46:56 -04:00
parent 9c2b3d79ad
commit e07ae87cf4
3 changed files with 61 additions and 2 deletions

View File

@ -38,6 +38,7 @@ import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.AuthorizationUtils; import org.elasticsearch.xpack.security.authz.AuthorizationUtils;
import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege;
import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.crypto.CryptoService;
import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.SystemUser;
@ -48,6 +49,8 @@ import static org.elasticsearch.xpack.security.support.Exceptions.authorizationE
public class SecurityActionFilter extends AbstractComponent implements ActionFilter { public class SecurityActionFilter extends AbstractComponent implements ActionFilter {
private static final Predicate<String> LICENSE_EXPIRATION_ACTION_MATCHER = HealthAndStatsPrivilege.INSTANCE.predicate(); private static final Predicate<String> LICENSE_EXPIRATION_ACTION_MATCHER = HealthAndStatsPrivilege.INSTANCE.predicate();
private static final Predicate<String> SECURITY_ACTION_MATCHER =
new GeneralPrivilege("_security_matcher", "cluster:admin/xpack/security*").predicate();
private final AuthenticationService authcService; private final AuthenticationService authcService;
private final AuthorizationService authzService; private final AuthorizationService authzService;
@ -104,6 +107,8 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
applyInternal(task, action, request, applyInternal(task, action, request,
new SigningListener(this, listener, restoreOriginalContext ? original : null), chain); new SigningListener(this, listener, restoreOriginalContext ? original : null), chain);
} }
} else if (SECURITY_ACTION_MATCHER.test(action)) {
throw LicenseUtils.newComplianceException(XPackPlugin.SECURITY);
} else { } else {
chain.proceed(task, action, request, listener); chain.proceed(task, action, request, listener);
} }

View File

@ -10,6 +10,8 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestChannel;
@ -18,6 +20,7 @@ import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener; import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.SecurityContext;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.security.action.user.AuthenticateAction; import org.elasticsearch.xpack.security.action.user.AuthenticateAction;
@ -29,11 +32,14 @@ import static org.elasticsearch.rest.RestRequest.Method.GET;
public class RestAuthenticateAction extends BaseRestHandler { public class RestAuthenticateAction extends BaseRestHandler {
private final SecurityContext securityContext; private final SecurityContext securityContext;
private final XPackLicenseState licenseState;
@Inject @Inject
public RestAuthenticateAction(Settings settings, RestController controller, SecurityContext securityContext) { public RestAuthenticateAction(Settings settings, RestController controller, SecurityContext securityContext,
XPackLicenseState licenseState) {
super(settings); super(settings);
this.securityContext = securityContext; this.securityContext = securityContext;
this.licenseState = licenseState;
controller.registerHandler(GET, "/_xpack/security/_authenticate", this); controller.registerHandler(GET, "/_xpack/security/_authenticate", this);
// @deprecated: Remove in 6.0 // @deprecated: Remove in 6.0
@ -45,6 +51,12 @@ public class RestAuthenticateAction extends BaseRestHandler {
@Override @Override
public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
// this API is a special case since we access the user here and we want it to fail with the proper error instead of a request
// validation error
if (licenseState.isAuthAllowed() == false) {
throw LicenseUtils.newComplianceException(XPackPlugin.SECURITY);
}
final User user = securityContext.getUser(); final User user = securityContext.getUser();
assert user != null; assert user != null;
final String username = user.runAs() == null ? user.principal() : user.runAs().principal(); final String username = user.runAs() == null ? user.principal() : user.runAs().principal();

View File

@ -8,6 +8,7 @@ package org.elasticsearch.license;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import org.apache.http.message.BasicHeader;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
@ -34,11 +35,15 @@ import org.elasticsearch.transport.Transport;
import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackTransportClient; import org.elasticsearch.xpack.XPackTransportClient;
import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.security.authc.support.SecuredString;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.client.SecurityClient;
import org.junit.Before; import org.junit.Before;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -160,13 +165,50 @@ public class LicensingTests extends SecurityIntegTestCase {
Response response = getRestClient().performRequest("GET", "/"); Response response = getRestClient().performRequest("GET", "/");
// the default of the licensing tests is basic // the default of the licensing tests is basic
assertThat(response.getStatusLine().getStatusCode(), is(200)); assertThat(response.getStatusLine().getStatusCode(), is(200));
ResponseException e = expectThrows(ResponseException.class,
() -> getRestClient().performRequest("GET", "/_xpack/security/_authenticate"));
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(403));
// generate a new license with a mode that enables auth // generate a new license with a mode that enables auth
License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL, License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL,
License.OperationMode.PLATINUM, License.OperationMode.STANDARD); License.OperationMode.PLATINUM, License.OperationMode.STANDARD);
enableLicensing(mode); enableLicensing(mode);
ResponseException e = expectThrows(ResponseException.class, () -> getRestClient().performRequest("GET", "/")); e = expectThrows(ResponseException.class, () -> getRestClient().performRequest("GET", "/"));
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401)); assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401));
e = expectThrows(ResponseException.class, () -> getRestClient().performRequest("GET", "/_xpack/security/_authenticate"));
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401));
final String basicAuthValue = UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME,
new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()));
response = getRestClient().performRequest("GET", "/", new BasicHeader("Authorization", basicAuthValue));
assertThat(response.getStatusLine().getStatusCode(), is(200));
response = getRestClient()
.performRequest("GET", "/_xpack/security/_authenticate", new BasicHeader("Authorization", basicAuthValue));
assertThat(response.getStatusLine().getStatusCode(), is(200));
}
public void testSecurityActionsByLicenseType() throws Exception {
// security actions should not work!
try (TransportClient client = new XPackTransportClient(internalCluster().transportClient().settings())) {
client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress());
new SecurityClient(client).prepareGetUsers().get();
fail("security actions should not be enabled!");
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), is(RestStatus.FORBIDDEN));
assertThat(e.getMessage(), containsString("non-compliant"));
}
// enable a license that enables security
License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL,
License.OperationMode.PLATINUM, License.OperationMode.STANDARD);
enableLicensing(mode);
// security actions should not work!
try (TransportClient client = new XPackTransportClient(internalCluster().transportClient().settings())) {
client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress());
GetUsersResponse response = new SecurityClient(client).prepareGetUsers().get();
assertNotNull(response);
}
} }
public void testTransportClientAuthenticationByLicenseType() throws Exception { public void testTransportClientAuthenticationByLicenseType() throws Exception {