fix: Better support for disallowed hosts (#16842)
This commit is contained in:
parent
e3f90db341
commit
769719ccfe
|
|
@ -35,10 +35,8 @@ import reactor.netty.resources.ConnectionProvider;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
@ -46,7 +44,6 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.appsmith.external.helpers.restApiUtils.helpers.URIUtils.DISALLOWED_HOSTS;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
|
@ -204,14 +201,10 @@ public class TriggerUtils {
|
||||||
* It redirects to partial URI : /api/character/
|
* It redirects to partial URI : /api/character/
|
||||||
* In this scenario we should convert the partial URI to complete URI
|
* In this scenario we should convert the partial URI to complete URI
|
||||||
*/
|
*/
|
||||||
URI redirectUri;
|
final URI redirectUri;
|
||||||
try {
|
try {
|
||||||
redirectUri = new URI(redirectUrl);
|
redirectUri = new URI(redirectUrl);
|
||||||
if (DISALLOWED_HOSTS.contains(redirectUri.getHost())
|
} catch (URISyntaxException e) {
|
||||||
|| DISALLOWED_HOSTS.contains(InetAddress.getByName(redirectUri.getHost()).getHostAddress())) {
|
|
||||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Host not allowed."));
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException | UnknownHostException e) {
|
|
||||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,21 @@ package com.appsmith.external.helpers.restApiUtils.helpers;
|
||||||
import com.appsmith.external.models.ActionConfiguration;
|
import com.appsmith.external.models.ActionConfiguration;
|
||||||
import com.appsmith.external.models.DatasourceConfiguration;
|
import com.appsmith.external.models.DatasourceConfiguration;
|
||||||
import com.appsmith.external.models.Property;
|
import com.appsmith.external.models.Property;
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.apache.commons.collections.CollectionUtils.isEmpty;
|
import static org.apache.commons.collections.CollectionUtils.isEmpty;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class URIUtils {
|
public class URIUtils {
|
||||||
public static final Set<String> DISALLOWED_HOSTS = Set.of(
|
|
||||||
"169.254.169.254",
|
|
||||||
"metadata.google.internal"
|
|
||||||
);
|
|
||||||
|
|
||||||
public URI createUriWithQueryParams(ActionConfiguration actionConfiguration,
|
public URI createUriWithQueryParams(ActionConfiguration actionConfiguration,
|
||||||
DatasourceConfiguration datasourceConfiguration, String url,
|
DatasourceConfiguration datasourceConfiguration, String url,
|
||||||
|
|
@ -53,7 +44,7 @@ public class URIUtils {
|
||||||
for (Property queryParam : allQueryParams) {
|
for (Property queryParam : allQueryParams) {
|
||||||
String key = queryParam.getKey();
|
String key = queryParam.getKey();
|
||||||
if (isNotEmpty(key)) {
|
if (isNotEmpty(key)) {
|
||||||
if (encodeParamsToggle == true) {
|
if (encodeParamsToggle) {
|
||||||
uriBuilder.queryParam(
|
uriBuilder.queryParam(
|
||||||
URLEncoder.encode(key, StandardCharsets.UTF_8),
|
URLEncoder.encode(key, StandardCharsets.UTF_8),
|
||||||
URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8)
|
URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8)
|
||||||
|
|
@ -78,9 +69,4 @@ public class URIUtils {
|
||||||
return "http://" + url;
|
return "http://" + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHostDisallowed(URI uri) throws UnknownHostException {
|
|
||||||
String host = uri.getHost();
|
|
||||||
return StringUtils.isEmpty(host) || DISALLOWED_HOSTS.contains(host)
|
|
||||||
|| DISALLOWED_HOSTS.contains(InetAddress.getByName(host).getHostAddress());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,31 @@
|
||||||
package com.appsmith.util;
|
package com.appsmith.util;
|
||||||
|
|
||||||
|
import io.netty.resolver.AddressResolver;
|
||||||
|
import io.netty.resolver.AddressResolverGroup;
|
||||||
|
import io.netty.resolver.InetNameResolver;
|
||||||
|
import io.netty.resolver.InetSocketAddressResolver;
|
||||||
|
import io.netty.util.concurrent.EventExecutor;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import io.netty.util.internal.SocketUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import reactor.netty.http.client.HttpClient;
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class WebClientUtils {
|
public class WebClientUtils {
|
||||||
|
|
||||||
|
private static final Set<String> DISALLOWED_HOSTS = Set.of(
|
||||||
|
"169.254.169.254",
|
||||||
|
"metadata.google.internal"
|
||||||
|
);
|
||||||
|
|
||||||
private WebClientUtils() {
|
private WebClientUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,15 +51,86 @@ public class WebClientUtils {
|
||||||
|
|
||||||
public static WebClient.Builder builder(HttpClient httpClient) {
|
public static WebClient.Builder builder(HttpClient httpClient) {
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
.clientConnector(new ReactorClientHttpConnector(applyProxyIfConfigured(httpClient)));
|
.clientConnector(new ReactorClientHttpConnector(makeSafeHttpClient(httpClient)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpClient applyProxyIfConfigured(HttpClient httpClient) {
|
private static HttpClient makeSafeHttpClient(HttpClient httpClient) {
|
||||||
if (shouldUseSystemProxy()) {
|
if (shouldUseSystemProxy()) {
|
||||||
httpClient = httpClient.proxyWithSystemProperties();
|
httpClient = httpClient.proxyWithSystemProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpClient;
|
return httpClient.resolver(ResolverGroup.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ResolverGroup extends AddressResolverGroup<InetSocketAddress> {
|
||||||
|
public static final ResolverGroup INSTANCE = new ResolverGroup();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) {
|
||||||
|
return new InetSocketAddressResolver(executor, new NameResolver(executor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
private static class NameResolver extends InetNameResolver {
|
||||||
|
|
||||||
|
public NameResolver(EventExecutor executor) {
|
||||||
|
super(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDisallowedAndFail(String host, Promise<?> promise) {
|
||||||
|
if (DISALLOWED_HOSTS.contains(host)) {
|
||||||
|
log.warn("Host {} is disallowed. Failing the request.", host);
|
||||||
|
promise.setFailure(new UnknownHostException("Host not allowed."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolve(String inetHost, Promise<InetAddress> promise) {
|
||||||
|
if (isDisallowedAndFail(inetHost, promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final InetAddress address;
|
||||||
|
try {
|
||||||
|
address = SocketUtils.addressByName(inetHost);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
promise.setFailure(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisallowedAndFail(address.getHostAddress(), promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.setSuccess(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise) {
|
||||||
|
if (isDisallowedAndFail(inetHost, promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<InetAddress> addresses;
|
||||||
|
try {
|
||||||
|
addresses = Arrays.asList(SocketUtils.allAddressesByName(inetHost));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
promise.setFailure(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if _one_ of the addresses is disallowed, we fail the request.
|
||||||
|
for (InetAddress address : addresses) {
|
||||||
|
if (isDisallowedAndFail(address.getHostAddress(), promise)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.setSuccess(addresses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -37,12 +36,12 @@ import java.util.Set;
|
||||||
import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromPropertyList;
|
import static com.appsmith.external.helpers.PluginUtils.getValueSafelyFromPropertyList;
|
||||||
import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInPropertyList;
|
import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInPropertyList;
|
||||||
import static com.external.utils.GraphQLBodyUtils.PAGINATION_DATA_INDEX;
|
import static com.external.utils.GraphQLBodyUtils.PAGINATION_DATA_INDEX;
|
||||||
import static com.external.utils.GraphQLDataTypeUtils.smartlyReplaceGraphQLQueryBodyPlaceholderWithValue;
|
|
||||||
import static com.external.utils.GraphQLPaginationUtils.updateVariablesWithPaginationValues;
|
|
||||||
import static com.external.utils.GraphQLBodyUtils.QUERY_VARIABLES_INDEX;
|
import static com.external.utils.GraphQLBodyUtils.QUERY_VARIABLES_INDEX;
|
||||||
import static com.external.utils.GraphQLBodyUtils.convertToGraphQLPOSTBodyFormat;
|
import static com.external.utils.GraphQLBodyUtils.convertToGraphQLPOSTBodyFormat;
|
||||||
import static com.external.utils.GraphQLBodyUtils.getGraphQLQueryParamsForBodyAndVariables;
|
import static com.external.utils.GraphQLBodyUtils.getGraphQLQueryParamsForBodyAndVariables;
|
||||||
import static com.external.utils.GraphQLBodyUtils.validateBodyAndVariablesSyntax;
|
import static com.external.utils.GraphQLBodyUtils.validateBodyAndVariablesSyntax;
|
||||||
|
import static com.external.utils.GraphQLDataTypeUtils.smartlyReplaceGraphQLQueryBodyPlaceholderWithValue;
|
||||||
|
import static com.external.utils.GraphQLPaginationUtils.updateVariablesWithPaginationValues;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
|
@ -186,18 +185,6 @@ public class GraphQLPlugin extends BasePlugin {
|
||||||
ActionExecutionRequest actionExecutionRequest =
|
ActionExecutionRequest actionExecutionRequest =
|
||||||
RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);
|
RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);
|
||||||
|
|
||||||
try {
|
|
||||||
if (uriUtils.isHostDisallowed(uri)) {
|
|
||||||
errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed."));
|
|
||||||
errorResult.setRequest(actionExecutionRequest);
|
|
||||||
return Mono.just(errorResult);
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host."));
|
|
||||||
errorResult.setRequest(actionExecutionRequest);
|
|
||||||
return Mono.just(errorResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
|
WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
|
||||||
datasourceConfiguration);
|
datasourceConfiguration);
|
||||||
|
|
||||||
|
|
@ -282,7 +269,7 @@ public class GraphQLPlugin extends BasePlugin {
|
||||||
EXCHANGE_STRATEGIES, requestCaptureFilter);
|
EXCHANGE_STRATEGIES, requestCaptureFilter);
|
||||||
|
|
||||||
/* Triggering the actual REST API call */
|
/* Triggering the actual REST API call */
|
||||||
Set<String> hintMessages = new HashSet<String>();
|
Set<String> hintMessages = new HashSet<>();
|
||||||
return triggerUtils.triggerApiCall(client, httpMethod, uri, requestBodyObj, actionExecutionRequest,
|
return triggerUtils.triggerApiCall(client, httpMethod, uri, requestBodyObj, actionExecutionRequest,
|
||||||
objectMapper,
|
objectMapper,
|
||||||
hintMessages, errorResult, requestCaptureFilter);
|
hintMessages, errorResult, requestCaptureFilter);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||||
import com.appsmith.external.helpers.DataTypeStringUtils;
|
import com.appsmith.external.helpers.DataTypeStringUtils;
|
||||||
import com.appsmith.external.helpers.MustacheHelper;
|
import com.appsmith.external.helpers.MustacheHelper;
|
||||||
|
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
|
||||||
|
import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter;
|
||||||
import com.appsmith.external.models.ActionConfiguration;
|
import com.appsmith.external.models.ActionConfiguration;
|
||||||
import com.appsmith.external.models.ActionExecutionRequest;
|
import com.appsmith.external.models.ActionExecutionRequest;
|
||||||
import com.appsmith.external.models.ActionExecutionResult;
|
import com.appsmith.external.models.ActionExecutionResult;
|
||||||
|
|
@ -15,18 +17,16 @@ import com.appsmith.external.models.Property;
|
||||||
import com.appsmith.external.plugins.BasePlugin;
|
import com.appsmith.external.plugins.BasePlugin;
|
||||||
import com.appsmith.external.plugins.BaseRestApiPluginExecutor;
|
import com.appsmith.external.plugins.BaseRestApiPluginExecutor;
|
||||||
import com.appsmith.external.services.SharedConfig;
|
import com.appsmith.external.services.SharedConfig;
|
||||||
import com.appsmith.external.helpers.restApiUtils.connections.APIConnection;
|
|
||||||
import com.appsmith.external.helpers.restApiUtils.helpers.RequestCaptureFilter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.Extension;
|
import org.pf4j.Extension;
|
||||||
import org.pf4j.PluginWrapper;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
@ -125,7 +125,7 @@ public class RestApiPlugin extends BasePlugin {
|
||||||
initUtils.initializeResponseWithError(errorResult);
|
initUtils.initializeResponseWithError(errorResult);
|
||||||
|
|
||||||
// Set of hint messages that can be returned to the user.
|
// Set of hint messages that can be returned to the user.
|
||||||
Set<String> hintMessages = new HashSet();
|
Set<String> hintMessages = new HashSet<>();
|
||||||
|
|
||||||
// Initializing request URL
|
// Initializing request URL
|
||||||
String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration);
|
String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration);
|
||||||
|
|
@ -148,18 +148,6 @@ public class RestApiPlugin extends BasePlugin {
|
||||||
ActionExecutionRequest actionExecutionRequest =
|
ActionExecutionRequest actionExecutionRequest =
|
||||||
RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);
|
RequestCaptureFilter.populateRequestFields(actionConfiguration, uri, insertedParams, objectMapper);
|
||||||
|
|
||||||
try {
|
|
||||||
if (uriUtils.isHostDisallowed(uri)) {
|
|
||||||
errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Host not allowed."));
|
|
||||||
errorResult.setRequest(actionExecutionRequest);
|
|
||||||
return Mono.just(errorResult);
|
|
||||||
}
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
errorResult.setBody(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR.getMessage("Unknown host."));
|
|
||||||
errorResult.setRequest(actionExecutionRequest);
|
|
||||||
return Mono.just(errorResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
|
WebClient.Builder webClientBuilder = triggerUtils.getWebClientBuilder(actionConfiguration,
|
||||||
datasourceConfiguration);
|
datasourceConfiguration);
|
||||||
String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration);
|
String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user