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

Servlerless API protection with annotations #93607

Conversation

masseyke
Copy link
Member

@masseyke masseyke commented Feb 8, 2023

This PR makes it so that only REST endpoints that are explicitly annotated with a ServerlessScope are visible when running in serverless mode. Each RestHandler can be given an annotation of either Scope.PUBLIC or Scope.INTERNAL. PUBLIC means that it is visible to everyone. INTERNAL means that it is meant to be visible only to the control plane, and requires a X-elastic-internal-origin HTTP header. No annotation means that the RestHandler is not visible at all in Serverless mode.
For now, this functionality is only enabled if the serverless.enabled node setting is set to true (false by default), but will be configured instead with the stateless.enabled node setting in the future. It is a separate setting in this initial commit because in this commit only two endpoints are enabled, which would make running stateless in a meaningful way impossible.
Also note that this PR only annotates / and /favicon.ico. Follow-up PRs will correctly annotate other endpoints.

@masseyke masseyke added >enhancement :Data Management/Indices APIs APIs to create and manage indices and templates v8.8.0 labels Feb 8, 2023
@elasticsearchmachine
Copy link
Collaborator

Hi @masseyke, I've created a changelog YAML for you.

…ub.com:masseyke/elasticsearch into feature/servlerless-api-protection-annotations
Copy link
Contributor

@tvernum tvernum left a comment

Choose a reason for hiding this comment

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

I left a few comments.
Only 1 of them is really meaningful in terms of "what do we learn from the proof of concept", point of view.

public enum Access {
PUBLIC,
INTERNAL,
HIDDEN
Copy link
Contributor

Choose a reason for hiding this comment

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

Without wanting to bikeshed a Proof of Concept, I think we had some ambiguity in meaning for HIDDEN - some people thought it was equivalent to "internal" (that is, hidden but still exists) but here it means "unavailable" (hidden and cannot be reached).

Could we use UNAVAILABLE / DISABLED / BLOCKED instead?

Copy link
Member

Choose a reason for hiding this comment

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

Do we need a name for it here at all? I think we agreed the default state should be blocking handlers, and that simply omitting the annotation would suffice for that? This way we also don't need any gradle magic to ensure all handlers have the annotation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah getting rid of it makes sense (it's not used in the logic at all). I had thought of it as an explicit marker that someone had already looked at this rest handler and decided that it ought to be hidden (vs one that someone just hadn't looked at yet).

Copy link
Contributor

Choose a reason for hiding this comment

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

I have a general preference for explict, but I really don't want two ways to say the same thing (no annotation, or an annotation with "UNAVAILABLE"), so I'm good with dropping it from the enum.

@@ -779,4 +801,13 @@ private static CircuitBreaker inFlightRequestsBreaker(CircuitBreakerService circ
// We always obtain a fresh breaker to reflect changes to the breaker configuration.
return circuitBreakerService.getBreaker(CircuitBreaker.IN_FLIGHT_REQUESTS);
}

@AccessLevel(Access.PUBLIC)
Copy link
Contributor

Choose a reason for hiding this comment

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

My concern with this Annotation based approach is that it only means something if rest.enforce_api_protections is enabled, which it won't be by default.
I think that is going to be confusing to people who read the code without that (relatively esoteric) piece of information.

When we spoke @rjernst suggested we name the annotation @ServerlessAccessLevel or something like that. That goes a long way to solving the confusion, but leaks "serverless" into the code, where it isn't actually needed.
I'd love to find a way to minimize the confusion that doesn't require us to inject "serverless" into the annotation name.

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 have not thought of a way around this. Is this a showstopper for doing this in the code (vs an external list)? Regardless of what we do in the code, it's going to be meaningless outside of Serverless.

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 not that concerned about leaking "serverless". It's a known thing, and it's a conscious choice. This is specifically for serverless, so calling it another name would be more confusing IMO.

@@ -80,6 +80,10 @@ public boolean allowSystemIndexAccessByDefault() {
return restHandler.allowSystemIndexAccessByDefault();
}

public RestHandler getRootRestHandler() {
return restHandler;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be

Suggested change
return restHandler;
return restHandler.getRootRestHandler();

in case we have Security wrapper around a Deprecation handler.

@@ -95,6 +95,11 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
handler.handleRequest(request, channel, client);
}

@Override
public RestHandler getRootRestHandler() {
return handler;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return handler;
return handler.getRootRestHandler();

import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLevel {
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think we agreed the name should be clearly for serverless, something like ServerlessScope?

@masseyke
Copy link
Member Author

@elasticmachine update branch

@masseyke masseyke marked this pull request as ready for review February 14, 2023 17:04
@elasticsearchmachine elasticsearchmachine added the Team:Data Management Meta label for data/management team label Feb 14, 2023
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-data-management (Team:Data Management)

@masseyke masseyke requested a review from rjernst February 16, 2023 14:56
Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

Looks pretty good, I have a couple nits

catActions.add((AbstractCatAction) handler);
}
restController.registerHandler(handler);
} // else there's no way this handler can be reached, so no point in keeping it around
Copy link
Member

Choose a reason for hiding this comment

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

What about having a dummy handler that returns a nicer error? That is, an error indicating this api is not available in serverless, instead of just "not found"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we want users to know that it's an API that they cannot access? I had been thinking we'd want to make it look as if the API just doesn't exist.

Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't phrase it as "can't access", but rather it is unavailable in fully managed Elasticsearch. We want it to not exist, but having a more clear error message may help guide users. For example, if a user is looking at documentation for self managed Elasticsearch, and tries to follow an a example, getting a 404 (that they might have typo'd) could be frustrating. If instead they get a message like "this API is not available in fully managed Elasticsearch" it hints to them they need to check what they are doing (looking at self managed docs instead of fully managed docs).

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright the response now says something like uri [/_cat/health] with method [GET] exists but is not available when running in serverless mode.

} else if (Scope.PUBLIC.equals(scope) == false) {
handleBadRequest(request.uri(), request.method(), responseChannel);
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps my suggestion about a better error message could be handled with a static dummy handler (that has no scope), and then an else case here which returns the nicer error response?

@@ -24,6 +26,7 @@
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.HEAD;

@ServerlessScope(Scope.PUBLIC)
public class RestMainAction extends BaseRestHandler {
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 sure the main action is in scope for serverless? In its current form it exposes a bunch of information that will not make sense in serverless: cluster name, cluster uuid, build information (which includes the self managed version number). Maybe this should be internal to start?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh hmm good point. I'll change it to internal.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -953,6 +967,139 @@ public void testRegisterWithReservedPath() {
}
}

public void testApiProtection() {
{
Copy link
Member

Choose a reason for hiding this comment

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

Can these be separate test methods?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

elasticsearchmachine pushed a commit that referenced this pull request Mar 31, 2023
…EST handlers (#94034)

#93607 added the ability to run Elasticsearch in "Serverless" mode,
where access to REST endpoints could be restricted so that the full
Elasticsearch API is not available (since a lot of it does not make
sense in Servlerless). By default no endpoints are available, but they
can be exposed with `ServerlessScope` annotations. This PR follows up on
#93607 by adding PUBLIC and INTERNAL annotations to the rest handlers
owned by the Distributed team. There are several rest endpoints still
under discussion. This PR does not label those, so they remain
unavailable in Serverless mode.
tvernum added a commit to tvernum/elasticsearch that referenced this pull request Apr 13, 2023
This commit enforces that the "X-elastic-internal-origin" header may
only be provided by users who have been marked as "operators".

Relates: elastic#93607
droberts195 added a commit to droberts195/elasticsearch that referenced this pull request Apr 14, 2023
…ransform REST handlers

elastic#93607 added the ability to run Elasticsearch in "Serverless" mode, where
access to REST endpoints could be restricted so that the full Elasticsearch
API is not available (since a lot of it does not make sense in Serverless).
By default no endpoints are available, but they can be exposed with
`ServerlessScope` annotations.

This PR follows up by annotating the transform REST handlers.
droberts195 added a commit that referenced this pull request Apr 17, 2023
…handlers (#95253)

#93607 added the ability to run Elasticsearch in "Serverless" mode, where
access to REST endpoints could be restricted so that the full Elasticsearch
API is not available (since a lot of it does not make sense in Serverless).
By default no endpoints are available, but they can be exposed with
`ServerlessScope` annotations.

This PR follows up by annotating the ML REST handlers.
droberts195 added a commit that referenced this pull request Apr 17, 2023
…ransform REST handlers (#95254)

#93607 added the ability to run Elasticsearch in "Serverless" mode, where
access to REST endpoints could be restricted so that the full Elasticsearch
API is not available (since a lot of it does not make sense in Serverless).
By default no endpoints are available, but they can be exposed with
`ServerlessScope` annotations.

This PR follows up by annotating the transform REST handlers.
elasticsearchmachine pushed a commit that referenced this pull request Apr 18, 2023
…ST handlers (#94037)

#93607 added the ability to run Elasticsearch in "Serverless" mode,
where access to REST endpoints could be restricted so that the full
Elasticsearch API is not available (since a lot of it does not make
sense in Servlerless). By default no endpoints are available, but they
can be exposed with `ServerlessScope` annotations. This PR follows up on
#93607 by adding PUBLIC and INTERNAL annotations to the rest handlers
owned by the Core Infra team. There are several rest endpoints still
under discussion. This PR does not label those, so they remain
unavailable in Serverless mode.
jakelandis added a commit that referenced this pull request Jun 22, 2023
A special header "X-elastic-internal-origin" was introduced to help facilitate testing of serverless 
scopes introduced in #93607. The use of this header has been superseded and as such this 
special header is no longer needed. This commit removes reference to the header and adjusts 
the tests accordingly. Internal vs. Public scopes are now controlled by operator privileges when 
running in serverless mode. This commit also changes the response from 400 to 404 if the 
requested path is not available. This matches the 404 response code returned by operator 
privs if internal scopes are not reachable.
tvernum added a commit to tvernum/elasticsearch that referenced this pull request Jun 26, 2023
This commit changes the method for enabling Serverless API protections
(see elastic#93607) from a system property to a runtime property.

Within this project there is no external way to configure this
property - it must be controlled by a plugin - but the RestController
has been changed to dynamically adapt to a change in that property.
tvernum added a commit that referenced this pull request Jun 28, 2023
This commit changes the method for enabling Serverless API protections
(see #93607) from a system property to a runtime property.

Within this project there is no external way to configure this
property - it must be controlled by a plugin - but the RestController
has been changed to dynamically adapt to a change in that property.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) :Data Management/Indices APIs APIs to create and manage indices and templates >enhancement Team:Data Management Meta label for data/management team v8.8.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants