Skip to content

Commit

Permalink
Adds few concrete method implementations in security plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Darshit Chanpura <[email protected]>
  • Loading branch information
DarshitChanpura committed Oct 14, 2024
1 parent 5213e6f commit d0cc8a8
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@ private void createResource(CreateResourceRequest request, ActionListener<Create
}

private static ActionListener<IndexResponse> getIndexResponseActionListener(ActionListener<CreateResourceResponse> listener) {
SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(
List.of(),
List.of(),
List.of()
);
SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(List.of(), List.of(), List.of());
SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope);
ShareWith shareWith = new ShareWith(List.of(sharedWithScope));
return ActionListener.wrap(idxResponse -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
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;
Expand Down Expand Up @@ -120,11 +121,13 @@
import org.opensearch.indices.IndicesService;
import org.opensearch.indices.SystemIndexDescriptor;
import org.opensearch.plugins.ClusterPlugin;
import org.opensearch.plugins.ExtensiblePlugin;
import org.opensearch.plugins.ExtensionAwarePlugin;
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;
Expand Down Expand Up @@ -178,6 +181,7 @@
import org.opensearch.security.resolver.IndexResolverReplacer;
import org.opensearch.security.resources.ResourceAccessHandler;
import org.opensearch.security.resources.ResourceManagementRepository;
import org.opensearch.security.resources.ResourceSharingIndexHandler;
import org.opensearch.security.resources.ResourceSharingIndexListener;
import org.opensearch.security.rest.DashboardsInfoAction;
import org.opensearch.security.rest.SecurityConfigUpdateAction;
Expand Down Expand Up @@ -237,10 +241,11 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
implements
ClusterPlugin,
MapperPlugin,
IdentityPlugin,
ResourceAccessControlPlugin,
// CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
ExtensionAwarePlugin,
IdentityPlugin,
ResourceAccessControlPlugin
ExtensiblePlugin
// CS-ENFORCE-SINGLE

{
Expand Down Expand Up @@ -854,6 +859,20 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) {
}
}

// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions
@Override
public void loadExtensions(ExtensionLoader loader) {

log.info("Loading resource plugins");
for (ResourcePlugin resourcePlugin : loader.loadExtensions(ResourcePlugin.class)) {
String resourceIndex = resourcePlugin.getResourceIndex();

this.indicesToListen.add(resourceIndex);
log.info("Loaded resource plugin: {}, index: {}", resourcePlugin, resourceIndex);
}
}
// CS-ENFORCE-SINGLE

@Override
public List<ActionFilter> getActionFilters() {
List<ActionFilter> filters = new ArrayList<>(1);
Expand Down Expand Up @@ -1213,9 +1232,11 @@ public Collection<Object> createComponents(
e.subscribeForChanges(dcf);
}

resourceAccessHandler = new ResourceAccessHandler(threadPool);
final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX;
ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler(resourceSharingIndex, localClient, threadPool);
resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns);

rmr = ResourceManagementRepository.create(settings, threadPool, localClient);
rmr = ResourceManagementRepository.create(settings, threadPool, localClient, rsIndexHandler);

components.add(adminDns);
components.add(cr);
Expand Down Expand Up @@ -2089,6 +2110,7 @@ public void onNodeStarted(DiscoveryNode localNode) {
if (!SSLConfig.isSslOnlyMode() && !client && !disabled && !useClusterStateToInitSecurityConfig(settings)) {
cr.initOnNodeStart();
}

// create resource sharing index if absent
rmr.createResourceSharingIndexIfAbsent();
final Set<ModuleInfo> securityModules = ReflectionHelper.getModulesLoaded();
Expand Down Expand Up @@ -2252,6 +2274,7 @@ public static class GuiceHolder implements LifecycleComponent {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@

package org.opensearch.security.resources;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -21,7 +24,9 @@
import org.opensearch.accesscontrol.resources.EntityType;
import org.opensearch.accesscontrol.resources.ResourceSharing;
import org.opensearch.accesscontrol.resources.ShareWith;
import org.opensearch.accesscontrol.resources.SharedWithScope;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.security.configuration.AdminDNs;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.user.User;
import org.opensearch.threadpool.ThreadPool;
Expand All @@ -30,67 +35,177 @@ public class ResourceAccessHandler {
private static final Logger LOGGER = LogManager.getLogger(ResourceAccessHandler.class);

private final ThreadContext threadContext;

public ResourceAccessHandler(final ThreadPool threadPool) {
private final ResourceSharingIndexHandler resourceSharingIndexHandler;
private final AdminDNs adminDNs;

public ResourceAccessHandler(
final ThreadPool threadPool,
final ResourceSharingIndexHandler resourceSharingIndexHandler,
AdminDNs adminDns
) {
super();
this.threadContext = threadPool.getThreadContext();
}

public Map<String, List<String>> listAccessibleResources() {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Listing accessible resource for: {}", user.getName());

// TODO add concrete implementation
return Map.of();
this.resourceSharingIndexHandler = resourceSharingIndexHandler;
this.adminDNs = adminDns;
}

public List<String> listAccessibleResourcesInPlugin(String systemIndex) {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
if (user == null) {
LOGGER.info("Unable to fetch user details ");
return Collections.emptyList();
}

LOGGER.info("Listing accessible resource within a system index {} for : {}", systemIndex, user.getName());

// TODO add concrete implementation
return List.of();
// TODO check if user is admin, if yes all resources should be accessible
if (adminDNs.isAdmin(user)) {
return loadAllResources(systemIndex);
}

Set<String> result = new HashSet<>();

// 0. Own resources
result.addAll(loadOwnResources(systemIndex, user.getName()));

// 1. By username
result.addAll(loadSharedWithResources(systemIndex, Set.of(user.getName()), "users"));

// 2. By roles
Set<String> roles = user.getSecurityRoles();
result.addAll(loadSharedWithResources(systemIndex, roles, "roles"));

// 3. By backend_roles
Set<String> backendRoles = user.getRoles();
result.addAll(loadSharedWithResources(systemIndex, backendRoles, "backend_roles"));

return result.stream().toList();
}

public boolean hasPermission(String resourceId, String systemIndexName, String scope) {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId);

// TODO add concrete implementation
Set<String> userRoles = user.getSecurityRoles();
Set<String> userBackendRoles = user.getRoles();

ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId);
if (document == null) {
LOGGER.warn("Resource {} not found in index {}", resourceId, systemIndexName);
return false; // If the document doesn't exist, no permissions can be granted
}

if (isSharedWithEveryone(document)
|| isOwnerOfResource(document, user.getName())
|| isSharedWithUser(document, user.getName(), scope)
|| isSharedWithGroup(document, userRoles, scope)
|| isSharedWithGroup(document, userBackendRoles, scope)) {
LOGGER.info("User {} has {} access to {}", user.getName(), scope, resourceId);
return true;
}

LOGGER.info("User {} does not have {} access to {} ", user.getName(), scope, resourceId);
return false;
}

public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith);
LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith.toString());

// TODO add concrete implementation
CreatedBy c = new CreatedBy("", null);
return new ResourceSharing(systemIndexName, resourceId, c, shareWith);
// TODO fix this to fetch user-name correctly, need to hydrate user context since context might have been stashed.
// (persistentHeader?)
CreatedBy createdBy = new CreatedBy("", "");
return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, createdBy, shareWith);
}

public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map<EntityType, List<String>> revokeAccess) {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess);

// TODO add concrete implementation
return null;
return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess);
}

public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, systemIndexName, user.getName());

// TODO add concrete implementation
return false;
ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId);
if (document == null) {
LOGGER.info("Document {} does not exist in index {}", resourceId, systemIndexName);
return false;
}
if (!(adminDNs.isAdmin(user) || isOwnerOfResource(document, user.getName()))) {
LOGGER.info("User {} does not have access to delete the record {} ", user.getName(), resourceId);
return false;
}
return this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, systemIndexName);
}

public boolean deleteAllResourceSharingRecordsForCurrentUser() {
final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
LOGGER.info("Deleting all resource sharing records for resource {}", user.getName());

// TODO add concrete implementation
return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName());
}

// Helper methods

private List<String> loadAllResources(String systemIndex) {
return this.resourceSharingIndexHandler.fetchAllDocuments(systemIndex);
}

private List<String> loadOwnResources(String systemIndex, String username) {
// TODO check if this magic variable can be replaced
return this.resourceSharingIndexHandler.fetchDocumentsByField(systemIndex, "created_by.user", username);
}

private List<String> loadSharedWithResources(String systemIndex, Set<String> accessWays, String shareWithType) {
return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, accessWays, shareWithType);
}

private boolean isOwnerOfResource(ResourceSharing document, String userName) {
return document.getCreatedBy() != null && document.getCreatedBy().getUser().equals(userName);
}

private boolean isSharedWithUser(ResourceSharing document, String userName, String scope) {
return checkSharing(document, "users", userName, scope);
}

private boolean isSharedWithGroup(ResourceSharing document, Set<String> roles, String scope) {
for (String role : roles) {
if (checkSharing(document, "roles", role, scope)) {
return true;
}
}
return false;
}

private boolean isSharedWithEveryone(ResourceSharing document) {
return document.getShareWith() != null
&& document.getShareWith().getSharedWithScopes().stream().anyMatch(sharedWithScope -> sharedWithScope.getScope().equals("*"));
}

private boolean checkSharing(ResourceSharing document, String sharingType, String identifier, String scope) {
if (document.getShareWith() == null) {
return false;
}

return document.getShareWith()
.getSharedWithScopes()
.stream()
.filter(sharedWithScope -> sharedWithScope.getScope().equals(scope))
.findFirst()
.map(sharedWithScope -> {
SharedWithScope.SharedWithPerScope scopePermissions = sharedWithScope.getSharedWithPerScope();

return switch (sharingType) {
case "users" -> scopePermissions.getUsers().contains(identifier);
case "roles" -> scopePermissions.getRoles().contains(identifier);
case "backend_roles" -> scopePermissions.getBackendRoles().contains(identifier);
default -> false;
};
})
.orElse(false); // Return false if no matching scope is found
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.opensearch.client.Client;
import org.opensearch.common.settings.Settings;
import org.opensearch.security.configuration.ConfigurationRepository;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.threadpool.ThreadPool;

public class ResourceManagementRepository {
Expand All @@ -40,13 +39,14 @@ protected ResourceManagementRepository(
this.resourceSharingIndexHandler = resourceSharingIndexHandler;
}

public static ResourceManagementRepository create(Settings settings, final ThreadPool threadPool, Client client) {
final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX;
return new ResourceManagementRepository(
threadPool,
client,
new ResourceSharingIndexHandler(resourceSharingIndex, settings, client, threadPool)
);
public static ResourceManagementRepository create(
Settings settings,
final ThreadPool threadPool,
Client client,
ResourceSharingIndexHandler resourceSharingIndexHandler
) {

return new ResourceManagementRepository(threadPool, client, resourceSharingIndexHandler);
}

public void createResourceSharingIndexIfAbsent() {
Expand Down
Loading

0 comments on commit d0cc8a8

Please sign in to comment.