fix: Better support for disallowed hosts (#16842)
This commit is contained in:
parent
bd5d574ab6
commit
ac01687557
|
|
@ -35,10 +35,8 @@ import reactor.netty.resources.ConnectionProvider;
|
|||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
|
@ -46,7 +44,6 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.appsmith.external.helpers.restApiUtils.helpers.URIUtils.DISALLOWED_HOSTS;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||
|
||||
@NoArgsConstructor
|
||||
|
|
@ -204,14 +201,10 @@ public class TriggerUtils {
|
|||
* It redirects to partial URI : /api/character/
|
||||
* In this scenario we should convert the partial URI to complete URI
|
||||
*/
|
||||
URI redirectUri;
|
||||
final URI redirectUri;
|
||||
try {
|
||||
redirectUri = new URI(redirectUrl);
|
||||
if (DISALLOWED_HOSTS.contains(redirectUri.getHost())
|
||||
|| 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) {
|
||||
} catch (URISyntaxException 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.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.Property;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.collections.CollectionUtils.isEmpty;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||
|
||||
@NoArgsConstructor
|
||||
public class URIUtils {
|
||||
public static final Set<String> DISALLOWED_HOSTS = Set.of(
|
||||
"169.254.169.254",
|
||||
"metadata.google.internal"
|
||||
);
|
||||
|
||||
public URI createUriWithQueryParams(ActionConfiguration actionConfiguration,
|
||||
DatasourceConfiguration datasourceConfiguration, String url,
|
||||
|
|
@ -53,7 +44,7 @@ public class URIUtils {
|
|||
for (Property queryParam : allQueryParams) {
|
||||
String key = queryParam.getKey();
|
||||
if (isNotEmpty(key)) {
|
||||
if (encodeParamsToggle == true) {
|
||||
if (encodeParamsToggle) {
|
||||
uriBuilder.queryParam(
|
||||
URLEncoder.encode(key, StandardCharsets.UTF_8),
|
||||
URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8)
|
||||
|
|
@ -78,9 +69,4 @@ public class URIUtils {
|
|||
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;
|
||||
|
||||
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.web.reactive.function.client.WebClient;
|
||||
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 {
|
||||
|
||||
private static final Set<String> DISALLOWED_HOSTS = Set.of(
|
||||
"169.254.169.254",
|
||||
"metadata.google.internal"
|
||||
);
|
||||
|
||||
private WebClientUtils() {
|
||||
}
|
||||
|
||||
|
|
@ -31,15 +51,86 @@ public class WebClientUtils {
|
|||
|
||||
public static WebClient.Builder builder(HttpClient httpClient) {
|
||||
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()) {
|
||||
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.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
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.setValueSafelyInPropertyList;
|
||||
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.convertToGraphQLPOSTBodyFormat;
|
||||
import static com.external.utils.GraphQLBodyUtils.getGraphQLQueryParamsForBodyAndVariables;
|
||||
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 org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
|
|
@ -186,18 +185,6 @@ public class GraphQLPlugin extends BasePlugin {
|
|||
ActionExecutionRequest actionExecutionRequest =
|
||||
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,
|
||||
datasourceConfiguration);
|
||||
|
||||
|
|
@ -282,7 +269,7 @@ public class GraphQLPlugin extends BasePlugin {
|
|||
EXCHANGE_STRATEGIES, requestCaptureFilter);
|
||||
|
||||
/* 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,
|
||||
objectMapper,
|
||||
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.helpers.DataTypeStringUtils;
|
||||
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.ActionExecutionRequest;
|
||||
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.BaseRestApiPluginExecutor;
|
||||
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 org.pf4j.Extension;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -125,7 +125,7 @@ public class RestApiPlugin extends BasePlugin {
|
|||
initUtils.initializeResponseWithError(errorResult);
|
||||
|
||||
// Set of hint messages that can be returned to the user.
|
||||
Set<String> hintMessages = new HashSet();
|
||||
Set<String> hintMessages = new HashSet<>();
|
||||
|
||||
// Initializing request URL
|
||||
String url = initUtils.initializeRequestUrl(actionConfiguration, datasourceConfiguration);
|
||||
|
|
@ -148,18 +148,6 @@ public class RestApiPlugin extends BasePlugin {
|
|||
ActionExecutionRequest actionExecutionRequest =
|
||||
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,
|
||||
datasourceConfiguration);
|
||||
String reqContentType = headerUtils.getRequestContentType(actionConfiguration, datasourceConfiguration);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user