-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DRAFT] [Feature] Introduces resource sharing and access-control #4746
base: feature/resource-permissions
Are you sure you want to change the base?
Changes from 40 commits
831dca3
1c65eff
118cb07
c41b67d
45d4fa5
ae2464d
aea2253
1e17dc0
84746e8
83e4da0
4b9b9b1
81216f1
1e33dad
a671cc1
47b73da
8942a80
2b06603
6f42bf1
45b002e
a30be57
58003f6
078a976
8e44cf3
16a0ba6
ac53c8f
9e6ae85
0fe9779
6d7f4c0
9d4ca1e
b4b22d6
e87bb80
5ad813b
c08a992
8e3d41c
cabbcd6
dc964ac
cc973c6
3ce3d92
428e11e
a55ac22
1c62d36
6a23f46
c24323c
f514859
193e846
13fdb81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,7 +43,3 @@ out/ | |
build/ | ||
gradle-build/ | ||
.gradle/ | ||
|
||
# nodejs | ||
node_modules/ | ||
package-lock.json | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ | |
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
@@ -68,6 +69,10 @@ | |
import org.opensearch.OpenSearchSecurityException; | ||
import org.opensearch.SpecialPermission; | ||
import org.opensearch.Version; | ||
import org.opensearch.accesscontrol.resources.EntityType; | ||
import org.opensearch.accesscontrol.resources.ResourceService; | ||
import org.opensearch.accesscontrol.resources.ResourceSharing; | ||
import org.opensearch.accesscontrol.resources.ShareWith; | ||
import org.opensearch.action.ActionRequest; | ||
import org.opensearch.action.search.PitService; | ||
import org.opensearch.action.search.SearchScrollAction; | ||
|
@@ -120,6 +125,8 @@ | |
import org.opensearch.plugins.IdentityPlugin; | ||
import org.opensearch.plugins.MapperPlugin; | ||
import org.opensearch.plugins.Plugin; | ||
import org.opensearch.plugins.ResourceAccessControlPlugin; | ||
import org.opensearch.plugins.ResourcePlugin; | ||
import org.opensearch.plugins.SecureHttpTransportSettingsProvider; | ||
import org.opensearch.plugins.SecureSettingsFactory; | ||
import org.opensearch.plugins.SecureTransportSettingsProvider; | ||
|
@@ -173,6 +180,10 @@ | |
import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; | ||
import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; | ||
import org.opensearch.security.resolver.IndexResolverReplacer; | ||
import org.opensearch.security.resources.ResourceAccessHandler; | ||
import org.opensearch.security.resources.ResourceSharingIndexHandler; | ||
import org.opensearch.security.resources.ResourceSharingIndexListener; | ||
import org.opensearch.security.resources.ResourceSharingIndexManagementRepository; | ||
import org.opensearch.security.rest.DashboardsInfoAction; | ||
import org.opensearch.security.rest.SecurityConfigUpdateAction; | ||
import org.opensearch.security.rest.SecurityHealthAction; | ||
|
@@ -230,9 +241,10 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin | |
implements | ||
ClusterPlugin, | ||
MapperPlugin, | ||
IdentityPlugin, | ||
ResourceAccessControlPlugin, | ||
// CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings | ||
ExtensionAwarePlugin, | ||
IdentityPlugin | ||
ExtensionAwarePlugin | ||
// CS-ENFORCE-SINGLE | ||
|
||
{ | ||
|
@@ -268,6 +280,9 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin | |
private volatile OpensearchDynamicSetting<Boolean> transportPassiveAuthSetting; | ||
private volatile PasswordHasher passwordHasher; | ||
private volatile DlsFlsBaseContext dlsFlsBaseContext; | ||
private ResourceSharingIndexManagementRepository rmr; | ||
private ResourceAccessHandler resourceAccessHandler; | ||
private final Set<String> indicesToListen = new HashSet<>(); | ||
|
||
public static boolean isActionTraceEnabled() { | ||
|
||
|
@@ -708,6 +723,14 @@ public void onIndexModule(IndexModule indexModule) { | |
dlsFlsBaseContext | ||
) | ||
); | ||
|
||
if (this.indicesToListen.contains(indexModule.getIndex().getName())) { | ||
ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); | ||
resourceSharingIndexListener.initialize(threadPool, localClient, auditLog); | ||
indexModule.addIndexOperationListener(resourceSharingIndexListener); | ||
log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); | ||
} | ||
|
||
indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() { | ||
|
||
@Override | ||
|
@@ -1183,14 +1206,25 @@ public Collection<Object> createComponents( | |
|
||
// NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires | ||
// security index to be accessible which means | ||
// communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence | ||
// communication with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence | ||
// the base values from opensearch.yml | ||
// is used to first establish trust between same cluster nodes and there after dynamic config is loaded if enabled. | ||
if (DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { | ||
DefaultInterClusterRequestEvaluator e = (DefaultInterClusterRequestEvaluator) interClusterRequestEvaluator; | ||
e.subscribeForChanges(dcf); | ||
} | ||
|
||
final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; | ||
ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler( | ||
resourceSharingIndex, | ||
localClient, | ||
threadPool, | ||
auditLog | ||
); | ||
resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); | ||
|
||
rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler); | ||
|
||
components.add(adminDns); | ||
components.add(cr); | ||
components.add(xffResolver); | ||
|
@@ -1367,7 +1401,7 @@ public List<Setting<?>> getSettings() { | |
|
||
settings.add(Setting.simpleString(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, Property.NodeScope, Property.Filtered)); | ||
settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + ".", Property.NodeScope)); // not filtered | ||
// here | ||
// here | ||
|
||
settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_OID, Property.NodeScope, Property.Filtered)); | ||
|
||
|
@@ -1383,8 +1417,8 @@ public List<Setting<?>> getSettings() { | |
);// not filtered here | ||
|
||
settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));// not | ||
// filtered | ||
// here | ||
// filtered | ||
// here | ||
|
||
settings.add( | ||
Setting.boolSetting( | ||
|
@@ -1428,8 +1462,8 @@ public List<Setting<?>> getSettings() { | |
Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered) | ||
); | ||
settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".", Property.NodeScope)); // not | ||
// filtered | ||
// here | ||
// filtered | ||
// here | ||
|
||
settings.add(Setting.simpleString(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, Property.NodeScope, Property.Filtered)); | ||
settings.add( | ||
|
@@ -2065,6 +2099,17 @@ public void onNodeStarted(DiscoveryNode localNode) { | |
if (!SSLConfig.isSslOnlyMode() && !client && !disabled && !useClusterStateToInitSecurityConfig(settings)) { | ||
cr.initOnNodeStart(); | ||
} | ||
|
||
// create resource sharing index if absent | ||
rmr.createResourceSharingIndexIfAbsent(); | ||
|
||
for (ResourcePlugin resourcePlugin : OpenSearchSecurityPlugin.GuiceHolder.getResourceService().listResourcePlugins()) { | ||
String resourceIndex = resourcePlugin.getResourceIndex(); | ||
|
||
this.indicesToListen.add(resourceIndex); | ||
log.info("Preparing to listen to index: {} of plugin: {}", resourceIndex, resourcePlugin); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same, debug? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it to warn. Good catch. |
||
} | ||
|
||
final Set<ModuleInfo> securityModules = ReflectionHelper.getModulesLoaded(); | ||
log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); | ||
} | ||
|
@@ -2082,6 +2127,7 @@ public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() | |
|
||
final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(1); | ||
services.add(GuiceHolder.class); | ||
log.info("Guice service classes loaded"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to change this to something lower like debug? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I will remove it. |
||
return services; | ||
} | ||
|
||
|
@@ -2109,8 +2155,12 @@ public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings sett | |
ConfigConstants.SECURITY_CONFIG_INDEX_NAME, | ||
ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX | ||
); | ||
final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); | ||
return Collections.singletonList(systemIndexDescriptor); | ||
final SystemIndexDescriptor securityIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); | ||
final SystemIndexDescriptor resourceSharingIndexDescriptor = new SystemIndexDescriptor( | ||
ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Do we want to add a TODO to support custom resource sharing names? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not right now no. Maybe in the next iteration if there is a request for it. |
||
"Resource Sharing index" | ||
); | ||
return List.of(securityIndexDescriptor, resourceSharingIndexDescriptor); | ||
} | ||
|
||
@Override | ||
|
@@ -2166,12 +2216,48 @@ private void tryAddSecurityProvider() { | |
}); | ||
} | ||
|
||
@Override | ||
public <T> Set<T> getAccessibleResourcesForCurrentUser(String systemIndexName, Class<T> clazz) { | ||
return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName, clazz); | ||
} | ||
|
||
@Override | ||
public boolean hasPermission(String resourceId, String systemIndexName, String scope) { | ||
return this.resourceAccessHandler.hasPermission(resourceId, systemIndexName, scope); | ||
} | ||
|
||
@Override | ||
public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { | ||
return this.resourceAccessHandler.shareWith(resourceId, systemIndexName, shareWith); | ||
} | ||
|
||
@Override | ||
public ResourceSharing revokeAccess( | ||
String resourceId, | ||
String systemIndexName, | ||
Map<EntityType, Set<String>> entities, | ||
Set<String> scopes | ||
) { | ||
return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities, scopes); | ||
} | ||
|
||
@Override | ||
public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { | ||
return this.resourceAccessHandler.deleteResourceSharingRecord(resourceId, systemIndexName); | ||
} | ||
|
||
@Override | ||
public boolean deleteAllResourceSharingRecordsForCurrentUser() { | ||
return this.resourceAccessHandler.deleteAllResourceSharingRecordsForCurrentUser(); | ||
} | ||
|
||
public static class GuiceHolder implements LifecycleComponent { | ||
|
||
private static RepositoriesService repositoriesService; | ||
private static RemoteClusterService remoteClusterService; | ||
private static IndicesService indicesService; | ||
private static PitService pitService; | ||
private static ResourceService resourceService; | ||
|
||
// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions | ||
private static ExtensionsManager extensionsManager; | ||
|
@@ -2182,13 +2268,15 @@ public GuiceHolder( | |
final TransportService remoteClusterService, | ||
IndicesService indicesService, | ||
PitService pitService, | ||
ExtensionsManager extensionsManager | ||
ExtensionsManager extensionsManager, | ||
ResourceService resourceService | ||
) { | ||
GuiceHolder.repositoriesService = repositoriesService; | ||
GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); | ||
GuiceHolder.indicesService = indicesService; | ||
GuiceHolder.pitService = pitService; | ||
GuiceHolder.extensionsManager = extensionsManager; | ||
GuiceHolder.resourceService = resourceService; | ||
} | ||
// CS-ENFORCE-SINGLE | ||
|
||
|
@@ -2214,6 +2302,10 @@ public static ExtensionsManager getExtensionsManager() { | |
} | ||
// CS-ENFORCE-SINGLE | ||
|
||
public static ResourceService getResourceService() { | ||
return resourceService; | ||
} | ||
|
||
@Override | ||
public void close() {} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -345,6 +345,7 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap | |
log.info("Transport auth in passive mode and no user found. Injecting default user"); | ||
user = User.DEFAULT_TRANSPORT_USER; | ||
threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); | ||
threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, user); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed a few of these putPersistent being added. I do not know the full context, but is there any security implications with putting it in persistently? For example maybe stashing the context wouldn't work if the user is persisted into the threadcontext. Any ideas on this usage @cwperks ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to add the same code in #4896. I added the notion of persistent TC headers in the core a while ago with the intent that these headers are not stashable and I'd like to move to the authenticated user as a non-stashable header. Its added here because Security is adding an IndexOperationListener on the resourceIndex and the ThreadContext has already been stashed by the sample plugin so that it can write to the resourceIndex. By adding this call here, @DarshitChanpura is guaranteed that he can read the userinfo from the TC inside the IndexOperationListener. I believe the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to @cwperks answer. User names are particularly required when creating or listing resources. This would ensure that a user who is current context-user is always present and thus not result in null value. At some point in future, the putTransient calls will be removed completely. |
||
} else { | ||
log.error( | ||
"No user found for " | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was automatically added. But we don't add node_modules and package-lock since we don't have any front-end components in this project.