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:
parent
0de6376452
commit
063ed78c42
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 <LogoutRequest>}
|
* The session identity is provided in a SAML {@code <LogoutRequest>}
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue