-
Notifications
You must be signed in to change notification settings - Fork 96
[Improvement] JDBC Connection Configuration with Flexible SSL and Property Injection #681
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
base: main
Are you sure you want to change the base?
Conversation
|
||
public boolean isOracleBackend() | ||
{ | ||
return oracleBackend; | ||
} | ||
|
||
public void setOracleBackend(boolean oracleBackend) | ||
{ | ||
this.oracleBackend = oracleBackend; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, with this change, now users will have to manually configure oracleBackend = true
in addition to setting their JDBC URL to jdbc:oracle://...
, is that right?
I have two ideas here.
First, in the interest of maintaining current behavior and reducing the burden on users, I would say we can just add a new method on DataStoreConfiguration
like:
public boolean isOracleBackend() {
return jdbcUrl.startsWith("jdbc:oracle:");
}
But, thinking on this more, I think we can do better. I am thinking we should create an enum for all of the supported backends:
enum DataStoreBackend {
ORACLE,
MYSQL,
POSTGRES;
}
Then in DataStoreConfiguration
we can default this to being set by the JDBC URL, but allow users to override if needed:
private DataStoreBackend dataStoreBackendType;
...
public DataStoreBackend getDataStoreBackendType() {
if (dataStoreBackendType != null) {
return dataStoreBackendType;
}
if (jdbcUrl.startsWith("jdbc:oracle") {
return ORACLE;
} else if (...) {
..
}
}
And then we can pass around this nice enum instead of a boolean. I suspect that over time we're going to need more DB-specific modifications throughout the system. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I think the enum DataStoreBackend is better, updated in the new commit. Thanks!
|
||
public class BasicJdbcPropertiesProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add Javadoc explaining that this is the fallback / default behavior in case no DB-specific prop provider is available, and that this should always be the last one in the list?
Current diff LGTM, cc @willmostly |
*/ | ||
package io.trino.gateway.ha.config; | ||
|
||
public enum DataStoreBackend { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DataStoreBackend -> DataStoreType
this.mysqlConfiguration = mysqlConfig; | ||
} | ||
|
||
public DataStoreBackend getDataStoreBackendType() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider using java.sql.Driver#acceptsURL
instead.
/** | ||
* Configuration for MySQL SSL (client cert and truststore settings). | ||
*/ | ||
public class MysqlConfiguration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mysql -> MySql
private SslMode sslMode = SslMode.DISABLED; | ||
private String clientCertificateKeyStoreUrl; | ||
private String clientCertificateKeyStorePassword; | ||
private String clientCertificateKeyStoreType; | ||
private String trustCertificateKeyStoreUrl; | ||
private String trustCertificateKeyStorePassword; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename these fields for consistency with Trino:
- tlsEnabled
- keystorePath
- keystorePassword
- keystoreType
- truststorePath
- truststorePassword
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The naming is from the mysql server: https://github.com/mysql/mysql-connector-j/blob/20ef8ee9eb4294a03858acccea0ddad3525f1ff9/src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java#L82-L84 so the driver can easily pick it up without translating it. I felt it might be easier keeping it this way, I can add some comments for the trino concept mapping though, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 with @felicity3786 -- we previously discussed that if we had these as generic, Gateway-level configs, we should use the names consistent with Trino like @ebyhr said. But currently these are MySQL-specific configs, so we should be consistent with the MySQL naming convention.
{ | ||
return this.routingManager; | ||
return new QueryCountBasedRouter(gatewayBackendManager, queryHistoryManager); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add requireNonNull
@@ -54,12 +61,18 @@ public Jdbi getJdbi(@Nullable String routingGroupDatabase) | |||
if (routingGroupDatabase == null) { | |||
return jdbi; | |||
} | |||
Properties props = jdbcPropertiesProvider.getProperties(configuration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props -> properties
@Inject | ||
public JdbcPropertiesProviderFactory(List<JdbcPropertiesProvider> orderedProviders) | ||
{ | ||
this.providers = requireNonNull(orderedProviders, "providers is null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove requireNonNull and add immutable copy.
public JdbcPropertiesProvider forConfig(DataStoreConfiguration config) | ||
{ | ||
return providers.stream() | ||
.filter(p -> p.supports(config)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
class TestJdbcPropertiesProviderFactory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
{ | ||
private JdbcPropertiesProviderFactory factory; | ||
|
||
@BeforeEach |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var cfg = makeMySqlConfig(SslMode.VERIFY_CA); | ||
var factory = factoryFor(cfg); | ||
var mySqlConfiguration = cfg.getMySqlConfiguration(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid var
per Trino guidelines (here and elsewhere): https://github.com/trinodb/trino/blob/master/.github/DEVELOPMENT.md#avoid-var
|
||
import java.util.Properties; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
class TestJdbcPropertiesProviderFactory | ||
@TestInstance(TestInstance.Lifecycle.PER_CLASS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I guess we don't really need it given I have remove all the @BeforeAll, removed it
@@ -30,13 +29,13 @@ public class JdbcPropertiesProviderFactory | |||
@Inject | |||
public JdbcPropertiesProviderFactory(List<JdbcPropertiesProvider> orderedProviders) | |||
{ | |||
this.providers = requireNonNull(orderedProviders, "providers is null"); | |||
this.providers = List.copyOf(orderedProviders); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Trino we typically use the Guava one i.e. ImmutableList.copyOf()
and we should still have requireNonNull
here:
this.providers = List.copyOf(orderedProviders); | |
this.providers = ImmutableList.copyOf(requireNonNull(orderedProviders, "providers is null")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
requireNonNull is redundant when we use ImmutableList.copyOf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that @ebyhr ? copyOf()
will throw NPE if given a null value (at elements.toArray()
):
https://github.com/google/guava/blob/6bbfc86eb2cf64600e1e30e112cad227f5e31f4b/guava/src/com/google/common/collect/ImmutableList.java#L258-L264
The Trino repo has hundreds of usages of this pattern: https://github.com/search?q=repo%3Atrinodb%2Ftrino%20%22ImmutableList.copyOf(requireNonNull(%22&type=code
if (jdbcUrl == null) { | ||
throw new IllegalStateException("jdbcUrl must be set"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If jdbcUrl
is required, let's add the non-null validation to getJdbcUrl()
, and then just use getJdbcUrl()
here, to keep that validation consistent
String driverClass = driver.getClass().getName(); | ||
if (driverClass.contains("oracle")) { | ||
return DataStoreType.ORACLE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fuzzy string match against class name doesn't seem right to me. We have all of the driver classes on the classpath, can we do an actual match against the class name? Like adding a field to DataStoreType
which tracks the expected instance of Class<?>
for that DataStoreType
. Then this iteration can become something like:
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.acceptsURL(jdbcUrl)) {
for (DataStoreType dataStoreType : DataStoreType.values() {
if (dataStoreType.driverClass() == driver.getClass()) {
return dataStoreType;
}
}
break;
}
}
return clientCertificateKeyStoreUrl; | ||
} | ||
|
||
public MySqlConfiguration setClientCertificateKeyStoreUrl(String url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename the parameter. Same for others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ebyhr, @xkrogen and I both think it's would make more sense to keep the mysql naming convention as we discussed in #681 (comment), wdyt?
Description
Summary:
Today, the Trino Gateway only supports very basic JDBC connection initialization, assuming standard username/password settings. It lacks support for important real-world features like client-side certificates (mTLS), custom truststore setups, or fine-grained SSL configurations when connecting to databases (e.g., MySQL).
I would like to help contributing a more flexible, extensible JDBC connection management system.
Additional context and related issues
Right now, current gateway code hardcodes simple username/password based JDBC setup.
It is difficult to configure SSL/TLS for databases that require client certificates (e.g.,
SslMode=VERIFY_CA
forMySQL
). Gateway users with stricter security requirements (like us at LinkedIn) must patch or fork the code. And it is not cleanly extensible for new future other database types.At LinkedIn, We already implemented the following internally and would like to contribute it upstream:
JdbcPropertiesProvider
interface for generating connection properties.BasicJdbcPropertiesProvider
(default simple username/password).MySqlJdbcPropertiesProvider
to handle MySQL-specific SSL properties:clientCertificateKeyStoreUrl, clientCertificateKeyStorePassword
, etc.SslMode
settings (DISABLED
,VERIFY_CA
, etc.).JdbcPropertiesProviderFactory
to pick the right provider automatically based on configuration.JdbcConnectionManager
to use these properties instead of hardcoded username/password.@Singleton
/ Guice bindings for better dependency injection.Benefits:
The changes will help
Release notes
( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required. Please propose a release note for me.
( ) Release notes are required, with the following suggested text: