Provide clearer errors if SAML is not licensed (elastic/x-pack-elasticsearch#4096)

SAML is only available on a Platinum license. If you try and use SAML
on a Gold license, then the error message is misleading - it gives the
equivalent of "cannot find saml realm".

This change adds a standard license error of
    "current license is non-compliant for [saml]"
if SAML rest actions are used when SAML is not licensed.

Original commit: elastic/x-pack-elasticsearch@7c0e26d58e
This commit is contained in:
Tim Vernum 2018-03-20 18:54:51 +10:00 committed by GitHub
parent 0de6376452
commit 063ed78c42
8 changed files with 111 additions and 10 deletions

View File

@ -266,7 +266,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
return converted; return converted;
} }
private static boolean isRealmTypeAvailable(AllowedRealmType enabledRealmType, String type) { public static boolean isRealmTypeAvailable(AllowedRealmType enabledRealmType, String type) {
switch (enabledRealmType) { switch (enabledRealmType) {
case ALL: case ALL:
return true; return true;

View File

@ -12,11 +12,10 @@ 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.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.core.XPackField;
import java.io.IOException; import java.io.IOException;
import static org.elasticsearch.xpack.core.XPackField.SECURITY;
/** /**
* Base class for security rest handlers. This handler takes care of ensuring that the license * Base class for security rest handlers. This handler takes care of ensuring that the license
* level is valid so that security can be used! * level is valid so that security can be used!
@ -45,13 +44,26 @@ public abstract class SecurityBaseRestHandler extends BaseRestHandler {
*/ */
protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
RestChannelConsumer consumer = innerPrepareRequest(request, client); RestChannelConsumer consumer = innerPrepareRequest(request, client);
if (licenseState.isAuthAllowed()) { final String failedFeature = checkLicensedFeature(request);
if (failedFeature == null) {
return consumer; return consumer;
} else { } else {
return channel -> channel.sendResponse(new BytesRestResponse(channel, LicenseUtils.newComplianceException(SECURITY))); return channel -> channel.sendResponse(new BytesRestResponse(channel, LicenseUtils.newComplianceException(failedFeature)));
} }
} }
/**
* Check whether the given request is allowed within the current license state, and return the name of any unlicensed feature.
* By default this returns {@link org.elasticsearch.xpack.core.XPackField#SECURITY} if the license state does not
* {@link XPackLicenseState#isAuthAllowed() allow authentication and authorization}.
* Sub-classes can override this method if they have additional licensing requirements.
* @return {@code null} if all required features are licensed, otherwise the name of the most significant unlicensed feature.
*/
protected String checkLicensedFeature(RestRequest request) {
return licenseState.isAuthAllowed() ? null : XPackField.SECURITY;
}
/** /**
* Implementers should implement this method as they normally would for * Implementers should implement this method as they normally would for
* {@link BaseRestHandler#prepareRequest(RestRequest, NodeClient)} and ensure that all request * {@link BaseRestHandler#prepareRequest(RestRequest, NodeClient)} and ensure that all request

View File

@ -34,7 +34,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
/** /**
* A REST handler that attempts to authenticate a user based on the provided SAML response/assertion. * A REST handler that attempts to authenticate a user based on the provided SAML response/assertion.
*/ */
public class RestSamlAuthenticateAction extends SecurityBaseRestHandler implements RestHandler { public class RestSamlAuthenticateAction extends SamlBaseRestHandler implements RestHandler {
static class Input { static class Input {
String content; String content;

View File

@ -31,7 +31,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
* Invalidates any security tokens associated with the provided SAML session. * Invalidates any security tokens associated with the provided SAML session.
* The session identity is provided in a SAML {@code &lt;LogoutRequest&gt;} * The session identity is provided in a SAML {@code &lt;LogoutRequest&gt;}
*/ */
public class RestSamlInvalidateSessionAction extends SecurityBaseRestHandler { public class RestSamlInvalidateSessionAction extends SamlBaseRestHandler {
static final ObjectParser<SamlInvalidateSessionRequest, RestSamlInvalidateSessionAction> PARSER = static final ObjectParser<SamlInvalidateSessionRequest, RestSamlInvalidateSessionAction> PARSER =
new ObjectParser<>("saml_invalidate_session", SamlInvalidateSessionRequest::new); new ObjectParser<>("saml_invalidate_session", SamlInvalidateSessionRequest::new);

View File

@ -23,7 +23,6 @@ import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutAction; import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutAction;
import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutRequest; import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutRequest;
import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutResponse; import org.elasticsearch.xpack.core.security.action.saml.SamlLogoutResponse;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.POST;
@ -33,7 +32,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
* This logout request is returned in the REST response as a redirect URI, and the REST client should * This logout request is returned in the REST response as a redirect URI, and the REST client should
* make it available to the browser. * make it available to the browser.
*/ */
public class RestSamlLogoutAction extends SecurityBaseRestHandler { public class RestSamlLogoutAction extends SamlBaseRestHandler {
static final ObjectParser<SamlLogoutRequest, Void> PARSER = new ObjectParser<>("saml_logout", SamlLogoutRequest::new); static final ObjectParser<SamlLogoutRequest, Void> PARSER = new ObjectParser<>("saml_logout", SamlLogoutRequest::new);

View File

@ -33,7 +33,7 @@ import static org.elasticsearch.rest.RestRequest.Method.POST;
* The request is returned in the REST response, and the REST client should make it available * The request is returned in the REST response, and the REST client should make it available
* to the browser. * to the browser.
*/ */
public class RestSamlPrepareAuthenticationAction extends SecurityBaseRestHandler { public class RestSamlPrepareAuthenticationAction extends SamlBaseRestHandler {
static final ObjectParser<SamlPrepareAuthenticationRequest, Void> PARSER = new ObjectParser<>("saml_prepare_authn", static final ObjectParser<SamlPrepareAuthenticationRequest, Void> PARSER = new ObjectParser<>("saml_prepare_authn",
SamlPrepareAuthenticationRequest::new); SamlPrepareAuthenticationRequest::new);

View File

@ -0,0 +1,38 @@
/*
* 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.security.rest.action.saml;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
/**
* An abstract implementation of {@link SecurityBaseRestHandler} that performs a license check for the SAML realm type
*/
public abstract class SamlBaseRestHandler extends SecurityBaseRestHandler {
private static final String SAML_REALM_TYPE = SamlRealmSettings.TYPE;
public SamlBaseRestHandler(Settings settings, XPackLicenseState licenseState) {
super(settings, licenseState);
}
@Override
protected String checkLicensedFeature(RestRequest request) {
String feature = super.checkLicensedFeature(request);
if (feature != null) {
return feature;
} else if (Realms.isRealmTypeAvailable(licenseState.allowedRealmType(), SAML_REALM_TYPE)) {
return null;
} else {
logger.info("The '{}' realm is not available under the current license", SAML_REALM_TYPE);
return SAML_REALM_TYPE;
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.security.rest.action.saml;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.License;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.hamcrest.Matchers;
public class SamlBaseRestHandlerTests extends ESTestCase {
public void testSamlAvailableOnTrialAndPlatinum() {
final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.TRIAL, License.OperationMode.PLATINUM));
assertThat(handler.checkLicensedFeature(new FakeRestRequest()), Matchers.nullValue());
}
public void testSecurityNotAvailableOnBasic() {
final SamlBaseRestHandler handler = buildHandler(License.OperationMode.BASIC);
assertThat(handler.checkLicensedFeature(new FakeRestRequest()), Matchers.equalTo("security"));
}
public void testSamlNotAvailableOnStandardOrGold() {
final SamlBaseRestHandler handler = buildHandler(randomFrom(License.OperationMode.STANDARD, License.OperationMode.GOLD));
assertThat(handler.checkLicensedFeature(new FakeRestRequest()), Matchers.equalTo("saml"));
}
private SamlBaseRestHandler buildHandler(License.OperationMode licenseMode) {
final TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState();
licenseState.update(licenseMode, true);
return new SamlBaseRestHandler(Settings.EMPTY, licenseState) {
@Override
public String getName() {
return "saml_test";
}
@Override
protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) {
return null;
}
};
}
}