-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Servlerless API protection with annotations #93607
Conversation
Hi @masseyke, I've created a changelog YAML for you. |
…ub.com:masseyke/elasticsearch into feature/servlerless-api-protection-annotations
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 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 |
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.
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?
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.
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.
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.
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).
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 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) |
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.
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.
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 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.
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'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; |
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 should be
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; |
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.
return handler; | |
return handler.getRootRestHandler(); |
import java.lang.annotation.RetentionPolicy; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface AccessLevel { |
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.
nit: I think we agreed the name should be clearly for serverless, something like ServerlessScope?
@elasticmachine update branch |
Pinging @elastic/es-data-management (Team:Data Management) |
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.
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 |
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.
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"?
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.
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.
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 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).
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.
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; | ||
} |
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.
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 { |
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.
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?
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.
Oh hmm good point. I'll change it to internal.
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.
Done.
@@ -953,6 +967,139 @@ public void testRegisterWithReservedPath() { | |||
} | |||
} | |||
|
|||
public void testApiProtection() { | |||
{ |
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 these be separate test methods?
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.
Done.
…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.
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
…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.
…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.
…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.
…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.
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.
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.
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.
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 totrue
(false
by default), but will be configured instead with thestateless.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.