Skip to content

Commit 9f91927

Browse files
committed
Merge branch '2.x' into security-subject-2.x-legacy-authz
2 parents 9bdaeee + a7d2c57 commit 9f91927

File tree

15 files changed

+138
-17
lines changed

15 files changed

+138
-17
lines changed

.github/actions/run-bwc-suite/action.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ runs:
5050
-Dbwc.version.previous=${{ steps.build-previous.outputs.built-version }}
5151
-Dbwc.version.next=${{ steps.build-next.outputs.built-version }} -i
5252
53-
- uses: actions/upload-artifact@v3
53+
- uses: actions/upload-artifact@v4
5454
if: always()
5555
with:
5656
name: ${{ inputs.report-artifact-name }}

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
arguments: |
119119
integrationTest -Dbuild.snapshot=false
120120
121-
- uses: alehechka/upload-tartifact@v2
121+
- uses: actions/upload-artifact@v4
122122
if: always()
123123
with:
124124
name: integration-${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
- run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew test
2626

27-
- uses: actions/upload-artifact@v3
27+
- uses: actions/upload-artifact@v4
2828
if: always()
2929
with:
3030
name: ${{ matrix.jdk }}-${{ matrix.test-run }}-reports

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,28 @@ public void wildcard() throws Exception {
126126
);
127127
}
128128

129+
@Test
130+
public void wildcardByUsername() throws Exception {
131+
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.empty(CType.ROLES);
132+
133+
ActionPrivileges subject = new ActionPrivileges(
134+
roles,
135+
FlattenedActionGroups.EMPTY,
136+
null,
137+
Settings.EMPTY,
138+
Map.of("plugin:org.opensearch.sample.SamplePlugin", Set.of("*"))
139+
);
140+
141+
assertThat(
142+
subject.hasClusterPrivilege(ctxByUsername("plugin:org.opensearch.sample.SamplePlugin"), "cluster:whatever"),
143+
isAllowed()
144+
);
145+
assertThat(
146+
subject.hasClusterPrivilege(ctx("plugin:org.opensearch.other.OtherPlugin"), "cluster:whatever"),
147+
isForbidden(missingPrivileges("cluster:whatever"))
148+
);
149+
}
150+
129151
@Test
130152
public void explicit_wellKnown() throws Exception {
131153
SecurityDynamicConfiguration<RoleV7> roles = SecurityDynamicConfiguration.fromYaml("non_explicit_role:\n" + //
@@ -455,7 +477,8 @@ public IndicesAndAliases(IndexSpec indexSpec, ActionSpec actionSpec, Statefulnes
455477
settings,
456478
WellKnownActions.CLUSTER_ACTIONS,
457479
WellKnownActions.INDEX_ACTIONS,
458-
WellKnownActions.INDEX_ACTIONS
480+
WellKnownActions.INDEX_ACTIONS,
481+
Map.of()
459482
);
460483

461484
if (statefulness == Statefulness.STATEFUL || statefulness == Statefulness.STATEFUL_LIMITED) {
@@ -1030,4 +1053,19 @@ static PrivilegesEvaluationContext ctx(String... roles) {
10301053
null
10311054
);
10321055
}
1056+
1057+
static PrivilegesEvaluationContext ctxByUsername(String username) {
1058+
User user = new User(username);
1059+
user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
1060+
return new PrivilegesEvaluationContext(
1061+
user,
1062+
ImmutableSet.of(),
1063+
null,
1064+
null,
1065+
null,
1066+
null,
1067+
new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY)),
1068+
null
1069+
);
1070+
}
10331071
}

