Skip to content

Commit 79f0c46

Browse files
authored
Api token authc/z implementation with Cache (#4992)
Signed-off-by: Derek Ho <[email protected]>
1 parent 190bfec commit 79f0c46

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1952
-638
lines changed

src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java

+142-16
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,20 @@
3838
import org.opensearch.common.util.concurrent.ThreadContext;
3939
import org.opensearch.core.common.unit.ByteSizeUnit;
4040
import org.opensearch.core.common.unit.ByteSizeValue;
41+
import org.opensearch.security.action.apitokens.ApiToken;
42+
import org.opensearch.security.action.apitokens.ApiTokenRepository;
43+
import org.opensearch.security.action.apitokens.Permissions;
4144
import org.opensearch.security.resolver.IndexResolverReplacer;
4245
import org.opensearch.security.securityconf.FlattenedActionGroups;
4346
import org.opensearch.security.securityconf.impl.CType;
4447
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
48+
import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7;
4549
import org.opensearch.security.securityconf.impl.v7.RoleV7;
4650
import org.opensearch.security.user.User;
4751
import org.opensearch.security.util.MockIndexMetadataBuilder;
4852

53+
import org.mockito.Mockito;
54+
4955
import static org.hamcrest.MatcherAssert.assertThat;
5056
import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isAllowed;
5157
import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isForbidden;
@@ -280,6 +286,69 @@ public void hasAny_wildcard() throws Exception {
280286
isForbidden(missingPrivileges("cluster:whatever"))
281287
);
282288
}
289+
290+
@Test
291+
public void apiToken_explicit_failsWithWildcard() throws Exception {
292+
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.empty(CType.ROLES);
293+
ActionPrivileges subject = new ActionPrivileges(roles, FlattenedActionGroups.EMPTY, null, Settings.EMPTY);
294+
String token = "blah";
295+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
296+
"apitoken:" + token,
297+
new Permissions(List.of("*"), List.of())
298+
);
299+
// Explicit fails
300+
assertThat(
301+
subject.hasExplicitClusterPrivilege(context, "cluster:whatever"),
302+
isForbidden(missingPrivileges("cluster:whatever"))
303+
);
304+
// Not explicit succeeds
305+
assertThat(subject.hasClusterPrivilege(context, "cluster:whatever"), isAllowed());
306+
// Any succeeds
307+
assertThat(subject.hasAnyClusterPrivilege(context, ImmutableSet.of("cluster:whatever")), isAllowed());
308+
}
309+
310+
@Test
311+
public void apiToken_succeedsWithExactMatch() throws Exception {
312+
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.empty(CType.ROLES);
313+
ActionPrivileges subject = new ActionPrivileges(roles, FlattenedActionGroups.EMPTY, null, Settings.EMPTY);
314+
String token = "blah";
315+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
316+
"apitoken:" + token,
317+
new Permissions(List.of("cluster:whatever"), List.of())
318+
);
319+
// Explicit succeeds
320+
assertThat(subject.hasExplicitClusterPrivilege(context, "cluster:whatever"), isAllowed());
321+
// Not explicit succeeds
322+
assertThat(subject.hasClusterPrivilege(context, "cluster:whatever"), isAllowed());
323+
// Any succeeds
324+
assertThat(subject.hasAnyClusterPrivilege(context, ImmutableSet.of("cluster:whatever")), isAllowed());
325+
// Any succeeds
326+
assertThat(subject.hasAnyClusterPrivilege(context, ImmutableSet.of("cluster:whatever", "cluster:other")), isAllowed());
327+
}
328+
329+
@Test
330+
public void apiToken_succeedsWithActionGroupsExapnded() throws Exception {
331+
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.empty(CType.ROLES);
332+
333+
SecurityDynamicConfiguration<ActionGroupsV7> config = SecurityDynamicConfiguration.fromYaml(
334+
"CLUSTER_ALL:\n allowed_actions:\n - \"cluster:*\"",
335+
CType.ACTIONGROUPS
336+
);
337+
338+
FlattenedActionGroups actionGroups = new FlattenedActionGroups(config);
339+
ActionPrivileges subject = new ActionPrivileges(roles, actionGroups, null, Settings.EMPTY);
340+
String token = "blah";
341+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
342+
"apitoken:" + token,
343+
new Permissions(List.of("CLUSTER_ALL"), List.of())
344+
);
345+
// Explicit succeeds
346+
assertThat(subject.hasExplicitClusterPrivilege(context, "cluster:whatever"), isAllowed());
347+
// Not explicit succeeds
348+
assertThat(subject.hasClusterPrivilege(context, "cluster:whatever"), isAllowed());
349+
// Any succeeds
350+
assertThat(subject.hasClusterPrivilege(context, "cluster:monitor/main"), isAllowed());
351+
}
283352
}
284353

