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

[Feature/Identity] Basic permissions check #6029

Merged

Conversation

cwperks
Copy link
Member

@cwperks cwperks commented Jan 26, 2023

Description

This PR shows a rudimentary implementation for permissions checking that works in common OpenSearch scenarios including any cluster related actions and indices actions like creating an index and search. This provides a base to build upon as more actions are tested with this permissions model.

In this PR the permissions are directly saved onto the user, though permissions resolution is still an open issue.

The InternalRealm has been switched to extend shiro's Authorizing realm which means that a function called doGetAuthorizationInfo is implemented to resolve a user to a list of permissions.

Actions are compared using shiro's WildcardPermission. For requests requiring indices, all indices in the request are compared against index patterns for the action that the user has been granted to and this implementation uses the internal Glob.globMatch to determine if the index pattern requested matches a pattern that's been granted access.

Issues Resolved

First step of: #5859

Related PRs:

Check List

  • New functionality includes testing.
    • All tests pass
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed per the DCO using --signoff
  • Commit changes are listed out in CHANGELOG.md file (See: Changelog)

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 false;
}

// Uncomment the following lines if index name -> concrete index resolution is required
Copy link
Member Author

Choose a reason for hiding this comment

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

To fully uncomment this block and use the IndexNameExpressionResolver then the following needs to be added to IdentityPlugin:

@Override
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() {

    if (!enabled) {
        return Collections.emptyList();
    }

    final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(1);
    services.add(GuiceHolder.class);
    return services;
}

public static class GuiceHolder implements LifecycleComponent {

    private static ClusterService clusterService;
    private static RepositoriesService repositoriesService;

    @Inject
    public GuiceHolder(final RepositoriesService repositoriesService, final ClusterService clusterService) {
        GuiceHolder.repositoriesService = repositoriesService;
        GuiceHolder.clusterService = clusterService;
    }

    public static RepositoriesService getRepositoriesService() {
        return repositoriesService;
    }

    public static ClusterService getClusterService() {
        return clusterService;
    }

    @Override
    public void close() {}

    @Override
    public Lifecycle.State lifecycleState() {
        return null;
    }

    @Override
    public void addLifecycleListener(LifecycleListener listener) {}

    @Override
    public void removeLifecycleListener(LifecycleListener listener) {}

    @Override
    public void start() {}

    @Override
    public void stop() {}

}

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should be attempting to resolve the name ever, if you have permissions to the URL you get access to target, so if there is an alias, so long as you have permission, its allowed. This prevents 'transitive' permissions due to the creation of aliases or other systems... views?, food food thought.

permission += "|" + Strings.join(resources.get(), ",");
}

if (log.isDebugEnabled()) {
Copy link
Member Author

@cwperks cwperks Jan 26, 2023

Choose a reason for hiding this comment

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

This block logs out the result of privilege evaluation.

Add logger.org.opensearch.identity: debug to opensearch.yml to enable debug logging for this module.

Signed-off-by: Craig Perkins <[email protected]>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@@ -67,6 +72,26 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap
ActionListener<Response> listener,
ActionFilterChain<Request, Response> chain
) {
// TODO The section below shows permission check working for index create and index search, but needs
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you know what changes will be required here for the plugins and extension functionality? We have the dual meaning of the permissions and extensions since they can both be the subject and have permissions regarding them. Do we need to define any requests for either to move things forward?

Copy link
Member Author

Choose a reason for hiding this comment

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

Extensions can register actions and those actions need to be permissible for cluster admins to be able to permit users to use features of an extension.

Plugins run in the same JVM as the OpenSearch node, but extensions run outside. Extensions interact with the OpenSearch cluster using a rest client to make requests to the cluster. Users have sets of permissions associated with them and extensions may as well, to give cluster admins the ability to further restrict how requests originating from an extension can interact with a cluster. At the very least, an extension making a request to a cluster on behalf of a user should perform the request with the same user permissions + restrictions, restrictions like preventing the ability to query system indices even if a user has access.

I was trying to make an analogy with microsoft word and saving files to the filesystem, but I believe that word is able to save to the same folders as the current logged in user.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see. Thank you for the explanation of what we will need to do moving forward. I have mostly been designing from the standpoint of generic permissions so wanted to see how this would fit with a more user-centric version like you have here. That makes sense though.

// Set<String> concretePermissionIndexNames = iner.resolveExpressions(cs, this.indexPatterns.toArray(new String[0]));
// Set<String> concreteRequestedIndexNames = iner.resolveExpressions(cs, requestedPermission.indexPatterns.toArray(new String[0]));

boolean allRequestedPatternsMatchGranted = requestedPermission.indexPatterns.stream()
Copy link
Contributor

Choose a reason for hiding this comment

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

This may need an inline comment just cause it is somewhat complicated. It first checks for any match then tries to all match after so it is just a little complex.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I can add one in. Capturing it here now:

User granted index patterns: my-index*,other-index*
Request permission on: my-index1

For all requested index patterns:
    requested index pattern must match at least one pattern that the user has been granted access to

Copy link
Contributor

Choose a reason for hiding this comment

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

Great thank you!

Signed-off-by: Craig Perkins <[email protected]>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

Signed-off-by: Craig Perkins <[email protected]>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: UNSTABLE ❕
  • TEST FAILURES:
      1 org.opensearch.identity.UserPersistenceIT.testUserPersistenceInvalidYml
      1 org.opensearch.identity.UserPersistenceIT.testUserPersistence
      1 org.opensearch.identity.BasicAuthIT.testBasicAuthUnauthorized
      1 org.opensearch.identity.BasicAuthIT.testBasicAuthSuccess
      1 org.opensearch.identity.BasicAuthIT.testBasicAuthSuccess
      1 org.opensearch.cluster.routing.allocation.decider.DiskThresholdDeciderIT.testIndexCreateBlockWithAReadOnlyBlock
      1 org.opensearch.cluster.routing.allocation.decider.DiskThresholdDeciderIT.testIndexCreateBlockIsRemovedWhenAnyNodesNotExceedHighWatermark

@codecov-commenter
Copy link

Codecov Report

Merging #6029 (8b16b01) into feature/identity (6abe64a) will decrease coverage by 0.48%.
The diff coverage is 30.76%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@                  Coverage Diff                   @@
##             feature/identity    #6029      +/-   ##
======================================================
- Coverage               71.08%   70.61%   -0.48%     
+ Complexity              59272    58899     -373     
======================================================
  Files                    4829     4831       +2     
  Lines                  282163   282225      +62     
  Branches                40686    40695       +9     
======================================================
- Hits                   200578   199294    -1284     
- Misses                  65477    66580    +1103     
- Partials                16108    16351     +243     
Impacted Files Coverage Δ
...in/java/org/opensearch/authn/noop/NoopSubject.java 60.00% <0.00%> (-6.67%) ⬇️
...identity/authmanager/internal/InternalSubject.java 28.57% <0.00%> (-4.77%) ⬇️
...hmanager/securityplugin/SecurityPluginSubject.java 0.00% <0.00%> (ø)
...pensearch/identity/authz/OpenSearchPermission.java 0.00% <0.00%> (ø)
...a/org/opensearch/identity/realm/InternalRealm.java 68.00% <8.33%> (-17.00%) ⬇️
...ty/src/main/java/org/opensearch/identity/User.java 76.92% <57.14%> (-8.08%) ⬇️
...ntity/authz/IndexNameExpressionResolverHolder.java 71.42% <71.42%> (ø)
...n/java/org/opensearch/identity/SecurityFilter.java 60.71% <75.00%> (+3.89%) ⬆️
...n/java/org/opensearch/identity/IdentityPlugin.java 67.10% <100.00%> (+0.43%) ⬆️
.../index/shard/IndexShardNotRecoveringException.java 0.00% <0.00%> (-50.00%) ⬇️
... and 457 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@cwperks
Copy link
Member Author

cwperks commented Jan 31, 2023

The gradle check failure is unrelated to this PR:

Execution failed for task ':qa:mixed-cluster:v2.6.0#mixedClusterTest'.
> `cluster{:qa:mixed-cluster:v2.6.0}` failed to wait for cluster health yellow after 40 SECONDS
    IO error while waiting cluster
    500 Internal Server Error

@@ -51,6 +53,9 @@ public User(final String username, final String bcryptHash, Map<String, String>
@JsonProperty(value = "attributes")
private Map<String, String> attributes = Collections.emptyMap();

@JsonProperty(value = "permissions")
private List<String> permissions = Collections.emptyList();
Copy link
Member

Choose a reason for hiding this comment

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

Not blocking this change: I think we should retrieve permissions from the store that @scrawfor99 has created

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. This is added here to show a permissions check working end to end and the magic of permissions resolution (Subject -> List of Permissions) happens in doGetAuthorizationInfo of the InternalRealm. That method can be updated when the long-term permissions store is decided upon and implemented.

Copy link
Member

@peternied peternied left a comment

Choose a reason for hiding this comment

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

Getting this merged for speed

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