Skip to content

Postgres/MySQL Source Strict Encrypt: stop enforce SSL if ssl mode disabled #19025

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 13 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/JdbcUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class JdbcUtils {
// NOTE: this is the plural version of SCHEMA_KEY
public static final String SCHEMAS_KEY = "schemas";
public static final String SSL_KEY = "ssl";
public static final List<String> SSL_MODE_DISABLE = List.of("disable", "disabled");
public static final String SSL_MODE_KEY = "ssl_mode";
public static final String TLS_KEY = "tls";
public static final String USERNAME_KEY = "username";
Expand Down Expand Up @@ -111,10 +112,17 @@ public static Map<String, String> parseJdbcParameters(final String jdbcPropertie
* (e.g. non-zero integers, string true, etc)
*
* @param config A configuration used to check Jdbc connection
* @return true: if ssl has not been set or it has been set with true, false: in all other cases
* @return true: if ssl has not been set and ssl mode not equals disabled or it has been set with
* true, false: in all other cases
*/
public static boolean useSsl(final JsonNode config) {
return !config.has(SSL_KEY) || config.get(SSL_KEY).asBoolean();
if (!config.has(SSL_KEY)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the javadoc description to update what this method now does so it's not de-synced from what this method originally did?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the javadoc description to update what this method now does so it's not de-synced from what this method originally did?

updated javadoc

if (config.has(SSL_MODE_KEY) && config.get(SSL_MODE_KEY).has(MODE_KEY)) {
return !SSL_MODE_DISABLE.contains(config.get(SSL_MODE_KEY).get(MODE_KEY).asText());
} else
return true;
} else
return config.get(SSL_KEY).asBoolean();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,42 @@ void testUssSslWithSslSetAndValueIntegerTrue() {
assertTrue(sslSet);
}

@Test
void testUseSslWithEmptySslKeyAndSslModeVerifyFull() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are good, let's also add an integration test for both source-mysql-strict-encrypt and source-postgres-strict-encrypt that cover the scenario that was failing for the customer which is when SSH tunnel was set and their ssl_mode: disabled

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are good, let's also add an integration test for both source-mysql-strict-encrypt and source-postgres-strict-encrypt that cover the scenario that was failing for the customer which is when SSH tunnel was set and their ssl_mode: disabled

@ryankfu i added a couple of tests for postgres and mysql in order to cover such a case that the user encountered (when SSH tunnel was set and ssl_mode: disabled)

final JsonNode config = Jsons.jsonNode(ImmutableMap.builder()
.put("host", PSQL_DB.getHost())
.put("port", PSQL_DB.getFirstMappedPort())
.put("database", dbName)
.put("username", PSQL_DB.getUsername())
.put("password", PSQL_DB.getPassword())
.put("ssl_mode", ImmutableMap.builder()
.put("mode", "verify-full")
.put("ca_certificate", "test_ca_cert")
.put("client_certificate", "test_client_cert")
.put("client_key", "test_client_key")
.put("client_key_password", "test_pass")
.build())
.build());
final boolean sslSet = JdbcUtils.useSsl(config);
assertTrue(sslSet);
}

@Test
void testUseSslWithEmptySslKeyAndSslModeDisable() {
final JsonNode config = Jsons.jsonNode(ImmutableMap.builder()
.put("host", PSQL_DB.getHost())
.put("port", PSQL_DB.getFirstMappedPort())
.put("database", dbName)
.put("username", PSQL_DB.getUsername())
.put("password", PSQL_DB.getPassword())
.put("ssl_mode", ImmutableMap.builder()
.put("mode", "disable")
.build())
.build());
final boolean sslSet = JdbcUtils.useSsl(config);
assertFalse(sslSet);
}

private static void createTableWithAllTypes(final Connection connection) throws SQLException {
// jdbctype not included because they are not directly supported in postgres: TINYINT, LONGVARCHAR,
// VARBINAR, LONGVARBINARY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
import io.airbyte.db.factory.DatabaseDriver;
import io.airbyte.db.jdbc.JdbcUtils;
import io.airbyte.integrations.base.Source;
import io.airbyte.integrations.base.ssh.SshBastionContainer;
import io.airbyte.integrations.base.ssh.SshHelpers;
import io.airbyte.integrations.base.ssh.SshTunnel;
import io.airbyte.integrations.source.jdbc.test.JdbcSourceAcceptanceTest;
import io.airbyte.integrations.source.mysql.MySqlSource;
import io.airbyte.integrations.source.relationaldb.models.DbStreamState;
import io.airbyte.integrations.util.HostPortResolver;
import io.airbyte.protocol.models.AirbyteCatalog;
import io.airbyte.protocol.models.AirbyteConnectionStatus;
import io.airbyte.protocol.models.AirbyteConnectionStatus.Status;
Expand All @@ -39,10 +42,8 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;

import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.junit.jupiter.api.AfterAll;
Expand All @@ -51,12 +52,15 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.Network;

class MySqlStrictEncryptJdbcSourceAcceptanceTest extends JdbcSourceAcceptanceTest {

protected static final String TEST_USER = "test";
protected static final String TEST_PASSWORD = "test";
protected static MySQLContainer<?> container;
private static final SshBastionContainer bastion = new SshBastionContainer();
private static final Network network = Network.newNetwork();

protected Database database;
protected DSLContext dslContext;
Expand Down Expand Up @@ -328,6 +332,33 @@ void testStrictSSLUnsecuredWithTunnel() throws Exception {
assertTrue(actual.getMessage().contains("Could not connect with provided SSH configuration."));
}


@Test
void testCheckWithSSlModeDisabled() throws Exception {
try (final MySQLContainer<?> db = new MySQLContainer<>("mysql:8.0").withNetwork(network)) {
bastion.initAndStartBastion(network);
db.start();
final JsonNode configWithSSLModeDisabled = bastion.getTunnelConfig(SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH, ImmutableMap.builder()
.put(JdbcUtils.HOST_KEY, Objects.requireNonNull(db.getContainerInfo()
.getNetworkSettings()
.getNetworks()
.entrySet().stream()
.findFirst()
.get().getValue().getIpAddress()))
.put(JdbcUtils.PORT_KEY, db.getExposedPorts().get(0))
.put(JdbcUtils.DATABASE_KEY, db.getDatabaseName())
.put(JdbcUtils.SCHEMAS_KEY, List.of("public"))
.put(JdbcUtils.USERNAME_KEY, db.getUsername())
.put(JdbcUtils.PASSWORD_KEY, db.getPassword())
.put(JdbcUtils.SSL_MODE_KEY, Map.of(JdbcUtils.MODE_KEY, "disable")));

final AirbyteConnectionStatus actual = source.check(configWithSSLModeDisabled);
assertEquals(AirbyteConnectionStatus.Status.SUCCEEDED, actual.getStatus());
} finally {
bastion.stopAndClose();
}
}

@Override
protected boolean supportsPerStream() {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.airbyte.integrations.source.postgres;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap;
import io.airbyte.db.jdbc.JdbcUtils;
import io.airbyte.integrations.base.ssh.SshBastionContainer;
import io.airbyte.integrations.base.ssh.SshTunnel;
import io.airbyte.protocol.models.AirbyteConnectionStatus;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.PostgreSQLContainer;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PostgresSourceStrictEncryptTest {
private static final SshBastionContainer bastion = new SshBastionContainer();
private static final Network network = Network.newNetwork();

@Test
void testCheckWithSSlModeDisable() throws Exception {

try (PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:13-alpine").withNetwork(network)) {
bastion.initAndStartBastion(network);
db.start();

// stop to enforce ssl for ssl_mode disable
final ImmutableMap.Builder<Object, Object> builderWithSSLModeDisable = getDatabaseConfigBuilderWithSSLMode(db, "disable");
final JsonNode configWithSSLModeDisable = bastion.getTunnelConfig(SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH, builderWithSSLModeDisable);
final AirbyteConnectionStatus connectionStatusForDisabledMode = new PostgresSourceStrictEncrypt().check(configWithSSLModeDisable);
assertEquals(AirbyteConnectionStatus.Status.SUCCEEDED, connectionStatusForDisabledMode.getStatus());

} finally {
bastion.stopAndClose();
}
}

@Test
void testCheckWithSSlModePrefer() throws Exception {

try (PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:13-alpine").withNetwork(network)) {
bastion.initAndStartBastion(network);
db.start();
//continue to enforce ssl because ssl mode is prefer
final ImmutableMap.Builder<Object, Object> builderWithSSLModePrefer = getDatabaseConfigBuilderWithSSLMode(db, "prefer");
final JsonNode configWithSSLModePrefer = bastion.getTunnelConfig(SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH, builderWithSSLModePrefer);
final AirbyteConnectionStatus connectionStatusForPreferredMode = new PostgresSourceStrictEncrypt().check(configWithSSLModePrefer);
assertEquals(AirbyteConnectionStatus.Status.FAILED, connectionStatusForPreferredMode.getStatus());
assertEquals("State code: 08004; Message: The server does not support SSL.", connectionStatusForPreferredMode.getMessage());

} finally {
bastion.stopAndClose();
}
}

private ImmutableMap.Builder<Object, Object> getDatabaseConfigBuilderWithSSLMode(PostgreSQLContainer<?> db, String sslMode) {
return ImmutableMap.builder()
.put(JdbcUtils.HOST_KEY, Objects.requireNonNull(db.getContainerInfo()
.getNetworkSettings()
.getNetworks()
.entrySet().stream()
.findFirst()
.get().getValue().getIpAddress()))
.put(JdbcUtils.PORT_KEY, db.getExposedPorts().get(0))
.put(JdbcUtils.DATABASE_KEY, db.getDatabaseName())
.put(JdbcUtils.SCHEMAS_KEY, List.of("public"))
.put(JdbcUtils.USERNAME_KEY, db.getUsername())
.put(JdbcUtils.PASSWORD_KEY, db.getPassword())
.put(JdbcUtils.SSL_MODE_KEY, Map.of(JdbcUtils.MODE_KEY, sslMode));
}



}
1 change: 1 addition & 0 deletions docs/integrations/sources/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura
## Changelog
| Version | Date | Pull Request | Subject |
|:--------|:-----------|:-----------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
| 1.0.12 | 2022-11-07 | [19025](https://github.com/airbytehq/airbyte/pull/19025) | Stop enforce SSL if ssl mode is disabled |
| 1.0.11 | 2022-11-03 | [18851](https://github.com/airbytehq/airbyte/pull/18851) | Fix bug with unencrypted CDC connections |
| 1.0.10 | 2022-11-02 | [18619](https://github.com/airbytehq/airbyte/pull/18619) | Fix bug with handling Tinyint(1) Unsigned values as boolean |
| 1.0.9 | 2022-10-31 | [18538](https://github.com/airbytehq/airbyte/pull/18538) | Encode database name |
Expand Down
1 change: 1 addition & 0 deletions docs/integrations/sources/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ The root causes is that the WALs needed for the incremental sync has been remove

| Version | Date | Pull Request | Subject |
|:--------|:-----------|:-------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1.0.23 | 2022-11-07 | [19025](https://github.com/airbytehq/airbyte/pull/19025) | Stop enforce SSL if ssl mode is disabled |
| 1.0.22 | 2022-10-31 | [18538](https://github.com/airbytehq/airbyte/pull/18538) | Encode database name |
| 1.0.21 | 2022-10-25 | [18256](https://github.com/airbytehq/airbyte/pull/18256) | Disable allow and prefer ssl modes in CDC mode |
| 1.0.20 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages |
Expand Down