Skip to content

Return a 410 (Gone) status code for unavailable API endpoints #97397

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

Merged
merged 10 commits into from
Aug 22, 2023
Merged
5 changes: 5 additions & 0 deletions docs/changelog/97397.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 97397
summary: Return a 410 (Gone) status code for unavailable API endpoints
area: Infra/REST API
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.DocumentParsingException;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.ApiNotAvailableException;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchException;
import org.elasticsearch.search.aggregations.MultiBucketConsumerService;
Expand Down Expand Up @@ -1844,7 +1845,8 @@ private enum ElasticsearchExceptionHandle {
ElasticsearchRoleRestrictionException::new,
170,
TransportVersion.V_8_500_016
);
),
API_NOT_AVAILABLE_EXCEPTION(ApiNotAvailableException.class, ApiNotAvailableException::new, 171, TransportVersion.V_8_500_065);

final Class<? extends ElasticsearchException> exceptionClass;
final CheckedFunction<StreamInput, ? extends ElasticsearchException, IOException> constructor;
Expand Down
3 changes: 2 additions & 1 deletion server/src/main/java/org/elasticsearch/TransportVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ private static TransportVersion registerTransportVersion(int id, String uniqueId
public static final TransportVersion V_8_500_062 = registerTransportVersion(8_500_062, "09CD9C9B-3207-4B40-8756-B7A12001A885");
public static final TransportVersion V_8_500_063 = registerTransportVersion(8_500_063, "31dedced-0055-4f34-b952-2f6919be7488");
public static final TransportVersion V_8_500_064 = registerTransportVersion(8_500_064, "3a795175-5e6f-40ff-90fe-5571ea8ab04e");
public static final TransportVersion V_8_500_065 = registerTransportVersion(8_500_065, "4e253c58-1b3d-11ee-be56-0242ac120002");

/*
* STOP! READ THIS FIRST! No, really,
Expand All @@ -201,7 +202,7 @@ private static TransportVersion registerTransportVersion(int id, String uniqueId
*/

private static class CurrentHolder {
private static final TransportVersion CURRENT = findCurrent(V_8_500_064);
private static final TransportVersion CURRENT = findCurrent(V_8_500_065);

// finds the pluggable current version, or uses the given fallback
private static TransportVersion findCurrent(TransportVersion fallback) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.rest;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;

import java.io.IOException;

import static org.elasticsearch.rest.RestStatus.GONE;

/**
* Thrown when an API is not available in the current environment.
*/
public class ApiNotAvailableException extends ElasticsearchException {

public ApiNotAvailableException(String msg, Object... args) {
super(msg, args);
}

public ApiNotAvailableException(StreamInput in) throws IOException {
super(in);
}

@Override
public RestStatus status() {
return GONE;
}
}
14 changes: 2 additions & 12 deletions server/src/main/java/org/elasticsearch/rest/RestController.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR;
import static org.elasticsearch.rest.RestStatus.METHOD_NOT_ALLOWED;
import static org.elasticsearch.rest.RestStatus.NOT_ACCEPTABLE;
import static org.elasticsearch.rest.RestStatus.NOT_FOUND;
import static org.elasticsearch.rest.RestStatus.OK;

public class RestController implements HttpServerTransport.Dispatcher {
Expand Down Expand Up @@ -664,17 +663,8 @@ public static void handleBadRequest(String uri, RestRequest.Method method, RestC

public static void handleServerlessRequestToProtectedResource(String uri, RestRequest.Method method, RestChannel channel)
throws IOException {
try (XContentBuilder builder = channel.newErrorBuilder()) {
builder.startObject();
{
builder.field(
"error",
"uri [" + uri + "] with method [" + method + "] exists but is not available when running in " + "serverless mode"
);
}
builder.endObject();
channel.sendResponse(new RestResponse(NOT_FOUND, builder));
}
String msg = "uri [" + uri + "] with method [" + method + "] exists but is not available when running in serverless mode";
channel.sendResponse(new RestResponse(channel, new ApiNotAvailableException(msg)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.elasticsearch.ingest.IngestProcessorException;
import org.elasticsearch.repositories.RepositoryConflictException;
import org.elasticsearch.repositories.RepositoryException;
import org.elasticsearch.rest.ApiNotAvailableException;
import org.elasticsearch.rest.RestResponseTests;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException;
Expand Down Expand Up @@ -830,6 +831,7 @@ public void testIds() {
ids.put(168, DocumentParsingException.class);
ids.put(169, HttpHeadersValidationException.class);
ids.put(170, ElasticsearchRoleRestrictionException.class);
ids.put(171, ApiNotAvailableException.class);

Map<Class<? extends ElasticsearchException>, Integer> reverse = new HashMap<>();
for (Map.Entry<Integer, Class<? extends ElasticsearchException>> entry : ids.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.util.MockPageCacheRecycler;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.RestApiVersion;
import org.elasticsearch.http.HttpHeadersValidationException;
Expand Down Expand Up @@ -939,8 +940,17 @@ public void testApiProtectionWithServerlessEnabledAsEndUser() {
});
final Consumer<List<String>> checkProtected = paths -> paths.forEach(path -> {
RestRequest request = new FakeRestRequest.Builder(xContentRegistry()).withPath(path).build();
AssertingChannel channel = new AssertingChannel(request, false, RestStatus.NOT_FOUND);
AssertingChannel channel = new AssertingChannel(request, true, RestStatus.GONE);
restController.dispatchRequest(request, channel, new ThreadContext(Settings.EMPTY));

RestResponse restResponse = channel.getRestResponse();
Map<String, Object> map = XContentHelper.convertToMap(restResponse.content(), false, XContentType.JSON).v2();
assertEquals(410, map.get("status"));
@SuppressWarnings("unchecked")
Map<String, Object> error = (Map<String, Object>) map.get("error");
assertEquals("api_not_available_exception", error.get("type"));
assertTrue(error.get("reason").toString().contains("not available when running in serverless mode"));

});

List<String> accessiblePaths = List.of("/public", "/internal");
Expand Down