Skip to content

Commit 2a1e7d7

Browse files
Point in time security changes (#2094) (#2223)
Description For 'Delete PIT' and 'PIT segments' API, when PIT IDs are passed as part of request, this custom evaluator decode the PITs to indices and resolve the indices with user permissions. If user has permission to all indices of PIT, then PIT is permitted to the user. Only when the user has permissions for all PITs in the request, then we allow the operation. For requests which operates on 'all' PITs, we skip the custom evaluator and evaluate via standard code Alias and data stream behavior : PIT IDs always contain the resolved indices ( underlying indices ) when saved. Based on this, For alias, user must have either 'index' or 'alias' permission for any PIT operation. For data stream, user must have both 'data stream' AND 'backing indices of data stream' permission ( eg : data-stream-11 + .ds-my-data-stream11-000001 ) for any PIT operation. With just data stream permission, user will be able to create pit but will not be able to use the PIT ID for other operations such as search without backing indices permission. Signed-off-by: Bharathwaj G <bharath78910@gmail.com> Signed-off-by: Bharathwaj G <58062316+bharath-techie@users.noreply.github.com> (cherry picked from commit 207cfcc)
1 parent b715b6a commit 2a1e7d7

File tree

11 files changed

+465
-9
lines changed

11 files changed

+465
-9
lines changed

config/roles.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,12 @@ snapshot_management_read_access:
246246
- 'cluster:admin/opensearch/snapshot_management/policy/explain'
247247
- 'cluster:admin/repository/get'
248248
- 'cluster:admin/snapshot/get'
249+
250+
# Allows user to use point in time functionality
251+
point_in_time_full_access:
252+
reserved: true
253+
index_permissions:
254+
- index_patterns:
255+
- '*'
256+
allowed_actions:
257+
- 'manage_point_in_time'

src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.opensearch.Version;
6767
import org.opensearch.action.ActionRequest;
6868
import org.opensearch.action.ActionResponse;
69+
import org.opensearch.action.search.PitService;
6970
import org.opensearch.action.search.SearchScrollAction;
7071
import org.opensearch.action.support.ActionFilter;
7172
import org.opensearch.client.Client;
@@ -1161,12 +1162,15 @@ public static class GuiceHolder implements LifecycleComponent {
11611162
private static RemoteClusterService remoteClusterService;
11621163
private static IndicesService indicesService;
11631164

1165+
private static PitService pitService;
1166+
11641167
@Inject
11651168
public GuiceHolder(final RepositoriesService repositoriesService,
1166-
final TransportService remoteClusterService, IndicesService indicesService) {
1169+
final TransportService remoteClusterService, IndicesService indicesService, PitService pitService) {
11671170
GuiceHolder.repositoriesService = repositoriesService;
11681171
GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService();
11691172
GuiceHolder.indicesService = indicesService;
1173+
GuiceHolder.pitService = pitService;
11701174
}
11711175

11721176
public static RepositoriesService getRepositoriesService() {
@@ -1180,6 +1184,8 @@ public static RemoteClusterService getRemoteClusterService() {
11801184
public static IndicesService getIndicesService() {
11811185
return indicesService;
11821186
}
1187+
1188+
public static PitService getPitService() { return pitService; }
11831189

11841190
@Override
11851191
public void close() {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
package org.opensearch.security.privileges;
12+
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.HashSet;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Set;
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.opensearch.action.ActionRequest;
22+
import org.opensearch.action.admin.indices.segments.PitSegmentsRequest;
23+
import org.opensearch.action.search.CreatePitRequest;
24+
import org.opensearch.action.search.DeletePitRequest;
25+
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
26+
import org.opensearch.cluster.service.ClusterService;
27+
import org.opensearch.common.unit.TimeValue;
28+
import org.opensearch.security.OpenSearchSecurityPlugin;
29+
import org.opensearch.security.resolver.IndexResolverReplacer;
30+
import org.opensearch.security.securityconf.SecurityRoles;
31+
import org.opensearch.security.user.User;
32+
33+
34+
/**
35+
* This class evaluates privileges for point in time (Delete and List all) operations.
36+
* For aliases - users must have either alias permission or backing index permissions
37+
* For data streams - users must have access to backing indices permission + data streams permission.
38+
*/
39+
public class PitPrivilegesEvaluator {
40+
41+
public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final ClusterService clusterService,
42+
final User user, final SecurityRoles securityRoles, final String action,
43+
final IndexNameExpressionResolver resolver,
44+
final PrivilegesEvaluatorResponse presponse,
45+
final IndexResolverReplacer irr) {
46+
47+
if(!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) {
48+
return presponse;
49+
}
50+
List<String> pitIds = new ArrayList<>();
51+
52+
if (request instanceof DeletePitRequest) {
53+
DeletePitRequest deletePitRequest = (DeletePitRequest) request;
54+
pitIds = deletePitRequest.getPitIds();
55+
} else if(request instanceof PitSegmentsRequest) {
56+
PitSegmentsRequest pitSegmentsRequest = (PitSegmentsRequest) request;
57+
pitIds = pitSegmentsRequest.getPitIds();
58+
}
59+
// if request is for all PIT IDs, skip custom pit ids evaluation
60+
if (pitIds.size() == 1 && "_all".equals(pitIds.get(0))) {
61+
return presponse;
62+
} else {
63+
return handlePitsAccess(pitIds, clusterService, user, securityRoles,
64+
action, resolver, presponse, irr);
65+
}
66+
}
67+
68+
/**
69+
* Handle access for delete operation / pit segments operation where PIT IDs are explicitly passed
70+
*/
71+
private PrivilegesEvaluatorResponse handlePitsAccess(List<String> pitIds, ClusterService clusterService,
72+
User user, SecurityRoles securityRoles, final String action,
73+
IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse,
74+
final IndexResolverReplacer irr) {
75+
Map<String, String[]> pitToIndicesMap = OpenSearchSecurityPlugin.
76+
GuiceHolder.getPitService().getIndicesForPits(pitIds);
77+
Set<String> pitIndices = new HashSet<>();
78+
// add indices across all PITs to a set and evaluate if user has access to all indices
79+
for(String[] indices: pitToIndicesMap.values()) {
80+
pitIndices.addAll(Arrays.asList(indices));
81+
}
82+
Set<String> allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user,
83+
securityRoles, action, resolver, irr);
84+
// Only if user has access to all PIT's indices, allow operation, otherwise continue evaluation in PrivilegesEvaluator.
85+
if(allPermittedIndices.containsAll(pitIndices)) {
86+
presponse.allowed = true;
87+
presponse.markComplete();
88+
}
89+
return presponse;
90+
}
91+
92+
/**
93+
* This method returns list of permitted indices for the PIT indices passed
94+
*/
95+
private Set<String> getPermittedIndices(Set<String> pitIndices, ClusterService clusterService,
96+
User user, SecurityRoles securityRoles, final String action,
97+
IndexNameExpressionResolver resolver, final IndexResolverReplacer irr) {
98+
String[] indicesArr = new String[pitIndices.size()];
99+
CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true,
100+
pitIndices.toArray(indicesArr));
101+
final IndexResolverReplacer.Resolved pitResolved = irr.resolveRequest(req);
102+
return securityRoles.reduce(pitResolved,
103+
user, new String[]{action}, resolver, clusterService);
104+
}
105+
}

src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public class PrivilegesEvaluator {
130130
private final SecurityIndexAccessEvaluator securityIndexAccessEvaluator;
131131
private final ProtectedIndexAccessEvaluator protectedIndexAccessEvaluator;
132132
private final TermsAggregationEvaluator termsAggregationEvaluator;
133+
private final PitPrivilegesEvaluator pitPrivilegesEvaluator;
133134
private final boolean dlsFlsEnabled;
134135
private final boolean dfmEmptyOverwritesAll;
135136
private DynamicConfigModel dcm;
@@ -158,6 +159,7 @@ public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool
158159
securityIndexAccessEvaluator = new SecurityIndexAccessEvaluator(settings, auditLog, irr);
159160
protectedIndexAccessEvaluator = new ProtectedIndexAccessEvaluator(settings, auditLog);
160161
termsAggregationEvaluator = new TermsAggregationEvaluator();
162+
pitPrivilegesEvaluator = new PitPrivilegesEvaluator();
161163
this.namedXContentRegistry = namedXContentRegistry;
162164
this.dlsFlsEnabled = dlsFlsEnabled;
163165
this.dfmEmptyOverwritesAll = settings.getAsBoolean(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false);
@@ -282,6 +284,12 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin
282284
return presponse;
283285
}
284286

287+
// check access for point in time requests
288+
if(pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles,
289+
action0, resolver, presponse, irr).isComplete()) {
290+
return presponse;
291+
}
292+
285293
final boolean dnfofEnabled = dcm.isDnfofEnabled();
286294

287295
final boolean isTraceEnabled = log.isTraceEnabled();

src/main/java/org/opensearch/security/resolver/IndexResolverReplacer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,11 @@ public final static class Resolved {
370370
private final boolean isLocalAll;
371371
private final IndicesOptions indicesOptions;
372372

373-
private Resolved(final ImmutableSet<String> aliases,
374-
final ImmutableSet<String> allIndices,
375-
final ImmutableSet<String> originalRequested,
376-
final ImmutableSet<String> remoteIndices,
377-
IndicesOptions indicesOptions) {
373+
public Resolved(final ImmutableSet<String> aliases,
374+
final ImmutableSet<String> allIndices,
375+
final ImmutableSet<String> originalRequested,
376+
final ImmutableSet<String> remoteIndices,
377+
IndicesOptions indicesOptions) {
378378
this.aliases = aliases;
379379
this.allIndices = allIndices;
380380
this.originalRequested = originalRequested;

src/main/resources/static_config/static_action_groups.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,9 @@ manage_point_in_time:
233233
static: true
234234
allowed_actions:
235235
- "indices:data/read/point_in_time/create"
236-
- "cluster:admin/point_in_time/delete"
237-
- "cluster:admin/point_in_time/read*"
236+
- "indices:data/read/point_in_time/delete"
237+
- "indices:data/read/point_in_time/readall"
238+
- "indices:data/read/search"
238239
- "indices:monitor/point_in_time/segments"
239-
type: "cluster"
240+
type: "index"
240241
description: "Manage point in time actions"

0 commit comments

Comments
 (0)