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

Changes to make PIT security model granular #2053

Closed

Conversation

bharath-techie
Copy link
Contributor

@bharath-techie bharath-techie commented Aug 28, 2022

Signed-off-by: Bharathwaj G bharath78910@gmail.com

Description

As part of point in time opensearch review, we got comments which will help PIT security model more granular - where each PIT can be permitted or rejected based on user's indices permissions.
opensearch-project/OpenSearch#4064 (comment)

Design doc - opensearch-project/OpenSearch#3960
This PR contains the changes for same.
As part of this change ,

  • All PIT APIs which are user facing will be using 'indices:data/read' action that is inline with search semantics
  • For 'List all PITs' API, we'll return only the PITs for which the user has access to.
    • If user has access to all indices part of the PIT, then the PIT will be permitted
    • If user has no access to any index of PIT, then we'll not give access to that PIT
    • If user has no access to any PITs or there are no PITs in cluster, then we'll throw permission error for the API request
  • For 'Delete list of PITs' API
    • User needs to have access for all indices of all PITs, only then we'll allow deletion otherwise we'll fail the operation. ( This is inline with other APIs where explicit values are given instead of 'all' or '*' )
  • For 'Delete all PITs' API
    • If user has access to all indices part of the PIT, then the PIT will be permitted and deleted
    • If user has no access to any index of PIT, then we'll not delete the PIT
    • If user has no access to any PITs or there are no PITs in cluster,, then we'll throw permission error for the API request
  • For 'PIT segments API for list of PITs' API
    • User needs to have access for all indices of all PITs, only then we'll allow PIT segments API otherwise we'll fail the operation. ( This is inline with other APIs where explicit values are given instead of 'all' or '*' )
  • For 'PIT segments API for all PITs' API
    • If user has access to all indices part of the PIT, then the PIT will be permitted and segments info will be shown
    • If user has no access to any index of PIT, then we'll not permit the PIT
    • If user has no access to any PITs or there are no PITs in cluster,, then we'll throw permission error for the API request
  • Category (Enhancement, New feature, Bug fix, Test fix, Refactoring, Maintenance, Documentation)
    New feature - Modification to point in time security model - It is a new feature and not released.
  • Why these changes are required?
    We're making these changes to enhance the security model of PIT as per review comments from opensearch community
  • What is the old behavior before changes and new behavior after changes?
    Add security changes for point in time API #2033 -- this is the old behavior and the above description gives an idea of new behavior. Basically old model gives user access to all PIT contexts or none of PIT contexts and the new model allows permission for PITs based on the underlying indices.

Issues Resolved

opensearch-project/OpenSearch#3960

Is this a backport? If so, please add backport PR # and/or commits #
2.3 / 2.x backport

Testing

[Please provide details of testing done: unit testing, integration testing and manual testing]
Tested all the changes with opensearch + security plugin locally. All changes are working as expected.

Check List

  • New functionality includes testing
  • New functionality has been documented
  • 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.

@bharath-techie
Copy link
Contributor Author

@reta @Bukhtawar please review

@bharath-techie
Copy link
Contributor Author

bharath-techie commented Aug 28, 2022

Build is failing because all the dependencies are in opensearch main branch ( 3.0.0 )

Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
@bharath-techie bharath-techie marked this pull request as ready for review September 2, 2022 14:47
@bharath-techie bharath-techie requested a review from a team September 2, 2022 14:47
@bharath-techie
Copy link
Contributor Author

@peternied @cliu123 please review this PR.

- "indices:data/read/point_in_time/delete"
- "indices:data/read/point_in_time/readall"
- "indices:data/read/search"
- "cluster:admin/point_in_time/read_from_nodes"

Choose a reason for hiding this comment

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

Sorry, for my understanding what is read_from_nodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the parent action that basically reads all active Point in time searches from nodes.

This the action for NodesGetAllPitAction.

We can rename it as well, if the name is confusing.

final ImmutableSet<String> allIndices,
final ImmutableSet<String> originalRequested,
final ImmutableSet<String> remoteIndices,
IndicesOptions indicesOptions) {
Copy link
Member

Choose a reason for hiding this comment

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

Please avoid the format changes if not necessary.

// Skip pit evaluation for "NodesGetAllPITs" action, since it fetches all PITs across the cluster
// for privilege evaluation
if(action.startsWith("cluster:admin")) {
return presponse;
Copy link
Member

Choose a reason for hiding this comment

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

How is the permission for NodesGetAllPITs evaluated if requests bypass privilege evaluator? Or is there no need to have access control on NodesGetAllPITs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It'll still get evaluated, we are just not evaluating it as part of pit access evaluator.

We're not marking presponse as complete, so it'll continue down the privilegeEvaluator code flow.

In short, User needs the action permission to execute nodes get all pits.

Copy link
Member

Choose a reason for hiding this comment

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

@bharath-techie looks like the permission for NodesGetAllPITs is designed to be evaluated in PitPrivilegesEvaluator.java? Would you please point me to where it gets evaluated and explain a bit more about how it gets evaluated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As long as we don't mark the presponse as complete, it'll continue down the flow in PrivilegesEvaluator

 // check access for point in time requests
        if(pitAccessEvaluator.evaluate(request, clusterService, user, securityRoles,
                action0, resolver, dcm.isDnfofForEmptyResultsEnabled(), presponse).isComplete()) {
            return presponse;
        }

It'll go inside this if block for evaluation after we return without marking it complete https://github.com/opensearch-project/security/pull/2053/files#diff-e746c76b234a31b6f95d428b7c40dabf805b609b110c560902c967db8f83fa7aR303

List<ListPitInfo> pitInfos = getAllPitInfos((GetAllPitNodesRequest) request);
// if cluster has no PITs, then allow the operation to pass with empty response
if(pitInfos.isEmpty()) {
presponse.allowed = true;
Copy link
Member

Choose a reason for hiding this comment

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

The authorization on a request should not be decided by whether there are PITs in a cluster. It should always be evaluated with permissions instead. If the user does not have right permissions, the request should be denied no matter whether there are PITs in a cluster or how many PITs there are.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the user does not have permissions, we do throw exception. I'll add some ITs which can assert the same.

Now this logic makes sure that we don't throw permission exception when there are simply no PITs created in the cluster.

Copy link
Member

Choose a reason for hiding this comment

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

Now this logic makes sure that we don't throw permission exception when there are simply no PITs created in the cluster.

If a user does not have PIT search permissions, even if there are not PITs in the cluster, the request still should be denied. As I said, authorizing a request or not should only be decided by the permissions the user has(privilege evaluator).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed the comment, we're denying if there are no pits so resolving this and similar ones

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @cliu123 - we are divulging information to the user they should not have - if PITs exist or not.

If you were building a client to connect with OpenSearch and it called this API and got a 200 because it was empty, but then someone added a PIT your requests would suddenly fail due to a side effect - best to be as consistent as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so as long as the user does not have PITs , we throw 403. The behavior is consistent and the above does not happen.

User might not have PITs because :

  • PITs are present in the cluster but the user does not have access
  • PITs are not present in the cluster

permittedPits.add(pitToPitInfoMap.get(pitId));
}
}
if (permittedPits.size() > 0) {
Copy link
Member

@cliu123 cliu123 Sep 4, 2022

Choose a reason for hiding this comment

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

When a user has right permissions, even if permittedPits.size() equals to 0, the request also should be authorized and get empty in the response. There could be 0 PITs in the cluster.

Copy link
Contributor Author

@bharath-techie bharath-techie Sep 4, 2022

Choose a reason for hiding this comment

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

That is the case we've handled in the line 124, we authorize the user. Because the whole custom logic just ensures if PIT IDs are present, the user should have associated indices permissions.

If not, at this point the user does have action permissions and there are no PITs present in cluster, so we mark operation as success.

}
}
// If there are any PITs for which the user has access to, then allow operation otherwise fail.
if(permittedPits.size() > 0) {
Copy link
Member

@cliu123 cliu123 Sep 4, 2022

Choose a reason for hiding this comment

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

When a user has right permissions, even if permittedPits.size() equals to 0, the request also should be authorized and get empty in the response. There could be 0 PITs in the cluster.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cliu123 i tried making this work, but looks like for index actions, there is no way to authorize the user when there are no indices to validate against.

dnfOfEnabled flag is used for indices actions where for empty we permit , otherwise we throw forbidden error.

can you please suggest if there are any alternate approaches ?

Copy link
Member

Choose a reason for hiding this comment

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

Here is a potential alternative approach:

  1. Collect authorization information for the user
  2. Does the user have access to any PITs? (If no- permission is denied)
  3. Does the user have access to the PITs in the request? (If no- permission is denied)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is what we are doing currently when the config is false ( which is default ).
This config is used in search and other index actions as well, so can we please take a look at this config? it is designed not to throw forbidden in case of empty results.

Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
Signed-off-by: Bharathwaj G <bharath78910@gmail.com>

OpenSearchSecurityPlugin.GuiceHolder.getPitService().getAllPits(latchedActionListener);

if(!latch.await(15, TimeUnit.SECONDS)) {

Choose a reason for hiding this comment

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

@reta as per this discussion we need to get rid of this latch since this has the potential to block http request handler threads.

Do you have thoughts on how we could have a reference to the response listener or perform non-blocking waits,

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry @Bukhtawar , I came a bit late, seems like this code is already gone 🥳

Choose a reason for hiding this comment

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

@bharath-techie would you be able to shed some light if you still have that commit handy

@reta apologies let's see if we that commit

Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
@bharath-techie
Copy link
Contributor Author

@elfisher @cliu123 please prioritize reviewing this PR as this is a 2.3 release blocker.

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.

I have concerns about the processing in the pit privileges evaluator, if there are updates or bugs create security issues. Please take a look at the individual comments, happy to discuss if there are alternatives or other considerations to weight.

Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
Signed-off-by: Bharathwaj G <bharath78910@gmail.com>
@elfisher
Copy link
Contributor

elfisher commented Sep 7, 2022

Hi I want to confirm this will work with relevant predefined roles. This should at minimum work with all_access, read_all, and read_all_and_monitor. Thanks!

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.

Thanks for the updates, there are still many unresolved threads please take a look.

One way we could see about reviewing/merging this change faster, I think there is less discussion around the workflows that intersect with handleExplicitPitsAccess. Consider splitting this PR into multiple parts to so we can make progress independently

permittedPits.add(pitToPitInfoMap.get(pitId));
}
}
if (permittedPits.size() > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

By hiding the inaccessible PITs we are making it harder to understand what "ALL" means. While we are just getting this feature off the ground I'd like us to use the most restrictive framing of permissions that we could open up in future revisions.

I understand this change requires more work from the user configuring the cluster; however, I think adding a role like the following better communicates what is secured and what isn't to the operator of the cluster.

point_in_time_all_index_manage:
  index_permissions:
    - index_patterns:
        - "*"
      allowed_actions:
        - "manage_point_in_time"

hidden: false
description: "Migrated from v6 (all types mapped)"
cluster_permissions:
- "cluster:admin/point_in_time/read_from_nodes"
Copy link
Member

Choose a reason for hiding this comment

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

I am concerned about adding cluster level permission it blurs the line between the index based permissions and PIT which are tightly coupled to indices, I know there is some discussion around it and I'd like to focus that discussion separately from the rest of this work to help get this PR merged more quickly - is that possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without cluster level permission we'd not be able to get all PITs from the cluster, as the request doesn't have indices as part of it. Any suggestions ?

Copy link
Member

Choose a reason for hiding this comment

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

Is there a place where all of the different Rest APIs are documented in the form below? I would like to be able to cross-reference the actions and associated permissions that are needed to perform them, I think that might help our understanding.

Create point in time

POST "/<target indices>/point_in_time?*keep_alive*=1h&routing=&expand_wildcards=&preference=" 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

opensearch-project/OpenSearch#3960 -- is the design doc

Copy link
Member

Choose a reason for hiding this comment

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

The doc and the comments on this PR have evolved over time and it is not clear to me what the permission setup, user action, and result should be for the many scenarios embodied - please make this as clear as possible.

Not only would this help us understand the specific scope and bounds of your request, this would be of great value for writing the end-user facing permissions documentation for this feature

List<ListPitInfo> pitInfos = getAllPitInfos((GetAllPitNodesRequest) request);
// if cluster has no PITs, then allow the operation to pass with empty response
if(pitInfos.isEmpty()) {
presponse.allowed = true;
Copy link
Member

Choose a reason for hiding this comment

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

I agree with @cliu123 - we are divulging information to the user they should not have - if PITs exist or not.

If you were building a client to connect with OpenSearch and it called this API and got a 200 because it was empty, but then someone added a PIT your requests would suddenly fail due to a side effect - best to be as consistent as possible.

}
}
// If there are any PITs for which the user has access to, then allow operation otherwise fail.
if(permittedPits.size() > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Here is a potential alternative approach:

  1. Collect authorization information for the user
  2. Does the user have access to any PITs? (If no- permission is denied)
  3. Does the user have access to the PITs in the request? (If no- permission is denied)

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.

6 participants