Skip to content

Commit

Permalink
Changes to make PIT security model granular
Browse files Browse the repository at this point in the history
Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
  • Loading branch information
bharath-techie committed Aug 28, 2022
1 parent 287e945 commit 62c6a15
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright 2015-2018 _floragunn_ GmbH
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.privileges;

import com.google.common.collect.ImmutableSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.OpenSearchException;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.StepListener;
import org.opensearch.action.search.DeletePitRequest;
import org.opensearch.action.search.GetAllPitNodesRequest;
import org.opensearch.action.search.GetAllPitNodesResponse;
import org.opensearch.action.search.ListPitInfo;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.security.OpenSearchSecurityPlugin;
import org.opensearch.security.resolver.IndexResolverReplacer;
import org.opensearch.security.securityconf.SecurityRoles;
import org.opensearch.security.user.User;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* This class evaluates privileges for point in time (Delete and List all) operations
*/
public class PitAccessEvaluator {

protected final Logger log = LogManager.getLogger(this.getClass());
private boolean isDebugEnabled = log.isDebugEnabled();

public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, ClusterService clusterService,
User user, SecurityRoles securityRoles, final String action,
IndexNameExpressionResolver resolver,
PrivilegesEvaluatorResponse presponse) {

// Skip pit evaluation for "NodesGetAllPITs" action, since it fetches all PITs across the cluster
// for privilege evaluation
if (action.startsWith("cluster:admin")) {
return presponse;
}
if (request instanceof GetAllPitNodesRequest) {
if (((GetAllPitNodesRequest) request).getGetAllPitNodesResponse() != null
&& !((GetAllPitNodesRequest) request).getGetAllPitNodesResponse().getPitInfos().isEmpty()) {
return presponse;
}
handleGetAllPitsAccess(request, clusterService, user, securityRoles,
action, resolver, presponse);
} else if (request instanceof DeletePitRequest) {
DeletePitRequest deletePitRequest = (DeletePitRequest) request;
List<String> pitIds = deletePitRequest.getPitIds();
if (pitIds.size() == 1 && "_all".equals(pitIds.get(0))) {
handleDeleteAllPitAccess(deletePitRequest, clusterService, user, securityRoles,
action, resolver, presponse);
} else {
handleDeletePitsAccess(deletePitRequest, clusterService, user, securityRoles,
action, resolver, presponse);
}
}
return presponse;
}

/**
* Handle access for Get All PITs access
*/
private void handleGetAllPitsAccess(final ActionRequest request, ClusterService clusterService,
User user, SecurityRoles securityRoles, final String action,
IndexNameExpressionResolver resolver,
PrivilegesEvaluatorResponse presponse) {
List<ListPitInfo> pitInfos = getAllPitInfos((GetAllPitNodesRequest) request);
List<String> pitIds = new ArrayList<>();
pitIds.addAll(pitInfos.stream().map(ListPitInfo::getPitId).collect(Collectors.toList()));
Map<String, String[]> pitToIndicesMap = OpenSearchSecurityPlugin.GuiceHolder.getPitService().getIndicesForPits(pitIds);
Map<String, ListPitInfo> pitToPitInfoMap = new HashMap<>();

for (ListPitInfo pitInfo : pitInfos) {
pitToPitInfoMap.put(pitInfo.getPitId(), pitInfo);
}
List<ListPitInfo> permittedPits = new ArrayList<>();
for (String pitId : pitIds) {
String[] indices = pitToIndicesMap.get(pitId);
HashSet<String> indicesSet = new HashSet<>(Arrays.asList(indices));

final ImmutableSet<String> INDICES_SET = ImmutableSet.copyOf(indicesSet);
final IndexResolverReplacer.Resolved pitResolved =
new IndexResolverReplacer.Resolved(INDICES_SET, INDICES_SET, INDICES_SET,
ImmutableSet.of(), SearchRequest.DEFAULT_INDICES_OPTIONS);

final Set<String> allPermittedIndices = securityRoles.reduce(pitResolved,
user, new String[]{action}, resolver, clusterService);
if (isDebugEnabled) {
log.debug("Evaluating PIT ID - {}", pitId);
}
if (allPermittedIndices.size() == INDICES_SET.size()) {
if (isDebugEnabled) {
log.debug(" Permitting PIT ID - {}", pitId);
}
permittedPits.add(pitToPitInfoMap.get(pitId));
}
}
if (permittedPits.size() > 0) {
GetAllPitNodesResponse resultResponse = ((GetAllPitNodesRequest) request).getGetAllPitNodesResponse();
resultResponse.clearAndSetPitInfos(permittedPits);
((GetAllPitNodesRequest) request).setGetAllPitNodesResponse(resultResponse);
presponse.allowed = true;
presponse.markComplete();
}
}

/**
* Handle access for 'delete all PITs' operation
*/
private void handleDeleteAllPitAccess(DeletePitRequest deletePitRequest, ClusterService clusterService,
User user, SecurityRoles securityRoles, final String action,
IndexNameExpressionResolver resolver,
PrivilegesEvaluatorResponse presponse) {
List<String> permittedPits = new ArrayList<>();
List<String> pitIds = getAllPitIds();
Map<String, String[]> pitToIndicesMap = OpenSearchSecurityPlugin.GuiceHolder.getPitService().getIndicesForPits(pitIds);
for (String pitId : pitIds) {
String[] indices = pitToIndicesMap.get(pitId);
HashSet<String> indicesSet = new HashSet<>(Arrays.asList(indices));
Set<String> allPermittedIndices = getPermittedIndices(indicesSet, clusterService, user,
securityRoles, action, resolver);
// user should have permissions for all indices associated with PIT, only then add PIT ID as permitted PIT
if (isDebugEnabled) {
log.debug("Evaluating PIT ID - {}", pitId);
}
if (allPermittedIndices.size() == indicesSet.size()) {
if (isDebugEnabled) {
log.debug(" Permitting PIT ID - {}", pitId);
}
permittedPits.add(pitId);
}
}
// If there are any PITs for which the user has access to, then allow operation otherwise fail.
if (permittedPits.size() > 0) {
deletePitRequest.clearAndSetPitIds(permittedPits);
presponse.allowed = true;
presponse.markComplete();
}
}

/**
* Handle access for delete operation where PIT IDs are explicitly passed
*/
private void handleDeletePitsAccess(DeletePitRequest deletePitRequest, ClusterService clusterService,
User user, SecurityRoles securityRoles, final String action,
IndexNameExpressionResolver resolver,
PrivilegesEvaluatorResponse presponse) {
List<String> pitIds = deletePitRequest.getPitIds();
Map<String, String[]> pitToIndicesMap = OpenSearchSecurityPlugin.
GuiceHolder.getPitService().getIndicesForPits(pitIds);
Set<String> pitIndices = new HashSet<>();
// add indices across all PITs to a set and evaluate if user has access to all indices
for (String[] indices : pitToIndicesMap.values()) {
pitIndices.addAll(Arrays.asList(indices));
}
Set<String> allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user,
securityRoles, action, resolver);
// In this case, PIT IDs are explicitly passed.
// So, only if user has access to all PIT's indices, allow delete operation, otherwise fail.
if (pitIndices.size() == allPermittedIndices.size()) {
presponse.allowed = true;
presponse.markComplete();
}
}

/**
* This method returns list of permitted indices for the PIT indices passed
*/
private Set<String> getPermittedIndices(Set<String> pitIndices, ClusterService clusterService,
User user, SecurityRoles securityRoles, final String action,
IndexNameExpressionResolver resolver) {
final ImmutableSet<String> INDICES_SET = ImmutableSet.copyOf(pitIndices);
final IndexResolverReplacer.Resolved pitResolved =
new IndexResolverReplacer.Resolved(INDICES_SET, INDICES_SET, INDICES_SET,
ImmutableSet.of(), SearchRequest.DEFAULT_INDICES_OPTIONS);
return securityRoles.reduce(pitResolved,
user, new String[]{action}, resolver, clusterService);
}

