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

Map extension routes to permission-able action names #622

Closed
wants to merge 69 commits into from

Conversation

cwperks
Copy link
Member

@cwperks cwperks commented Mar 29, 2023

Description

This PR relies on an an earlier PR (#619) to setup TLS for extensions.

This PR also has a companion core and security PR

Isolated changes can be viewed using the compare tool: cwperks/opensearch-sdk-java@setup-extension-tls...ssl-and-handler-naming

This change modifies the extension settings by adding a new shortName setting (an abbreviation for an extension) that is used when creating a name for a route. /hello of the HelloWorld extension is mapped to the permission-able name extensions:hw/greet. The RouteHandler moves to core as part of this change, its needed by the security plugin to determine if a RestRequest is destined for an extension.

Endpoint registration has been updated to send the name of the route to core upon registration.

Issues Resolved

Related to opensearch-project/security#2589

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.

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@owaiskazi19
Copy link
Member

owaiskazi19 commented Mar 31, 2023

Did a quick pass. The changes overall LGTM @cwperks! I'm still trying to figure out the best place for these changes. Should it be in OpenSearch like we have ExtensionRestRequest there or SDK?

public static ExtensionRouteHandlerFactory getInstance() {
if(INSTANCE == null) {
INSTANCE = new ExtensionRouteHandlerFactory();
}
Copy link
Member

Choose a reason for hiding this comment

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

Are we trying to create SINGLETON object for INSTANCE here? if yes is it multithreaded safe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the problem I was facing is that I needed the extensions shortName (abbreviation) inside the RestHandler classes like RestHelloAction in the sample extension. Instead of passing the value as a constructor arg or passing that extension runner into those classes, I thought it would be better to have singular place where its registered and can be used throughout.

Do you know how I can test if this is multi-threaded safe?

import org.opensearch.OpenSearchException;

public class ExtensionRouteHandlerFactory {
private static ExtensionRouteHandlerFactory INSTANCE;
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we can use Guice here to achieve this?

Copy link
Member Author

@cwperks cwperks May 19, 2023

Choose a reason for hiding this comment

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

That would be great. I'll look into it. The main problem I was facing was that I needed to have the shortName of the extension available in the REST Handler (i.e. inside of RestHelloAction for the sample extension). The way I went about doing that was to create a singleton class that could be fetched throughout the SDK but if there is a better way to do it then I will switch to using that.

Copy link
Member

Choose a reason for hiding this comment

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

if there is a better way to do it then I will switch to using that.

Extensions are always initialized with their settings. getExtensionSettings() must be implemented on the Extension interface. (The BaseExtension class offers the convenience of either a settings object or yml file.)

Any method on the mainExtension class ought to be able to call getExtensionSettings().getShortExtensionName() to access this value without any factory/singletons.

In the AD Extension we usually pass the ExtensionsRunner class to every REST handler, and we have a getter on the runner for the extension. So all the AD REST handlers can do runner.getExtension().getExtensionSettings().getShortExtensionName().

public ExtensionSettings(String extensionName, String hostAddress, String hostPort, String opensearchAddress, String opensearchPort) {
public ExtensionSettings(
String extensionName,
String shortName,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
String shortName,
String shortExtensionName,

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll update it, I was torn between different names for this variable. One of the naming ideas was extensionAbbr, but I didn't know if was appropriate to use an abbreviation in a variable name signifying that this variable is an abbreviation. It felt like it was a crossword puzzle clue.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated

(mHandlers, newMHandler) -> mHandlers.addMethods(extensionRestHandler, method)
);
String restPathWithName = restPathToString(method, path, name);
registeredPaths.add(restPathWithName);
Copy link
Member

Choose a reason for hiding this comment

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

We are missing adding paths to deprecated paths here. One way to handle this would be to merge both the registerHandler methods and add a check for name.

this.securitySettings = securitySettings;
}

public String getExtensionName() {
return extensionName;
}

public String getShortName() {
return shortName;
}
Copy link
Member

Choose a reason for hiding this comment

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

This getter should have a test in TestExtensionSettings.java

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 has been added to a few different tests to verify its behavior

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@codecov
Copy link

codecov bot commented May 19, 2023

Codecov Report

Merging #622 (e0df300) into main (eff7635) will increase coverage by 0.03%.
The diff coverage is 65.85%.

❗ Current head e0df300 differs from pull request most recent head 6d2ee04. Consider uploading reports for the commit 6d2ee04 to get more accurate results

@@             Coverage Diff              @@
##               main     #622      +/-   ##
============================================
+ Coverage     43.57%   43.61%   +0.03%     
- Complexity      314      324      +10     
============================================
  Files            69       71       +2     
  Lines          1983     2011      +28     
  Branches        141      144       +3     
============================================
+ Hits            864      877      +13     
- Misses         1101     1113      +12     
- Partials         18       21       +3     
Impacted Files Coverage Δ
.../opensearch/sdk/rest/BaseExtensionRestHandler.java 89.23% <ø> (-0.63%) ⬇️
.../sample/helloworld/rest/RestRemoteHelloAction.java 25.00% <ø> (ø)
...main/java/org/opensearch/sdk/ExtensionsRunner.java 72.85% <30.00%> (-2.88%) ⬇️
...g/opensearch/sdk/ExtensionRouteHandlerFactory.java 66.66% <66.66%> (ø)
...opensearch/sdk/rest/ExtensionRestPathRegistry.java 89.36% <73.33%> (-7.94%) ⬇️
...java/org/opensearch/sdk/ExtensionRouteHandler.java 100.00% <100.00%> (ø)
...ain/java/org/opensearch/sdk/ExtensionSettings.java 85.10% <100.00%> (+1.01%) ⬆️
...ch/sdk/sample/helloworld/rest/RestHelloAction.java 80.85% <100.00%> (ø)

... and 2 files with indirect coverage changes

Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks
Copy link
Member Author

cwperks commented Jun 7, 2023

Can the maintainers take another look at this PR?

Copy link
Member

@dbwiddis dbwiddis left a comment

Choose a reason for hiding this comment

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

I think this is overly complex. Details below.

* compatible open source license.
*/

package org.opensearch.sdk;
Copy link
Member

Choose a reason for hiding this comment

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

If this class stays, it should not be in the base package. Probably would fit in the .rest subpackage.

String path,
Function<RestRequest, ExtensionRestResponse> handler
) {
super(ExtensionRouteHandlerFactory.getInstance().generateRouteName(handlerName), method, path, handler);
Copy link
Member

Choose a reason for hiding this comment

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

I'm trying to understand why we need an entire (sub)class just to call a generator function for one parameter. I don't think this class is necessary; we should just stick in an appropriate (probably static) method that does this name generation whenever we call new RouteHandler() from an extension.

* @return Returns a name prepended with the extension's shortName
*/
public String generateRouteName(String handlerName) {
return extensionShortName + ":" + handlerName;
Copy link
Member

Choose a reason for hiding this comment

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

This method call doesn't check whether extensionShortName has been initialized. ExtensionRouteHandlerFactory.getInstance().generateRouteName() will throw an exception.

There's no documentation in this method that it requires initialization first.

This seems like a whole lot of class overhead, initializations, and initialization checks to create a factory object whose only purpose seems to be to prepend one string to another, that seems to be possible with a simple static method somewhere.

@@ -49,10 +49,12 @@
public class ExtensionSettings {

private String extensionName;
private String shortExtensionName;
Copy link
Member

Choose a reason for hiding this comment

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

Given that this is used for singleton security setup later, should it be final?

private String hostAddress;
private String hostPort;
private String opensearchAddress;
private String opensearchPort;
private Map<String, String> securitySettings;
Copy link
Member

Choose a reason for hiding this comment

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

Should this also be final?

@@ -194,6 +197,14 @@ protected ExtensionsRunner(Extension extension) throws IOException {
logger.info("SSL is " + sslText + " for transport");
this.settings = settingsBuilder.build();

if (extensionSettings.getShortExtensionName() != null) {
Copy link
Member

Choose a reason for hiding this comment

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

When is it possible for this to be null? It looks like you've required it in all the ExtensionSettings constructors.

Assuming you allow someone to pass null to the constructor, then not including it means the factory is never initialized (assuming we need a factory) which means that all attempts to generate a route name without checking for the initialization will throw an exception.

if (extensionSettings.getShortExtensionName() != null) {
// initialize ExtensionRouteHandlerFactory
ExtensionRouteHandlerFactory factory = ExtensionRouteHandlerFactory.getInstance();
if (!factory.isInitialized()) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm trying to think of a situation where this would ever be initialized before the ExtensionsRunner constructor was called. Possible but someone would have to go out of their way to do it.

Not sure we need a factory at all, and this is just one more reason this solution is overly complex.

if (name.isPresent()) {
registeredPaths.add(restPathToString(method, path, name));
} else {
registeredPaths.add(restPathToString(method, path, name));
Copy link
Member

Choose a reason for hiding this comment

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

The method called in else is exactly the same as the method called in the isPresent() case. Why does this conditional exist?

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed this conditional

* @return A string appending the method and path.
*/
public static String restPathToString(Method method, String path) {
public static String restPathToString(Method method, String path, Optional<String> name) {
Copy link
Member

Choose a reason for hiding this comment

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

Originally this was a more important method but I think it's been reduced to a cosmetic/logging/exception-message method as the rest paths are stored in the pathTrie. So I think the plain space-delimited text may not be useful or may be more confusing than using some other custom output here.

new RouteHandler(POST, "/hello", handlePostRequest),
new RouteHandler(PUT, "/hello/{name}", handlePutRequest),
new RouteHandler(DELETE, "/goodbye", handleDeleteRequest)
new ExtensionRouteHandler("greet", GET, "/hello", handleGetRequest),
Copy link
Member

Choose a reason for hiding this comment

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

This appears to be the only case where we are actually using the new ExtensionRouteHandler class.

This method signature is an override that only exists if we are extending BaseExtensionRestHandler so it seems to me we could add all this logic in that superclass. The BaseExtensionRestHandler class could fetch the shortname from the settings and then just expose a protected routePrefix(String name) method that returns "shortname:name". Then this line would just be new RouteHandler(routePrefix("greet"), GET, ...) and you'd save a ton of intermediate objects and initialization checking, etc.

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks
Copy link
Member Author

cwperks commented Jun 14, 2023

@dbwiddis I removed the ExtensionRouteHandler and ExtensionRouteHandlerFactory in favor of your suggestion of including the routePrefix static method in the BaseExtensionRestHandler.

@cwperks
Copy link
Member Author

cwperks commented Jul 5, 2023

Closing in favor of #827

@cwperks cwperks closed this Jul 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants