Skip to content

Commit 49979a0

Browse files
committed
Add exception metadata for disabled features
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: elastic#52311, elastic#47759 Backport of: elastic#52811
1 parent 27c8bcb commit 49979a0

File tree

5 files changed

+80
-11
lines changed

5 files changed

+80
-11
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
7171
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
7272
import org.elasticsearch.xpack.core.security.user.User;
73+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
74+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException.Feature;
7375
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
7476

7577
import java.io.Closeable;
@@ -586,7 +588,7 @@ private void ensureEnabled() {
586588
throw LicenseUtils.newComplianceException("api keys");
587589
}
588590
if (enabled == false) {
589-
throw new IllegalStateException("api keys are not enabled");
591+
throw new FeatureNotEnabledException(Feature.API_KEY_SERVICE, "api keys are not enabled");
590592
}
591593
}
592594

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989
import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
9090
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
9191
import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationResult;
92+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
93+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException.Feature;
9294
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
9395

9496
import javax.crypto.Cipher;
@@ -1459,7 +1461,7 @@ private void ensureEnabled() {
14591461
throw LicenseUtils.newComplianceException("security tokens");
14601462
}
14611463
if (enabled == false) {
1462-
throw new IllegalStateException("security tokens are not enabled");
1464+
throw new FeatureNotEnabledException(Feature.TOKEN_SERVICE, "security tokens are not enabled");
14631465
}
14641466
}
14651467

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.security.support;
8+
9+
import org.elasticsearch.ElasticsearchException;
10+
import org.elasticsearch.rest.RestStatus;
11+
12+
public class FeatureNotEnabledException extends ElasticsearchException {
13+
14+
public static final String DISABLED_FEATURE_METADATA = "es.disabled.feature";
15+
16+
/**
17+
* The features names here are constants that form part of our API contract.
18+
* Callers (e.g. Kibana) may be dependent on these strings. Do not change them without consideration of BWC.
19+
*/
20+
public enum Feature {
21+
TOKEN_SERVICE("security_tokens"),
22+
API_KEY_SERVICE("api_keys");
23+
24+
private final String featureName;
25+
26+
Feature(String featureName) {
27+
this.featureName = featureName;
28+
}
29+
}
30+
31+
public FeatureNotEnabledException(Feature feature, String message, Object... args) {
32+
super(message, args);
33+
addMetadata(DISABLED_FEATURE_METADATA, feature.featureName);
34+
}
35+
36+
@Override
37+
public RestStatus status() {
38+
return RestStatus.BAD_REQUEST;
39+
}
40+
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package org.elasticsearch.xpack.security.authc;
88

9+
import org.elasticsearch.ElasticsearchException;
910
import org.elasticsearch.Version;
1011
import org.elasticsearch.action.ActionListener;
1112
import org.elasticsearch.action.support.PlainActionFuture;
@@ -38,6 +39,7 @@
3839
import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptors;
3940
import org.elasticsearch.xpack.security.authc.ApiKeyService.CachedApiKeyHashResult;
4041
import org.elasticsearch.xpack.security.authz.store.NativePrivilegeStore;
42+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
4143
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
4244
import org.elasticsearch.xpack.security.test.SecurityMocks;
4345
import org.junit.After;
@@ -59,8 +61,10 @@
5961
import java.util.concurrent.Semaphore;
6062
import java.util.concurrent.atomic.AtomicInteger;
6163

64+
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
6265
import static org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR;
6366
import static org.hamcrest.Matchers.arrayContaining;
67+
import static org.hamcrest.Matchers.contains;
6468
import static org.hamcrest.Matchers.containsString;
6569
import static org.hamcrest.Matchers.equalTo;
6670
import static org.hamcrest.Matchers.instanceOf;
@@ -435,6 +439,20 @@ public void testGetRolesForApiKey() throws Exception {
435439
}
436440
}
437441

