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:
parent
9c2b3d79ad
commit
e07ae87cf4
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue