Skip to content
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

Draft
wants to merge 46 commits into
base: feature/resource-permissions
Choose a base branch
from

Conversation

DarshitChanpura
Copy link
Member

@DarshitChanpura DarshitChanpura commented Sep 22, 2024

companion PR: opensearch-project/OpenSearch#16030

Description

Introduces a new authorization mechanism to control access to resources defined by plugins.

This PR also introduces a sample resource plugin to show the APIs in action.

  • Category : New feature
  • Why these changes are required?
    • At present, plugins have implemented in-house authorization mechanisms for a lack of centralized framework. This PR introduces a centralized way to offload resource permissions check to security plugin. Plugins will leverage the java APIs introduced in core.

Issues Resolved

Testing

  • automated and manual testing

Check List

  • New functionality includes testing
  • New functionality has been documented
  • New Roles/Permissions have a corresponding security dashboards plugin PR
  • API changes companion pull request created
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

return services;
}

public static class GuiceHolder implements LifecycleComponent {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This GuiceHolder pattern from the security plugin is IMHO only a hack to get access to dependencies which are available via dependency injection, but not via the createComponents() API in the Plugin interface. The un-hackish way to give plugins access to ResourceService would be to add it to the param list of createComponents().

@DarshitChanpura
Copy link
Member Author

DarshitChanpura commented Oct 4, 2024

Resource-sharing index gets created successfully upon first cluster start:

[2024-10-04T14:09:04,570][INFO ][o.o.p.PluginsService     ] [80a9970ace0f] PluginService:onIndexModule index:[.opensearch_resource_sharing/G4yJ1aVmRRaQStn45aWdDg]
[2024-10-04T14:09:04,604][INFO ][o.o.c.m.MetadataCreateIndexService] [80a9970ace0f] [.opensearch_resource_sharing] creating index, cause [api], templates [], shards [1]/[1]
[2024-10-04T14:09:04,607][INFO ][o.o.c.r.a.AllocationService] [80a9970ace0f] updating number_of_replicas to [0] for indices [.opensearch_resource_sharing]
[2024-10-04T14:09:04,608][WARN ][o.o.c.r.a.AllocationService] [80a9970ace0f] Falling back to single shard assignment since batch mode disable or multiple custom allocators set
[2024-10-04T14:09:04,638][INFO ][o.o.p.PluginsService     ] [80a9970ace0f] PluginService:onIndexModule index:[.opensearch_resource_sharing/G4yJ1aVmRRaQStn45aWdDg]
[2024-10-04T14:09:04,748][INFO ][o.o.c.r.a.AllocationService] [80a9970ace0f] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.opensearch_resource_sharing][0]]]).
[2024-10-04T14:09:04,768][INFO ][o.o.s.r.ResourceSharingIndexHandler] [80a9970ace0f] Resource sharing index .opensearch_resource_sharing created.

and subsequent starts show that the index is present:

[2024-10-04T14:11:08,755][INFO ][o.o.s.r.ResourceSharingIndexHandler] [80a9970ace0f] Index .opensearch_resource_sharing already exists.

Sample Resource Plugin loaded successfully:

[2024-10-04T14:49:06,630][INFO ][o.o.s.s.SampleResourcePlugin] [80a9970ace0f] Loaded SampleResourcePlugin components.

@DarshitChanpura DarshitChanpura changed the base branch from main to feature/resource-permissions November 11, 2024 18:06
@DarshitChanpura
Copy link
Member Author

Moved sample resource plugin code to a separate PR: #4893

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Comment on lines -48 to -49
node_modules/
package-lock.json
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Member Author

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.

@@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 ?

Copy link
Member

Choose a reason for hiding this comment

The 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 postIndex hook is running within the same block as the IndexRequest to the resourceIndex.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

public void createResourceSharingIndexIfAbsent() {
// TODO check if this should be wrapped in an atomic completable future

this.resourceSharingIndexHandler.createResourceSharingIndexIfAbsent(() -> null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this () -> null

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's callback functionality. Its not used currently but was added in case there were any listeners or related items are to be added.

private static final ResourceSharingIndexListener INSTANCE = new ResourceSharingIndexListener();
private ResourceSharingIndexHandler resourceSharingIndexHandler;

private boolean initialized;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not use this extra boolean and just base it on the indexlistener itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its to ensure that ReosurceSharingIndexListener is only initialized once.


boolean success = this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex);
if (success) {
log.info("Successfully deleted resource sharing entries for resource {}", resourceId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Should this be singular since there would only be one resource sharing entry for each resource?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@@ -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");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to change this to something lower like debug?

Copy link
Member Author

@DarshitChanpura DarshitChanpura Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I will remove it.

return Collections.singletonList(systemIndexDescriptor);
final SystemIndexDescriptor securityIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index");
final SystemIndexDescriptor resourceSharingIndexDescriptor = new SystemIndexDescriptor(
ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX,
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

String resourceIndex = resourcePlugin.getResourceIndex();

this.indicesToListen.add(resourceIndex);
log.info("Preparing to listen to index: {} of plugin: {}", resourceIndex, resourcePlugin);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, debug?

Copy link
Member Author

@DarshitChanpura DarshitChanpura Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to warn. Good catch.

if (areArgumentsInvalid(resourceIndex, clazz)) {
return Collections.emptySet();
}
final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we putting this persistently here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it to access the system index within the handler?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to get user which is then used when creating a resource or listing resources. Without this the values may be null.

Comment on lines +114 to +116
if (adminDNs.isAdmin(user)) {
return true;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this design decision has already been made, but at least with tenancy there is a concept of a private tenant, which can contain private work/shouldn't be accessible to other people (except for I guess super admin which can search the index directly), do we have any plans to implement a similar feature here?

Copy link
Member Author

@DarshitChanpura DarshitChanpura Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adminDNs is super-admin, AFAIK

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-super-admin will not be able to access these resources.

LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes);

// check if user is admin, if yes the user has permission
boolean isAdmin = adminDNs.isAdmin(user);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to split this out into a utility function such as hasAdminLevelAcess? Maybe there are additional use cases that we want to support here, such as a "super" role, and not just based on the DN... wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This here is specific to super-admin. All "super" roles will require user to have the resource shared with them with that scope.

Copy link
Collaborator

@derek-ho derek-ho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add some test cases for the various handlers?

@DarshitChanpura
Copy link
Member Author

Can we also add some test cases for the various handlers?

This is a WIP. Will flip to ready for review once test cases are added.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura DarshitChanpura changed the base branch from feature/resource-permissions to main December 27, 2024 17:07
@DarshitChanpura DarshitChanpura changed the base branch from main to feature/resource-permissions December 27, 2024 17:07
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants