From a68fb27a23882dd1ced249a160e641dc847070c1 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 27 Jun 2017 13:15:11 -0500 Subject: [PATCH] Upgrade security index to use only one (the default) index type (elastic/x-pack-elasticsearch#1780) The .security index used several different types to differentiate the documents added to the index (users, reserved-users, roles, etc). Since types are deprecated in 6.x, this commit changes the .security index access layer to only use a single type and have all documents in the index be of that single type. To differentiate documents that may have the same id (e.g. the same user name and role name), the appropriate type of the document is prepended to the id. For example, a user named "jdoe" will now have the document id "user-jdoe". This commit also ensures that any native realm security index operations that lead to auto creation of the security index first go through the process of creating the internal security index (.security-v6) and creating the alias .security to point to the internal index. Lastly, anytime the security index is accessed without having been upgraded, an exception is thrown notifying the user to use the upgrade API to upgrade the security index. Original commit: elastic/x-pack-elasticsearch@cc0a474aedcf729ebd57c33e08b41ffdd1f21e60 --- .../security/SecurityLifecycleService.java | 25 +++ .../security/audit/index/IndexAuditTrail.java | 2 +- .../security/authc/ExpiredTokenRemover.java | 3 +- .../xpack/security/authc/TokenService.java | 18 +- .../esnative/ESNativeRealmMigrateTool.java | 2 +- .../authc/esnative/NativeRealmMigrator.java | 72 ++++--- .../authc/esnative/NativeUsersStore.java | 182 ++++++++++++------ .../mapper/NativeRoleMappingStore.java | 38 +++- .../xpack/security/authz/RoleDescriptor.java | 24 ++- .../authz/store/NativeRolesStore.java | 88 +++++++-- .../support/IndexLifecycleManager.java | 49 ++++- .../xpack/security/user/User.java | 1 + .../resources/security-index-template.json | 59 +----- .../main/resources/security_audit_log.json | 2 +- ...urityIndexBackwardsCompatibilityTests.java | 4 + .../elasticsearch/license/LicensingTests.java | 21 +- .../test/NativeRealmIntegTestCase.java | 2 - .../test/SecurityIntegTestCase.java | 11 +- .../SecurityLifecycleServiceTests.java | 7 +- .../xpack/security/SecurityTribeIT.java | 11 +- .../security/authc/TokenAuthIntegTests.java | 9 +- .../esnative/ESNativeMigrateToolTests.java | 4 +- .../ESNativeRealmMigrateToolTests.java | 4 +- .../authc/esnative/NativeRealmIntegTests.java | 11 +- .../esnative/NativeRealmMigratorTests.java | 21 +- .../authc/esnative/NativeUsersStoreTests.java | 15 +- .../authz/store/NativeRolesStoreTests.java | 19 +- .../support/IndexLifecycleManagerTests.java | 2 +- .../xpack/restart/FullClusterRestartIT.java | 2 + .../UpgradeClusterClientYamlTestSuiteIT.java | 3 + .../test/TribeWithSecurityIT.java | 10 +- 31 files changed, 484 insertions(+), 237 deletions(-) diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java index 7d4a1456017..8a527d87029 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security; import org.apache.logging.log4j.Logger; import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; @@ -123,6 +124,10 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust return securityIndex.indexExists(); } + public boolean isSecurityIndexUpToDate() { + return securityIndex.isIndexUpToDate(); + } + public boolean isSecurityIndexAvailable() { return securityIndex.isAvailable(); } @@ -174,4 +179,24 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust public static List indexNames() { return Collections.singletonList(SECURITY_INDEX_NAME); } + + /** + * Creates the security index, if it does not already exist, then runs the given + * action on the security index. + */ + public void createIndexIfNeededThenExecute(final ActionListener listener, final Runnable andThen) { + if (!isSecurityIndexExisting() || isSecurityIndexUpToDate()) { + securityIndex.createIndexIfNeededThenExecute(listener, andThen); + } else { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + } + } + + /** + * Checks if the security index is out of date with the current version. + */ + public boolean isSecurityIndexOutOfDate() { + return securityIndex.indexExists() && !securityIndex.isIndexUpToDate(); + } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java b/plugin/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java index 464d78a6cac..e89031f7cff 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java @@ -108,7 +108,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl public static final String NAME = "index"; public static final String INDEX_NAME_PREFIX = ".security_audit_log"; - public static final String DOC_TYPE = "event"; + public static final String DOC_TYPE = "doc"; public static final String INDEX_TEMPLATE_NAME = "security_audit_log"; private static final int DEFAULT_BULK_SIZE = 1000; diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java index 5f6926d9399..455e5491ccc 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java @@ -18,6 +18,7 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool.Names; import org.elasticsearch.xpack.security.InternalClient; +import org.elasticsearch.xpack.security.SecurityLifecycleService; import java.time.Instant; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,7 +43,7 @@ final class ExpiredTokenRemover extends AbstractRunnable { @Override public void doRun() { - SearchRequest searchRequest = new SearchRequest(TokenService.INDEX_NAME); + SearchRequest searchRequest = new SearchRequest(SecurityLifecycleService.SECURITY_INDEX_NAME); DeleteByQueryRequest dbq = new DeleteByQueryRequest(searchRequest); if (timeout != TimeValue.MINUS_ONE) { dbq.setTimeout(timeout); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index e4cc18934f5..ea2d7be226f 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -92,7 +92,6 @@ public final class TokenService extends AbstractComponent { "\", error=\"invalid_token\", error_description=\"The access token is malformed\""; private static final String TYPE = "doc"; - public static final String INDEX_NAME = SecurityLifecycleService.SECURITY_INDEX_NAME; public static final String THREAD_POOL_NAME = XPackPlugin.SECURITY + "-token-key"; public static final Setting TOKEN_PASSPHRASE = SecureSetting.secureString("xpack.security.authc.token.passphrase", null); public static final Setting TOKEN_EXPIRATION = Setting.timeSetting("xpack.security.authc.token.timeout", @@ -255,7 +254,11 @@ public final class TokenService extends AbstractComponent { */ public void invalidateToken(String tokenString, ActionListener listener) { ensureEnabled(); - if (lifecycleService.isSecurityIndexWriteable() == false) { + if (lifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } else if (lifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("cannot write to the tokens index")); } else if (Strings.isNullOrEmpty(tokenString)) { listener.onFailure(new IllegalArgumentException("token must be provided")); @@ -270,7 +273,8 @@ public final class TokenService extends AbstractComponent { listener.onResponse(false); } else { final String id = getDocumentId(userToken); - internalClient.prepareIndex(INDEX_NAME, TYPE, id) + lifecycleService.createIndexIfNeededThenExecute(listener, () -> { + internalClient.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE, id) .setOpType(OpType.CREATE) .setSource("doc_type", DOC_TYPE, "expiration_time", getExpirationTime().toEpochMilli()) .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) @@ -290,6 +294,7 @@ public final class TokenService extends AbstractComponent { } } }); + }); } }, listener::onFailure)); } catch (IOException e) { @@ -315,7 +320,12 @@ public final class TokenService extends AbstractComponent { */ private void checkIfTokenIsRevoked(UserToken userToken, ActionListener listener) { if (lifecycleService.isSecurityIndexAvailable()) { - internalClient.prepareGet(INDEX_NAME, TYPE, getDocumentId(userToken)) + if (lifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } + internalClient.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE, getDocumentId(userToken)) .execute(new ActionListener() { @Override diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java index 93d3ee55196..c031175b7af 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java @@ -303,7 +303,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand { static String createRoleJson(RoleDescriptor rd) throws IOException { XContentBuilder builder = jsonBuilder(); - rd.toXContent(builder, ToXContent.EMPTY_PARAMS, false); + rd.toXContent(builder, ToXContent.EMPTY_PARAMS, true); return builder.string(); } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigrator.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigrator.java index dc9971b9147..5dad52b8f4f 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigrator.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigrator.java @@ -39,6 +39,8 @@ import java.util.function.BiConsumer; import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.INDEX_TYPE; +import static org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.RESERVED_USER_TYPE; /** * Performs migration steps for the {@link NativeRealm} and {@link ReservedRealm}. @@ -123,15 +125,16 @@ public class NativeRealmMigrator implements IndexLifecycleManager.IndexDataMigra // otherwise the license management checks will prevent it from completing successfully. final boolean clearCache = licenseState.isAuthAllowed(); final String userName = info.getName(); - client.prepareGet(SECURITY_INDEX_NAME, NativeUsersStore.RESERVED_USER_DOC_TYPE, userName).execute( + client.prepareGet(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(userName)).execute( ActionListener.wrap(getResponse -> { if (getResponse.isExists()) { // the document exists - we shouldn't do anything listener.onResponse(null); } else { - client.prepareIndex(SECURITY_INDEX_NAME, NativeUsersStore.RESERVED_USER_DOC_TYPE, userName) + client.prepareIndex(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(userName)) .setSource(Requests.INDEX_CONTENT_TYPE, User.Fields.ENABLED.getPreferredName(), false, - User.Fields.PASSWORD.getPreferredName(), "") + User.Fields.PASSWORD.getPreferredName(), "", + User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) .setCreate(true) .execute(ActionListener.wrap(r -> { if (clearCache) { @@ -161,36 +164,36 @@ public class NativeRealmMigrator implements IndexLifecycleManager.IndexDataMigra @SuppressWarnings("unused") private void doConvertDefaultPasswords(@Nullable Version previousVersion, ActionListener listener) { client.prepareSearch(SECURITY_INDEX_NAME) - .setTypes(NativeUsersStore.RESERVED_USER_DOC_TYPE) - .setQuery(QueryBuilders.matchAllQuery()) - .setFetchSource(true) - .execute(ActionListener.wrap(searchResponse -> { - assert searchResponse.getHits().getTotalHits() <= 10 : - "there are more than 10 reserved users we need to change this to retrieve them all!"; - Set toConvert = new HashSet<>(); - for (SearchHit searchHit : searchResponse.getHits()) { - Map sourceMap = searchHit.getSourceAsMap(); - if (hasOldStyleDefaultPassword(sourceMap)) { - toConvert.add(searchHit.getId()); + .setQuery(QueryBuilders.termQuery(User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)) + .setFetchSource(true) + .execute(ActionListener.wrap(searchResponse -> { + assert searchResponse.getHits().getTotalHits() <= 10 : + "there are more than 10 reserved users we need to change this to retrieve them all!"; + Set toConvert = new HashSet<>(); + for (SearchHit searchHit : searchResponse.getHits()) { + Map sourceMap = searchHit.getSourceAsMap(); + if (hasOldStyleDefaultPassword(sourceMap)) { + toConvert.add(searchHit.getId()); + } } - } - if (toConvert.isEmpty()) { - listener.onResponse(null); - } else { - GroupedActionListener countDownListener = new GroupedActionListener<>( - ActionListener.wrap((r) -> listener.onResponse(null), listener::onFailure), toConvert.size(), emptyList() - ); - toConvert.forEach(username -> { - logger.debug("Upgrading security from version [{}] - marking reserved user [{}] as having default password", - previousVersion, username); - client.prepareUpdate(SECURITY_INDEX_NAME, NativeUsersStore.RESERVED_USER_DOC_TYPE, username) - .setDoc(User.Fields.PASSWORD.getPreferredName(), "") - .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .execute(countDownListener); - }); - } - }, listener::onFailure)); + if (toConvert.isEmpty()) { + listener.onResponse(null); + } else { + GroupedActionListener countDownListener = new GroupedActionListener<>( + ActionListener.wrap((r) -> listener.onResponse(null), listener::onFailure), toConvert.size(), emptyList() + ); + toConvert.forEach(username -> { + logger.debug("Upgrading security from version [{}] - marking reserved user [{}] as having default password", + previousVersion, username); + client.prepareUpdate(SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(username)) + .setDoc(User.Fields.PASSWORD.getPreferredName(), "", + User.Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .execute(countDownListener); + }); + } + }, listener::onFailure)); } /** @@ -210,4 +213,11 @@ public class NativeRealmMigrator implements IndexLifecycleManager.IndexDataMigra return Hasher.BCRYPT.verify(ReservedRealm.DEFAULT_PASSWORD_TEXT, secureString.getChars()); } } + + /** + * Gets the document's id field for the given user name. + */ + private static String getIdForUser(final String userName) { + return RESERVED_USER_TYPE + "-" + userName; + } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 8215e15e891..da50010d203 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -66,8 +66,9 @@ import java.util.function.Consumer; */ public class NativeUsersStore extends AbstractComponent { + static final String INDEX_TYPE = "doc"; private static final String USER_DOC_TYPE = "user"; - public static final String RESERVED_USER_DOC_TYPE = "reserved-user"; + static final String RESERVED_USER_TYPE = "reserved-user"; private final Hasher hasher = Hasher.BCRYPT; private final InternalClient client; @@ -110,16 +111,22 @@ public class NativeUsersStore extends AbstractComponent { (uap) -> listener.onResponse(uap == null ? Collections.emptyList() : Collections.singletonList(uap.user())), handleException::accept)); } else { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } try { final QueryBuilder query; if (userNames == null || userNames.length == 0) { - query = QueryBuilders.matchAllQuery(); + query = QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), USER_DOC_TYPE); } else { - query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(USER_DOC_TYPE).addIds(userNames)); + final String[] users = Arrays.asList(userNames).stream() + .map(s -> getIdForUser(USER_DOC_TYPE, s)).toArray(String[]::new); + query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_TYPE).addIds(users)); } SearchRequest request = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) .setScroll(TimeValue.timeValueSeconds(10L)) - .setTypes(USER_DOC_TYPE) .setQuery(query) .setSize(1000) .setFetchSource(true) @@ -140,8 +147,14 @@ public class NativeUsersStore extends AbstractComponent { * Async method to retrieve a user and their password */ private void getUserAndPassword(final String user, final ActionListener listener) { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } try { - GetRequest request = client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, USER_DOC_TYPE, user).request(); + GetRequest request = client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, + INDEX_TYPE, getIdForUser(USER_DOC_TYPE, user)).request(); client.get(request, new ActionListener() { @Override public void onResponse(GetResponse response) { @@ -181,6 +194,10 @@ public class NativeUsersStore extends AbstractComponent { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); return; + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("password cannot be changed as user service cannot write until template and " + "mappings are up to date")); @@ -189,12 +206,13 @@ public class NativeUsersStore extends AbstractComponent { final String docType; if (ReservedRealm.isReserved(username, settings)) { - docType = RESERVED_USER_DOC_TYPE; + docType = RESERVED_USER_TYPE; } else { docType = USER_DOC_TYPE; } - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, docType, username) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(docType, username)) .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.PASSWORD.getPreferredName(), String.valueOf(request.passwordHash())) .setRefreshPolicy(request.getRefreshPolicy()) .execute(new ActionListener() { @@ -207,7 +225,7 @@ public class NativeUsersStore extends AbstractComponent { @Override public void onFailure(Exception e) { if (isIndexNotFoundOrDocumentMissing(e)) { - if (docType.equals(RESERVED_USER_DOC_TYPE)) { + if (docType.equals(RESERVED_USER_TYPE)) { createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener); } else { logger.debug((Supplier) () -> @@ -220,7 +238,7 @@ public class NativeUsersStore extends AbstractComponent { listener.onFailure(e); } } - }); + })); } /** @@ -228,8 +246,15 @@ public class NativeUsersStore extends AbstractComponent { * has been indexed */ private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener listener) { - client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username) - .setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash), Fields.ENABLED.getPreferredName(), true) + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(RESERVED_USER_TYPE, username)) + .setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash), Fields.ENABLED.getPreferredName(), true, + Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) .setRefreshPolicy(refresh) .execute(new ActionListener() { @Override @@ -241,7 +266,7 @@ public class NativeUsersStore extends AbstractComponent { public void onFailure(Exception e) { listener.onFailure(e); } - }); + })); } /** @@ -254,7 +279,11 @@ public class NativeUsersStore extends AbstractComponent { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("user cannot be created or changed as the user service cannot write until " + "template and mappings are up to date")); return; @@ -277,15 +306,19 @@ public class NativeUsersStore extends AbstractComponent { */ private void updateUserWithoutPassword(final PutUserRequest putUserRequest, final ActionListener listener) { assert putUserRequest.passwordHash() == null; + assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; // We must have an existing document - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, USER_DOC_TYPE, putUserRequest.username()) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, + getIdForUser(USER_DOC_TYPE, putUserRequest.username())) .setDoc(Requests.INDEX_CONTENT_TYPE, - User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), - User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), - User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), - User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), - User.Fields.METADATA.getPreferredName(), putUserRequest.metadata(), - User.Fields.ENABLED.getPreferredName(), putUserRequest.enabled()) + Fields.USERNAME.getPreferredName(), putUserRequest.username(), + Fields.ROLES.getPreferredName(), putUserRequest.roles(), + Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), + Fields.EMAIL.getPreferredName(), putUserRequest.email(), + Fields.METADATA.getPreferredName(), putUserRequest.metadata(), + Fields.ENABLED.getPreferredName(), putUserRequest.enabled(), + Fields.TYPE.getPreferredName(), USER_DOC_TYPE) .setRefreshPolicy(putUserRequest.getRefreshPolicy()) .execute(new ActionListener() { @Override @@ -308,20 +341,23 @@ public class NativeUsersStore extends AbstractComponent { } listener.onFailure(failure); } - }); + })); } private void indexUser(final PutUserRequest putUserRequest, final ActionListener listener) { assert putUserRequest.passwordHash() != null; - client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, - USER_DOC_TYPE, putUserRequest.username()) - .setSource(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), - User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), - User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), - User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), - User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), - User.Fields.METADATA.getPreferredName(), putUserRequest.metadata(), - User.Fields.ENABLED.getPreferredName(), putUserRequest.enabled()) + assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, + getIdForUser(USER_DOC_TYPE, putUserRequest.username())) + .setSource(Fields.USERNAME.getPreferredName(), putUserRequest.username(), + Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), + Fields.ROLES.getPreferredName(), putUserRequest.roles(), + Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), + Fields.EMAIL.getPreferredName(), putUserRequest.email(), + Fields.METADATA.getPreferredName(), putUserRequest.metadata(), + Fields.ENABLED.getPreferredName(), putUserRequest.enabled(), + Fields.TYPE.getPreferredName(), USER_DOC_TYPE) .setRefreshPolicy(putUserRequest.getRefreshPolicy()) .execute(new ActionListener() { @Override @@ -333,7 +369,7 @@ public class NativeUsersStore extends AbstractComponent { public void onFailure(Exception e) { listener.onFailure(e); } - }); + })); } /** @@ -345,7 +381,11 @@ public class NativeUsersStore extends AbstractComponent { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("enabled status cannot be changed as user service cannot write until template " + "and mappings are up to date")); return; @@ -360,9 +400,11 @@ public class NativeUsersStore extends AbstractComponent { private void setRegularUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy, final ActionListener listener) { + assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; try { - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, USER_DOC_TYPE, username) - .setDoc(Requests.INDEX_CONTENT_TYPE, User.Fields.ENABLED.getPreferredName(), enabled) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(USER_DOC_TYPE, username)) + .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) .setRefreshPolicy(refreshPolicy) .execute(new ActionListener() { @Override @@ -384,7 +426,7 @@ public class NativeUsersStore extends AbstractComponent { } listener.onFailure(failure); } - }); + })); } catch (Exception e) { listener.onFailure(e); } @@ -392,12 +434,15 @@ public class NativeUsersStore extends AbstractComponent { private void setReservedUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy, boolean clearCache, final ActionListener listener) { + assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; try { - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username) - .setDoc(Requests.INDEX_CONTENT_TYPE, User.Fields.ENABLED.getPreferredName(), enabled) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(RESERVED_USER_TYPE, username)) + .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) .setUpsert(XContentType.JSON, - User.Fields.PASSWORD.getPreferredName(), "", - User.Fields.ENABLED.getPreferredName(), enabled) + Fields.PASSWORD.getPreferredName(), "", + Fields.ENABLED.getPreferredName(), enabled, + Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) .setRefreshPolicy(refreshPolicy) .execute(new ActionListener() { @Override @@ -413,7 +458,7 @@ public class NativeUsersStore extends AbstractComponent { public void onFailure(Exception e) { listener.onFailure(e); } - }); + })); } catch (Exception e) { listener.onFailure(e); } @@ -423,7 +468,11 @@ public class NativeUsersStore extends AbstractComponent { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be deleted using a tribe node")); return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("user cannot be deleted as user service cannot write until template and " + "mappings are up to date")); return; @@ -431,7 +480,7 @@ public class NativeUsersStore extends AbstractComponent { try { DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, - USER_DOC_TYPE, deleteUserRequest.username()).request(); + INDEX_TYPE, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())).request(); request.indicesOptions().ignoreUnavailable(); request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy()); client.delete(request, new ActionListener() { @@ -474,14 +523,18 @@ public class NativeUsersStore extends AbstractComponent { if (!securityLifecycleService.isSecurityIndexExisting()) { listener.onFailure(new IllegalStateException("Attempt to get reserved user info but the security index does not exist")); return; + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; } - client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, RESERVED_USER_DOC_TYPE, username) + client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(RESERVED_USER_TYPE, username)) .execute(new ActionListener() { @Override public void onResponse(GetResponse getResponse) { if (getResponse.isExists()) { Map sourceMap = getResponse.getSourceAsMap(); - String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName()); + String password = (String) sourceMap.get(Fields.PASSWORD.getPreferredName()); Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName()); if (password == null) { listener.onFailure(new IllegalStateException("password hash must not be null!")); @@ -514,9 +567,13 @@ public class NativeUsersStore extends AbstractComponent { } void getAllReservedUserInfo(ActionListener> listener) { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setTypes(RESERVED_USER_DOC_TYPE) - .setQuery(QueryBuilders.matchAllQuery()) + .setQuery(QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)) .setFetchSource(true) .execute(new ActionListener() { @Override @@ -526,8 +583,12 @@ public class NativeUsersStore extends AbstractComponent { "this to retrieve them all!"; for (SearchHit searchHit : searchResponse.getHits().getHits()) { Map sourceMap = searchHit.getSourceAsMap(); - String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName()); + String password = (String) sourceMap.get(Fields.PASSWORD.getPreferredName()); Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName()); + final String id = searchHit.getId(); + assert id != null && id.startsWith(RESERVED_USER_TYPE) : + "id [" + id + "] does not start with reserved-user prefix"; + final String username = id.substring(RESERVED_USER_TYPE.length() + 1); if (password == null) { listener.onFailure(new IllegalStateException("password hash must not be null!")); return; @@ -535,9 +596,9 @@ public class NativeUsersStore extends AbstractComponent { listener.onFailure(new IllegalStateException("enabled must not be null!")); return; } else if (password.isEmpty()) { - userInfos.put(searchHit.getId(), new ReservedUserInfo(ReservedRealm.DEFAULT_PASSWORD_HASH, enabled, true)); + userInfos.put(username, new ReservedUserInfo(ReservedRealm.DEFAULT_PASSWORD_HASH, enabled, true)); } else { - userInfos.put(searchHit.getId(), new ReservedUserInfo(password.toCharArray(), enabled, false)); + userInfos.put(username, new ReservedUserInfo(password.toCharArray(), enabled, false)); } } listener.onResponse(userInfos); @@ -577,21 +638,23 @@ public class NativeUsersStore extends AbstractComponent { } @Nullable - private UserAndPassword transformUser(String username, Map sourceMap) { + private UserAndPassword transformUser(final String id, final Map sourceMap) { if (sourceMap == null) { return null; } + assert id != null && id.startsWith(USER_DOC_TYPE) : "id [" + id + "] does not start with user prefix"; + final String username = id.substring(USER_DOC_TYPE.length() + 1); try { - String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName()); - String[] roles = ((List) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY); - String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName()); - String email = (String) sourceMap.get(User.Fields.EMAIL.getPreferredName()); - Boolean enabled = (Boolean) sourceMap.get(User.Fields.ENABLED.getPreferredName()); + String password = (String) sourceMap.get(Fields.PASSWORD.getPreferredName()); + String[] roles = ((List) sourceMap.get(Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY); + String fullName = (String) sourceMap.get(Fields.FULL_NAME.getPreferredName()); + String email = (String) sourceMap.get(Fields.EMAIL.getPreferredName()); + Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName()); if (enabled == null) { // fallback mechanism as a user from 2.x may not have the enabled field enabled = Boolean.TRUE; } - Map metadata = (Map) sourceMap.get(User.Fields.METADATA.getPreferredName()); + Map metadata = (Map) sourceMap.get(Fields.METADATA.getPreferredName()); return new UserAndPassword(new User(username, roles, fullName, email, metadata, enabled), password.toCharArray()); } catch (Exception e) { logger.error((Supplier) () -> new ParameterizedMessage("error in the format of data for user [{}]", username), e); @@ -609,6 +672,13 @@ public class NativeUsersStore extends AbstractComponent { return false; } + /** + * Gets the document id for the given user and user type (reserved user or regular user). + */ + public static String getIdForUser(final String docType, final String userName) { + return docType + "-" + userName; + } + static class ReservedUserInfo { public final char[] passwordHash; diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index 301992de801..ed917fb033c 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -31,6 +31,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilder; @@ -47,6 +48,7 @@ import org.elasticsearch.xpack.security.client.SecurityClient; import static org.elasticsearch.action.DocWriteResponse.Result.CREATED; import static org.elasticsearch.action.DocWriteResponse.Result.DELETED; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; /** * This store reads + writes {@link ExpressionRoleMapping role mappings} in an Elasticsearch @@ -94,8 +96,13 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol * package private for unit testing */ void loadMappings(ActionListener> listener) { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } final QueryBuilder query = QueryBuilders.termQuery(DOC_TYPE_FIELD, DOC_TYPE_ROLE_MAPPING); - SearchRequest request = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) + SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) .setScroll(TimeValue.timeValueSeconds(10L)) .setTypes(SECURITY_GENERIC_TYPE) .setQuery(query) @@ -107,7 +114,7 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol listener.onResponse(mappings.stream().filter(Objects::nonNull).collect(Collectors.toList())), ex -> { logger.error(new ParameterizedMessage("failed to load role mappings from index [{}] skipping all mappings.", - SecurityLifecycleService.SECURITY_INDEX_NAME), ex); + SECURITY_INDEX_NAME), ex); listener.onResponse(Collections.emptyList()); }), doc -> buildMapping(getNameFromId(doc.getId()), doc.getSourceRef())); @@ -144,6 +151,9 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol Request request, ActionListener listener) { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("role-mappings may not be modified using a tribe node")); + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("role-mappings cannot be modified until template and mappings are up to date")); } else { @@ -156,10 +166,18 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol } } - private void innerPutMapping(PutRoleMappingRequest request, ActionListener listener) throws IOException { + private void innerPutMapping(PutRoleMappingRequest request, ActionListener listener) { final ExpressionRoleMapping mapping = request.getMapping(); - client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, SECURITY_GENERIC_TYPE, getIdForName(mapping.getName())) - .setSource(mapping.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true)) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + final XContentBuilder xContentBuilder; + try { + xContentBuilder = mapping.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true); + } catch (IOException e) { + listener.onFailure(e); + return; + } + client.prepareIndex(SECURITY_INDEX_NAME, SECURITY_GENERIC_TYPE, getIdForName(mapping.getName())) + .setSource(xContentBuilder) .setRefreshPolicy(request.getRefreshPolicy()) .execute(new ActionListener() { @Override @@ -174,10 +192,16 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol listener.onFailure(e); } }); + }); } private void innerDeleteMapping(DeleteRoleMappingRequest request, ActionListener listener) throws IOException { - client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, SECURITY_GENERIC_TYPE, getIdForName(request.getName())) + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } + client.prepareDelete(SECURITY_INDEX_NAME, SECURITY_GENERIC_TYPE, getIdForName(request.getName())) .setRefreshPolicy(request.getRefreshPolicy()) .execute(new ActionListener() { @@ -229,7 +253,7 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol logger.info("The security index is not yet available - no role mappings can be loaded"); if (logger.isDebugEnabled()) { logger.debug("Security Index [{}] [exists: {}] [available: {}] [writable: {}]", - SecurityLifecycleService.SECURITY_INDEX_NAME, + SECURITY_INDEX_NAME, securityLifecycleService.isSecurityIndexExisting(), securityLifecycleService.isSecurityIndexAvailable(), securityLifecycleService.isSecurityIndexWriteable() diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java index 9a22a624d73..94908b41dcd 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java @@ -41,6 +41,8 @@ import java.util.Map; */ public class RoleDescriptor implements ToXContentObject { + public static final String ROLE_TYPE = "role"; + private final String name; private final String[] clusterPrivileges; private final IndicesPrivileges[] indicesPrivileges; @@ -149,10 +151,21 @@ public class RoleDescriptor implements ToXContentObject { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return toXContent(builder, params, true); + return toXContent(builder, params, false); } - public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean includeTransient) throws IOException { + /** + * Generates x-content for this {@link RoleDescriptor} instance. + * + * @param builder the x-content builder + * @param params the parameters for x-content generation directives + * @param docCreation {@code true} if the x-content is being generated for creating a document + * in the security index, {@code false} if the x-content being generated + * is for API display purposes + * @return x-content builder + * @throws IOException if there was an error writing the x-content to the builder + */ + public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean docCreation) throws IOException { builder.startObject(); builder.array(Fields.CLUSTER.getPreferredName(), clusterPrivileges); builder.array(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges); @@ -160,7 +173,9 @@ public class RoleDescriptor implements ToXContentObject { builder.array(Fields.RUN_AS.getPreferredName(), runAs); } builder.field(Fields.METADATA.getPreferredName(), metadata); - if (includeTransient) { + if (docCreation) { + builder.field(Fields.TYPE.getPreferredName(), ROLE_TYPE); + } else { builder.field(Fields.TRANSIENT_METADATA.getPreferredName(), transientMetadata); } return builder.endObject(); @@ -251,6 +266,8 @@ public class RoleDescriptor implements ToXContentObject { throw new ElasticsearchParseException("expected field [{}] to be an object, but found [{}] instead", currentFieldName, token); } + } else if (Fields.TYPE.match(currentFieldName)) { + // don't need it } else { throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName); } @@ -687,5 +704,6 @@ public class RoleDescriptor implements ToXContentObject { ParseField EXCEPT_FIELDS = new ParseField("except"); ParseField METADATA = new ParseField("metadata"); ParseField TRANSIENT_METADATA = new ParseField("transient_metadata"); + ParseField TYPE = new ParseField("type"); } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 69414c47220..a0085f43e01 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.get.GetResult; @@ -46,6 +47,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; import org.elasticsearch.xpack.security.client.SecurityClient; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -58,6 +60,7 @@ import java.util.Objects; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.existsQuery; import static org.elasticsearch.xpack.security.Security.setting; +import static org.elasticsearch.xpack.security.authz.RoleDescriptor.ROLE_TYPE; /** * NativeRolesStore is a {@code RolesStore} that, instead of reading from a @@ -74,8 +77,7 @@ public class NativeRolesStore extends AbstractComponent { Setting.intSetting(setting("authz.store.roles.index.cache.max_size"), 10000, Property.NodeScope, Property.Deprecated); private static final Setting CACHE_TTL_SETTING = Setting.timeSetting(setting("authz.store.roles.index.cache.ttl"), TimeValue.timeValueMinutes(20), Property.NodeScope, Property.Deprecated); - - private static final String ROLE_DOC_TYPE = "role"; + private static final String ROLE_DOC_TYPE = "doc"; private final InternalClient client; private final XPackLicenseState licenseState; @@ -102,16 +104,20 @@ public class NativeRolesStore extends AbstractComponent { getRoleDescriptor(Objects.requireNonNull(names[0]), ActionListener.wrap(roleDescriptor -> listener.onResponse(roleDescriptor == null ? Collections.emptyList() : Collections.singletonList(roleDescriptor)), listener::onFailure)); + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; } else { try { QueryBuilder query; if (names == null || names.length == 0) { - query = QueryBuilders.matchAllQuery(); + query = QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE); } else { - query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(ROLE_DOC_TYPE).addIds(names)); + final String[] roleNames = Arrays.asList(names).stream().map(s -> getIdForUser(s)).toArray(String[]::new); + query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(ROLE_DOC_TYPE).addIds(roleNames)); } SearchRequest request = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setTypes(ROLE_DOC_TYPE) .setScroll(TimeValue.timeValueSeconds(10L)) .setQuery(query) .setSize(1000) @@ -131,6 +137,10 @@ public class NativeRolesStore extends AbstractComponent { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("roles may not be deleted using a tribe node")); return; + } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { listener.onFailure(new IllegalStateException("role cannot be deleted as service cannot write until template and " + "mappings are up to date")); @@ -139,7 +149,7 @@ public class NativeRolesStore extends AbstractComponent { try { DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, - ROLE_DOC_TYPE, deleteRoleRequest.name()).request(); + ROLE_DOC_TYPE, getIdForUser(deleteRoleRequest.name())).request(); request.setRefreshPolicy(deleteRoleRequest.getRefreshPolicy()); client.delete(request, new ActionListener() { @Override @@ -179,9 +189,22 @@ public class NativeRolesStore extends AbstractComponent { // pkg-private for testing void innerPutRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener listener) { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } try { - client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role.getName()) - .setSource(role.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, false)) + securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + final XContentBuilder xContentBuilder; + try { + xContentBuilder = role.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true); + } catch (IOException e) { + listener.onFailure(e); + return; + } + client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role.getName())) + .setSource(xContentBuilder) .setRefreshPolicy(request.getRefreshPolicy()) .execute(new ActionListener() { @Override @@ -195,7 +218,8 @@ public class NativeRolesStore extends AbstractComponent { logger.error((Supplier) () -> new ParameterizedMessage("failed to put role [{}]", request.name()), e); listener.onFailure(e); } - }); + }); + }); } catch (Exception e) { logger.error((Supplier) () -> new ParameterizedMessage("unable to put role [{}]", request.name()), e); listener.onFailure(e); @@ -210,23 +234,29 @@ public class NativeRolesStore extends AbstractComponent { usageStats.put("dls", false); listener.onResponse(usageStats); } else { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } client.prepareMultiSearch() .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setTypes(ROLE_DOC_TYPE) - .setQuery(QueryBuilders.matchAllQuery()) + .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) .setSize(0)) .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setTypes(ROLE_DOC_TYPE) .setQuery(QueryBuilders.boolQuery() - .should(existsQuery("indices.field_security.grant")) - .should(existsQuery("indices.field_security.except")) - // for backwardscompat with 2.x - .should(existsQuery("indices.fields"))) + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .must(QueryBuilders.boolQuery() + .should(existsQuery("indices.field_security.grant")) + .should(existsQuery("indices.field_security.except")) + // for backwardscompat with 2.x + .should(existsQuery("indices.fields")))) .setSize(0) .setTerminateAfter(1)) .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setTypes(ROLE_DOC_TYPE) - .setQuery(existsQuery("indices.query")) + .setQuery(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .filter(existsQuery("indices.query"))) .setSize(0) .setTerminateAfter(1)) .execute(new ActionListener() { @@ -289,15 +319,22 @@ public class NativeRolesStore extends AbstractComponent { } private void executeGetRoleRequest(String role, ActionListener listener) { + if (securityLifecycleService.isSecurityIndexOutOfDate()) { + listener.onFailure(new IllegalStateException( + "Security index is not on the current version - please upgrade with the upgrade api")); + return; + } try { - GetRequest request = client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role).request(); + GetRequest request = client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, + ROLE_DOC_TYPE, getIdForUser(role)).request(); client.get(request, listener); } catch (IndexNotFoundException e) { logger.trace( (Supplier) () -> new ParameterizedMessage( "unable to retrieve role [{}] since security index does not exist", role), e); listener.onResponse(new GetResponse( - new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role, -1, false, null, null))); + new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, + getIdForUser(role), -1, false, null, null))); } catch (Exception e) { logger.error("unable to retrieve role", e); listener.onFailure(e); @@ -332,7 +369,9 @@ public class NativeRolesStore extends AbstractComponent { } @Nullable - static RoleDescriptor transformRole(String name, BytesReference sourceBytes, Logger logger, XPackLicenseState licenseState) { + static RoleDescriptor transformRole(String id, BytesReference sourceBytes, Logger logger, XPackLicenseState licenseState) { + assert id.startsWith(ROLE_TYPE) : "[" + id + "] does not have role prefix"; + final String name = id.substring(ROLE_TYPE.length() + 1); try { // we pass true as last parameter because we do not want to reject permissions if the field permissions // are given in 2.x syntax @@ -372,4 +411,11 @@ public class NativeRolesStore extends AbstractComponent { settings.add(CACHE_SIZE_SETTING); settings.add(CACHE_TTL_SETTING); } + + /** + * Gets the document's id field for the given role name. + */ + private static String getIdForUser(final String roleName) { + return ROLE_TYPE + "-" + roleName; + } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java b/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java index 780ba078a92..a229ea3bb12 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java @@ -23,8 +23,11 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.ResourceAlreadyExistsException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest; @@ -50,6 +53,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.template.TemplateUtils; +import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING; import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap; /** @@ -57,6 +61,8 @@ import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap; */ public class IndexLifecycleManager extends AbstractComponent { + public static final String INTERNAL_SECURITY_INDEX = ".security-v6"; + private static final int INTERNAL_INDEX_FORMAT = 6; private static final String SECURITY_VERSION_STRING = "security-version"; public static final String TEMPLATE_VERSION_PATTERN = Pattern.quote("${security.template.version}"); @@ -78,6 +84,7 @@ public class IndexLifecycleManager extends AbstractComponent { private volatile boolean templateIsUpToDate; private volatile boolean indexExists; + private volatile boolean isIndexUpToDate; private volatile boolean indexAvailable; private volatile boolean canWriteToIndex; private volatile boolean mappingIsUpToDate; @@ -132,6 +139,10 @@ public class IndexLifecycleManager extends AbstractComponent { return indexExists; } + public boolean isIndexUpToDate() { + return isIndexUpToDate; + } + public boolean isAvailable() { return indexAvailable; } @@ -151,7 +162,10 @@ public class IndexLifecycleManager extends AbstractComponent { private void processClusterState(ClusterState state) { assert state != null; - this.indexExists = resolveConcreteIndex(indexName, state.metaData()) != null; + final IndexMetaData securityIndex = resolveConcreteIndex(indexName, state.metaData()); + this.indexExists = securityIndex != null; + this.isIndexUpToDate = (securityIndex != null + && INDEX_FORMAT_SETTING.get(securityIndex.getSettings()).intValue() == INTERNAL_INDEX_FORMAT); this.indexAvailable = checkIndexAvailable(state); this.templateIsUpToDate = TemplateUtils.checkTemplateExistsAndIsUpToDate(templateName, SECURITY_VERSION_STRING, state, logger); @@ -442,4 +456,37 @@ public class IndexLifecycleManager extends AbstractComponent { } }); } + + /** + * Creates the security index, if it does not already exist, then runs the given + * action on the security index. + */ + public void createIndexIfNeededThenExecute(final ActionListener listener, final Runnable andThen) { + if (indexExists) { + andThen.run(); + } else { + CreateIndexRequest request = new CreateIndexRequest(INTERNAL_SECURITY_INDEX); + client.admin().indices().create(request, new ActionListener() { + @Override + public void onResponse(CreateIndexResponse createIndexResponse) { + if (createIndexResponse.isAcknowledged()) { + andThen.run(); + } else { + listener.onFailure(new ElasticsearchException("Failed to create security index")); + } + } + + @Override + public void onFailure(Exception e) { + if (e instanceof ResourceAlreadyExistsException) { + // the index already exists - it was probably just created so this + // node hasn't yet received the cluster state update with the index + andThen.run(); + } else { + listener.onFailure(e); + } + } + }); + } + } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/user/User.java b/plugin/src/main/java/org/elasticsearch/xpack/security/user/User.java index c9bcaee6351..f67464c1910 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/user/User.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/user/User.java @@ -252,5 +252,6 @@ public class User implements ToXContentObject { ParseField EMAIL = new ParseField("email"); ParseField METADATA = new ParseField("metadata"); ParseField ENABLED = new ParseField("enabled"); + ParseField TYPE = new ParseField("type"); } } diff --git a/plugin/src/main/resources/security-index-template.json b/plugin/src/main/resources/security-index-template.json index 12be24a1199..07062768dd9 100644 --- a/plugin/src/main/resources/security-index-template.json +++ b/plugin/src/main/resources/security-index-template.json @@ -1,8 +1,7 @@ { - "index_patterns" : ".security", + "index_patterns" : ".security-*", "order" : 1000, "settings" : { - "mapping.single_type": false, "number_of_shards" : 1, "number_of_replicas" : 0, "auto_expand_replicas" : "0-all", @@ -34,7 +33,7 @@ } }, "mappings" : { - "user" : { + "doc" : { "_meta": { "security-version": "${security.template.version}" }, @@ -64,15 +63,7 @@ }, "enabled": { "type": "boolean" - } - } - }, - "role" : { - "_meta": { - "security-version": "${security.template.version}" - }, - "dynamic" : "strict", - "properties" : { + }, "cluster" : { "type" : "keyword" }, @@ -106,56 +97,24 @@ "run_as" : { "type" : "keyword" }, - "metadata" : { - "type" : "object", - "dynamic" : true - } - } - }, - "reserved-user" : { - "_meta": { - "security-version": "${security.template.version}" - }, - "dynamic" : "strict", - "properties" : { - "password": { - "type" : "keyword", - "index" : false, - "doc_values" : false - }, - "enabled": { - "type": "boolean" - } - } - }, - "doc": { - "_meta": { - "security-version": "${security.template.version}" - }, - "dynamic": "strict", - "properties": { "doc_type": { "type" : "keyword" }, + "type": { + "type" : "keyword" + }, "expiration_time": { "type": "date", "format": "epoch_millis" }, - "roles": { - "type": "keyword" - }, "rules": { "type" : "object", "dynamic" : true - }, - "enabled": { - "type": "boolean" - }, - "metadata" : { - "type" : "object", - "dynamic" : true } } } + }, + "aliases" : { + ".security": {} } } diff --git a/plugin/src/main/resources/security_audit_log.json b/plugin/src/main/resources/security_audit_log.json index b91b5194c4b..4432f4f0b17 100644 --- a/plugin/src/main/resources/security_audit_log.json +++ b/plugin/src/main/resources/security_audit_log.json @@ -6,7 +6,7 @@ "index.mapper.dynamic" : false }, "mappings": { - "event": { + "doc": { "dynamic" : "strict", "properties": { "@timestamp": { diff --git a/plugin/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java b/plugin/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java index 6e9c7b1c690..85daca9ff03 100644 --- a/plugin/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java +++ b/plugin/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java @@ -5,6 +5,8 @@ */ package org.elasticsearch; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.LuceneTestCase.AwaitsFix; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.IndicesOptions; @@ -68,6 +70,8 @@ import static org.hamcrest.Matchers.hasSize; *
  • This document in {@code index3}: {@code {"title": "bwc_test_user should not see this index"}}
  • * **/ +// This will only work when the upgrade API is in place! +@AwaitsFix(bugUrl = "https://github.com/elastic/dev/issues/741") public class OldSecurityIndexBackwardsCompatibilityTests extends AbstractOldXPackIndicesBackwardsCompatibilityTestCase { protected void checkVersion(Version version) throws Exception { diff --git a/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java b/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java index 1aee88fce0a..011e3fd8878 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java @@ -54,6 +54,7 @@ import org.elasticsearch.xpack.security.SecurityLifecycleService; import org.elasticsearch.xpack.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.client.SecurityClient; +import org.elasticsearch.xpack.security.support.IndexLifecycleManager; import org.elasticsearch.xpack.template.TemplateUtils; import org.junit.Before; @@ -264,8 +265,9 @@ public class LicensingTests extends SecurityIntegTestCase { } public void testNativeRealmMigratorWorksUnderBasicLicense() throws Exception { - final String securityIndex = ".security"; - final String reservedUserType = "reserved-user"; + final String internalSecurityIndex = IndexLifecycleManager.INTERNAL_SECURITY_INDEX; + final String aliasedIndex = SecurityLifecycleService.SECURITY_INDEX_NAME; + final String reservedUserType = "doc"; final String securityVersionField = "security-version"; final String oldVersionThatRequiresMigration = Version.V_5_0_2.toString(); final String expectedVersionAfterMigration = Version.CURRENT.toString(); @@ -281,7 +283,7 @@ public class LicensingTests extends SecurityIntegTestCase { final PutIndexTemplateResponse putTemplateResponse = client.admin().indices().putTemplate(putTemplateRequest).actionGet(); assertThat(putTemplateResponse.isAcknowledged(), equalTo(true)); - final CreateIndexRequest createIndexRequest = client.admin().indices().prepareCreate(securityIndex).request(); + final CreateIndexRequest createIndexRequest = client.admin().indices().prepareCreate(internalSecurityIndex).request(); final CreateIndexResponse createIndexResponse = client.admin().indices().create(createIndexRequest).actionGet(); assertThat(createIndexResponse.isAcknowledged(), equalTo(true)); @@ -290,17 +292,17 @@ public class LicensingTests extends SecurityIntegTestCase { final Map reservedUserMapping = (Map) mappings.get(reservedUserType); final PutMappingRequest putMappingRequest = client.admin().indices() - .preparePutMapping(securityIndex).setSource(reservedUserMapping).setType(reservedUserType).request(); + .preparePutMapping(internalSecurityIndex).setSource(reservedUserMapping).setType(reservedUserType).request(); final PutMappingResponse putMappingResponse = client.admin().indices().putMapping(putMappingRequest).actionGet(); assertThat(putMappingResponse.isAcknowledged(), equalTo(true)); - final GetMappingsRequest getMappingsRequest = client.admin().indices().prepareGetMappings(securityIndex).request(); + final GetMappingsRequest getMappingsRequest = client.admin().indices().prepareGetMappings(internalSecurityIndex).request(); logger.info("Waiting for '{}' in mapping meta-data of index '{}' to equal '{}'", - securityVersionField, securityIndex, expectedVersionAfterMigration); + securityVersionField, aliasedIndex, expectedVersionAfterMigration); final boolean upgradeOk = awaitBusy(() -> { final GetMappingsResponse getMappingsResponse = client.admin().indices().getMappings(getMappingsRequest).actionGet(); - final MappingMetaData metaData = getMappingsResponse.mappings().get(securityIndex).get(reservedUserType); + final MappingMetaData metaData = getMappingsResponse.mappings().get(internalSecurityIndex).get(reservedUserType); try { Map meta = (Map) metaData.sourceAsMap().get("_meta"); return meta != null && expectedVersionAfterMigration.equals(meta.get(securityVersionField)); @@ -310,8 +312,9 @@ public class LicensingTests extends SecurityIntegTestCase { }, 3, TimeUnit.SECONDS); assertThat("Update of " + securityVersionField + " did not happen within allowed time limit", upgradeOk, equalTo(true)); - logger.info("Update of {}/{} complete, checking that logstash_system user exists", securityIndex, securityVersionField); - final GetRequest getRequest = client.prepareGet(securityIndex, reservedUserType, "logstash_system").setRefresh(true).request(); + logger.info("Update of {}/{} complete, checking that logstash_system user exists", aliasedIndex, securityVersionField); + final String logstashUser = "reserved-user-logstash_system"; + final GetRequest getRequest = client.prepareGet(aliasedIndex, reservedUserType, logstashUser).setRefresh(true).request(); final GetResponse getResponse = client.get(getRequest).actionGet(); assertThat(getResponse.isExists(), equalTo(true)); assertThat(getResponse.getFields(), equalTo(Collections.emptyMap())); diff --git a/plugin/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java b/plugin/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java index 4febea4a9e9..8af467a0977 100644 --- a/plugin/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java +++ b/plugin/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java @@ -5,8 +5,6 @@ */ package org.elasticsearch.test; -import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.xpack.security.SecurityLifecycleService; import org.elasticsearch.xpack.security.client.SecurityClient; import org.junit.After; import org.junit.Before; diff --git a/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index 2d4a1e9d6b9..7e686ccf951 100644 --- a/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -9,6 +9,8 @@ import org.elasticsearch.AbstractOldXPackIndicesBackwardsCompatibilityTestCase; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder; import org.elasticsearch.client.Client; import org.elasticsearch.client.node.NodeClient; @@ -33,7 +35,6 @@ import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.ml.MachineLearning; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.SecurityLifecycleService; import org.elasticsearch.xpack.security.client.SecurityClient; import org.junit.AfterClass; import org.junit.Before; @@ -52,6 +53,7 @@ import java.util.stream.Collectors; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; +import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateSufficientToRead; import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateUpToDate; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; @@ -479,14 +481,17 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { protected void deleteSecurityIndex() { try { // this is a hack to clean up the .security index since only the XPack user can delete it - internalClient().admin().indices().prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME).get(); + final IndicesAliasesRequest request = new IndicesAliasesRequest(); + final AliasActions aliasActions = AliasActions.removeIndex().index(SECURITY_INDEX_NAME); + request.addAliasAction(aliasActions); + internalClient().admin().indices().aliases(request).actionGet(); } catch (IndexNotFoundException e) { // ignore it since not all tests create this index... } } private static Index resolveSecurityIndex(MetaData metaData) { - final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(SecurityLifecycleService.SECURITY_INDEX_NAME); + final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(SECURITY_INDEX_NAME); if (aliasOrIndex != null) { return aliasOrIndex.getIndices().get(0).getIndex(); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java index b8d92192811..d822c1efaa2 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java @@ -221,8 +221,7 @@ public class SecurityLifecycleServiceTests extends ESTestCase { } private void checkMappingUpdateWorkCorrectly(ClusterState.Builder clusterStateBuilder, Version expectedOldVersion) { - final int numberOfSecurityTypes = 4; // we have 4 types in the security mapping - final int totalNumberOfTypes = numberOfSecurityTypes ; + final int totalNumberOfTypes = 1; AtomicReference migratorVersionRef = new AtomicReference<>(null); AtomicReference> migratorListenerRef = new AtomicReference<>(null); @@ -283,11 +282,11 @@ public class SecurityLifecycleServiceTests extends ESTestCase { this.listeners.clear(); securityLifecycleService.clusterChanged(new ClusterChangedEvent("test-event", clusterStateBuilder.build(), EMPTY_CLUSTER_STATE)); - assertThat(this.listeners.size(), equalTo(numberOfSecurityTypes)); + assertThat(this.listeners.size(), equalTo(totalNumberOfTypes)); int counter = 0; for (ActionListener actionListener : this.listeners) { actionListener.onResponse(new TestPutMappingResponse(true)); - if (++counter < numberOfSecurityTypes) { + if (++counter < totalNumberOfTypes) { assertThat(securityIndex.isMappingUpdatePending(), equalTo(true)); } else { assertThat(securityIndex.isMappingUpdatePending(), equalTo(false)); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java index c89dbc2d605..8db981f127a 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTribeIT.java @@ -33,6 +33,7 @@ import org.elasticsearch.xpack.security.action.role.PutRoleResponse; import org.elasticsearch.xpack.security.action.user.PutUserResponse; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.client.SecurityClient; +import org.elasticsearch.xpack.security.support.IndexLifecycleManager; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -111,7 +112,7 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { try { // this is a hack to clean up the .security index since only the XPack user or superusers can delete it cluster2.getInstance(InternalClient.class) - .admin().indices().prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME).get(); + .admin().indices().prepareDelete(IndexLifecycleManager.INTERNAL_SECURITY_INDEX).get(); } catch (IndexNotFoundException e) { // ignore it since not all tests create this index... } @@ -256,9 +257,9 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { List shouldFailUsers = new ArrayList<>(); final Client preferredClient = "t1".equals(preferredTribe) ? cluster1Client : cluster2Client; // always ensure the index exists on all of the clusters in this test - assertAcked(internalClient().admin().indices().prepareCreate(SecurityLifecycleService.SECURITY_INDEX_NAME).get()); + assertAcked(internalClient().admin().indices().prepareCreate(IndexLifecycleManager.INTERNAL_SECURITY_INDEX).get()); assertAcked(cluster2.getInstance(InternalClient.class).admin().indices() - .prepareCreate(SecurityLifecycleService.SECURITY_INDEX_NAME).get()); + .prepareCreate(IndexLifecycleManager.INTERNAL_SECURITY_INDEX).get()); for (int i = 0; i < randomUsers; i++) { final String username = "user" + i; Client clusterClient = randomBoolean() ? cluster1Client : cluster2Client; @@ -344,9 +345,9 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase { List shouldFailRoles = new ArrayList<>(); final Client preferredClient = "t1".equals(preferredTribe) ? cluster1Client : cluster2Client; // always ensure the index exists on all of the clusters in this test - assertAcked(internalClient().admin().indices().prepareCreate(SecurityLifecycleService.SECURITY_INDEX_NAME).get()); + assertAcked(internalClient().admin().indices().prepareCreate(IndexLifecycleManager.INTERNAL_SECURITY_INDEX).get()); assertAcked(cluster2.getInstance(InternalClient.class).admin().indices() - .prepareCreate(SecurityLifecycleService.SECURITY_INDEX_NAME).get()); + .prepareCreate(IndexLifecycleManager.INTERNAL_SECURITY_INDEX).get()); for (int i = 0; i < randomRoles; i++) { final String rolename = "role" + i; diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java index 46d2974a4f2..563bde61c6a 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; +import org.elasticsearch.xpack.security.SecurityLifecycleService; import org.elasticsearch.xpack.security.action.token.CreateTokenResponse; import org.elasticsearch.xpack.security.action.token.InvalidateTokenResponse; import org.elasticsearch.xpack.security.client.SecurityClient; @@ -57,7 +58,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { InvalidateTokenResponse invalidateResponse = securityClient.prepareInvalidateToken(response.getTokenString()).get(); assertTrue(invalidateResponse.isCreated()); assertBusy(() -> { - SearchResponse searchResponse = client.prepareSearch(TokenService.INDEX_NAME) + SearchResponse searchResponse = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) .setSource(SearchSourceBuilder.searchSource().query(QueryBuilders.termQuery("doc_type", TokenService.DOC_TYPE))) .setSize(0) .setTerminateAfter(1) @@ -76,8 +77,8 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { assertEquals("token malformed", e.getMessage()); } } - client.admin().indices().prepareRefresh(TokenService.INDEX_NAME).get(); - SearchResponse searchResponse = client.prepareSearch(TokenService.INDEX_NAME) + client.admin().indices().prepareRefresh(SecurityLifecycleService.SECURITY_INDEX_NAME).get(); + SearchResponse searchResponse = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) .setSource(SearchSourceBuilder.searchSource().query(QueryBuilders.termQuery("doc_type", TokenService.DOC_TYPE))) .setSize(0) .setTerminateAfter(1) @@ -123,7 +124,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { try { // this is a hack to clean up the .security index since only superusers can delete it and the default test user is not a // superuser since the role used there is a file based role since we cannot guarantee the superuser role is always available - internalClient().admin().indices().prepareDelete(TokenService.INDEX_NAME).get(); + internalClient().admin().indices().prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME).get(); } catch (IndexNotFoundException e) { logger.warn("security index does not exist", e); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index cdc0dab8941..ddd75551424 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -94,9 +94,9 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { OptionSet options = parser.parse("-u", username, "-p", password, "-U", url, "--path.conf", conf); logger.info("--> options: {}", options.asMap()); Set users = muor.getUsersThatExist(t, settings, new Environment(settings), options); - logger.info("--> output: \n{}", t.getOutput());; + logger.info("--> output: \n{}", t.getOutput()); for (String u : addedUsers) { - assertThat("expected list to contain: " + u, users.contains(u), is(true)); + assertThat("expected list to contain: " + u + ", real list: " + users, users.contains(u), is(true)); } } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java index a1add2663c2..59002f8b15f 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java @@ -16,7 +16,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.io.FileNotFoundException; import java.nio.file.Files; @@ -59,7 +58,8 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase { RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs); assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd), equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," + - "\"privileges\":[\"all\"],\"field_security\":{\"grant\":[\"body\"]}}],\"run_as\":[],\"metadata\":{}}")); + "\"privileges\":[\"all\"],\"field_security\":{\"grant\":[\"body\"]}}]," + + "\"run_as\":[],\"metadata\":{},\"type\":\"role\"}")); } public void testTerminalLogger() throws Exception { diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 80fe6e8e9cf..8458ab36f6e 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -54,6 +54,7 @@ import java.util.concurrent.CountDownLatch; import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.INTERNAL_SECURITY_INDEX; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -131,7 +132,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertTrue("user should exist", resp.hasUsers()); User joe = resp.users()[0]; - assertEquals(joe.principal(), "joe"); + assertEquals("joe", joe.principal()); assertArrayEquals(joe.roles(), new String[]{"role1", "user"}); logger.info("--> adding two more users"); @@ -503,9 +504,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { IndicesStatsResponse response = client().admin().indices().prepareStats("foo", SecurityLifecycleService.SECURITY_INDEX_NAME).get(); assertThat(response.getFailedShards(), is(0)); assertThat(response.getIndices().size(), is(2)); - assertThat(response.getIndices().get(SecurityLifecycleService.SECURITY_INDEX_NAME), notNullValue()); - assertThat(response.getIndices().get(SecurityLifecycleService.SECURITY_INDEX_NAME).getIndex(), - is(SecurityLifecycleService.SECURITY_INDEX_NAME)); + assertThat(response.getIndices().get(INTERNAL_SECURITY_INDEX), notNullValue()); + assertThat(response.getIndices().get(INTERNAL_SECURITY_INDEX).getIndex(), + is(INTERNAL_SECURITY_INDEX)); } public void testOperationsOnReservedUsers() throws Exception { @@ -601,6 +602,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { public void testRolesUsageStats() throws Exception { NativeRolesStore rolesStore = internalCluster().getInstance(NativeRolesStore.class); long roles = anonymousEnabled && roleExists ? 1L: 0L; + logger.info("--> running testRolesUsageStats with anonymousEnabled=[{}], roleExists=[{}]", + anonymousEnabled, roleExists); PlainActionFuture> future = new PlainActionFuture<>(); rolesStore.usageStats(future); Map usage = future.get(); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigratorTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigratorTests.java index b097c4f2ec0..73d46dfc1e9 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigratorTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmMigratorTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc.esnative; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -113,11 +114,12 @@ public class NativeRealmMigratorTests extends ESTestCase { GetRequest request = (GetRequest) invocationOnMock.getArguments()[1]; ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2]; if (request.indices().length == 1 && request.indices()[0].equals(SecurityLifecycleService.SECURITY_INDEX_NAME) - && request.type().equals(NativeUsersStore.RESERVED_USER_DOC_TYPE)) { + && request.type().equals(NativeUsersStore.INDEX_TYPE) + && request.id().startsWith(NativeUsersStore.RESERVED_USER_TYPE)) { final boolean exists = reservedUsers.get(request.id()) != null; - GetResult getResult = new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, NativeUsersStore.RESERVED_USER_DOC_TYPE, - request.id(), randomLong(), exists, JsonXContent.contentBuilder().map(reservedUsers.get(request.id())).bytes(), - emptyMap()); + GetResult getResult = new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, NativeUsersStore.INDEX_TYPE, + NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, request.id()), + randomLong(), exists, JsonXContent.contentBuilder().map(reservedUsers.get(request.id())).bytes(), emptyMap()); listener.onResponse(new GetResponse(getResult)); } else { listener.onResponse(null); @@ -170,7 +172,8 @@ public class NativeRealmMigratorTests extends ESTestCase { .put(User.Fields.ENABLED.getPreferredName(), false) .immutableMap() ); - String[] disabledUsers = new String[]{LogstashSystemUser.NAME, BeatsSystemUser.NAME}; + final String[] disabledUsers = Arrays.asList(LogstashSystemUser.NAME, BeatsSystemUser.NAME) + .stream().map(s -> NativeUsersStore.RESERVED_USER_TYPE + "-" + s).toArray(String[]::new); verifyUpgrade(randomFrom(Version.V_5_1_1, Version.V_5_0_2, Version.V_5_0_0), disabledUsers, true); } @@ -182,7 +185,7 @@ public class NativeRealmMigratorTests extends ESTestCase { .put(User.Fields.ENABLED.getPreferredName(), false) .immutableMap() ); - String[] disabledUsers = new String[]{BeatsSystemUser.NAME}; + String[] disabledUsers = new String[]{ NativeUsersStore.RESERVED_USER_TYPE + "-" + BeatsSystemUser.NAME }; Version version = randomFrom(Version.V_5_3_0, Version.V_5_2_1); verifyUpgrade(version, disabledUsers, true); } @@ -196,7 +199,7 @@ public class NativeRealmMigratorTests extends ESTestCase { .put(User.Fields.ENABLED.getPreferredName(), randomBoolean()) .immutableMap() )); - String[] disabledUsers = new String[]{BeatsSystemUser.NAME}; + String[] disabledUsers = new String[]{ NativeUsersStore.RESERVED_USER_TYPE + "-" + BeatsSystemUser.NAME }; verifyUpgrade(Version.V_5_2_0, disabledUsers, true); } @@ -232,7 +235,9 @@ public class NativeRealmMigratorTests extends ESTestCase { .execute(eq(UpdateAction.INSTANCE), captor.capture(), any(ActionListener.class)); final List requests = captor.getAllValues(); this.reservedUsers.keySet().forEach(u -> { - UpdateRequest request = requests.stream().filter(r -> r.id().equals(u)).findFirst().get(); + UpdateRequest request = requests.stream() + .filter(r -> r.id().equals(NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, u))) + .findFirst().get(); assertThat(request.validate(), nullValue(ActionRequestValidationException.class)); assertThat(request.doc().sourceAsMap(), hasEntry(is(User.Fields.PASSWORD.getPreferredName()), is(""))); assertThat(request.getRefreshPolicy(), equalTo(WriteRequest.RefreshPolicy.IMMEDIATE)); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java index 806d089dfab..46bb8f01e5a 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java @@ -36,6 +36,8 @@ import org.junit.Before; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -94,8 +96,8 @@ public class NativeUsersStoreTests extends ESTestCase { final GetResult result = new GetResult( SecurityLifecycleService.SECURITY_INDEX_NAME, - NativeUsersStore.RESERVED_USER_DOC_TYPE, - randomAlphaOfLength(12), + NativeUsersStore.INDEX_TYPE, + NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, randomAlphaOfLength(12)), 1L, true, jsonBuilder().map(values).bytes(), @@ -132,6 +134,15 @@ public class NativeUsersStoreTests extends ESTestCase { when(securityLifecycleService.isSecurityIndexAvailable()).thenReturn(true); when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(true); when(securityLifecycleService.isSecurityIndexWriteable()).thenReturn(true); + when(securityLifecycleService.isSecurityIndexOutOfDate()).thenReturn(false); + when(securityLifecycleService.isSecurityIndexUpToDate()).thenReturn(true); + doAnswer((i) -> { + Runnable action = (Runnable) i.getArguments()[1]; + action.run(); + ActionListener listener = (ActionListener) i.getArguments()[0]; + listener.onResponse(null); + return null; + }).when(securityLifecycleService).createIndexIfNeededThenExecute(any(ActionListener.class), any(Runnable.class)); final NativeUsersStore nativeUsersStore = new NativeUsersStore(Settings.EMPTY, internalClient, securityLifecycleService); return nativeUsersStore; } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java index a28cfb62efe..17dca7ad31c 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java @@ -83,7 +83,8 @@ public class NativeRolesStoreTests extends ESTestCase { Path path = getDataPath("roles2xformat.json"); byte[] bytes = Files.readAllBytes(path); String roleString = new String(bytes, Charset.defaultCharset()); - RoleDescriptor role = NativeRolesStore.transformRole("role1", new BytesArray(roleString), logger, new XPackLicenseState()); + RoleDescriptor role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "role1", + new BytesArray(roleString), logger, new XPackLicenseState()); assertNotNull(role); assertNotNull(role.getIndicesPrivileges()); RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; @@ -125,7 +126,7 @@ public class NativeRolesStoreTests extends ESTestCase { XContentBuilder builder = flsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); BytesReference bytes = builder.bytes(); - RoleDescriptor role = NativeRolesStore.transformRole("fls", bytes, logger, licenseState); + RoleDescriptor role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "-fls", bytes, logger, licenseState); assertNotNull(role); assertTrue(role.getTransientMetadata().containsKey("unlicensed_features")); assertThat(role.getTransientMetadata().get("unlicensed_features"), instanceOf(List.class)); @@ -133,7 +134,7 @@ public class NativeRolesStoreTests extends ESTestCase { builder = dlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "dls", bytes, logger, licenseState); assertNotNull(role); assertTrue(role.getTransientMetadata().containsKey("unlicensed_features")); assertThat(role.getTransientMetadata().get("unlicensed_features"), instanceOf(List.class)); @@ -141,7 +142,7 @@ public class NativeRolesStoreTests extends ESTestCase { builder = flsDlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("fls_dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "fls_dls", bytes, logger, licenseState); assertNotNull(role); assertTrue(role.getTransientMetadata().containsKey("unlicensed_features")); assertThat(role.getTransientMetadata().get("unlicensed_features"), instanceOf(List.class)); @@ -149,32 +150,32 @@ public class NativeRolesStoreTests extends ESTestCase { builder = noFlsDlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("no_fls_dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "no_fls_dls", bytes, logger, licenseState); assertNotNull(role); assertFalse(role.getTransientMetadata().containsKey("unlicensed_features")); when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true); builder = flsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("fls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "fls", bytes, logger, licenseState); assertNotNull(role); assertFalse(role.getTransientMetadata().containsKey("unlicensed_features")); builder = dlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "dls", bytes, logger, licenseState); assertNotNull(role); assertFalse(role.getTransientMetadata().containsKey("unlicensed_features")); builder = flsDlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("fls_dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "fls_dls", bytes, logger, licenseState); assertNotNull(role); assertFalse(role.getTransientMetadata().containsKey("unlicensed_features")); builder = noFlsDlsRole.toXContent(XContentBuilder.builder(XContentType.JSON.xContent()), ToXContent.EMPTY_PARAMS); bytes = builder.bytes(); - role = NativeRolesStore.transformRole("no_fls_dls", bytes, logger, licenseState); + role = NativeRolesStore.transformRole(RoleDescriptor.ROLE_TYPE + "no_fls_dls", bytes, logger, licenseState); assertNotNull(role); assertFalse(role.getTransientMetadata().containsKey("unlicensed_features")); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java index 0d511278a30..18f2b27b986 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java @@ -59,7 +59,7 @@ public class IndexLifecycleManagerTests extends ESTestCase { private static final ClusterName CLUSTER_NAME = new ClusterName("index-lifecycle-manager-tests"); private static final ClusterState EMPTY_CLUSTER_STATE = new ClusterState.Builder(CLUSTER_NAME).build(); public static final String INDEX_NAME = "IndexLifecycleManagerTests"; - public static final String TEMPLATE_NAME = "IndexLifecycleManagerTests-template"; + private static final String TEMPLATE_NAME = "IndexLifecycleManagerTests-template"; private IndexLifecycleManager manager; private IndexLifecycleManager.IndexDataMigrator migrator; private Map, Map>> actions; diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java index 6e4ac24e963..81a1b1aff0e 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java @@ -84,6 +84,8 @@ public class FullClusterRestartIT extends ESRestTestCase { assertThat(toStr(client().performRequest("GET", docLocation)), containsString(doc)); } + // This will only work when the upgrade API is in place! + @AwaitsFix(bugUrl = "https://github.com/elastic/dev/issues/741") public void testSecurityNativeRealm() throws IOException { XContentBuilder userBuilder = JsonXContent.contentBuilder().startObject(); userBuilder.field("password", "j@rV1s"); diff --git a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java index ae979c0db1b..3c7eb68b9a0 100644 --- a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java +++ b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java @@ -8,6 +8,7 @@ package org.elasticsearch.upgrades; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite; +import org.apache.lucene.util.LuceneTestCase.AwaitsFix; import org.apache.lucene.util.TimeUnits; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -21,6 +22,8 @@ import org.junit.Before; import java.nio.charset.StandardCharsets; import java.util.Base64; +// This will only work, when the upgrade API is in place! +@AwaitsFix(bugUrl = "https://github.com/elastic/dev/issues/741") @TimeoutSuite(millis = 5 * TimeUnits.MINUTE) // to account for slow as hell VMs public class UpgradeClusterClientYamlTestSuiteIT extends SecurityClusterClientYamlTestCase { diff --git a/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java b/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java index e8ddcf3a6c0..6c94bce2543 100644 --- a/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java +++ b/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Set; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; -import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.security.support.IndexLifecycleManager.INTERNAL_SECURITY_INDEX; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.arrayContaining; @@ -77,16 +77,16 @@ public class TribeWithSecurityIT extends SecurityIntegTestCase { @After public void removeSecurityIndex() { - client().admin().indices().prepareDelete(SECURITY_INDEX_NAME).get(); - cluster2.client().admin().indices().prepareDelete(SECURITY_INDEX_NAME).get(); + client().admin().indices().prepareDelete(INTERNAL_SECURITY_INDEX).get(); + cluster2.client().admin().indices().prepareDelete(INTERNAL_SECURITY_INDEX).get(); securityClient(client()).prepareClearRealmCache().get(); securityClient(cluster2.client()).prepareClearRealmCache().get(); } @Before public void addSecurityIndex() { - client().admin().indices().prepareCreate(SECURITY_INDEX_NAME).get(); - cluster2.client().admin().indices().prepareCreate(SECURITY_INDEX_NAME).get(); + client().admin().indices().prepareCreate(INTERNAL_SECURITY_INDEX).get(); + cluster2.client().admin().indices().prepareCreate(INTERNAL_SECURITY_INDEX).get(); } @Override