-
Notifications
You must be signed in to change notification settings - Fork 275
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
Implement new extension points in IdentityPlugin and add ContextProvidingPluginSubject #4665
base: main
Are you sure you want to change the base?
Conversation
…dingPluginSubject Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
… plugin user Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
src/main/java/org/opensearch/security/support/SafeSerializationUtils.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Craig Perkins <[email protected]>
…ackwards compatibility Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
src/main/java/org/opensearch/security/securityconf/SecurityRoles.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
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.
@cwperks Thanks for working on this - great forward movement to granularize the permissions available to plugins.
I'm having a little trouble grokking the scope of these changes - it seems like there should be a new component that serves as a source of truth for plugin permissions that is missing - I've called this out.
Could you create a diagram showing the new parts of the system and how they interplay with with the existing security architecture?
Finally, can you speak to where this design change is in the security review process?
@@ -97,5 +98,7 @@ Set<String> getAllPermittedIndicesForDashboards( | |||
|
|||
SecurityRoles filter(Set<String> roles); | |||
|
|||
SecurityRoles createSecurityRole(String roleName, Set<String> clusterPerms, Map<String, Set<String>> indexPatternToAllowedActions); |
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.
createSecurityRole
doesn't align with how these configuration has been used before, its meant to be a representation of the security configuration from the security index, not a way to update/generate/create security objects.
Rather than overload SecurityRole with this responsibility, lets make a new component that is initialized in its own way and separately from our existing configuration. Its strange to have the logic to create these roles inside of configuration model specific code and for its state to be held inside of the privilege evaluator.
/** | ||
* @return true if this instance is of the type PluginUser | ||
*/ | ||
public boolean isPluginUser() { |
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.
This goes against the pattern of polymorphism, the base class shouldn't be updated to know if it is a subclass. This forces callers of User to determine what subclasses are supported.
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.
Changed to using instanceof
. I'll look into other ways to do this as well, but I'm reticent to user the same user attributes approach that we used with service accounts.
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.
Can you help me understand why not? To take this to an extreme, if a user object representing plugin could have bad interactions with existing scenarios, why re-use the User object at all?
@peternied Thanks for the review! I'll get back to the comments ASAP. I think you may be interested in this PR I made in core that allows a plugin dev to specify what transport actions a plugin would need to perform outside the authenticated user context. The security plugin is a good example because there's a use-case where security needs to be able to write to the audit log index (if using an opensearch index) regardless of the authenticated user's permissions. Does that help answer your first question?
In the PR in core, the cluster admin will be prompted at installation time what a plugin is requesting to be able to perform. That PR was made in response to what you called out earlier and hearing back from plugin devs on opensearch-project/opensearch-plugins#238 Edit: Plugins will also inherently have system index access to the system indices that they have registered with SystemIndexPlugin.getSystemIndexDescriptors |
Signed-off-by: Craig Perkins <[email protected]>
That's a good point. I pushed a commit to abstract this out into a separate class called InMemorySecurityRolesV7 and made it distinct from any of the ConfigV7/ConfigV6 classes which are backed by the security index. |
@peternied Here's some mermaid diagrams that I made with the help of ChatGPT-4o Here's the prompt: In the plugin installation process:
In the OpenSearch Bootstrap process:
Low-Level Detail The permissions defined in
Plugin Installation Sequence sequenceDiagram
participant Admin as Administrator
participant OpenSearch as OpenSearch
participant Plugin as Plugin
OpenSearch ->> Plugin: Reads `plugin-security.policy` (JSM Permissions)
OpenSearch ->> Plugin: Reads `plugin-permissions.yml` (Transport Actions)
OpenSearch ->> Admin: Prompt for requested permissions
alt Admin accepts risks
Admin ->> OpenSearch: Accept permissions
OpenSearch ->> Plugin: Continue with installation
else Admin aborts
Admin ->> OpenSearch: Abort installation
end
OpenSearch Bootstrap Sequence: sequenceDiagram
participant Bootstrap as OpenSearch Bootstrap
participant IdentityService as IdentityService
participant IdentityPlugin as IdentityPlugin
participant Plugin as IdentityAwarePlugin
Bootstrap ->> IdentityService: Initialize IdentityService
IdentityService ->> IdentityPlugin: Use singular IdentityPlugin
loop For each IdentityAwarePlugin
IdentityPlugin ->> Plugin: Assign PluginSubject
Plugin ->> IdentityPlugin: PluginSubject executes transport actions
end
Plugin ->> Plugin: Executes actions with associated identity
|
Just one heads up that one goal of #4380 is to remove |
Signed-off-by: Craig Perkins <[email protected]>
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.
presponse.markComplete(); | ||
return; | ||
} | ||
} | ||
|
||
if (user instanceof PluginUser) { |
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.
This doesn't seem necessary, if the user principle that represents a plugin is trustworthy the lookup for allowed system indices should return an empty set for a normal user. Lets treat all users the same in code, but allow there configuration data to be populated in a way that distinguishes them well.
By special casing for a plugin user vs normal user create a huge potential for bugs down the line where these permissions are not accounted correctly that would result in a CVE. Note; If there are performance concerns then we need to revisit how these data structures are build to be O(1) lookup.
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.
That makes sense, I just pushed a change gets rid of the need for the subclass.
I plan to use a username convention for these special "plugin users". The name will be plugin:<pluginCanonicalClassName>
. I'm choosing this because :
is restricted from usernames, so this will provide assurance that this is a special kind of system username and not a user that could have been created through any other means.
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Sync'ed with the latest from main |
Resolved conflicts after merge of latest PRs |
Thank you @nibix! I will check to see what changes are necessary to incorporate this change with Optimized Privileges Evaluation. I'm introducing the "in-memory" role for a plugin in this PR with the goal of eventually incorporating the changes from this PR in core so that we can re-use the authz checks when plugins want to operate outside the authenticated user context. |
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.
Thanks for making progress here, I've created some new comment threads and followed up on existing ones.
|
||
import org.opensearch.identity.PluginSubject; | ||
|
||
public class PluginContextSwitcher { |
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.
All User's support runAs(...)
, not sure what the case for this class is
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.
Its a vehicle for carrying the subject that's been assigned to a plugin to the transport layer. Take a look at SystemIndexPlugin1
where it instantiates class and returns it from createComponents
so that its injectable to transport actions.
src/main/java/org/opensearch/security/identity/SecurityUserSubject.java
Outdated
Show resolved
Hide resolved
src/main/java/org/opensearch/security/identity/SecurityUserSubject.java
Outdated
Show resolved
Hide resolved
if (user.isPluginUser()) { | ||
securityRoles = getSecurityRoleForPlugin(user.getName()); | ||
} else { | ||
securityRoles = getSecurityRoles(mappedRoles); | ||
} |
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.
Shouldn't special case user vs non-user, seems like they could both be checked against either registry.
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.
Abstracted this logic to a separate method. The problem with the latter in this block is that its actually a filtering operation and it filters based on the roles in the security index.
For these plugin users, the permissions are not tied to the security index. If opensearch-project/OpenSearch#15778 gets merged, then the permissions given to a plugin are defined in a yaml file that is read on bootstrap and kept in the memory of the process. There is no plan to support dynamic updates to these permissions as they are static and prompted to a cluster administrator on plugin install to accept.
@@ -47,6 +47,10 @@ public Set<String> getMissingPrivileges() { | |||
return new HashSet<String>(missingPrivileges); | |||
} | |||
|
|||
public boolean addMissingPrivileges(String action) { |
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.
Good idea!
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.
IMO This should always have been exposed through methods instead of directly accessing fields of this class.
presponse.markComplete(); | ||
return; | ||
} | ||
} | ||
|
||
if (user.isPluginUser()) { |
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.
Can we remove the distinction between different kinds of users, it adds complexity in these downstream systems that will make the code harder to maintain over time.
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.
Note; It seems reasonable that no matter the source so long as the expected permissions grants are on the user (if it represents a person or a plugin) they can access system indices even if practically they aren't available at this point in time. What do you think?
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.
I think this distinction should be kept here since this is an "ownership" model. Authorization is done based on identity here and not a role. i.e. PluginA registers SystemIndexA and gets full access to it by default.
We have a separate mechanism for provisioning system index access to regular user's but it relies on a feature being enabled. The feature flag is plugins.security.system_indices.permission.enabled
and its off by default.
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.
Authorization is done based on identity here and not a role. i.e. PluginA registers SystemIndexA and gets full access to it by default.
Why add an alternative authorization model for this case? Seems like this makes the system more complex to understand and verify vs role checks.
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.
@nibix @DarshitChanpura I've got a preference for consistent treatment Users objects no matter if they represent a person or a plugin, I'd be curious for your opinions one way or the other.
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.
@peternied For what its worth, there is service account specific logic in the same file. Service account is analogous to plugin user, but the difference is that its meant for extensions. i.e. If there are extensions in a cluster that register system indices, then on bootstrap the security plugin issues a token to the extension that it can use to access its registered system indices if it wants to use OpenSearch as a datastore.
Edit: IMO I'd be in favor of unifying the service account logic and plugin user logic longer-term. I think there would need to be some additional changes in the extension registration process to allow configuration of security settings like reserving (system) indices.
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.
From what I understand, we are trying to introduce a concept of Plugin Awareness. This requires Identification of the plugin. To enforce this identity we are leveraging already existing User authz flow. Each plugin should have access to its own resource by default, but no other indices. We already have "permission-types" in place, so IMO introducing a plugin user is manageable. We can mark plugin specific users and roles as reserved and static (probly hidden?) to prevent them from being unintentionally modified.
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.
We can mark plugin specific users and roles as reserved and static (probly hidden?) to prevent them from being unintentionally modified.
These aren't tied to an index. They are purely in-memory and cannot be altered.
/** | ||
* @return true if this instance is of the type PluginUser | ||
*/ | ||
public boolean isPluginUser() { |
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.
Can you help me understand why not? To take this to an extreme, if a user object representing plugin could have bad interactions with existing scenarios, why re-use the User object at all?
The only real distinction is in that logic to get the "roles". For a regular user, it would filter from the roles that are in the cache, but for a plugin user it would get or create an instance of a role that's only kept in the memory of the process and not backed by the security index. With this PR, pluginSubject's are limited to interactions only with their own system indices. I plan to do a follow-up to this PR once another corresponding PR in core is merged that let's plugins request additional permissions at installation time. Again, the distinction is in how to compute the role or roles that are used to evaluate permissions. I will abstract the logic into a single method. |
Signed-off-by: Craig Perkins <[email protected]>
…ic logic into method Signed-off-by: Craig Perkins <[email protected]>
Description
Companion PR in core: opensearch-project/OpenSearch#14630
This PR by itself does not add additional functionality, it simply implements the new extension points in core and introduces a new class called
ContextProvidingPluginSubject
which populates a header in the ThreadContext with the canonical class name of the plugin that is executing code usingpluginSystemSubject.runAs(() -> { ... })
. See./gradlew integrationTest --tests SystemIndexTests
for an example that verifies that a block of code run withpluginSystemSubject.runAs(() -> { ... })
has contextual info in the thread context.Enhancement
Issues Resolved
Related to #4439
Check List
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.