Skip to content

[Backport] Add exception metadata for disabled features #53941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
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;
Expand Down Expand Up @@ -586,7 +588,7 @@ private void ensureEnabled() {
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");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
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;
Expand Down Expand Up @@ -1459,7 +1461,7 @@ private void ensureEnabled() {
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");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,6 +39,7 @@
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;
Expand All @@ -59,8 +61,10 @@
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;
Expand Down Expand Up @@ -435,6 +439,20 @@ public void testGetRolesForApiKey() throws Exception {
}
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,6 +55,7 @@
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;
Expand All @@ -63,7 +65,6 @@
import org.junit.BeforeClass;

import javax.crypto.SecretKey;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
Expand All @@ -79,8 +80,11 @@
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;
Expand Down Expand Up @@ -561,20 +565,23 @@ public void testTokenServiceDisabled() throws Exception {
.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() {
Expand Down