Identity and access control foundation for OpenSearch#5925
Identity and access control foundation for OpenSearch#5925peternied wants to merge 3 commits intoopensearch-project:mainfrom
Conversation
|
FYI @opensearch-project/security Please take a look, this is a departure in a couple important ways from what we were doing on the feature branch. I'm going to keep iterating on the todo list, please feel free to comment on things in this pull request or for functionality on the todo list as well. |
Gradle Check (Jenkins) Run Completed with:
|
|
@dbwiddis @saratvemulapalli What do you think of adding a new plugin interface, this would be notable since the identity plugin should not be supported via the extensions interface performance would be a killer? |
Gradle Check (Jenkins) Run Completed with:
|
This seems to overly simplify the trade-offs here. We have three locations we can put things: extensions, plugins, or directly in core. I think some/most of the identity functionality should be present in core. What's the use case for putting it in a plugin rather than core? There might be room for some extensions interacting with identity providers (providing SSO, etc.). Sign-in would generally be a one-time thing where a few milliseconds may not matter as much, while core/plugin would be better for repeated checks during operation. |
|
@peternied looking at this now. This is a bit of a deviation of what is in @dbwiddis I'll explain the current thinking and state of the The way it is organized is in a library ( The library contains the APIs for interacting with security conveniently. This will provide richer APIs for interacting with security than common-utils. This would include methods like The identity module is where the internal implementation of security is located and it does use the current plugin extension points of the security plugin to implement REST request authentication via This is from the developer guide on modules in OpenSearch:
We haven't run into any issues building identity as a module, but there's active discussion around leveraging existing extension points vs putting the implementation directly in server. |
|
Basic authentication is working |
Gradle Check (Jenkins) Run Completed with:
|
Yes - we are in heavy-prototyping in the feature branch - which is great. However, following the model established by the extensions team, I want to go directly into what I'd expect to be the final resting place of the identity code and associated module(s). I know this is going to create some complex merge issues in feature/identity, but I've stripped out features that we are not ready to merge into main yet. Setting aside package naming and file placement, I've created the |
|
@peternied This PR is a good segue into "identity as core feature". Is that the long term vision for this feature? If so how should the task priorities be considered for future development. |
Yes! This is a refinement of the work done in
This is a good topic for the meta issue [^2] around this work, but I would say the project board's readme captures the current status the best [^3] |
Security evolved outside of core for many years - there are many external implementations of identity and access controls built as plugins. While we own the Security plugin in this project, there are others customers that want to keep using alternative implementations. By having the a plugin layer it will protect us from ourselves from too much coupling as we iterate. I recently pushed an implementation of this interface I'd be curious what you think of the module and its separation of responsibility. |
| public ShiroIdentityPlugin(final Settings settings) { | ||
| this.settings = settings; | ||
|
|
||
| SecurityManager securityManager = new DefaultSecurityManager(InternalRealm.INSTANCE); |
There was a problem hiding this comment.
The DefaultSecurityManager will create sessions by default. This will not be stateless as basic auth should be.
stephen-crawford
left a comment
There was a problem hiding this comment.
I left some comments places with general notes and a couple of things to check.
| String username = ((UsernamePasswordToken) token).getUsername(); | ||
| // Look up the user by the provide username | ||
| User userRecord = getInternalUser(username); | ||
| // Check for other things, like a locked account, expired password, etc. |
There was a problem hiding this comment.
Is this supposed to be a TODO or is that part of the getInternalUser call?
| throw new IncorrectCredentialsException(); | ||
| } | ||
| } | ||
| // Don't know what to do with this token |
There was a problem hiding this comment.
Should we be including this into main? It seems like this section is still a work in progress.
| } | ||
|
|
||
| // TODO: Fix this test | ||
| // public void testShouldExtractBasicAuthTokenSuccessfully_twoSemiColonPassword() { |
There was a problem hiding this comment.
Should this be removed?
There was a problem hiding this comment.
I need to fix these tests before I can merge, they should be uncommented before then. The TODO above captures
modules/identity-shiro/src/test/java/org/opensearch/identity/AuthTokenHandlerTests.java
Outdated
Show resolved
Hide resolved
modules/identity-shiro/src/test/java/org/opensearch/identity/BasicAuthTests.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/opensearch/identity/tokens/UnsupportedHttpAuthenticationToken.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/opensearch/identity/IdentityModule.java
Outdated
Show resolved
Hide resolved
Gradle Check (Jenkins) Run Completed with:
|
RyanL1997
left a comment
There was a problem hiding this comment.
Do we have the lint check for this PR? As Stephen mentioned above, it seems like we are missing some of the additional line space at end of couple files.
This pull request is in draft and fixing all CR checks will be required to merge. I'll get around to these, but the functionality is where I am concentrating my efforts. |
server/src/main/java/org/opensearch/identity/IdentityModule.java
Outdated
Show resolved
Hide resolved
...es/identity-shiro/src/main/java/org/opensearch/identity/shiro/OpenSearchSecurityManager.java
Outdated
Show resolved
Hide resolved
modules/identity-shiro/src/main/java/org/opensearch/identity/shiro/AuthTokenHandler.java
Show resolved
Hide resolved
Gradle Check (Jenkins) Run Completed with:
|
b2162a7 to
403cdec
Compare
Gradle Check (Jenkins) Run Completed with:
|
|
@reta @nknize @saratvemulapalli I haven't seen any comments - I'm going to flush out the all test and get gradle check passing without waiting on any more feedback. |
|
|
||
| apply plugin: 'opensearch.internal-cluster-test' | ||
|
|
||
| opensearchplugin { |
There was a problem hiding this comment.
I think this module will be an ideal candidate for sandbox ?
There was a problem hiding this comment.
There are a couple of ways to introduce non-GA features into OpenSearch, sandbox changes the build output which is easy to test locally, but difficult from the distribution channels. Using feature flags was recently introduced and is easier to control outside this repository. What do you think of this tradeoff to include the module but have it disabled via feature flag?
There was a problem hiding this comment.
I believe there are 2 parts to it:
- the core support (new plugin interfaces), this could be good candidate for feature flags
- the new experimental plugin (like this one), this could go to sandbox
There was a problem hiding this comment.
👍 I'll move the module into sandbox
There was a problem hiding this comment.
sandbox changes the build output which is easy to test locally, but difficult from the distribution channels
@peternied Feel free to weigh in on #3677
| * @opensearch.experimental | ||
| */ | ||
| // TODO not sure why ThreadLeakScope.NONE is required | ||
| @ThreadLeakScope(ThreadLeakScope.Scope.NONE) |
There was a problem hiding this comment.
We probably could remove the @ThreadLeakScope(ThreadLeakScope.Scope.NONE), the thread leaks have to be detected
| } | ||
|
|
||
| public InternalRealm build() { | ||
| // TODO: Replace hardcoded admin user / user map with an external provider |
There was a problem hiding this comment.
👍 totally agree, we should not hardcode
My apologies @peternied , didn't have time today, did a quick pass but will take a closer look tomorrow, thank you |
CHANGELOG.md
Outdated
| - Add identity and access control extension point ([5925](https://github.com/opensearch-project/OpenSearch/pull/5925)) | ||
|
|
||
| ### Dependencies | ||
| - Bumps `wiremock-jre8-standalone` from 2.33.2 to 2.35.0 |
There was a problem hiding this comment.
These changes are not related, right?
There was a problem hiding this comment.
Bad merge, cleaning up...
| * @return the shiro auth token to be for login | ||
| */ | ||
| public AuthenticationToken translateAuthToken(org.opensearch.identity.tokens.AuthToken authenticationToken) { | ||
| final AuthenticationToken authToken = null; |
There was a problem hiding this comment.
The authToken is not used here
There was a problem hiding this comment.
Good call, cleaning this up.
|
|
||
| // By default shiro stores session information into a cache, there were performance | ||
| // issues with this sessions cache and so are defaulting to a stateless configuration | ||
| this.subjectDAO = new StatelessDAO(); |
There was a problem hiding this comment.
I don't think you need StatelessDAO class, you could just disable session storage (and consequently, the cache if needed):
final DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
evaluator.setSessionStorageEnabled(false);
final DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
subjectDAO.setSessionStorageEvaluator(evaluator);
setSubjectDAO(subjectDAO);There was a problem hiding this comment.
Thanks for the example, using this.
|
|
||
| public ShiroSubject(final AuthTokenHandler authTokenHandler, final org.apache.shiro.subject.Subject subject) { | ||
| this.authTokenHandler = authTokenHandler; | ||
| this.shiroSubject = subject; |
There was a problem hiding this comment.
Could either authTokenHandler or subject be null?
There was a problem hiding this comment.
They shouldn't be, adding Objects.requireNonNull(...) to ensure this
| @Override | ||
| public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { | ||
| final UsernamePasswordToken userToken = (UsernamePasswordToken) token; | ||
| final String password = new String(userToken.getPassword()); |
There was a problem hiding this comment.
The password is not needed, you convert from char[] to String and back to char[], just use userToken.getPassword() directly
| realmName | ||
| ); | ||
|
|
||
| // TODO: Doesn't appear to check the password |
| } | ||
|
|
||
| // If the token was not handled, it was unsupported | ||
| throw new UnsupportedTokenException("Unable to support authentication token " + token.getClass().getName()); |
There was a problem hiding this comment.
token could be null here, better to check
| * Base test case for integration tests against the identity plugin. | ||
| */ | ||
| @ThreadLeakScope(ThreadLeakScope.Scope.NONE) | ||
| public abstract class AbstractIdentityTestCase extends OpenSearchIntegTestCase { |
There was a problem hiding this comment.
Where this test case is used?
| /** | ||
| * Represents a principal which has not been authenticated | ||
| */ | ||
| UNAUTHENTICATED(new StringPrincipal("Unauthenticated")); |
There was a problem hiding this comment.
This seems to be very limited set of possible principals, I think we should not model it as an enumeration but constants:
public final class BuiltinPrincipals {
public static final Principal UNAUTHENTICATED = new StringPrincipal("Unauthenticated");
}
There was a problem hiding this comment.
I think enums are much more expressive and flexible. I'd like to leave it in, we can clean it up if its cumbersome with further implementation.
There was a problem hiding this comment.
Enums imply closed set of choices - this is not the case here
| * | ||
| * @opensearch.experimental | ||
| */ | ||
| public class StringPrincipal implements Principal { |
There was a problem hiding this comment.
Should we rename it to UsernamePrincipal instead? StringPrincipal is definitely not giving the context what this principal is about.
There was a problem hiding this comment.
To me user implies human, I think we will be using this object to represent segments of the OpenSearch runtime. I believe we will replace many of the ThreadPool.Names with these principals to limit the kinds of access those thread have.
There was a problem hiding this comment.
Ok, what about NamedPrincipal?
| return new BasicAuthToken(authHeaderValueStr); | ||
| } else { | ||
| if (logger.isDebugEnabled()) { | ||
| String tokenTypeTruncated = Strings.substring(authHeaderValueStr, 0, 5); |
There was a problem hiding this comment.
I think we should remove the tokenTypeTruncated, it would output weird token types (for example, imagine Digest ... here)
There was a problem hiding this comment.
In a debugging scenario, I think seeing those weird token times would be useful if you were expecting the request to authenticated and wasn't sure why. What do you think?
There was a problem hiding this comment.
I mean, in debugging you could always say that the auth header was not Basic (if you ever need to do that).
| /** | ||
| * Get the current subject | ||
| * */ | ||
| public Subject getSubject(); |
There was a problem hiding this comment.
Could getSubject() return null vaues? If yes, we have to reconsider the API and return Optional<Subject> instead
There was a problem hiding this comment.
Subject should never be null.
Sorry for not getting back on this, I am resurfacing from hibernation :). |
saratvemulapalli
left a comment
There was a problem hiding this comment.
Lot of barebones and learning to understand Shiro.
These 2 resources helped me learn:
Couple of questions and dropped few comments:
- What are your thoughts on re-using existing security plugins authn/authz mechanisms.
Can we write realms to use all of our business logic we have already built - I dont see compliance built into the framework, how do you plan to handle it (atleast to start with audit logging).
CHANGELOG.md
Outdated
| - Allow mmap to use new JDK-19 preview APIs in Apache Lucene 9.4+ ([#5151](https://github.com/opensearch-project/OpenSearch/pull/5151)) | ||
| - Add support for ppc64le architecture ([#5459](https://github.com/opensearch-project/OpenSearch/pull/5459)) | ||
| - Add support to disallow search request with preference parameter with strict weighted shard routing([#5874](https://github.com/opensearch-project/OpenSearch/pull/5874)) | ||
| - Add identity and access control extension point ([5925](https://github.com/opensearch-project/OpenSearch/pull/5925)) |
There was a problem hiding this comment.
This is assuming changes will not be available in OpenSearch 2.x.
Is that the intention?
There was a problem hiding this comment.
Thanks for noticing, I've moved this into the Unreleased 2.x section.
This document is confusing to work with for the first time.
| * | ||
| * @opensearch.experimental | ||
| */ | ||
| class AuthTokenHandler { |
There was a problem hiding this comment.
No opinions, trying to understand: Looks like a utility class. This class doesn't have any state, probably can we go static?
Are there more implementations which will end up here?
There was a problem hiding this comment.
Non static so this class can be dependency injected easily.
| private static final Logger logger = LogManager.getLogger(AuthTokenHandler.class); | ||
|
|
||
| /** | ||
| * Translates shiro auth token from the given header token |
There was a problem hiding this comment.
super nit:
| * Translates shiro auth token from the given header token | |
| * Translates into shiro auth token from the given header token |
| private final org.apache.shiro.subject.Subject shiroSubject; | ||
|
|
||
| public ShiroSubject(final AuthTokenHandler authTokenHandler, final org.apache.shiro.subject.Subject subject) { | ||
| this.authTokenHandler = authTokenHandler; |
There was a problem hiding this comment.
Isn't authTokenHandler for all subjects the same for OpenSearch?
Does it have to be a member for each subject?
There was a problem hiding this comment.
Passing via constructor for dependency injection / testing - is there a convention to follow for classes that aren't guice injected but use DI?
| @Override | ||
| public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { | ||
| final UsernamePasswordToken userToken = (UsernamePasswordToken) token; | ||
| final String password = new String(userToken.getPassword()); |
| adminUser.setUsername(new StringPrincipal("admin")); | ||
| adminUser.setBcryptHash("$2a$12$VcCDgh2NDk07JGN0rjGbM.Ad41qVR/YFJcgHp0UGns5JDymv..TOG"); // Password 'admin' | ||
| final Map<String, User> internalUsers = Map.of("admin", adminUser); | ||
| return new InternalRealm(name, internalUsers); |
There was a problem hiding this comment.
How do we plan to integrate this with existing security plugin users:
- For new clusters, read through the yml configs and populate them.
- For existing clusters which already has loaded yml configs, load it from security system index.
There was a problem hiding this comment.
We've got tasks for building up the UserService and management APIs - when we work on #5883 all these pieces will be included.
| final SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo( | ||
| userRecord.getUsername(), | ||
| userRecord.getBcryptHash(), | ||
| realmName |
There was a problem hiding this comment.
Do you plan to have multiple realms for OpenSearch (one for authn and one for authz)?
There was a problem hiding this comment.
No plans for multiple realms.
| public Subject getSubject() { | ||
| return identityPlugin.getSubject(); | ||
| } | ||
| } |
| if (!FeatureFlags.isEnabled(FeatureFlags.IDENTITY)) { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
I would prefer moving this to L406, lets not get to handleAuthenticateUser if the feature is not enabled.
| return false; | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
By default we should always false and return true only when the subject is authenticated (at L537)
There was a problem hiding this comment.
Authentication didn't fail so this is accurate, see the other commment thread in this file https://github.com/opensearch-project/OpenSearch/pull/5925/files#r1089137958. Adding a comment in the code that clarifies.
Signed-off-by: Peter Nied <petern@amazon.com>
Signed-off-by: Peter Nied <petern@amazon.com>
peternied
left a comment
There was a problem hiding this comment.
I'll do the migration to sandbox and the test fixes fast following this most recent push
CHANGELOG.md
Outdated
| - Allow mmap to use new JDK-19 preview APIs in Apache Lucene 9.4+ ([#5151](https://github.com/opensearch-project/OpenSearch/pull/5151)) | ||
| - Add support for ppc64le architecture ([#5459](https://github.com/opensearch-project/OpenSearch/pull/5459)) | ||
| - Add support to disallow search request with preference parameter with strict weighted shard routing([#5874](https://github.com/opensearch-project/OpenSearch/pull/5874)) | ||
| - Add identity and access control extension point ([5925](https://github.com/opensearch-project/OpenSearch/pull/5925)) |
There was a problem hiding this comment.
Thanks for noticing, I've moved this into the Unreleased 2.x section.
This document is confusing to work with for the first time.
CHANGELOG.md
Outdated
| - Add identity and access control extension point ([5925](https://github.com/opensearch-project/OpenSearch/pull/5925)) | ||
|
|
||
| ### Dependencies | ||
| - Bumps `wiremock-jre8-standalone` from 2.33.2 to 2.35.0 |
There was a problem hiding this comment.
Bad merge, cleaning up...
| * @opensearch.experimental | ||
| */ | ||
| // TODO not sure why ThreadLeakScope.NONE is required | ||
| @ThreadLeakScope(ThreadLeakScope.Scope.NONE) |
| * | ||
| * @opensearch.experimental | ||
| */ | ||
| class AuthTokenHandler { |
There was a problem hiding this comment.
Non static so this class can be dependency injected easily.
| private static final Logger logger = LogManager.getLogger(AuthTokenHandler.class); | ||
|
|
||
| /** | ||
| * Translates shiro auth token from the given header token |
| /** | ||
| * Get the current subject | ||
| * */ | ||
| public Subject getSubject(); |
There was a problem hiding this comment.
Subject should never be null.
| if (!FeatureFlags.isEnabled(FeatureFlags.IDENTITY)) { | ||
| return true; | ||
| } |
|
|
||
| try { | ||
| final AuthToken token = RestTokenExtractor.extractToken(request); | ||
| // If no token was found, continue executing the request |
There was a problem hiding this comment.
This method is authenticate - not authorize. Consider anonymous scenarios, maybe calls like cluster health would be allowed for anonymous requests. These authorizing calls would happen at lower layer where details about the resource associated with the request is against can factor in 403 or allow.
Note; authorization is not included in this change.
|
|
||
| try { | ||
| final AuthToken token = RestTokenExtractor.extractToken(request); | ||
| // If no token was found, continue executing the request |
There was a problem hiding this comment.
Adding this as a comment in the code
| return false; | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
Authentication didn't fail so this is accurate, see the other commment thread in this file https://github.com/opensearch-project/OpenSearch/pull/5925/files#r1089137958. Adding a comment in the code that clarifies.
Gradle Check (Jenkins) Run Completed with:
|
|
This change was merged as part of the following pull request |
Description
Adding a new module and plugin interface that provides identity and access control inside of OpenSearch. This is the founding building block, see more high level thoughts here on our recent blog.
The new extension point, IdentityPlugin, is added with IdentityService handling that plugin interface. IdentitySevice authenticates users and enables access control systems. Adding HTTP basic authentication in the RestController the default NoopIdentityPlugin changes no behavior.
When the identity feature flag is enabled the identity-shiro module is loaded into the IdentityService. This implementations includes an admin user backed through the Apache Shiro system. By partitioning these features through a plugin interface it isolate the dependencies if we ever switch out the underlying library.
Project tracking addition features and functionality
Outstanding Changes
Issues Resolved
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.