Skip to content

Commit ffcb697

Browse files
committed
Modularized PrivilegeEvaluator
Signed-off-by: Nils Bandener <[email protected]>
1 parent 062ea71 commit ffcb697

38 files changed

+1949
-1198
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3535
- Replace AccessController and remove restriction on word Extension ([#5750](https://github.com/opensearch-project/security/pull/5750))
3636
- Add security provider earlier in bootstrap process ([#5749](https://github.com/opensearch-project/security/pull/5749))
3737
- [GRPC] Fix compilation errors from core protobuf version bump to 0.23.0 ([#5763](https://github.com/opensearch-project/security/pull/5763))
38+
- Modularized PrivilegesEvaluator ([#5791](https://github.com/opensearch-project/security/pull/5791))
3839

3940
### Maintenance
4041
- Bump `org.junit.jupiter:junit-jupiter` from 5.13.4 to 5.14.1 ([#5678](https://github.com/opensearch-project/security/pull/5678), [#5764](https://github.com/opensearch-project/security/pull/5764))

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

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
import org.opensearch.security.auditlog.config.AuditConfig.Filter.FilterEntries;
143143
import org.opensearch.security.auditlog.impl.AuditLogImpl;
144144
import org.opensearch.security.auth.BackendRegistry;
145+
import org.opensearch.security.auth.RolesInjector;
145146
import org.opensearch.security.compliance.ComplianceIndexingOperationListener;
146147
import org.opensearch.security.compliance.ComplianceIndexingOperationListenerImpl;
147148
import org.opensearch.security.configuration.AdminDNs;
@@ -150,7 +151,6 @@
150151
import org.opensearch.security.configuration.ConfigurationRepository;
151152
import org.opensearch.security.configuration.DlsFlsRequestValve;
152153
import org.opensearch.security.configuration.DlsFlsValveImpl;
153-
import org.opensearch.security.configuration.PrivilegesInterceptorImpl;
154154
import org.opensearch.security.configuration.SecurityConfigVersionHandler;
155155
import org.opensearch.security.configuration.SecurityFlsDlsIndexSearcherWrapper;
156156
import org.opensearch.security.dlic.rest.api.Endpoint;
@@ -166,12 +166,13 @@
166166
import org.opensearch.security.http.XFFResolver;
167167
import org.opensearch.security.identity.SecurePluginSubject;
168168
import org.opensearch.security.identity.SecurityTokenManager;
169+
import org.opensearch.security.privileges.ConfigurableRoleMapper;
170+
import org.opensearch.security.privileges.PrivilegesConfiguration;
169171
import org.opensearch.security.privileges.PrivilegesEvaluationContext;
170172
import org.opensearch.security.privileges.PrivilegesEvaluationException;
171-
import org.opensearch.security.privileges.PrivilegesEvaluator;
172-
import org.opensearch.security.privileges.PrivilegesInterceptor;
173173
import org.opensearch.security.privileges.ResourceAccessEvaluator;
174174
import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator;
175+
import org.opensearch.security.privileges.RoleMapper;
175176
import org.opensearch.security.privileges.actionlevel.RoleBasedActionPrivileges;
176177
import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext;
177178
import org.opensearch.security.resolver.IndexResolverReplacer;
@@ -270,7 +271,8 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
270271

271272
private boolean sslCertReloadEnabled;
272273
private volatile SecurityInterceptor si;
273-
private volatile PrivilegesEvaluator evaluator;
274+
private volatile PrivilegesConfiguration privilegesConfiguration;
275+
private volatile RoleMapper roleMapper;
274276
private volatile UserService userService;
275277
private volatile RestLayerPrivilegesEvaluator restLayerEvaluator;
276278
private volatile ConfigurationRepository cr;
@@ -623,19 +625,25 @@ public List<RestHandler> getRestHandlers(
623625
// FGAC enabled == not sslOnly
624626
if (!SSLConfig.isSslOnlyMode()) {
625627
handlers.add(
626-
new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))
628+
new SecurityInfoAction(
629+
settings,
630+
restController,
631+
Objects.requireNonNull(privilegesConfiguration),
632+
Objects.requireNonNull(threadPool)
633+
)
627634
);
628635
handlers.add(
629636
new SecurityHealthAction(
630637
settings,
631638
restController,
632639
Objects.requireNonNull(backendRegistry),
633-
Objects.requireNonNull(evaluator)
640+
Objects.requireNonNull(privilegesConfiguration)
634641
)
635642
);
636643
handlers.add(
637644
new DashboardsInfoAction(
638-
Objects.requireNonNull(evaluator),
645+
Objects.requireNonNull(privilegesConfiguration),
646+
Objects.requireNonNull(cr),
639647
Objects.requireNonNull(threadPool),
640648
resourceSharingEnabledSetting
641649
)
@@ -644,7 +652,7 @@ public List<RestHandler> getRestHandlers(
644652
new TenantInfoAction(
645653
settings,
646654
restController,
647-
Objects.requireNonNull(evaluator),
655+
Objects.requireNonNull(privilegesConfiguration),
648656
Objects.requireNonNull(threadPool),
649657
Objects.requireNonNull(cs),
650658
Objects.requireNonNull(adminDns),
@@ -682,7 +690,8 @@ public List<RestHandler> getRestHandlers(
682690
cr,
683691
cs,
684692
principalExtractor,
685-
evaluator,
693+
roleMapper,
694+
privilegesConfiguration,
686695
threadPool,
687696
Objects.requireNonNull(auditLog),
688697
sslSettingsManager,
@@ -753,7 +762,8 @@ public void onIndexModule(IndexModule indexModule) {
753762
cs,
754763
auditLog,
755764
ciol,
756-
evaluator,
765+
privilegesConfiguration,
766+
roleMapper,
757767
dlsFlsValve::getCurrentConfig,
758768
dlsFlsBaseContext
759769
)
@@ -1139,15 +1149,11 @@ public Collection<Object> createComponents(
11391149

11401150
UserFactory userFactory = new UserFactory.Caching(settings);
11411151

1142-
final PrivilegesInterceptor privilegesInterceptor;
1143-
11441152
namedXContentRegistry.set(xContentRegistry);
11451153
if (SSLConfig.isSslOnlyMode()) {
11461154
auditLog = new NullAuditLog();
1147-
privilegesInterceptor = new PrivilegesInterceptor(resolver, clusterService, localClient, threadPool);
11481155
} else {
11491156
auditLog = new AuditLogImpl(settings, configPath, localClient, threadPool, resolver, clusterService, environment, userFactory);
1150-
privilegesInterceptor = new PrivilegesInterceptorImpl(resolver, clusterService, localClient, threadPool);
11511157
}
11521158

11531159
sslExceptionHandler = new AuditLogSslExceptionHandler(auditLog);
@@ -1169,21 +1175,29 @@ public Collection<Object> createComponents(
11691175
final CompatConfig compatConfig = new CompatConfig(environment, transportPassiveAuthSetting);
11701176

11711177
rsIndexHandler = new ResourceSharingIndexHandler(localClient, threadPool, resourcePluginInfo);
1172-
evaluator = new PrivilegesEvaluator(
1178+
1179+
RoleMapper roleMapper = new RolesInjector.InjectedRoleMapper(
1180+
new ConfigurableRoleMapper(cr, settings),
1181+
threadPool.getThreadContext()
1182+
);
1183+
this.roleMapper = roleMapper;
1184+
1185+
PrivilegesConfiguration privilegesConfiguration = new PrivilegesConfiguration(
1186+
cr,
11731187
clusterService,
11741188
clusterService::state,
1189+
localClient,
1190+
roleMapper,
11751191
threadPool,
1176-
threadPool.getThreadContext(),
1177-
cr,
11781192
resolver,
11791193
auditLog,
11801194
settings,
1181-
privilegesInterceptor,
1182-
cih,
1195+
cih::getReasonForUnavailability,
11831196
irr
11841197
);
1198+
this.privilegesConfiguration = privilegesConfiguration;
11851199

1186-
dlsFlsBaseContext = new DlsFlsBaseContext(evaluator, threadPool.getThreadContext(), adminDns);
1200+
dlsFlsBaseContext = new DlsFlsBaseContext(privilegesConfiguration, threadPool.getThreadContext(), adminDns);
11871201

11881202
if (SSLConfig.isSslOnlyMode()) {
11891203
dlsFlsValve = new DlsFlsRequestValve.NoopDlsFlsRequestValve();
@@ -1203,7 +1217,7 @@ public Collection<Object> createComponents(
12031217
cr.subscribeOnChange(configMap -> { ((DlsFlsValveImpl) dlsFlsValve).updateConfiguration(cr.getConfiguration(CType.ROLES)); });
12041218
}
12051219

1206-
resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns, evaluator, resourcePluginInfo);
1220+
resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns, resourcePluginInfo);
12071221

12081222
// Assign resource sharing client to each extension
12091223
// Using the non-gated client (i.e. no additional permissions required)
@@ -1228,7 +1242,7 @@ public Collection<Object> createComponents(
12281242

12291243
sf = new SecurityFilter(
12301244
settings,
1231-
evaluator,
1245+
privilegesConfiguration,
12321246
adminDns,
12331247
dlsFlsValve,
12341248
auditLog,
@@ -1249,7 +1263,7 @@ public Collection<Object> createComponents(
12491263
principalExtractor = ReflectionHelper.instantiatePrincipalExtractor(principalExtractorClass);
12501264
}
12511265

1252-
restLayerEvaluator = new RestLayerPrivilegesEvaluator(evaluator);
1266+
restLayerEvaluator = new RestLayerPrivilegesEvaluator(privilegesConfiguration);
12531267

12541268
securityRestHandler = new SecurityRestFilter(
12551269
backendRegistry,
@@ -1266,7 +1280,6 @@ public Collection<Object> createComponents(
12661280
dcf.registerDCFListener(compatConfig);
12671281
dcf.registerDCFListener(irr);
12681282
dcf.registerDCFListener(xffResolver);
1269-
dcf.registerDCFListener(evaluator);
12701283
dcf.registerDCFListener(securityRestHandler);
12711284
dcf.registerDCFListener(tokenManager);
12721285
if (!(auditLog instanceof NullAuditLog)) {
@@ -1306,7 +1319,7 @@ public Collection<Object> createComponents(
13061319
components.add(cr);
13071320
components.add(xffResolver);
13081321
components.add(backendRegistry);
1309-
components.add(evaluator);
1322+
components.add(privilegesConfiguration);
13101323
components.add(restLayerEvaluator);
13111324
components.add(si);
13121325
components.add(dcf);
@@ -2408,7 +2421,7 @@ public PluginSubject getPluginSubject(Plugin plugin) {
24082421
}
24092422
}
24102423
pluginPermissions.getCluster_permissions().add(BulkAction.NAME);
2411-
evaluator.updatePluginToActionPrivileges(pluginPrincipal, pluginPermissions);
2424+
privilegesConfiguration.updatePluginToActionPrivileges(pluginPrincipal, pluginPermissions);
24122425
}
24132426
return subject;
24142427
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package org.opensearch.security.auth;
1717

18+
import java.util.Arrays;
19+
import java.util.HashSet;
1820
import java.util.Map;
1921
import java.util.Set;
2022

@@ -23,8 +25,12 @@
2325
import org.apache.logging.log4j.LogManager;
2426
import org.apache.logging.log4j.Logger;
2527

28+
import org.opensearch.OpenSearchSecurityException;
2629
import org.opensearch.common.util.concurrent.ThreadContext;
30+
import org.opensearch.core.common.transport.TransportAddress;
31+
import org.opensearch.core.rest.RestStatus;
2732
import org.opensearch.security.auditlog.AuditLog;
33+
import org.opensearch.security.privileges.RoleMapper;
2834
import org.opensearch.security.support.ConfigConstants;
2935
import org.opensearch.security.user.User;
3036
import org.opensearch.threadpool.ThreadPool;
@@ -90,6 +96,52 @@ private void addUser(final User user, final ThreadPool threadPool) {
9096
if (ctx.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER) == null) {
9197
ctx.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, new UserSubjectImpl(threadPool, user));
9298
}
99+
}
100+
101+
/**
102+
* For users injected by this class, no role mapping shall be performed. This RoleMapper checks whether there
103+
* is an injected user (by header) and skips default role mapping (realized by the delegate) if so.
104+
*/
105+
public static class InjectedRoleMapper implements RoleMapper {
106+
107+
private final ThreadContext threadContext;
108+
private final RoleMapper defaultRoleMapper;
109+
110+
public InjectedRoleMapper(RoleMapper defaultRoleMapper, ThreadContext threadContext) {
111+
this.threadContext = threadContext;
112+
this.defaultRoleMapper = defaultRoleMapper;
113+
}
93114

115+
@Override
116+
public ImmutableSet<String> map(User user, TransportAddress caller) {
117+
ImmutableSet<String> mappedRoles;
118+
119+
if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES) != null) {
120+
// Just return the security roles, like they were initialized in the injectUserAndRoles() method above
121+
mappedRoles = user.getSecurityRoles();
122+
} else {
123+
// No injected user => use default role mapping
124+
mappedRoles = defaultRoleMapper.map(user, caller);
125+
}
126+
127+
String injectedRolesValidationString = threadContext.getTransient(
128+
ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION
129+
);
130+
if (injectedRolesValidationString != null) {
131+
// Moved from
132+
// https://github.com/opensearch-project/security/blob/d29095f26dba1a26308c69b608dc926bd40c0f52/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java#L406
133+
// See also https://github.com/opensearch-project/security/pull/1367
134+
HashSet<String> injectedRolesValidationSet = new HashSet<>(Arrays.asList(injectedRolesValidationString.split(",")));
135+
if (!mappedRoles.containsAll(injectedRolesValidationSet)) {
136+
throw new OpenSearchSecurityException(
137+
String.format("No mapping for %s on roles %s", user, injectedRolesValidationSet),
138+
RestStatus.FORBIDDEN
139+
);
140+
}
141+
mappedRoles = ImmutableSet.copyOf(injectedRolesValidationSet);
142+
}
143+
144+
return mappedRoles;
145+
}
94146
}
95147
}

src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,12 @@ public Boolean hasClusterManager() {
101101
}
102102
return false;
103103
}
104+
105+
public String getReasonForUnavailability() {
106+
if (!hasClusterManager()) {
107+
return CLUSTER_MANAGER_NOT_PRESENT;
108+
} else {
109+
return null;
110+
}
111+
}
104112
}

src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
import org.opensearch.threadpool.ThreadPool;
9090
import org.opensearch.transport.client.Client;
9191

92-
import static org.opensearch.security.privileges.PrivilegesEvaluator.isClusterPerm;
92+
import static org.opensearch.security.privileges.PrivilegesEvaluatorImpl.isClusterPerm;
9393

9494
public class DlsFlsValveImpl implements DlsFlsRequestValve {
9595

src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import java.util.Map;
1515
import java.util.Set;
16+
import java.util.function.Supplier;
1617

1718
import com.google.common.collect.ImmutableMap;
1819
import com.google.common.collect.ImmutableSet;
@@ -45,12 +46,12 @@
4546
import org.opensearch.cluster.metadata.IndexMetadata;
4647
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
4748
import org.opensearch.cluster.service.ClusterService;
49+
import org.opensearch.security.privileges.DashboardsMultiTenancyConfiguration;
4850
import org.opensearch.security.privileges.DocumentAllowList;
4951
import org.opensearch.security.privileges.PrivilegesEvaluationContext;
5052
import org.opensearch.security.privileges.PrivilegesInterceptor;
5153
import org.opensearch.security.privileges.TenantPrivileges;
5254
import org.opensearch.security.resolver.IndexResolverReplacer.Resolved;
53-
import org.opensearch.security.securityconf.DynamicConfigModel;
5455
import org.opensearch.security.user.User;
5556
import org.opensearch.threadpool.ThreadPool;
5657
import org.opensearch.transport.client.Client;
@@ -77,13 +78,20 @@ public class PrivilegesInterceptorImpl extends PrivilegesInterceptor {
7778

7879
protected final Logger log = LogManager.getLogger(this.getClass());
7980

81+
private final Supplier<TenantPrivileges> tenantPrivilegesSupplier;
82+
private final Supplier<DashboardsMultiTenancyConfiguration> multiTenancyConfigurationSupplier;
83+
8084
public PrivilegesInterceptorImpl(
8185
IndexNameExpressionResolver resolver,
8286
ClusterService clusterService,
8387
Client client,
84-
ThreadPool threadPool
88+
ThreadPool threadPool,
89+
Supplier<TenantPrivileges> tenantPrivilegesSupplier,
90+
Supplier<DashboardsMultiTenancyConfiguration> multiTenancyConfigurationSupplier
8591
) {
8692
super(resolver, clusterService, client, threadPool);
93+
this.tenantPrivilegesSupplier = tenantPrivilegesSupplier;
94+
this.multiTenancyConfigurationSupplier = multiTenancyConfigurationSupplier;
8795
}
8896

8997
/**
@@ -97,25 +105,26 @@ public ReplaceResult replaceDashboardsIndex(
97105
final ActionRequest request,
98106
final String action,
99107
final User user,
100-
final DynamicConfigModel config,
101108
final Resolved requestedResolved,
102-
final PrivilegesEvaluationContext context,
103-
final TenantPrivileges tenantPrivileges
109+
final PrivilegesEvaluationContext context
104110
) {
111+
DashboardsMultiTenancyConfiguration config = this.multiTenancyConfigurationSupplier.get();
105112

106-
final boolean enabled = config.isDashboardsMultitenancyEnabled();// config.dynamic.kibana.multitenancy_enabled;
113+
final boolean enabled = config.multitenancyEnabled();// config.dynamic.kibana.multitenancy_enabled;
107114

108115
if (!enabled) {
109116
return CONTINUE_EVALUATION_REPLACE_RESULT;
110117
}
111118

119+
TenantPrivileges tenantPrivileges = this.tenantPrivilegesSupplier.get();
120+
112121
// next two lines needs to be retrieved from configuration
113-
final String dashboardsServerUsername = config.getDashboardsServerUsername();// config.dynamic.kibana.server_username;
114-
final String dashboardsIndexName = config.getDashboardsIndexname();// config.dynamic.kibana.index;
122+
final String dashboardsServerUsername = config.dashboardsServerUsername();// config.dynamic.kibana.server_username;
123+
final String dashboardsIndexName = config.dashboardsIndex();// config.dynamic.kibana.index;
115124

116125
String requestedTenant = user.getRequestedTenant();
117126
if (USER_TENANT.equals(requestedTenant)) {
118-
final boolean private_tenant_enabled = config.isDashboardsPrivateTenantEnabled();
127+
final boolean private_tenant_enabled = config.privateTenantEnabled();
119128
if (!private_tenant_enabled) {
120129
return ACCESS_DENIED_REPLACE_RESULT;
121130
}

0 commit comments

Comments
 (0)