Add exception metadata for disabled features (#53941)
This change adds a new exception with consistent metadata for when security features are not enabled. This allows clients to be able to tell that an API failed due to a configuration option, and respond accordingly. Relates: kibana#55255 Resolves: #52311, #47759 Backport of: #52811
This commit is contained in:
parent
27c8bcbbd1
commit
f003a419a5
|
@ -70,6 +70,8 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
|||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException.Feature;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
@ -586,7 +588,7 @@ public class ApiKeyService {
|
|||
throw LicenseUtils.newComplianceException("api keys");
|
||||
}
|
||||
if (enabled == false) {
|
||||
throw new IllegalStateException("api keys are not enabled");
|
||||
throw new FeatureNotEnabledException(Feature.API_KEY_SERVICE, "api keys are not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,8 @@ import org.elasticsearch.xpack.core.security.authc.KeyAndTimestamp;
|
|||
import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationResult;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException.Feature;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -1459,7 +1461,7 @@ public final class TokenService {
|
|||
throw LicenseUtils.newComplianceException("security tokens");
|
||||
}
|
||||
if (enabled == false) {
|
||||
throw new IllegalStateException("security tokens are not enabled");
|
||||
throw new FeatureNotEnabledException(Feature.TOKEN_SERVICE, "security tokens are not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.support;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
public class FeatureNotEnabledException extends ElasticsearchException {
|
||||
|
||||
public static final String DISABLED_FEATURE_METADATA = "es.disabled.feature";
|
||||
|
||||
/**
|
||||
* The features names here are constants that form part of our API contract.
|
||||
* Callers (e.g. Kibana) may be dependent on these strings. Do not change them without consideration of BWC.
|
||||
*/
|
||||
public enum Feature {
|
||||
TOKEN_SERVICE("security_tokens"),
|
||||
API_KEY_SERVICE("api_keys");
|
||||
|
||||
private final String featureName;
|
||||
|
||||
Feature(String featureName) {
|
||||
this.featureName = featureName;
|
||||
}
|
||||
}
|
||||
|
||||
public FeatureNotEnabledException(Feature feature, String message, Object... args) {
|
||||
super(message, args);
|
||||
addMetadata(DISABLED_FEATURE_METADATA, feature.featureName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestStatus status() {
|
||||
return RestStatus.BAD_REQUEST;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
package org.elasticsearch.xpack.security.authc;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
|
@ -38,6 +39,7 @@ import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyCredentials;
|
|||
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors;
|
||||
import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult;
|
||||
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
import org.elasticsearch.xpack.security.test.SecurityMocks;
|
||||
import org.junit.After;
|
||||
|
@ -59,8 +61,10 @@ import java.util.Map;
|
|||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
@ -435,6 +439,20 @@ public class ApiKeyServiceTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testApiKeyServiceDisabled() throws Exception {
|
||||
final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), false).build();
|
||||
final ApiKeyService service = createApiKeyService(settings);
|
||||
|
||||
ElasticsearchException e = expectThrows(ElasticsearchException.class,
|
||||
() -> service.getApiKeys(randomAlphaOfLength(6), randomAlphaOfLength(8), null, null, new PlainActionFuture<>()));
|
||||
|
||||
assertThat(e, instanceOf(FeatureNotEnabledException.class));
|
||||
// Older Kibana version looked for this exact text:
|
||||
assertThat(e, throwableWithMessage("api keys are not enabled"));
|
||||
// Newer Kibana versions will check the metadata for this string literal:
|
||||
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("api_keys"));
|
||||
}
|
||||
|
||||
public void testApiKeyCache() {
|
||||
final String apiKey = randomAlphaOfLength(16);
|
||||
Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
|
@ -54,6 +55,7 @@ import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
|
|||
import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationResult;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
|
||||
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
import org.elasticsearch.xpack.security.test.SecurityMocks;
|
||||
import org.hamcrest.Matchers;
|
||||
|
@ -63,7 +65,6 @@ import org.junit.Before;
|
|||
import org.junit.BeforeClass;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -79,8 +80,11 @@ import java.util.Map;
|
|||
import static java.time.Clock.systemUTC;
|
||||
import static org.elasticsearch.repositories.blobstore.ESBlobStoreRepositoryIntegTestCase.randomBytes;
|
||||
import static org.elasticsearch.test.ClusterServiceUtils.setState;
|
||||
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.Matchers.any;
|
||||
|
@ -561,20 +565,23 @@ public class TokenServiceTests extends ESTestCase {
|
|||
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
|
||||
.build(),
|
||||
Clock.systemUTC(), client, licenseState, securityContext, securityMainIndex, securityTokensIndex, clusterService);
|
||||
IllegalStateException e = expectThrows(IllegalStateException.class,
|
||||
ElasticsearchException e = expectThrows(ElasticsearchException.class,
|
||||
() -> tokenService.createOAuth2Tokens(null, null, null, true, null));
|
||||
assertEquals("security tokens are not enabled", e.getMessage());
|
||||
assertThat(e, throwableWithMessage("security tokens are not enabled"));
|
||||
assertThat(e, instanceOf(FeatureNotEnabledException.class));
|
||||
// Client can check the metadata for this value, and depend on an exact string match:
|
||||
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("security_tokens"));
|
||||
|
||||
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
|
||||
tokenService.getAndValidateToken(null, future);
|
||||
assertNull(future.get());
|
||||
|
||||
e = expectThrows(IllegalStateException.class, () -> {
|
||||
PlainActionFuture<TokensInvalidationResult> invalidateFuture = new PlainActionFuture<>();
|
||||
tokenService.invalidateAccessToken((String) null, invalidateFuture);
|
||||
invalidateFuture.actionGet();
|
||||
});
|
||||
assertEquals("security tokens are not enabled", e.getMessage());
|
||||
PlainActionFuture<TokensInvalidationResult> invalidateFuture = new PlainActionFuture<>();
|
||||
e = expectThrows(ElasticsearchException.class, () -> tokenService.invalidateAccessToken((String) null, invalidateFuture));
|
||||
assertThat(e, throwableWithMessage("security tokens are not enabled"));
|
||||
assertThat(e, instanceOf(FeatureNotEnabledException.class));
|
||||
// Client can check the metadata for this value, and depend on an exact string match:
|
||||
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("security_tokens"));
|
||||
}
|
||||
|
||||
public void testBytesKeyEqualsHashCode() {
|
||||
|
|
Loading…
Reference in New Issue