Skip to content

Commit

Permalink
feat: Added SSH Tunnel for Postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
AnnaHariprasad5123 committed Aug 12, 2024
1 parent 88111ac commit bff288d
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.helpers.DataTypeServiceUtils;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.helpers.SSHUtils;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.ConnectionContext;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceStructure;
Expand All @@ -22,6 +24,7 @@
import com.appsmith.external.models.Property;
import com.appsmith.external.models.PsParameterDTO;
import com.appsmith.external.models.RequestParamDTO;
import com.appsmith.external.models.SSHConnection;
import com.appsmith.external.models.SSLDetails;
import com.appsmith.external.plugins.BasePlugin;
import com.appsmith.external.plugins.PluginExecutor;
Expand Down Expand Up @@ -83,10 +86,17 @@
import java.util.stream.Stream;

import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY;
import static com.appsmith.external.constants.PluginConstants.HostName.LOCALHOST;
import static com.appsmith.external.constants.PluginConstants.PluginName.POSTGRES_PLUGIN_NAME;
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.DS_INVALID_SSH_HOSTNAME_ERROR_MSG;
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.DS_MISSING_SSH_HOSTNAME_ERROR_MSG;
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.DS_MISSING_SSH_KEY_ERROR_MSG;
import static com.appsmith.external.exceptions.pluginExceptions.BasePluginErrorMessages.DS_MISSING_SSH_USERNAME_ERROR_MSG;
import static com.appsmith.external.helpers.PluginUtils.getColumnsListForJdbcPlugin;
import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns;
import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel;
import static com.appsmith.external.helpers.SSHUtils.getConnectionContext;
import static com.appsmith.external.helpers.SSHUtils.isSSHEnabled;
import static com.appsmith.external.helpers.Sizeof.sizeof;
import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex;
import static com.external.plugins.utils.PostgresDataTypeUtils.DataType.BOOL;
Expand Down Expand Up @@ -135,6 +145,8 @@ public class PostgresPlugin extends BasePlugin {

private static int MAX_SIZE_SUPPORTED;

private static final int CONNECTION_METHOD_INDEX = 1;

public static PostgresDatasourceUtils postgresDatasourceUtils = new PostgresDatasourceUtils();

public PostgresPlugin(PluginWrapper wrapper) {
Expand Down Expand Up @@ -297,6 +309,7 @@ public ActionConfiguration getSchemaPreviewActionConfig(Template queryTemplate,
@Override
public Mono<String> getEndpointIdentifierForRateLimit(DatasourceConfiguration datasourceConfiguration) {
List<Endpoint> endpoints = datasourceConfiguration.getEndpoints();
SSHConnection sshProxy = datasourceConfiguration.getSshProxy();
String identifier = "";
// When hostname and port both are available, both will be used as identifier
// When port is not present, default port along with hostname will be used
Expand All @@ -308,6 +321,12 @@ public Mono<String> getEndpointIdentifierForRateLimit(DatasourceConfiguration da
identifier = hostName + "_" + ObjectUtils.defaultIfNull(port, DEFAULT_POSTGRES_PORT);
}
}
if (SSHUtils.isSSHEnabled(datasourceConfiguration, CONNECTION_METHOD_INDEX)
&& sshProxy != null
&& !isBlank(sshProxy.getHost())) {
identifier += "_" + sshProxy.getHost() + "_"
+ SSHUtils.getSSHPortFromConfigOrDefault(datasourceConfiguration);
}
return Mono.just(identifier);
}

Expand Down Expand Up @@ -700,6 +719,36 @@ public Set<String> validateDatasource(DatasourceConfiguration datasourceConfigur
invalids.add(PostgresErrorMessages.SSL_CONFIGURATION_ERROR_MSG);
}

if (isSSHEnabled(datasourceConfiguration, CONNECTION_METHOD_INDEX)) {
if (datasourceConfiguration.getSshProxy() == null
|| isBlank(datasourceConfiguration.getSshProxy().getHost())) {
invalids.add(DS_MISSING_SSH_HOSTNAME_ERROR_MSG);
} else {
String sshHost = datasourceConfiguration.getSshProxy().getHost();
if (sshHost.contains("/") || sshHost.contains(":")) {
invalids.add(DS_INVALID_SSH_HOSTNAME_ERROR_MSG);
}
}

if (StringUtils.isEmpty(datasourceConfiguration.getSshProxy().getPort())) {
invalids.add(PostgresErrorMessages.DS_MISSING_SSH_PORT_ERROR_MSG);
}

if (isBlank(datasourceConfiguration.getSshProxy().getUsername())) {
invalids.add(DS_MISSING_SSH_USERNAME_ERROR_MSG);
}

if (datasourceConfiguration.getSshProxy().getPrivateKey() == null
|| datasourceConfiguration.getSshProxy().getPrivateKey().getKeyFile() == null
|| isBlank(datasourceConfiguration
.getSshProxy()
.getPrivateKey()
.getKeyFile()
.getBase64Content())) {
invalids.add(DS_MISSING_SSH_KEY_ERROR_MSG);
}
}

return invalids;
}

Expand Down Expand Up @@ -1130,9 +1179,20 @@ private static HikariDataSource createConnectionPool(
// Set up the connection URL
StringBuilder urlBuilder = new StringBuilder("jdbc:postgresql://");

List<String> hosts = datasourceConfiguration.getEndpoints().stream()
.map(endpoint -> endpoint.getHost() + ":" + ObjectUtils.defaultIfNull(endpoint.getPort(), 5432L))
.collect(Collectors.toList());
List<String> hosts = new ArrayList<>();

if (!isSSHEnabled(datasourceConfiguration, CONNECTION_METHOD_INDEX)) {
for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) {
hosts.add(endpoint.getHost() + ":" + endpoint.getPort());
}
} else {
ConnectionContext<HikariDataSource> connectionContext;
connectionContext = getConnectionContext(
datasourceConfiguration, CONNECTION_METHOD_INDEX, DEFAULT_POSTGRES_PORT, HikariDataSource.class);

hosts.add(LOCALHOST + ":"
+ connectionContext.getSshTunnelContext().getServerSocket().getLocalPort());
}

urlBuilder.append(String.join(",", hosts)).append("/");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class PostgresErrorMessages extends BasePluginErrorMessages {

public static final String DS_MISSING_HOSTNAME_ERROR_MSG = "Missing hostname.";

public static final String DS_MISSING_SSH_PORT_ERROR_MSG = "Missing SSH port.";

public static final String DS_INVALID_HOSTNAME_ERROR_MSG =
"Host value cannot contain `/` or `:` characters. Found `%s`.";

Expand Down
155 changes: 137 additions & 18 deletions app/server/appsmith-plugins/postgresPlugin/src/main/resources/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,43 @@
"sectionName": "Connection",
"id": 1,
"children": [
{
"label": "Connection method",
"configProperty": "datasourceConfiguration.properties[1].key",
"initialValue": "Connection method",
"hidden": true,
"controlType": "INPUT_TEXT"
},
{
"label": "Connection method",
"configProperty": "datasourceConfiguration.properties[1].value",
"controlType": "SEGMENTED_CONTROL",
"initialValue": "STANDARD",
"options": [
{
"label": "Standard",
"value": "STANDARD"
},
{
"label": "SSH tunnel",
"value": "SSH"
}
]
},
{
"label": "Connection mode",
"configProperty": "datasourceConfiguration.connection.mode",
"controlType": "SEGMENTED_CONTROL",
"initialValue": "READ_WRITE",
"options": [
{ "label": "Read / Write", "value": "READ_WRITE" },
{ "label": "Read only", "value": "READ_ONLY" }
{
"label": "Read / Write",
"value": "READ_WRITE"
},
{
"label": "Read only",
"value": "READ_ONLY"
}
]
},
{
Expand All @@ -35,6 +64,32 @@
}
]
},
{
"sectionName": null,
"children": [
{
"label": "SSH host address",
"configProperty": "datasourceConfiguration.sshProxy.endpoints[*].host",
"controlType": "KEYVALUE_ARRAY",
"validationMessage": "Please enter a valid host",
"validationRegex": "^((?![/:]).)*$",
"placeholderText": "myapp.abcde.sshHost.net"
},
{
"label": "SSH port",
"configProperty": "datasourceConfiguration.sshProxy.endpoints[*].port",
"dataType": "NUMBER",
"controlType": "KEYVALUE_ARRAY",
"initialValue": ["22"],
"placeholderText": "22"
}
],
"hidden": {
"path": "datasourceConfiguration.properties[1].value",
"comparison": "NOT_EQUALS",
"value": "SSH"
}
},
{
"label": "Database name",
"configProperty": "datasourceConfiguration.authentication.databaseName",
Expand Down Expand Up @@ -64,6 +119,28 @@
"controlType": "INPUT_TEXT",
"placeholderText": "Password",
"encrypted": true
},
{
"label": "SSH username",
"configProperty": "datasourceConfiguration.sshProxy.username",
"controlType": "INPUT_TEXT",
"placeholderText": "Username",
"hidden": {
"path": "datasourceConfiguration.properties[1].value",
"comparison": "NOT_EQUALS",
"value": "SSH"
}
},
{
"label": "SSH key",
"configProperty": "datasourceConfiguration.sshProxy.privateKey.keyFile",
"controlType": "FILE_PICKER",
"encrypted": true,
"hidden": {
"path": "datasourceConfiguration.properties[1].value",
"comparison": "NOT_EQUALS",
"value": "SSH"
}
}
]
}
Expand All @@ -79,13 +156,34 @@
"controlType": "DROP_DOWN",
"initialValue": "DEFAULT",
"options": [
{ "label": "Default", "value": "DEFAULT" },
{ "label": "Allow", "value": "ALLOW" },
{ "label": "Prefer", "value": "PREFER" },
{ "label": "Require", "value": "REQUIRE" },
{ "label": "Disable", "value": "DISABLE" },
{ "label": "Verify CA", "value": "VERIFY_CA" },
{ "label": "Verify Full", "value": "VERIFY_FULL" }
{
"label": "Default",
"value": "DEFAULT"
},
{
"label": "Allow",
"value": "ALLOW"
},
{
"label": "Prefer",
"value": "PREFER"
},
{
"label": "Require",
"value": "REQUIRE"
},
{
"label": "Disable",
"value": "DISABLE"
},
{
"label": "Verify CA",
"value": "VERIFY_CA"
},
{
"label": "Verify Full",
"value": "VERIFY_FULL"
}
]
},
{
Expand All @@ -94,8 +192,14 @@
"controlType": "DROP_DOWN",
"initialValue": "-Select-",
"options": [
{ "label": "Upload File", "value": "FILE" },
{ "label": "Base64 String", "value": "BASE64_STRING" }
{
"label": "Upload File",
"value": "FILE"
},
{
"label": "Base64 String",
"value": "BASE64_STRING"
}
],
"hidden": {
"conditionType": "AND",
Expand All @@ -114,10 +218,15 @@
}
},
{
"sectionStyles": { "display": "flex", "flex-wrap": "wrap" },
"sectionStyles": {
"display": "flex",
"flex-wrap": "wrap"
},
"children": [
{
"sectionStyles": { "marginRight": "10px" },
"sectionStyles": {
"marginRight": "10px"
},
"children": [
{
"label": "Client CA Certificate File",
Expand Down Expand Up @@ -145,7 +254,9 @@
]
},
{
"sectionStyles": { "marginRight": "10px" },
"sectionStyles": {
"marginRight": "10px"
},
"children": [
{
"label": "Server CA Certificate File",
Expand Down Expand Up @@ -173,7 +284,9 @@
]
},
{
"sectionStyles": { "marginRight": "10px" },
"sectionStyles": {
"marginRight": "10px"
},
"children": [
{
"label": "Client Key Certificate File",
Expand Down Expand Up @@ -201,15 +314,21 @@
]
},
{
"sectionStyles": { "flex": 1 },
"sectionStyles": {
"flex": 1
},
"children": []
},
{
"sectionStyles": { "flex": 1 },
"sectionStyles": {
"flex": 1
},
"children": []
},
{
"sectionStyles": { "flex": 1 },
"sectionStyles": {
"flex": 1
},
"children": []
}
],
Expand Down
Loading

0 comments on commit bff288d

Please sign in to comment.