285354
/**
@@ -314,9 +383,20 @@ public void positive_full() throws Exception {
314383
assertThat(result, isAllowed());
315384
}
316385

386+
@Test
387+
public void apiTokens_positive_full() throws Exception {
388+
String token = "blah";
389+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
390+
"apitoken:" + token,
391+
new Permissions(List.of("index_a11"), List.of(new ApiToken.IndexPermission(List.of("index_a11"), List.of("*"))))
392+
);
393+
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(context, requiredActions, resolved("index_a11"));
394+
assertThat(result, isAllowed());
395+
}
396+
317397
@Test
318398
public void positive_partial() throws Exception {
319-
PrivilegesEvaluationContext ctx = ctx("test_role");
399+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
320400
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, requiredActions, resolved("index_a11", "index_a12"));
321401

322402
if (covers(ctx, "index_a11", "index_a12")) {
@@ -330,7 +410,7 @@ public void positive_partial() throws Exception {
330410

331411
@Test
332412
public void positive_partial2() throws Exception {
333-
PrivilegesEvaluationContext ctx = ctx("test_role");
413+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
334414
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(
335415
ctx,
336416
requiredActions,
@@ -363,14 +443,26 @@ public void positive_noLocal() throws Exception {
363443

364444
@Test
365445
public void negative_wrongRole() throws Exception {
366-
PrivilegesEvaluationContext ctx = ctx("other_role");
446+
RoleBasedPrivilegesEvaluationContext ctx = ctx("other_role");
367447
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, requiredActions, resolved("index_a11"));
368448
assertThat(result, isForbidden(missingPrivileges(requiredActions)));
369449
}
370450

451+
@Test
452+
public void apiToken_negative_noPermissions() throws Exception {
453+
String token = "blah";
454+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
455+
"apitoken:" + token,
456+
new Permissions(List.of(), List.of(new ApiToken.IndexPermission(List.of(), List.of())))
457+
);
458+
459+
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(context, requiredActions, resolved("index_a11"));
460+
assertThat(result, isForbidden());
461+
}
462+
371463
@Test
372464
public void negative_wrongAction() throws Exception {
373-
PrivilegesEvaluationContext ctx = ctx("test_role");
465+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
374466
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, otherActions, resolved("index_a11"));
375467

376468
if (actionSpec.givenPrivs.contains("*")) {
@@ -382,7 +474,7 @@ public void negative_wrongAction() throws Exception {
382474

383475
@Test
384476
public void positive_hasExplicit_full() {
385-
PrivilegesEvaluationContext ctx = ctx("test_role");
477+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
386478
PrivilegesEvaluatorResponse result = subject.hasExplicitIndexPrivilege(ctx, requiredActions, resolved("index_a11"));
387479

388480
if (actionSpec.givenPrivs.contains("*")) {
@@ -397,7 +489,21 @@ public void positive_hasExplicit_full() {
397489
}
398490
}
399491

400-
private boolean covers(PrivilegesEvaluationContext ctx, String... indices) {
492+
@Test
493+
public void apiTokens_positive_hasExplicit_full() {
494+
String token = "blah";
495+
PermissionBasedPrivilegesEvaluationContext context = ctxForApiToken(
496+
"apitoken:" + token,
497+
new Permissions(List.of("index_a11"), List.of(new ApiToken.IndexPermission(List.of("index_a11"), List.of("*"))))
498+
);
499+
500+
PrivilegesEvaluatorResponse result = subject.hasExplicitIndexPrivilege(context, requiredActions, resolved("index_a11"));
501+
502+
assertThat(result, isForbidden());
503+
504+
}
505+
506+
private boolean covers(RoleBasedPrivilegesEvaluationContext ctx, String... indices) {
401507
for (String index : indices) {
402508
if (!indexSpec.covers(ctx.getUser(), index)) {
403509
return false;
@@ -522,7 +628,7 @@ public static class DataStreams {
522628

523629
@Test
524630
public void positive_full() throws Exception {
525-
PrivilegesEvaluationContext ctx = ctx("test_role");
631+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
526632
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, requiredActions, resolved("data_stream_a11"));
527633
if (covers(ctx, "data_stream_a11")) {
528634
assertThat(result, isAllowed());
@@ -538,7 +644,7 @@ public void positive_full() throws Exception {
538644

539645
@Test
540646
public void positive_partial() throws Exception {
541-
PrivilegesEvaluationContext ctx = ctx("test_role");
647+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
542648
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(
543649
ctx,
544650
requiredActions,
@@ -569,19 +675,19 @@ public void positive_partial() throws Exception {
569675

570676
@Test
571677
public void negative_wrongRole() throws Exception {
572-
PrivilegesEvaluationContext ctx = ctx("other_role");
678+
RoleBasedPrivilegesEvaluationContext ctx = ctx("other_role");
573679
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, requiredActions, resolved("data_stream_a11"));
574680
assertThat(result, isForbidden(missingPrivileges(requiredActions)));
575681
}
576682

577683
@Test
578684
public void negative_wrongAction() throws Exception {
579-
PrivilegesEvaluationContext ctx = ctx("test_role");
685+
RoleBasedPrivilegesEvaluationContext ctx = ctx("test_role");
580686
PrivilegesEvaluatorResponse result = subject.hasIndexPrivilege(ctx, otherActions, resolved("data_stream_a11"));
581687
assertThat(result, isForbidden(missingPrivileges(otherActions)));
582688
}
583689

584-
private boolean covers(PrivilegesEvaluationContext ctx, String... indices) {
690+
private boolean covers(RoleBasedPrivilegesEvaluationContext ctx, String... indices) {
585691
for (String index : indices) {
586692
if (!indexSpec.covers(ctx.getUser(), index)) {
587693
return false;
@@ -1039,10 +1145,15 @@ static SecurityDynamicConfiguration<RoleV7> createRoles(int numberOfRoles, int n
10391145
}
10401146
}
10411147

1042-
static PrivilegesEvaluationContext ctx(String... roles) {
1043-
User user = new User("test_user");
1148+
static RoleBasedPrivilegesEvaluationContext ctx(String... roles) {
1149+
return ctxWithUserName("test-user", roles);
1150+
}
1151+
1152+
static RoleBasedPrivilegesEvaluationContext ctxWithUserName(String userName, String... roles) {
1153+
User user = new User(userName);
10441154
user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
1045-
return new PrivilegesEvaluationContext(
1155+
ApiTokenRepository mockRepository = Mockito.mock(ApiTokenRepository.class);
1156+
return new RoleBasedPrivilegesEvaluationContext(
10461157
user,
10471158
ImmutableSet.copyOf(roles),
10481159
null,
@@ -1054,10 +1165,25 @@ static PrivilegesEvaluationContext ctx(String... roles) {
10541165
);
10551166
}
10561167

1057-
static PrivilegesEvaluationContext ctxByUsername(String username) {
1168+
static PermissionBasedPrivilegesEvaluationContext ctxForApiToken(String userName, Permissions permissions) {
1169+
User user = new User(userName);
1170+
user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
1171+
return new PermissionBasedPrivilegesEvaluationContext(
1172+
user,
1173+
null,
1174+
null,
1175+
null,
1176+
null,
1177+
new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)),
1178+
null,
1179+
permissions
1180+
);
1181+
}
1182+
1183+
static RoleBasedPrivilegesEvaluationContext ctxByUsername(String username) {
10581184
User user = new User(username);
10591185
user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
1060-
return new PrivilegesEvaluationContext(
1186+
return new RoleBasedPrivilegesEvaluationContext(
10611187
user,
10621188
ImmutableSet.of(),
10631189
null,

src/integrationTest/java/org/opensearch/security/privileges/IndexPatternTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,14 @@ public void equals() {
231231
assertFalse(a1.equals(a1.toString()));
232232
}
233233

234-
private static PrivilegesEvaluationContext ctx() {
234+
private static RoleBasedPrivilegesEvaluationContext ctx() {
235235
IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY));
236236
IndexResolverReplacer indexResolverReplacer = new IndexResolverReplacer(indexNameExpressionResolver, () -> CLUSTER_STATE, null);
237237
User user = new User("test_user");
238238
user.addAttributes(ImmutableMap.of("attrs.a11", "a11"));
239239
user.addAttributes(ImmutableMap.of("attrs.year", "year"));
240240

241-
return new PrivilegesEvaluationContext(
241+
return new RoleBasedPrivilegesEvaluationContext(
242242
user,
243243
ImmutableSet.of(),
244244
"indices:action/test",

src/integrationTest/java/org/opensearch/security/privileges/RestEndpointPermissionTests.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,13 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() {
188188
.collect(Collectors.toList());
189189
for (final Endpoint endpoint : noSslEndpoints) {
190190
final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build();
191-
final PrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(endpoint.name().toLowerCase(Locale.ROOT)));
191+
final RoleBasedPrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(endpoint.name().toLowerCase(Locale.ROOT)));
192192
Assert.assertTrue(endpoint.name(), actionPrivileges.hasExplicitClusterPrivilege(ctx, permission).isAllowed());
193193
assertHasNoPermissionsForRestApiAdminOnePermissionRole(endpoint, ctx);
194194
}
195195
// verify SSL endpoint with 2 actions
196196
for (final String sslAction : ImmutableSet.of(CERTS_INFO_ACTION, RELOAD_CERTS_ACTION)) {
197-
final PrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(sslAction));
197+
final RoleBasedPrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(sslAction));
198198
final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(Endpoint.SSL);
199199
Assert.assertTrue(
200200
Endpoint.SSL + "/" + sslAction,
@@ -203,7 +203,7 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() {
203203
assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.SSL, ctx);
204204
}
205205
// verify CONFIG endpoint with 1 action
206-
final PrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(SECURITY_CONFIG_UPDATE));
206+
final RoleBasedPrivilegesEvaluationContext ctx = ctx(restAdminApiRoleName(SECURITY_CONFIG_UPDATE));
207207
final PermissionBuilder permissionBuilder = ENDPOINTS_WITH_PERMISSIONS.get(Endpoint.CONFIG);
208208
Assert.assertTrue(
209209
Endpoint.SSL + "/" + SECURITY_CONFIG_UPDATE,
@@ -212,7 +212,10 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() {
212212
assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.CONFIG, ctx);
213213
}
214214

215-
void assertHasNoPermissionsForRestApiAdminOnePermissionRole(final Endpoint allowEndpoint, final PrivilegesEvaluationContext ctx) {
215+
void assertHasNoPermissionsForRestApiAdminOnePermissionRole(
216+
final Endpoint allowEndpoint,
217+
final RoleBasedPrivilegesEvaluationContext ctx
218+
) {
216219
final Collection<Endpoint> noPermissionEndpoints = ENDPOINTS_WITH_PERMISSIONS.keySet()
217220
.stream()
218221
.filter(e -> e != allowEndpoint)
@@ -250,8 +253,17 @@ static SecurityDynamicConfiguration<RoleV7> createRolesConfig() throws IOExcepti
250253
return SecurityDynamicConfiguration.fromNode(rolesNode, CType.ROLES, 2, 0, 0);
251254
}
252255

253-
static PrivilegesEvaluationContext ctx(String... roles) {
254-
return new PrivilegesEvaluationContext(new User("test_user"), ImmutableSet.copyOf(roles), null, null, null, null, null, null);
256+
static RoleBasedPrivilegesEvaluationContext ctx(String... roles) {
257+
return new RoleBasedPrivilegesEvaluationContext(
258+
new User("test_user"),
259+
ImmutableSet.copyOf(roles),
260+
null,
261+
null,
262+
null,
263+
null,
264+
null,
265+
null
266+
);
255267
}
256268

257269
}

0 commit comments

Comments
 (0)