442+
public void testApiKeyServiceDisabled() throws Exception {
443+
final Settings settings = Settings.builder().put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), false).build();
444+
final ApiKeyService service = createApiKeyService(settings);
445+
446+
ElasticsearchException e = expectThrows(ElasticsearchException.class,
447+
() -> service.getApiKeys(randomAlphaOfLength(6), randomAlphaOfLength(8), null, null, new PlainActionFuture<>()));
448+
449+
assertThat(e, instanceOf(FeatureNotEnabledException.class));
450+
// Older Kibana version looked for this exact text:
451+
assertThat(e, throwableWithMessage("api keys are not enabled"));
452+
// Newer Kibana versions will check the metadata for this string literal:
453+
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("api_keys"));
454+
}
455+
438456
public void testApiKeyCache() {
439457
final String apiKey = randomAlphaOfLength(16);
440458
Hasher hasher = randomFrom(Hasher.PBKDF2, Hasher.BCRYPT4, Hasher.BCRYPT);

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java

+16-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.security.authc;
77

8+
import org.elasticsearch.ElasticsearchException;
89
import org.elasticsearch.ElasticsearchSecurityException;
910
import org.elasticsearch.Version;
1011
import org.elasticsearch.action.ActionListener;
@@ -54,6 +55,7 @@
5455
import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationResult;
5556
import org.elasticsearch.xpack.core.security.user.User;
5657
import org.elasticsearch.xpack.core.watcher.watch.ClockMock;
58+
import org.elasticsearch.xpack.security.support.FeatureNotEnabledException;
5759
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
5860
import org.elasticsearch.xpack.security.test.SecurityMocks;
5961
import org.hamcrest.Matchers;
@@ -63,7 +65,6 @@
6365
import org.junit.BeforeClass;
6466

6567
import javax.crypto.SecretKey;
66-
6768
import java.io.IOException;
6869
import java.net.URLEncoder;
6970
import java.nio.charset.StandardCharsets;
@@ -79,8 +80,11 @@
7980
import static java.time.Clock.systemUTC;
8081
import static org.elasticsearch.repositories.blobstore.ESBlobStoreRepositoryIntegTestCase.randomBytes;
8182
import static org.elasticsearch.test.ClusterServiceUtils.setState;
83+
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
84+
import static org.hamcrest.Matchers.contains;
8285
import static org.hamcrest.Matchers.containsString;
8386
import static org.hamcrest.Matchers.equalTo;
87+
import static org.hamcrest.Matchers.instanceOf;
8488
import static org.hamcrest.Matchers.notNullValue;
8589
import static org.hamcrest.Matchers.nullValue;
8690
import static org.mockito.Matchers.any;
@@ -561,20 +565,23 @@ public void testTokenServiceDisabled() throws Exception {
561565
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false)
562566
.build(),
563567
Clock.systemUTC(), client, licenseState, securityContext, securityMainIndex, securityTokensIndex, clusterService);
564-
IllegalStateException e = expectThrows(IllegalStateException.class,
568+
ElasticsearchException e = expectThrows(ElasticsearchException.class,
565569
() -> tokenService.createOAuth2Tokens(null, null, null, true, null));
566-
assertEquals("security tokens are not enabled", e.getMessage());
570+
assertThat(e, throwableWithMessage("security tokens are not enabled"));
571+
assertThat(e, instanceOf(FeatureNotEnabledException.class));
572+
// Client can check the metadata for this value, and depend on an exact string match:
573+
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("security_tokens"));
567574

568575
PlainActionFuture<UserToken> future = new PlainActionFuture<>();
569576
tokenService.getAndValidateToken(null, future);
570577
assertNull(future.get());
571578

572-
e = expectThrows(IllegalStateException.class, () -> {
573-
PlainActionFuture<TokensInvalidationResult> invalidateFuture = new PlainActionFuture<>();
574-
tokenService.invalidateAccessToken((String) null, invalidateFuture);
575-
invalidateFuture.actionGet();
576-
});
577-
assertEquals("security tokens are not enabled", e.getMessage());
579+
PlainActionFuture<TokensInvalidationResult> invalidateFuture = new PlainActionFuture<>();
580+
e = expectThrows(ElasticsearchException.class, () -> tokenService.invalidateAccessToken((String) null, invalidateFuture));
581+
assertThat(e, throwableWithMessage("security tokens are not enabled"));
582+
assertThat(e, instanceOf(FeatureNotEnabledException.class));
583+
// Client can check the metadata for this value, and depend on an exact string match:
584+
assertThat(e.getMetadata(FeatureNotEnabledException.DISABLED_FEATURE_METADATA), contains("security_tokens"));
578585
}
579586

580587
public void testBytesKeyEqualsHashCode() {

0 commit comments

Comments
 (0)