Skip to content

Commit

Permalink
Done
Browse files Browse the repository at this point in the history
  • Loading branch information
albertzaharovits committed Jul 30, 2024
1 parent ed2987f commit 4a4a68e
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Tuple<ClusterState, Map<String, String>> execute(ClusterState state) {
}

void success(Map<String, String> value) {
listener.onResponse(new SetIndexMetadataPropertyResponse(key, value));
listener.onResponse(new SetIndexMetadataPropertyResponse(value));
}

@Override
Expand Down Expand Up @@ -184,7 +184,7 @@ protected void masterOperation(
);
} else {
// returns existing value when expectation is not met
listener.onResponse(new SetIndexMetadataPropertyResponse(request.key(), existingValue));
listener.onResponse(new SetIndexMetadataPropertyResponse(existingValue));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@
import static org.elasticsearch.xpack.core.security.action.SetIndexMetadataPropertyRequest.writeOptionalStringMap;

public class SetIndexMetadataPropertyResponse extends ActionResponse {
private final String key;
@Nullable
private final Map<String, String> value;

public SetIndexMetadataPropertyResponse(String key, @Nullable Map<String, String> value) {
this.key = key;
public SetIndexMetadataPropertyResponse(@Nullable Map<String, String> value) {
this.value = value;
}

public SetIndexMetadataPropertyResponse(StreamInput in) throws IOException {
key = in.readString();
value = readOptionalStringMap(in);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(key);
writeOptionalStringMap(value, out);
}

public @Nullable Map<String, String> value() {
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
import org.elasticsearch.xpack.core.ilm.action.ILMActions;
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
Expand All @@ -40,6 +41,7 @@
import java.util.stream.Collectors;

import static java.util.Map.entry;
import static java.util.stream.Collectors.toUnmodifiableMap;

public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>> {
/** "Security Solutions" only legacy signals index */
Expand Down Expand Up @@ -978,6 +980,18 @@ public static Collection<RoleDescriptor> roleDescriptors() {
return RESERVED_ROLES.values();
}

public static Map<String, String> versionMap() {
return ReservedRolesStore.roleDescriptors()
.stream()
.map(
roleDescriptor -> new Tuple<>(
roleDescriptor.getName(),
(String) roleDescriptor.getMetadata().get(MetadataUtils.RESERVED_ROLE_VERSION_METADATA_KEY)
)
)
.collect(toUnmodifiableMap(Tuple::v1, Tuple::v2));
}

public static Set<String> names() {
if (RESERVED_ROLES == null) {
throw new IllegalStateException("ReserveRolesStore is not initialized properly");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@

public class MetadataUtils {

public static final String RESERVED_ROLE_VERSION_METADATA_KEY = "reserved_role_version";
public static final String RESERVED_PREFIX = "_";
public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
public static final String DEPRECATED_METADATA_KEY = RESERVED_PREFIX + "deprecated";
public static final String DEPRECATED_REASON_METADATA_KEY = RESERVED_PREFIX + "deprecated_reason";
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Map.of(RESERVED_METADATA_KEY, true);
/* !!!!!! IMPORTANT !!!!!!!
* reserved role version must be incremented when builtin reserved roles are changed
*/
public static final String RESERVED_ROLE_VERSION_VALUE = "2";
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Map.of(
RESERVED_METADATA_KEY,
true,
RESERVED_ROLE_VERSION_METADATA_KEY,
RESERVED_ROLE_VERSION_VALUE
);

private MetadataUtils() {}

Expand All @@ -28,6 +38,15 @@ public static boolean containsReservedMetadata(Map<String, Object> metadata) {
}

public static Map<String, Object> getDeprecatedReservedMetadata(String reason) {
return Map.of(RESERVED_METADATA_KEY, true, DEPRECATED_METADATA_KEY, true, DEPRECATED_REASON_METADATA_KEY, reason);
return Map.of(
RESERVED_METADATA_KEY,
true,
DEPRECATED_METADATA_KEY,
true,
DEPRECATED_REASON_METADATA_KEY,
reason,
RESERVED_ROLE_VERSION_METADATA_KEY,
RESERVED_ROLE_VERSION_VALUE
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ protected void doExecute(Task task, QueryRoleRequest request, ActionListener<Que
if (accessesRoleName.get()) {
searchSourceBuilder.runtimeMappings(ROLE_NAME_RUNTIME_MAPPING);
}
nativeRolesStore.queryRoleDescriptors(
searchSourceBuilder,
ActionListener.wrap(
queryRoleResults -> listener.onResponse(new QueryRoleResponse(queryRoleResults.total(), queryRoleResults.items())),
listener::onFailure
)
);
nativeRolesStore.ensureBuiltinRolesAreQueriable(ActionListener.wrap(onResponse -> {
nativeRolesStore.queryRoleDescriptors(
searchSourceBuilder,
ActionListener.wrap(
queryRoleResults -> listener.onResponse(new QueryRoleResponse(queryRoleResults.total(), queryRoleResults.items())),
listener::onFailure
)
);
}, listener::onFailure));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequestValidationException;
Expand Down Expand Up @@ -42,6 +43,7 @@
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xcontent.NamedXContentRegistry;
Expand Down Expand Up @@ -546,24 +548,13 @@ public void ensureBuiltinRolesAreQueriable(ActionListener<Void> listener) {
listener.onResponse(null);
} else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) {
listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS));
} else if (frozenSecurityIndex.areReservedRolesIndexed()) {
logger.debug("security index already contains the latest reserved roles indexed");
listener.onResponse(null);
} else {
frozenSecurityIndex.checkIndexVersionThenExecute(
listener::onFailure,
() -> executeAsyncWithOrigin(
client.threadPool().getThreadContext(),
SECURITY_ORIGIN,
ReservedRolesStore.roleDescriptors(),
listener,
(roles, innerListener) -> putRoles(
frozenSecurityIndex,
WriteRequest.RefreshPolicy.IMMEDIATE,
ReservedRolesStore.roleDescriptors(),
false,
ActionListener.wrap(onResponse -> {

}, innerListener::onFailure)
)
)
indexReservedRoles(
frozenSecurityIndex,
ActionListener.wrap(onResponse -> frozenSecurityIndex.markReservedRolesAsIndexed(listener), listener::onFailure)
);
}
}
Expand All @@ -576,6 +567,28 @@ public void putRoles(
putRoles(securityIndex, refreshPolicy, roles, true, listener);
}

private void indexReservedRoles(SecurityIndexManager frozenSecurityIndex, ActionListener<Void> listener) {
putRoles(
frozenSecurityIndex,
WriteRequest.RefreshPolicy.IMMEDIATE,
ReservedRolesStore.roleDescriptors(),
false,
ActionListener.wrap(onResponse -> {
if (onResponse.getItems().stream().anyMatch(BulkRolesResponse.Item::isFailed)) {
logger.warn("Automatic indexing of builtin reserved roles failed, {}", onResponse);
listener.onFailure(
new ElasticsearchStatusException(
"Automatic indexing of builtin reserved roles failed",
RestStatus.INTERNAL_SERVER_ERROR
)
);
} else {
listener.onResponse(null);
}
}, listener::onFailure)
);
}

private void putRoles(
SecurityIndexManager securityIndexManager,
final WriteRequest.RefreshPolicy refreshPolicy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.routing.IndexRoutingTable;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.features.FeatureService;
import org.elasticsearch.features.NodeFeature;
Expand All @@ -43,6 +44,9 @@
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.core.security.action.SetIndexMetadataPropertyAction;
import org.elasticsearch.xpack.core.security.action.SetIndexMetadataPropertyRequest;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.security.SecurityFeatures;

import java.time.Instant;
Expand All @@ -58,10 +62,12 @@
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_FORMAT_SETTING;
import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_INDEX_VERSION_CREATED;
import static org.elasticsearch.indices.SystemIndexDescriptor.VERSION_META_KEY;
import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
import static org.elasticsearch.xpack.core.security.action.UpdateIndexMigrationVersionAction.MIGRATION_VERSION_CUSTOM_DATA_KEY;
import static org.elasticsearch.xpack.core.security.action.UpdateIndexMigrationVersionAction.MIGRATION_VERSION_CUSTOM_KEY;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.State.UNRECOVERED_STATE;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MIGRATION_FRAMEWORK;

/**
Expand All @@ -71,7 +77,7 @@
public class SecurityIndexManager implements ClusterStateListener {

public static final String SECURITY_VERSION_STRING = "security-version";
private static final String METADATA_PROPERTY_FOR_INDEXED_BUILTIN_ROLES = "indexed-builtin-roles";
private static final String METADATA_PROPERTY_FOR_INDEXED_RESERVED_ROLES = "indexed-reserved-roles";

private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class);

Expand Down Expand Up @@ -291,18 +297,22 @@ public void clusterChanged(ClusterChangedEvent event) {
: indexMetadata.getIndex().getName();
final ClusterHealthStatus indexHealth;
final IndexMetadata.State indexState;
final Map<String, String> indexedReservedRolesVersionMap;
if (indexMetadata == null) {
// Index does not exist
indexState = null;
indexHealth = null;
indexedReservedRolesVersionMap = null;
} else if (indexMetadata.getState() == IndexMetadata.State.CLOSE) {
indexState = IndexMetadata.State.CLOSE;
indexHealth = null;
indexedReservedRolesVersionMap = null;
logger.warn("Index [{}] is closed. This is likely to prevent security from functioning correctly", concreteIndexName);
} else {
indexState = IndexMetadata.State.OPEN;
final IndexRoutingTable routingTable = event.state().getRoutingTable().index(indexMetadata.getIndex());
indexHealth = new ClusterIndexHealth(indexMetadata, routingTable).getStatus();
indexedReservedRolesVersionMap = indexMetadata.getCustomData(METADATA_PROPERTY_FOR_INDEXED_RESERVED_ROLES);
}
final String indexUUID = indexMetadata != null ? indexMetadata.getIndexUUID() : null;
final State newState = new State(
Expand All @@ -321,7 +331,8 @@ public void clusterChanged(ClusterChangedEvent event) {
indexUUID,
allSecurityFeatures.stream()
.filter(feature -> featureService.clusterHasFeature(event.state(), feature))
.collect(Collectors.toSet())
.collect(Collectors.toSet()),
indexedReservedRolesVersionMap
);
this.state = newState;

Expand Down Expand Up @@ -480,6 +491,48 @@ public String getConcreteIndexName() {
return state.concreteIndexName;
}

public boolean areReservedRolesIndexed() {
return areReservedRolesIndexed(state.indexedReservedRolesVersionMap);
}

private static boolean areReservedRolesIndexed(Map<String, String> reservedRolesVersionMap) {
if (reservedRolesVersionMap == null) {
return false;
}
Map<String, String> reservedRolesVersions = ReservedRolesStore.versionMap();
if (reservedRolesVersionMap.keySet().equals(reservedRolesVersions.keySet()) == false) {
return false;
}
for (String roleName : reservedRolesVersions.keySet()) {
if (Integer.parseInt(reservedRolesVersions.get(roleName)) > Integer.parseInt(reservedRolesVersionMap.get(roleName))) {
return false;
}
}
return true;
}

public void markReservedRolesAsIndexed(ActionListener<Void> listener) {
executeAsyncWithOrigin(
client,
SECURITY_ORIGIN,
SetIndexMetadataPropertyAction.INSTANCE,
new SetIndexMetadataPropertyRequest(
TimeValue.MINUS_ONE,
SECURITY_MAIN_ALIAS,
METADATA_PROPERTY_FOR_INDEXED_RESERVED_ROLES,
state.indexedReservedRolesVersionMap,
ReservedRolesStore.versionMap()
),
ActionListener.wrap(response -> {
if (areReservedRolesIndexed(response.value()) == false) {
listener.onFailure(new IllegalStateException("Failed to mark reserved roles as indexed"));
} else {
listener.onResponse(null);
}
}, listener::onFailure)
);
}

/**
* Prepares the index by creating it if it doesn't exist, then executes the runnable.
* @param consumer a handler for any exceptions that are raised either during preparation or execution
Expand Down Expand Up @@ -627,7 +680,8 @@ public static class State {
null,
null,
null,
Set.of()
Set.of(),
null
);
public final Instant creationTime;
public final boolean isIndexUpToDate;
Expand All @@ -645,6 +699,7 @@ public static class State {
public final IndexMetadata.State indexState;
public final String indexUUID;
public final Set<NodeFeature> securityFeatures;
private final Map<String, String> indexedReservedRolesVersionMap;

public State(
Instant creationTime,
Expand All @@ -660,7 +715,8 @@ public State(
ClusterHealthStatus indexHealth,
IndexMetadata.State indexState,
String indexUUID,
Set<NodeFeature> securityFeatures
Set<NodeFeature> securityFeatures,
Map<String, String> indexedReservedRolesVersionMap
) {
this.creationTime = creationTime;
this.isIndexUpToDate = isIndexUpToDate;
Expand All @@ -676,6 +732,7 @@ public State(
this.indexState = indexState;
this.indexUUID = indexUUID;
this.securityFeatures = securityFeatures;
this.indexedReservedRolesVersionMap = indexedReservedRolesVersionMap;
}

@Override
Expand All @@ -695,7 +752,8 @@ public boolean equals(Object o) {
&& Objects.equals(concreteIndexName, state.concreteIndexName)
&& indexHealth == state.indexHealth
&& indexState == state.indexState
&& Objects.equals(securityFeatures, state.securityFeatures);
&& Objects.equals(securityFeatures, state.securityFeatures)
&& Objects.equals(indexedReservedRolesVersionMap, state.indexedReservedRolesVersionMap);
}

public boolean indexExists() {
Expand All @@ -716,7 +774,8 @@ public int hashCode() {
indexMappingVersion,
concreteIndexName,
indexHealth,
securityFeatures
securityFeatures,
indexedReservedRolesVersionMap
);
}
}
Expand Down

0 comments on commit 4a4a68e

Please sign in to comment.