src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/IndexDocumentIntoSystemIndexAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
public class IndexDocumentIntoSystemIndexAction extends ActionType<AcknowledgedResponse> {
1919
public static final IndexDocumentIntoSystemIndexAction INSTANCE = new IndexDocumentIntoSystemIndexAction();
20-
public static final String NAME = "mock:systemindex/index";
20+
public static final String NAME = "cluster:mock/systemindex/index";
2121

2222
private IndexDocumentIntoSystemIndexAction() {
2323
super(NAME, AcknowledgedResponse::new);

src/integrationTest/java/org/opensearch/security/systemindex/sampleplugin/RunClusterHealthAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
public class RunClusterHealthAction extends ActionType<AcknowledgedResponse> {
1919
public static final RunClusterHealthAction INSTANCE = new RunClusterHealthAction();
20-
public static final String NAME = "mock:cluster/monitor/health";
20+
public static final String NAME = "cluster:mock/monitor/health";
2121

2222
private RunClusterHealthAction() {
2323
super(NAME, AcknowledgedResponse::new);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.opensearch.SpecialPermission;
6969
import org.opensearch.Version;
7070
import org.opensearch.action.ActionRequest;
71+
import org.opensearch.action.bulk.BulkAction;
7172
import org.opensearch.action.search.PitService;
7273
import org.opensearch.action.search.SearchScrollAction;
7374
import org.opensearch.action.support.ActionFilter;
@@ -2199,7 +2200,11 @@ public SecurityTokenManager getTokenManager() {
21992200

22002201
@Override
22012202
public PluginSubject getPluginSubject(Plugin plugin) {
2202-
return new ContextProvidingPluginSubject(threadPool, settings, plugin);
2203+
Set<String> clusterActions = new HashSet<>();
2204+
clusterActions.add(BulkAction.NAME);
2205+
PluginSubject subject = new ContextProvidingPluginSubject(threadPool, settings, plugin);
2206+
sf.updatePluginToClusterActions(subject.getPrincipal().getName(), clusterActions);
2207+
return subject;
22032208
}
22042209

22052210
@Override

src/main/java/org/opensearch/security/auth/BackendRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ public boolean authenticate(final SecurityRequestChannel request) {
226226
// PKI authenticated REST call
227227
User superuser = new User(sslPrincipal);
228228
UserSubject subject = new UserSubjectImpl(threadPool, superuser);
229-
threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject);
229+
threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject);
230230
threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, superuser);
231231
auditLog.logSucceededLogin(sslPrincipal, true, null, request);
232232
return true;

src/main/java/org/opensearch/security/filter/SecurityFilter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,15 @@ private boolean checkImmutableIndices(Object request, ActionListener listener) {
522522
return false;
523523
}
524524

525+
public void updatePluginToClusterActions(String pluginIdentifier, Set<String> clusterActions) {
526+
if (evalp instanceof org.opensearch.security.privileges.PrivilegesEvaluatorImpl) {
527+
((org.opensearch.security.privileges.PrivilegesEvaluatorImpl) evalp).updatePluginToClusterActions(
528+
pluginIdentifier,
529+
clusterActions
530+
);
531+
}
532+
}
533+
525534
private boolean isRequestIndexImmutable(Object request) {
526535
final IndexResolverReplacer.Resolved resolved = indexResolverReplacer.resolveRequest(request);
527536
if (resolved.isLocalAll()) {

src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ public class ContextProvidingPluginSubject implements PluginSubject {
2929
public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) {
3030
super();
3131
this.threadPool = threadPool;
32-
this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName());
32+
String principal = "plugin:" + plugin.getClass().getCanonicalName();
33+
this.pluginPrincipal = new NamedPrincipal(principal);
3334
// Convention for plugin username. Prefixed with 'plugin:'. ':' is forbidden from usernames, so this
3435
// guarantees that a user with this username cannot be created by other means.
35-
this.pluginUser = new User("plugin:" + pluginPrincipal.getName());
36+
this.pluginUser = new User(principal);
3637
}
3738

3839
@Override

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

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ public ActionPrivileges(
9090
Settings settings,
9191
ImmutableSet<String> wellKnownClusterActions,
9292
ImmutableSet<String> wellKnownIndexActions,
93-
ImmutableSet<String> explicitlyRequiredIndexActions
93+
ImmutableSet<String> explicitlyRequiredIndexActions,
94+
Map<String, Set<String>> pluginToClusterActions
9495
) {
95-
this.cluster = new ClusterPrivileges(roles, actionGroups, wellKnownClusterActions);
96+
this.cluster = new ClusterPrivileges(roles, actionGroups, wellKnownClusterActions, pluginToClusterActions);
9697
this.index = new IndexPrivileges(roles, actionGroups, wellKnownIndexActions, explicitlyRequiredIndexActions);
9798
this.roles = roles;
9899
this.actionGroups = actionGroups;
@@ -115,7 +116,27 @@ public ActionPrivileges(
115116
settings,
116117
WellKnownActions.CLUSTER_ACTIONS,
117118
WellKnownActions.INDEX_ACTIONS,
118-
WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS
119+
WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS,
120+
Map.of()
121+
);
122+
}
123+
124+
public ActionPrivileges(
125+
SecurityDynamicConfiguration<RoleV7> roles,
126+
FlattenedActionGroups actionGroups,
127+
Supplier<Map<String, IndexAbstraction>> indexMetadataSupplier,
128+
Settings settings,
129+
Map<String, Set<String>> pluginToClusterActions
130+
) {
131+
this(
132+
roles,
133+
actionGroups,
134+
indexMetadataSupplier,
135+
settings,
136+
WellKnownActions.CLUSTER_ACTIONS,
137+
WellKnownActions.INDEX_ACTIONS,
138+
WellKnownActions.EXPLICITLY_REQUIRED_INDEX_ACTIONS,
139+
pluginToClusterActions
119140
);
120141
}
121142

@@ -297,6 +318,8 @@ static class ClusterPrivileges {
297318
*/
298319
private final ImmutableMap<String, WildcardMatcher> rolesToActionMatcher;
299320

321+
private final ImmutableMap<String, WildcardMatcher> usersToActionMatcher;
322+
300323
private final ImmutableSet<String> wellKnownClusterActions;
301324

302325
/**
@@ -310,14 +333,16 @@ static class ClusterPrivileges {
310333
ClusterPrivileges(
311334
SecurityDynamicConfiguration<RoleV7> roles,
312335
FlattenedActionGroups actionGroups,
313-
ImmutableSet<String> wellKnownClusterActions
336+
ImmutableSet<String> wellKnownClusterActions,
337+
Map<String, Set<String>> pluginToClusterActions
314338
) {
315339
DeduplicatingCompactSubSetBuilder<String> roleSetBuilder = new DeduplicatingCompactSubSetBuilder<>(
316340
roles.getCEntries().keySet()
317341
);
318342
Map<String, DeduplicatingCompactSubSetBuilder.SubSetBuilder<String>> actionToRoles = new HashMap<>();
319343
ImmutableSet.Builder<String> rolesWithWildcardPermissions = ImmutableSet.builder();
320344
ImmutableMap.Builder<String, WildcardMatcher> rolesToActionMatcher = ImmutableMap.builder();
345+
ImmutableMap.Builder<String, WildcardMatcher> usersToActionMatcher = ImmutableMap.builder();
321346

322347
for (Map.Entry<String, RoleV7> entry : roles.getCEntries().entrySet()) {
323348
try {
@@ -367,13 +392,22 @@ static class ClusterPrivileges {
367392
}
368393
}
369394

395+
if (pluginToClusterActions != null) {
396+
for (String pluginIdentifier : pluginToClusterActions.keySet()) {
397+
Set<String> clusterActions = pluginToClusterActions.get(pluginIdentifier);
398+
WildcardMatcher matcher = WildcardMatcher.from(clusterActions);
399+
usersToActionMatcher.put(pluginIdentifier, matcher);
400+
}
401+
}
402+
370403
DeduplicatingCompactSubSetBuilder.Completed<String> completedRoleSetBuilder = roleSetBuilder.build();
371404

372405
this.actionToRoles = actionToRoles.entrySet()
373406
.stream()
374407
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().build(completedRoleSetBuilder)));
375408
this.rolesWithWildcardPermissions = rolesWithWildcardPermissions.build();
376409
this.rolesToActionMatcher = rolesToActionMatcher.build();
410+
this.usersToActionMatcher = usersToActionMatcher.build();
377411
this.wellKnownClusterActions = wellKnownClusterActions;
378412
}
379413

@@ -407,6 +441,14 @@ PrivilegesEvaluatorResponse providesPrivilege(PrivilegesEvaluationContext contex
407441
}
408442
}
409443

444+
// 4: If plugin is performing the action, check if plugin has permission
445+
if (context.getUser().isPluginUser() && this.usersToActionMatcher.containsKey(context.getUser().getName())) {
446+
WildcardMatcher matcher = this.usersToActionMatcher.get(context.getUser().getName());
447+
if (matcher != null && matcher.test(action)) {
448+
return PrivilegesEvaluatorResponse.ok();
449+
}
450+
}
451+
410452
return PrivilegesEvaluatorResponse.insufficient(action);
411453
}
412454

@@ -476,6 +518,16 @@ PrivilegesEvaluatorResponse providesAnyPrivilege(PrivilegesEvaluationContext con
476518
}
477519
}
478520

521+
// 4: If plugin is performing the action, check if plugin has permission
522+
if (this.usersToActionMatcher.containsKey(context.getUser().getName())) {
523+
WildcardMatcher matcher = this.usersToActionMatcher.get(context.getUser().getName());
524+
for (String action : actions) {
525+
if (matcher != null && matcher.test(action)) {
526+
return PrivilegesEvaluatorResponse.ok();
527+
}
528+
}
529+
}
530+
479531
if (actions.size() == 1) {
480532
return PrivilegesEvaluatorResponse.insufficient(actions.iterator().next());
481533
} else {

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.ArrayList;
3030
import java.util.Arrays;
3131
import java.util.Collections;
32+
import java.util.HashMap;
3233
import java.util.HashSet;
3334
import java.util.Iterator;
3435
import java.util.List;
@@ -142,6 +143,7 @@ public class PrivilegesEvaluatorImpl implements PrivilegesEvaluator {
142143
private final boolean checkSnapshotRestoreWritePrivileges;
143144

144145
private final ClusterInfoHolder clusterInfoHolder;
146+
private final ConfigurationRepository configurationRepository;
145147
private ConfigModel configModel;
146148
private final IndexResolverReplacer irr;
147149
private final SnapshotRestoreEvaluator snapshotRestoreEvaluator;
@@ -152,6 +154,7 @@ public class PrivilegesEvaluatorImpl implements PrivilegesEvaluator {
152154
private DynamicConfigModel dcm;
153155
private final NamedXContentRegistry namedXContentRegistry;
154156
private final Settings settings;
157+
private final Map<String, Set<String>> pluginToClusterActions;
155158
private final AtomicReference<ActionPrivileges> actionPrivileges = new AtomicReference<>();
156159

157160
public PrivilegesEvaluatorImpl(
@@ -175,6 +178,7 @@ public PrivilegesEvaluatorImpl(
175178

176179
this.threadContext = threadContext;
177180
this.privilegesInterceptor = privilegesInterceptor;
181+
this.pluginToClusterActions = new HashMap<>();
178182
this.clusterStateSupplier = clusterStateSupplier;
179183
this.settings = settings;
180184

@@ -191,6 +195,7 @@ public PrivilegesEvaluatorImpl(
191195
termsAggregationEvaluator = new TermsAggregationEvaluator();
192196
pitPrivilegesEvaluator = new PitPrivilegesEvaluator();
193197
this.namedXContentRegistry = namedXContentRegistry;
198+
this.configurationRepository = configurationRepository;
194199

195200
if (configurationRepository != null) {
196201
configurationRepository.subscribeOnChange(configMap -> {
@@ -231,7 +236,8 @@ void updateConfiguration(
231236
DynamicConfigFactory.addStatics(rolesConfiguration.clone()),
232237
flattenedActionGroups,
233238
() -> clusterStateSupplier.get().metadata().getIndicesLookup(),
234-
settings
239+
settings,
240+
pluginToClusterActions
235241
);
236242
Metadata metadata = clusterStateSupplier.get().metadata();
237243
actionPrivileges.updateStatefulIndexPrivileges(metadata.getIndicesLookup(), metadata.version());
@@ -843,4 +849,8 @@ private List<String> toString(List<AliasMetadata> aliases) {
843849

844850
return Collections.unmodifiableList(ret);
845851
}
852+
853+
public void updatePluginToClusterActions(String pluginIdentifier, Set<String> clusterActions) {
854+
pluginToClusterActions.put(pluginIdentifier, clusterActions);
855+
}
846856
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ private void evaluateSystemIndicesAccess(
323323
);
324324
}
325325
presponse.allowed = false;
326+
presponse.getMissingPrivileges();
326327
presponse.markComplete();
327328
}
328329
return;

src/main/java/org/opensearch/security/support/ConfigConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ public class ConfigConstants {
114114
public static final String OPENDISTRO_SECURITY_AUTHENTICATED_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "authenticated_user";
115115
public static final String OPENDISTRO_SECURITY_USER_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_header";
116116

117+
// persistent header. This header is set once and cannot be stashed
118+
public static final String OPENDISTRO_SECURITY_AUTHENTICATED_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "authenticated_user";
119+
117120
public static final String OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_info";
118121

119122
public static final String OPENDISTRO_SECURITY_INJECTED_USER = "injected_user";

src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ public void testSecurityUserSubjectRunAs() throws Exception {
3737

3838
final Plugin testPlugin = new TestIdentityAwarePlugin();
3939

40-
final User pluginUser = new User("plugin:" + testPlugin.getClass().getCanonicalName());
40+
final String pluginPrincipal = "plugin:" + testPlugin.getClass().getCanonicalName();
41+
42+
final User pluginUser = new User(pluginPrincipal);
4143

4244
ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin);
4345

44-
assertThat(subject.getPrincipal().getName(), equalTo(testPlugin.getClass().getCanonicalName()));
46+
assertThat(subject.getPrincipal().getName(), equalTo(pluginPrincipal));
4547

4648
assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER));
4749

0 commit comments

Comments
 (0)