Skip to content

Check Security Roles in Deprecation Info API #49212

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
import org.elasticsearch.xpack.core.security.action.role.GetFileRolesResponse;
import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -36,6 +41,7 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.elasticsearch.action.ValidateActions.addValidationError;

Expand All @@ -60,23 +66,6 @@ public static <T> List<DeprecationIssue> filterChecks(List<T> checks, Function<T
return checks.stream().map(mapper).filter(Objects::nonNull).collect(Collectors.toList());
}

private static List<DeprecationIssue> mergeNodeIssues(NodesDeprecationCheckResponse response) {
Map<DeprecationIssue, List<String>> issueListMap = new HashMap<>();
for (NodesDeprecationCheckAction.NodeResponse resp : response.getNodes()) {
for (DeprecationIssue issue : resp.getDeprecationIssues()) {
issueListMap.computeIfAbsent(issue, (key) -> new ArrayList<>()).add(resp.getNode().getName());
}
}

return issueListMap.entrySet().stream()
.map(entry -> {
DeprecationIssue issue = entry.getKey();
String details = issue.getDetails() != null ? issue.getDetails() + " " : "";
return new DeprecationIssue(issue.getLevel(), issue.getMessage(), issue.getUrl(),
details + "(nodes impacted: " + entry.getValue() + ")");
}).collect(Collectors.toList());
}

