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:
Tim Vernum 2020-03-23 14:13:15 +11:00 committed by GitHub
parent 27c8bcbbd1
commit f003a419a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 11 deletions

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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() {