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@cc0a474aed
This commit is contained in:
Ali Beyad 2017-06-27 13:15:11 -05:00 committed by Igor Motov
parent d2bdd99308
commit a68fb27a23
31 changed files with 484 additions and 237 deletions

View File

@ -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<String> 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 <T> void createIndexIfNeededThenExecute(final ActionListener<T> 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();
}
}

View File

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

View File

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

View File

@ -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<SecureString> TOKEN_PASSPHRASE = SecureSetting.secureString("xpack.security.authc.token.passphrase", null);
public static final Setting<TimeValue> 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<Boolean> 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<UserToken> 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<GetResponse>() {
@Override

View File

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

View File

@ -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<Void> 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<String> toConvert = new HashSet<>();
for (SearchHit searchHit : searchResponse.getHits()) {
Map<String, Object> 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<String> toConvert = new HashSet<>();
for (SearchHit searchHit : searchResponse.getHits()) {
Map<String, Object> sourceMap = searchHit.getSourceAsMap();
if (hasOldStyleDefaultPassword(sourceMap)) {
toConvert.add(searchHit.getId());
}
}
}
if (toConvert.isEmpty()) {
listener.onResponse(null);
} else {
GroupedActionListener<UpdateResponse> 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<UpdateResponse> 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;
}
}

View File

@ -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<UserAndPassword> 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<GetResponse>() {
@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<UpdateResponse>() {
@ -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<Void> 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<IndexResponse>() {
@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<Boolean> 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<UpdateResponse>() {
@Override
@ -308,20 +341,23 @@ public class NativeUsersStore extends AbstractComponent {
}
listener.onFailure(failure);
}
});
}));
}
private void indexUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> 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<IndexResponse>() {
@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<Void> 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<UpdateResponse>() {
@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<Void> 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<UpdateResponse>() {
@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<DeleteResponse>() {
@ -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<GetResponse>() {
@Override
public void onResponse(GetResponse getResponse) {
if (getResponse.isExists()) {
Map<String, Object> 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<Map<String, ReservedUserInfo>> 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<SearchResponse>() {
@Override
@ -526,8 +583,12 @@ public class NativeUsersStore extends AbstractComponent {
"this to retrieve them all!";
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
Map<String, Object> 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<String, Object> sourceMap) {
private UserAndPassword transformUser(final String id, final Map<String, Object> 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<String>) 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<String>) 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<String, Object> metadata = (Map<String, Object>) sourceMap.get(User.Fields.METADATA.getPreferredName());
Map<String, Object> metadata = (Map<String, Object>) 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;

View File

@ -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
* <em>package private</em> for unit testing
*/
void loadMappings(ActionListener<List<ExpressionRoleMapping>> 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<Result> 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<Boolean> listener) throws IOException {
private void innerPutMapping(PutRoleMappingRequest request, ActionListener<Boolean> 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<IndexResponse>() {
@Override
@ -174,10 +192,16 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol
listener.onFailure(e);
}
});
});
}
private void innerDeleteMapping(DeleteRoleMappingRequest request, ActionListener<Boolean> 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<DeleteResponse>() {
@ -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()

View File

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

View File

@ -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<TimeValue> 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<DeleteResponse>() {
@Override
@ -179,9 +189,22 @@ public class NativeRolesStore extends AbstractComponent {
// pkg-private for testing
void innerPutRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener<Boolean> 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<IndexResponse>() {
@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<MultiSearchResponse>() {
@ -289,15 +319,22 @@ public class NativeRolesStore extends AbstractComponent {
}
private void executeGetRoleRequest(String role, ActionListener<GetResponse> 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;
}
}

View File

@ -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 <T> void createIndexIfNeededThenExecute(final ActionListener<T> listener, final Runnable andThen) {
if (indexExists) {
andThen.run();
} else {
CreateIndexRequest request = new CreateIndexRequest(INTERNAL_SECURITY_INDEX);
client.admin().indices().create(request, new ActionListener<CreateIndexResponse>() {
@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);
}
}
});
}
}
}

View File

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

View File

@ -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": {}
}
}

View File

@ -6,7 +6,7 @@
"index.mapper.dynamic" : false
},
"mappings": {
"event": {
"doc": {
"dynamic" : "strict",
"properties": {
"@timestamp": {

View File

@ -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;
* <li>This document in {@code index3}: {@code {"title": "bwc_test_user should not see this index"}}</li>
* </ul>
**/
// 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 {

View File

@ -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<String, Object> reservedUserMapping = (Map<String, Object>) 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<String, Object> meta = (Map<String, Object>) 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()));

View File

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

View File

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

View File

@ -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<Version> migratorVersionRef = new AtomicReference<>(null);
AtomicReference<ActionListener<Boolean>> 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));

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Map<String, Object>> future = new PlainActionFuture<>();
rolesStore.usageStats(future);
Map<String, Object> usage = future.get();

View File

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

View File

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

View File

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

View File

@ -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<Action<?, ?, ?>, Map<ActionRequest, ActionListener<?>>> actions;

View File

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

View File

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

View File

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