public static class Response extends ActionResponse implements ToXContentObject {
private List<DeprecationIssue> clusterSettingsIssues;
private List<DeprecationIssue> nodeSettingsIssues;
Expand Down Expand Up @@ -162,27 +151,32 @@ public int hashCode() {
* @param indexNameExpressionResolver Used to resolve indices into their concrete names
* @param indices The list of index expressions to evaluate using `indexNameExpressionResolver`
* @param indicesOptions The options to use when resolving and filtering which indices to check
* @param datafeeds The ml datafeed configurations
* @param nodeDeprecationResponse The response containing the deprecation issues found on each node
* @param datafeeds The ml datafeed configurations, if ML is enabled
* @param rolesResponse The configured roles, if security is enabled
* @param fileRolesResponse The configured file-based roles, for each node, if security is enabled
* @param indexSettingsChecks The list of index-level checks that will be run across all specified
* concrete indices
* @param clusterSettingsChecks The list of cluster-level checks
* @param mlSettingsCheck The list of ml checks
* @param rolesChecks The list of security checks
* @param nodeDeprecationResponse The response containing the deprecation issues found on each node
* @return The list of deprecation issues found in the cluster
*/
public static DeprecationInfoAction.Response from(ClusterState state,
NamedXContentRegistry xContentRegistry,
IndexNameExpressionResolver indexNameExpressionResolver,
String[] indices, IndicesOptions indicesOptions,
List<DatafeedConfig> datafeeds,
NodesDeprecationCheckResponse nodeDeprecationResponse,
GetRolesResponse rolesResponse,
GetFileRolesResponse fileRolesResponse,
List<Function<IndexMetaData, DeprecationIssue>> indexSettingsChecks,
List<Function<ClusterState, DeprecationIssue>> clusterSettingsChecks,
List<BiFunction<DatafeedConfig, NamedXContentRegistry, DeprecationIssue>>
mlSettingsCheck) {
mlSettingsCheck,
List<Function<List<RoleDescriptor>, DeprecationIssue>> rolesChecks,
NodesDeprecationCheckResponse nodeDeprecationResponse) {
List<DeprecationIssue> clusterSettingsIssues = filterChecks(clusterSettingsChecks,
(c) -> c.apply(state));
List<DeprecationIssue> nodeSettingsIssues = mergeNodeIssues(nodeDeprecationResponse);
List<DeprecationIssue> mlSettingsIssues = new ArrayList<>();
for (DatafeedConfig config : datafeeds) {
mlSettingsIssues.addAll(filterChecks(mlSettingsCheck, (c) -> c.apply(config, xContentRegistry)));
Expand All @@ -200,7 +194,50 @@ public static DeprecationInfoAction.Response from(ClusterState state,
}
}

return new DeprecationInfoAction.Response(clusterSettingsIssues, nodeSettingsIssues, indexSettingsIssues, mlSettingsIssues);
// Security stuff
final List<DeprecationIssue> rolesIssues = filterChecks(rolesChecks, c -> c.apply(Arrays.asList(rolesResponse.roles())));
final List<DeprecationIssue> mergedClusterIssues = Stream.concat(clusterSettingsIssues.stream(), rolesIssues.stream())
.collect(Collectors.toList());

final Map<DiscoveryNode, List<DeprecationIssue>> rolesIssuesByNode = runNodeRoleChecks(fileRolesResponse, rolesChecks);
final Map<DiscoveryNode, List<DeprecationIssue>> nodeIssues = nodeResponseToMap(nodeDeprecationResponse);
final List<DeprecationIssue> mergedNodeIssues = mergeNodeIssues(nodeIssues, rolesIssuesByNode);

return new DeprecationInfoAction.Response(mergedClusterIssues, mergedNodeIssues, indexSettingsIssues, mlSettingsIssues);
}

private static List<DeprecationIssue> mergeNodeIssues(Map<DiscoveryNode, List<DeprecationIssue>> nodeIssues,
Map<DiscoveryNode, List<DeprecationIssue>> rolesIssuesByNode) {
Map<DeprecationIssue, List<String>> issuesToNodeMap = new HashMap<>();
Stream.concat(nodeIssues.entrySet().stream(), rolesIssuesByNode.entrySet().stream())
.forEach(entry -> {
String nodeName = entry.getKey().getName();
entry.getValue().stream()
.forEach(issue -> issuesToNodeMap.computeIfAbsent(issue, i -> new ArrayList<>()).add(nodeName));
});
return issuesToNodeMap.entrySet().stream()
.map(entry -> {
DeprecationIssue issue = entry.getKey();
String details = issue.getDetails() != null ? issue.getDetails() + " " : "";
return new DeprecationIssue(issue.getLevel(), issue.getMessage(), issue.getUrl(),
details + "(nodes impacted: " + entry.getValue() + ")");
}).collect(Collectors.toList());
}

private static Map<DiscoveryNode, List<DeprecationIssue>> runNodeRoleChecks(GetFileRolesResponse fileRolesResponse,
List<Function<List<RoleDescriptor>,
DeprecationIssue>> rolesChecks) {
return fileRolesResponse.getNodes().stream()
.collect(Collectors.groupingBy(BaseNodeResponse::getNode,
Collectors.flatMapping(nodeResp -> filterChecks(rolesChecks, (c) -> c.apply(nodeResp.getRoles())).stream(),
Collectors.toList())));
}

private static Map<DiscoveryNode, List<DeprecationIssue>> nodeResponseToMap(NodesDeprecationCheckResponse nodeDeprecationResponse) {
return nodeDeprecationResponse.getNodes().stream()
.collect(Collectors.groupingBy(BaseNodeResponse::getNode,
Collectors.flatMapping(nodeResp -> nodeResp.getDeprecationIssues().stream(),
Collectors.toList())));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.role;

import org.elasticsearch.action.ActionType;
import org.elasticsearch.action.support.nodes.BaseNodeRequest;
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

public class GetFileRolesAction extends ActionType<GetFileRolesResponse> {
public static final GetFileRolesAction INSTANCE = new GetFileRolesAction();
public static final String NAME = "cluster:admin/xpack/security/role/file/get";


private GetFileRolesAction() {
super(NAME, GetFileRolesResponse::new);
}

public static class NodeRequest extends BaseNodeRequest {
GetFileRolesRequest request;

public NodeRequest(StreamInput in) throws IOException {
super(in);
request = new GetFileRolesRequest(in);
}

public NodeRequest(GetFileRolesRequest request) {
this.request = request;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
request.writeTo(out);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeRequest that = (NodeRequest) o;
return Objects.equals(request, that.request);
}

@Override
public int hashCode() {
return Objects.hash(request);
}
}

public static class NodeResponse extends BaseNodeResponse {
private final List<RoleDescriptor> roles;

public NodeResponse(StreamInput in) throws IOException {
super(in);
roles = in.readList(RoleDescriptor::new);
}

public NodeResponse(DiscoveryNode node, List<RoleDescriptor> roles) {
super(node);
this.roles = roles;
}

public List<RoleDescriptor> getRoles() {
return roles;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(roles);
}

@Override
public String toString() {
return "GetFileRolesNodeResponse[roles=" + roles.toString() + "]";
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeResponse that = (NodeResponse) o;
return Objects.equals(this.getNode(), that.getNode())
&& Objects.equals(roles, that.roles);
}

@Override
public int hashCode() {
return Objects.hash(getNode(), roles);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.role;

import org.elasticsearch.action.support.nodes.BaseNodesRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

public class GetFileRolesRequest extends BaseNodesRequest<GetFileRolesRequest> {
public GetFileRolesRequest(StreamInput in) throws IOException {
super(in);
}

public GetFileRolesRequest(String... nodesIds) {
super(nodesIds);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
}

@Override
public int hashCode() {
return Objects.hash((Object[]) this.nodesIds());
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
GetFileRolesRequest that = (GetFileRolesRequest) obj;
return Arrays.equals(this.nodesIds(), that.nodesIds());
}

@Override
public String toString() {
return "GetFileRolesRequest[nodes: " + Arrays.toString(this.nodesIds()) + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.role;

import org.elasticsearch.action.FailedNodeException;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

public class GetFileRolesResponse extends BaseNodesResponse<GetFileRolesAction.NodeResponse> {

public GetFileRolesResponse(StreamInput in) throws IOException {
super(in);
}

public GetFileRolesResponse(ClusterName clusterName, List<GetFileRolesAction.NodeResponse> nodes,
List<FailedNodeException> failures) {
super(clusterName, nodes, failures);
}

@Override
protected List<GetFileRolesAction.NodeResponse> readNodesFrom(StreamInput in) throws IOException {
return in.readList(GetFileRolesAction.NodeResponse::new);
}

@Override
protected void writeNodesTo(StreamOutput out, List<GetFileRolesAction.NodeResponse> nodes) throws IOException {
out.writeList(nodes);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GetFileRolesResponse that = (GetFileRolesResponse) o;
return Objects.equals(getClusterName(), that.getClusterName())
&& Objects.equals(getNodes(), that.getNodes())
&& Objects.equals(failures(), that.failures());
}

@Override
public int hashCode() {
return Objects.hash(getClusterName(), getNodes(), failures());
}

@Override
public String toString() {
return "GetFileRolesResponse[nodeResponses= " + this.getNodes().toString() + "]";
}
}
Loading