/**
* Get all active PITs
*/
private List<ListPitInfo> getAllPitInfos(GetAllPitNodesRequest request) {
StepListener<GetAllPitNodesResponse> stepListener = new StepListener();
OpenSearchSecurityPlugin.GuiceHolder.getPitService().getAllPits(stepListener);
List<ListPitInfo> pitInfos = new ArrayList<>();

stepListener.whenComplete(response -> {
pitInfos.addAll(response.getPitInfos());
request.setGetAllPitNodesResponse(response);
}, failure -> {
throwPitException(failure);
});
return pitInfos;
}

/**
* Get all active PIT IDs
*/
private List<String> getAllPitIds() {
StepListener<GetAllPitNodesResponse> stepListener = new StepListener();
OpenSearchSecurityPlugin.GuiceHolder.getPitService().getAllPits(stepListener);
List<String> pitIds = new ArrayList<>();

stepListener.whenComplete(response -> {
pitIds.addAll(response.getPitInfos().stream().map(r -> r.getPitId()).collect(Collectors.toList()));
}, failure -> {
throwPitException(failure);
});
return pitIds;
}

private void throwPitException(Exception e) {
log.error("List all PITs failed ", e);
throw new OpenSearchException("List all PITs failed");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public class PrivilegesEvaluator {
private final SecurityIndexAccessEvaluator securityIndexAccessEvaluator;
private final ProtectedIndexAccessEvaluator protectedIndexAccessEvaluator;
private final TermsAggregationEvaluator termsAggregationEvaluator;
private final PitAccessEvaluator pitAccessEvaluator;
private final boolean dlsFlsEnabled;
private final boolean dfmEmptyOverwritesAll;
private DynamicConfigModel dcm;
Expand Down Expand Up @@ -158,6 +159,7 @@ public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool
securityIndexAccessEvaluator = new SecurityIndexAccessEvaluator(settings, auditLog, irr);
protectedIndexAccessEvaluator = new ProtectedIndexAccessEvaluator(settings, auditLog);
termsAggregationEvaluator = new TermsAggregationEvaluator();
pitAccessEvaluator = new PitAccessEvaluator();
this.namedXContentRegistry = namedXContentRegistry;
this.dlsFlsEnabled = dlsFlsEnabled;
this.dfmEmptyOverwritesAll = settings.getAsBoolean(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false);
Expand Down Expand Up @@ -282,6 +284,12 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin
return presponse;
}

// check access for point in time requests
if(pitAccessEvaluator.evaluate(request, clusterService, user, securityRoles,
action0, resolver, presponse).isComplete()) {
return presponse;
}

final boolean dnfofEnabled = dcm.isDnfofEnabled();

final boolean isTraceEnabled = log.isTraceEnabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,11 @@ public final static class Resolved {
private final boolean isLocalAll;
private final IndicesOptions indicesOptions;

private Resolved(final ImmutableSet<String> aliases,
final ImmutableSet<String> allIndices,
final ImmutableSet<String> originalRequested,
final ImmutableSet<String> remoteIndices,
IndicesOptions indicesOptions) {
public Resolved(final ImmutableSet<String> aliases,
final ImmutableSet<String> allIndices,
final ImmutableSet<String> originalRequested,
final ImmutableSet<String> remoteIndices,
IndicesOptions indicesOptions) {
this.aliases = aliases;
this.allIndices = allIndices;
this.originalRequested = originalRequested;
Expand Down
7 changes: 4 additions & 3 deletions src/main/resources/static_config/static_action_groups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ manage_point_in_time:
static: true
allowed_actions:
- "indices:data/read/point_in_time/create"
- "cluster:admin/point_in_time/delete"
- "cluster:admin/point_in_time/read*"
- "indices:data/read/point_in_time/delete"
- "indices:data/read/point_in_time/readall"
- "cluster:admin/point_in_time/read_from_nodes"
- "indices:monitor/point_in_time/segments"
type: "cluster"
type: "index"
description: "Manage point in time actions"

0 comments on commit 62c6a15

Please sign in to comment.