From 81584654c190b303d67f63b760e5b259de616789 Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:41:10 -0600 Subject: [PATCH] Add explicit PostgreSQL TLS verification mode --- SimpleAPI/pom.xml | 2 + .../simpleapi/core/sql/MysqlConfigView.java | 6 +- .../sql/mysql/ConnectionManager.java | 45 +++++++++- .../bencodez/simpleapi/sql/mysql/MySQL.java | 1 + .../sql/mysql/PostgreSqlTlsMode.java | 33 ++++++++ .../sql/mysql/config/MysqlConfig.java | 10 +++ .../sql/mysql/config/MysqlConfigBungee.java | 2 + .../sql/mysql/config/MysqlConfigSpigot.java | 2 + .../sql/mysql/config/MysqlConfigVelocity.java | 3 + .../sql/mysql/PostgreSqlTlsConfigTest.java | 53 ++++++++++++ .../sql/mysql/PostgreSqlTlsModeTest.java | 82 +++++++++++++++++++ docs/postgresql-tls.md | 25 ++++++ 12 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java create mode 100644 SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java create mode 100644 SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java create mode 100644 docs/postgresql-tls.md diff --git a/SimpleAPI/pom.xml b/SimpleAPI/pom.xml index 84123654..da0f62aa 100644 --- a/SimpleAPI/pom.xml +++ b/SimpleAPI/pom.xml @@ -89,6 +89,7 @@ com/bencodez/simpleapi/sql/mysql/AbstractSqlTable*.class com/bencodez/simpleapi/sql/mysql/ConnectionManager*.class com/bencodez/simpleapi/sql/mysql/DbType.class + com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.class com/bencodez/simpleapi/sql/mysql/MySQL*.class com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.class com/bencodez/simpleapi/sql/mysql/config/MysqlConfigView.class @@ -117,6 +118,7 @@ com/bencodez/simpleapi/sql/mysql/AbstractSqlTable.java com/bencodez/simpleapi/sql/mysql/ConnectionManager.java com/bencodez/simpleapi/sql/mysql/DbType.java + com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java com/bencodez/simpleapi/sql/mysql/MySQL.java com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java com/bencodez/simpleapi/sql/mysql/config/MysqlConfigView.java diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java index c807ea36..57f1b2ef 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java @@ -1,10 +1,11 @@ package com.bencodez.simpleapi.core.sql; -import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig; - import java.util.Objects; + import com.bencodez.simpleapi.file.config.ConfigView; import com.bencodez.simpleapi.sql.mysql.DbType; +import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig; /** Platform-neutral snapshot of the existing MySQL section keys and defaults. */ public class MysqlConfigView extends MysqlConfig { @@ -31,6 +32,7 @@ public MysqlConfigView(ConfigView section) { : section.getBoolean("UseMariaDB", false) ? DbType.MARIADB : DbType.MYSQL); setDriver(section.getString("Driver", "")); setUseSSL(section.getBoolean("UseSSL", false)); + setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY"))); setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false)); setUseMariaDB(section.getBoolean("UseMariaDB", false)); setLine(section.getString("Line", "")); diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java index 92fd4afe..d1639056 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java @@ -1,7 +1,11 @@ package com.bencodez.simpleapi.sql.mysql; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.SQLException; +import java.util.Locale; +import java.util.Objects; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @@ -81,6 +85,13 @@ public class ConnectionManager { @Setter private boolean useSSL = false; + @Getter + private PostgreSqlTlsMode postgreSqlTlsMode = PostgreSqlTlsMode.LEGACY; + + public void setPostgreSqlTlsMode(PostgreSqlTlsMode mode) { + postgreSqlTlsMode = Objects.requireNonNull(mode, "mode"); + } + /** * Legacy flag still supported; only used if dbType isn't explicitly set. */ @@ -207,7 +218,7 @@ private String resolveMariaDbWithFallback() throws ClassNotFoundException { } } - private String buildJdbcUrl(String driverClassName) { + String buildJdbcUrl(String driverClassName) { String extra = (str == null ? "" : str); // Postgres @@ -216,10 +227,17 @@ private String buildJdbcUrl(String driverClassName) { // Defaults: // - reWriteBatchedInserts improves batch perf - // - sslmode if useSSL + // - absent explicit mode preserves the legacy UseSSL behavior String defaults = "reWriteBatchedInserts=true"; - if (useSSL) { + if (postgreSqlTlsMode == PostgreSqlTlsMode.LEGACY && useSSL) { defaults += "&sslmode=require"; + } else if (postgreSqlTlsMode != PostgreSqlTlsMode.LEGACY) { + rejectConflictingPostgreSqlTlsOptions(extra); + defaults += "&sslmode=" + postgreSqlTlsMode.getJdbcValue(); + if (postgreSqlTlsMode == PostgreSqlTlsMode.REQUIRE + || postgreSqlTlsMode == PostgreSqlTlsMode.VERIFY_FULL) { + defaults += "&gssEncMode=disable"; + } } if (extra.isEmpty()) { @@ -240,6 +258,9 @@ private String buildJdbcUrl(String driverClassName) { } // MySQL / MariaDB + if (postgreSqlTlsMode != PostgreSqlTlsMode.LEGACY) { + throw new IllegalArgumentException("PostgreSqlTlsMode applies only to PostgreSQL connections"); + } boolean maria = (dbType == DbType.MARIADB) || "org.mariadb.jdbc.Driver".equals(driverClassName); String base = maria ? String.format("jdbc:mariadb://%s:%s/%s", host, port, database) : String.format("jdbc:mysql://%s:%s/%s", host, port, database); @@ -250,6 +271,24 @@ private String buildJdbcUrl(String driverClassName) { + extra; } + private void rejectConflictingPostgreSqlTlsOptions(String extra) { + String options = extra.startsWith("?") || extra.startsWith("&") ? extra.substring(1) : extra; + for (String parameter : options.split("&", -1)) { + String rawKey = parameter.split("=", 2)[0]; + String key; + try { + key = URLDecoder.decode(rawKey, StandardCharsets.UTF_8).trim().toLowerCase(Locale.ROOT); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("Invalid encoded parameter name in PostgreSQL Line", ex); + } + if (key.equals("sslmode") || key.equals("ssl") || key.equals("sslfactory") + || key.equals("sslhostnameverifier") + || (postgreSqlTlsMode != PostgreSqlTlsMode.DISABLE && key.equals("gssencmode"))) { + throw new IllegalArgumentException("PostgreSqlTlsMode conflicts with a TLS parameter in Line"); + } + } + } + // --- Pool Configuration --- public boolean open() { diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java index 526756df..57753d45 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java @@ -54,6 +54,7 @@ public boolean connect(MysqlConfig config) { } else { connectionManager.setDbType(config.isUseMariaDB() ? DbType.MARIADB : DbType.MYSQL); } + connectionManager.setPostgreSqlTlsMode(config.getPostgreSqlTlsMode()); String driver = config.getDriver(); if (driver != null && !driver.isEmpty()) { diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java new file mode 100644 index 00000000..fa403e5c --- /dev/null +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java @@ -0,0 +1,33 @@ +package com.bencodez.simpleapi.sql.mysql; + +import java.util.Locale; + +/** PostgreSQL JDBC TLS policy. LEGACY preserves the existing UseSSL setting. */ +public enum PostgreSqlTlsMode { + LEGACY(null), + DISABLE("disable"), + REQUIRE("require"), + VERIFY_FULL("verify-full"); + + private final String jdbcValue; + + PostgreSqlTlsMode(String jdbcValue) { + this.jdbcValue = jdbcValue; + } + + public String getJdbcValue() { + return jdbcValue; + } + + public static PostgreSqlTlsMode fromString(String raw) { + if (raw == null || raw.trim().isEmpty()) { + return LEGACY; + } + String normalized = raw.trim().replace('-', '_').toUpperCase(Locale.ROOT); + try { + return valueOf(normalized); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("Invalid PostgreSqlTlsMode; expected LEGACY, DISABLE, REQUIRE, or VERIFY_FULL", ex); + } + } +} diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java index 02951415..b5e6098a 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java @@ -1,6 +1,9 @@ package com.bencodez.simpleapi.sql.mysql.config; +import java.util.Objects; + import com.bencodez.simpleapi.sql.mysql.DbType; +import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode; import lombok.Getter; import lombok.Setter; @@ -82,6 +85,13 @@ public class MysqlConfig { @Setter private boolean useSSL; + @Getter + private PostgreSqlTlsMode postgreSqlTlsMode = PostgreSqlTlsMode.LEGACY; + + public void setPostgreSqlTlsMode(PostgreSqlTlsMode mode) { + postgreSqlTlsMode = Objects.requireNonNull(mode, "mode"); + } + @Getter @Setter private boolean publicKeyRetrieval; diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java index e42c87c8..3b03b69d 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java @@ -1,6 +1,7 @@ package com.bencodez.simpleapi.sql.mysql.config; import com.bencodez.simpleapi.sql.mysql.DbType; +import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode; import net.md_5.bungee.config.Configuration; @@ -50,6 +51,7 @@ public MysqlConfigBungee(Configuration section) { // --- Driver / Behavior Options --- setUseSSL(section.getBoolean("UseSSL", false)); + setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY"))); setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false)); setUseMariaDB(section.getBoolean("UseMariaDB", false)); diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java index 54976805..d6241a4f 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java @@ -3,6 +3,7 @@ import org.bukkit.configuration.ConfigurationSection; import com.bencodez.simpleapi.sql.mysql.DbType; +import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode; public class MysqlConfigSpigot extends MysqlConfig { @@ -49,6 +50,7 @@ public MysqlConfigSpigot(ConfigurationSection section) { // --- Driver / Behavior Options --- setUseSSL(section.getBoolean("UseSSL", false)); + setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY"))); setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false)); setUseMariaDB(section.getBoolean("UseMariaDB", false)); diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java index e06ac3c1..f293ed48 100644 --- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java +++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java @@ -4,6 +4,7 @@ import com.bencodez.simpleapi.file.velocity.VelocityYMLFile; import com.bencodez.simpleapi.sql.mysql.DbType; +import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode; public class MysqlConfigVelocity extends MysqlConfig { @@ -59,6 +60,8 @@ private void load(String prePath, VelocityYMLFile config) { // --- Driver / Behavior Options --- setUseSSL(config.getBoolean(node(config, prePath, "UseSSL"), false)); + setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString( + config.getString(node(config, prePath, "PostgreSqlTlsMode"), "LEGACY"))); setPublicKeyRetrieval(config.getBoolean(node(config, prePath, "PublicKeyRetrieval"), false)); setUseMariaDB(config.getBoolean(node(config, prePath, "UseMariaDB"), false)); diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java new file mode 100644 index 00000000..925566f7 --- /dev/null +++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java @@ -0,0 +1,53 @@ +package com.bencodez.simpleapi.sql.mysql; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; + +import org.bukkit.configuration.MemoryConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.spongepowered.configurate.BasicConfigurationNode; + +import com.bencodez.simpleapi.file.config.configurate.ConfigurateConfigView; +import com.bencodez.simpleapi.file.velocity.VelocityYMLFile; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigBungee; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigVelocity; +import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigView; + +import net.md_5.bungee.config.Configuration; + +class PostgreSqlTlsConfigTest { + @TempDir Path tempDir; + + @Test void allAdaptersReadExplicitModeAndPreserveLegacyDefault() { + MemoryConfiguration spigot = new MemoryConfiguration(); + Configuration bungee = new Configuration(); + var shared = BasicConfigurationNode.root(); + VelocityYMLFile velocity = new VelocityYMLFile(tempDir.resolve("mysql.yml").toFile()); + assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigSpigot(spigot).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigBungee(bungee).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.LEGACY, + new MysqlConfigView(new ConfigurateConfigView(shared)).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigVelocity(velocity).getPostgreSqlTlsMode()); + + spigot.set("PostgreSqlTlsMode", "verify-full"); + bungee.set("PostgreSqlTlsMode", "verify-full"); + shared.node("PostgreSqlTlsMode").raw("verify-full"); + velocity.getNode("MySQL", "PostgreSqlTlsMode").raw("verify-full"); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, new MysqlConfigSpigot(spigot).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, new MysqlConfigBungee(bungee).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, + new MysqlConfigView(new ConfigurateConfigView(shared)).getPostgreSqlTlsMode()); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, + new MysqlConfigVelocity("MySQL", velocity).getPostgreSqlTlsMode()); + } + + @Test void invalidModeFailsAtConfigLoad() { + MemoryConfiguration spigot = new MemoryConfiguration(); + spigot.set("PostgreSqlTlsMode", "anything"); + assertThrows(IllegalArgumentException.class, () -> new MysqlConfigSpigot(spigot)); + } +} diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java new file mode 100644 index 00000000..ae7c119e --- /dev/null +++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java @@ -0,0 +1,82 @@ +package com.bencodez.simpleapi.sql.mysql; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class PostgreSqlTlsModeTest { + private ConnectionManager postgres() { + ConnectionManager manager = new ConnectionManager("db.example.test", "5432", "user", "password", "votes"); + manager.setDbType(DbType.POSTGRESQL); + return manager; + } + + @Test void legacyModePreservesBothUseSslSettingsAndRawLine() { + ConnectionManager manager = postgres(); + assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true", + manager.buildJdbcUrl("org.postgresql.Driver")); + manager.setUseSSL(true); + assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=require", + manager.buildJdbcUrl("org.postgresql.Driver")); + manager.setStr("?sslmode=disable"); + assertEquals("jdbc:postgresql://db.example.test:5432/votes?sslmode=disable&reWriteBatchedInserts=true&sslmode=require", + manager.buildJdbcUrl("org.postgresql.Driver")); + } + + @Test void explicitModeControlsPostgresTlsRegardlessOfUseSsl() { + ConnectionManager manager = postgres(); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL); + manager.setStr("?sslrootcert=/etc/db/ca.pem&applicationName=votes"); + String verified = "jdbc:postgresql://db.example.test:5432/votes?sslrootcert=/etc/db/ca.pem" + + "&applicationName=votes&reWriteBatchedInserts=true&sslmode=verify-full&gssEncMode=disable"; + assertEquals(verified, manager.buildJdbcUrl("org.postgresql.Driver")); + manager.setUseSSL(true); + assertEquals(verified, manager.buildJdbcUrl("org.postgresql.Driver")); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.REQUIRE); + manager.setStr("&applicationName=votes"); + assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=require&gssEncMode=disable&applicationName=votes", + manager.buildJdbcUrl("org.postgresql.Driver")); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.DISABLE); + manager.setStr(""); + assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=disable", + manager.buildJdbcUrl("org.postgresql.Driver")); + } + + @Test void explicitModeRejectsConflictingRawLineOptions() { + ConnectionManager manager = postgres(); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL); + for (String line : new String[] {"?sslmode=disable", "&SSLMODE=require", "ssl%6dode=disable", + "?applicationName=votes&ssl=false", "&sslfactory=com.example.TrustAll", + "sslhostnameverifier=com.example.TrustAll", "gssEncMode=prefer"}) { + manager.setStr(line); + assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.postgresql.Driver"), line); + } + manager.setStr("?ssl%zz=disable"); + assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.postgresql.Driver")); + } + + @Test void mysqlAndMariaDbKeepLegacyUrlsAndRejectPostgresModes() { + ConnectionManager manager = postgres(); + manager.setDbType(DbType.MYSQL); + String mysqlUrl = manager.buildJdbcUrl("com.mysql.cj.jdbc.Driver"); + assertEquals("jdbc:mysql://db.example.test:5432/votes?useSSL=false&allowMultiQueries=true" + + "&rewriteBatchedStatements=true&useDynamicCharsetInfo=false&allowPublicKeyRetrieval=false" + + "&tcpKeepAlive=true&connectTimeout=10000&socketTimeout=30000&serverTimezone=UTC", mysqlUrl); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL); + assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("com.mysql.cj.jdbc.Driver")); + manager.setDbType(DbType.MARIADB); + assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.mariadb.jdbc.Driver")); + manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.LEGACY); + assertEquals(mysqlUrl.replace("jdbc:mysql:", "jdbc:mariadb:"), + manager.buildJdbcUrl("org.mariadb.jdbc.Driver")); + } + + @Test void parsesModeNamesAndRejectsUnknownValues() { + assertEquals(PostgreSqlTlsMode.LEGACY, PostgreSqlTlsMode.fromString(null)); + assertEquals(PostgreSqlTlsMode.LEGACY, PostgreSqlTlsMode.fromString(" ")); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, PostgreSqlTlsMode.fromString(" verify-full ")); + assertEquals(PostgreSqlTlsMode.VERIFY_FULL, PostgreSqlTlsMode.fromString("verify_full")); + assertThrows(IllegalArgumentException.class, () -> PostgreSqlTlsMode.fromString("verify-ca")); + } +} diff --git a/docs/postgresql-tls.md b/docs/postgresql-tls.md new file mode 100644 index 00000000..d83e4e60 --- /dev/null +++ b/docs/postgresql-tls.md @@ -0,0 +1,25 @@ +# PostgreSQL TLS configuration + +Set `DbType: POSTGRESQL` and choose `PostgreSqlTlsMode` in the SQL configuration section. The same key works with the Bukkit, Bungee, Velocity, and platform-neutral configuration adapters. + +| `PostgreSqlTlsMode` | PostgreSQL JDBC setting | Behavior | +| --- | --- | --- | +| omitted or `LEGACY` | Existing `UseSSL` behavior | `UseSSL: true` sets `sslmode=require`, which encrypts without checking server identity. `UseSSL: false` leaves `sslmode` unset, so the driver's default applies. | +| `VERIFY_FULL` | `sslmode=verify-full`, `gssEncMode=disable` | Encrypts and verifies the server certificate and hostname. | +| `REQUIRE` | `sslmode=require`, `gssEncMode=disable` | Explicit TLS encryption-only compatibility mode; no server identity verification. | +| `DISABLE` | `sslmode=disable` | Explicitly disables PostgreSQL TLS. | + +To migrate an existing PostgreSQL connection to identity verification: + +```yaml +DbType: POSTGRESQL +Host: db.example.com +PostgreSqlTlsMode: VERIFY_FULL +Line: sslrootcert=/path/to/ca.pem +``` + +Use a hostname covered by the server certificate and provide a trusted CA certificate as needed. `VERIFY_FULL` fails the connection if verification fails. An explicit mode takes precedence over `UseSSL`. Existing configurations retain their current behavior until a mode is selected. See the [pgJDBC connection parameters](https://jdbc.postgresql.org/documentation/use/#connection-parameters) for certificate and hostname requirements. + +When a mode other than `LEGACY` is selected, `Line` cannot set `sslmode`, `ssl`, `sslfactory`, or `sslhostnameverifier`, including differently cased or encoded parameter names. `REQUIRE` and `VERIFY_FULL` also reject `gssEncMode` in `Line` and disable GSS encryption negotiation, so the selected TLS policy is used. Other options such as `sslrootcert` remain available. `LEGACY` keeps existing `Line` handling for compatibility. + +`PostgreSqlTlsMode` applies only to PostgreSQL. MySQL and MariaDB connections continue to use their existing `UseSSL` behavior; setting a non-legacy PostgreSQL mode for those drivers is a configuration error. Their `UseSSL` flag alone does not promise hostname verification.