Added sslmode/sslrootcert for postgresql and cockroachdb (#66)
This commit is contained in:
@@ -20,6 +20,9 @@ import org.springframework.cloud.bindings.Binding;
|
||||
import org.springframework.cloud.bindings.Bindings;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.cloud.bindings.boot.Guards.isTypeEnabled;
|
||||
@@ -35,6 +38,21 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp
|
||||
* The {@link Binding} type that this processor is interested in: {@value}.
|
||||
**/
|
||||
public static final String TYPE = "postgresql";
|
||||
/**
|
||||
* sslmode determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server.
|
||||
*/
|
||||
public static final String SSL_MODE = "sslmode";
|
||||
/**
|
||||
* sslrootcert specifies the name of a file containing SSL certificate authority (CA) certificate(s).
|
||||
*/
|
||||
public static final String SSL_ROOT_CERT = "sslrootcert";
|
||||
/**
|
||||
* options Specifies command-line options to send to the server at connection start.
|
||||
* CockroachDB uses this to pass in cluster routing id
|
||||
*/
|
||||
public static final String OPTIONS = "options";
|
||||
public static final String SPRING_DATASOURCE_URL = "spring.datasource.url";
|
||||
public static final String SPRING_R2DBC_URL = "spring.r2dbc.url";
|
||||
|
||||
@Override
|
||||
public void process(Environment environment, Bindings bindings, Map<String, Object> properties) {
|
||||
@@ -47,8 +65,21 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp
|
||||
|
||||
//jdbc properties
|
||||
map.from("password").to("spring.datasource.password");
|
||||
map.from("host", "port", "database").to("spring.datasource.url",
|
||||
map.from("host", "port", "database").to(SPRING_DATASOURCE_URL,
|
||||
(host, port, database) -> String.format("jdbc:postgresql://%s:%s/%s", host, port, database));
|
||||
|
||||
String sslParam = buildSslModeParam(binding);
|
||||
String dbOptions = buildDbOptions(binding);
|
||||
String sslModeOptions = dbOptions;
|
||||
if (!"".equals(sslParam) && !"".equals(sslModeOptions)) {
|
||||
sslModeOptions = sslParam + "&" + sslModeOptions;
|
||||
} else if (!"".equals(sslParam) ) {
|
||||
sslModeOptions = sslParam;
|
||||
}
|
||||
|
||||
if (!"".equals(sslModeOptions)) {
|
||||
properties.put(SPRING_DATASOURCE_URL, properties.get(SPRING_DATASOURCE_URL) + "?" + sslModeOptions);
|
||||
}
|
||||
map.from("username").to("spring.datasource.username");
|
||||
|
||||
// jdbcURL takes precedence
|
||||
@@ -58,8 +89,11 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp
|
||||
|
||||
//r2dbc properties
|
||||
map.from("password").to("spring.r2dbc.password");
|
||||
map.from("host", "port", "database").to("spring.r2dbc.url",
|
||||
map.from("host", "port", "database").to(SPRING_R2DBC_URL,
|
||||
(host, port, database) -> String.format("r2dbc:postgresql://%s:%s/%s", host, port, database));
|
||||
if (!"".equals(sslModeOptions)) {
|
||||
properties.put(SPRING_R2DBC_URL, properties.get(SPRING_R2DBC_URL) + "?" + sslModeOptions);
|
||||
}
|
||||
map.from("username").to("spring.r2dbc.username");
|
||||
|
||||
// r2dbcURL takes precedence
|
||||
@@ -67,4 +101,68 @@ public final class PostgreSqlBindingsPropertiesProcessor implements BindingsProp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a concatenated list of options parameters defined in the bound file `options` in the format specified in
|
||||
* <a href="https://www.postgresql.org/docs/14/libpq-connect.html">PostgreSQL Doc</a>.
|
||||
* <p>
|
||||
* CockroachDB, which shares the same 'postgresql://' protocol as PostgreSQL, has customized options to meet its
|
||||
* distributed database nature.
|
||||
* Refer to <a href="https://www.cockroachlabs.com/docs/v21.2/connection-parameters#additional-connection-parameters">Client Connection Parameters</a>.
|
||||
*/
|
||||
private String buildDbOptions(Binding binding) {
|
||||
String options = binding.getSecret().getOrDefault(OPTIONS, "");
|
||||
String crdbOption = "";
|
||||
List<String> dbOptions = new ArrayList<>();
|
||||
if (!options.equals("")) {
|
||||
String[] allOpts = options.split("&");
|
||||
for (String o : allOpts) {
|
||||
String[] keyval = o.split("=");
|
||||
if (keyval.length != 2 || keyval[0].length() == 0 || keyval[1].length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (keyval[0].equals("--cluster")) {
|
||||
crdbOption = keyval[0] + "=" + keyval[1];
|
||||
} else {
|
||||
dbOptions.add("-c " + keyval[0] + "=" + keyval[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
String combinedOptions = crdbOption;
|
||||
if (dbOptions.size() > 0) {
|
||||
String otherOpts = String.join(" ", dbOptions);
|
||||
if (!combinedOptions.equals("")) {
|
||||
combinedOptions = combinedOptions + " " + otherOpts;
|
||||
} else {
|
||||
combinedOptions = otherOpts;
|
||||
}
|
||||
}
|
||||
if (!"".equals(combinedOptions)) {
|
||||
combinedOptions = "options=" + combinedOptions;
|
||||
}
|
||||
return combinedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a concatenated string of all ssl parameters for enabling one-way TLS (PostgreSQL certifies itself)
|
||||
* Refer to <a href="https://www.postgresql.org/docs/14/libpq-connect.html">PostgreSQL Doc</a>
|
||||
*/
|
||||
private String buildSslModeParam(Binding binding) {
|
||||
//process ssl params
|
||||
//https://www.postgresql.org/docs/14/libpq-connect.html
|
||||
String sslmode = binding.getSecret().getOrDefault(SSL_MODE, "");
|
||||
String sslRootCert = binding.getSecret().getOrDefault(SSL_ROOT_CERT, "");
|
||||
StringBuilder sslparam = new StringBuilder();
|
||||
if (!"".equals(sslmode)) {
|
||||
sslparam.append(SSL_MODE).append("=").append(sslmode);
|
||||
}
|
||||
if (!"".equals(sslRootCert)) {
|
||||
if (!"".equals(sslmode)) {
|
||||
sslparam.append("&");
|
||||
}
|
||||
sslparam.append(SSL_ROOT_CERT).append("=")
|
||||
.append(binding.getPath()).append(FileSystems.getDefault().getSeparator())
|
||||
.append(sslRootCert);
|
||||
}
|
||||
return sslparam.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,4 +111,158 @@ final class PostgreSqlBindingsPropertiesProcessorTest {
|
||||
assertThat(properties).isEmpty();
|
||||
}
|
||||
|
||||
private final FluentMap secretSsl = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("sslmode", "verify-full")
|
||||
.withEntry("sslrootcert", "root.cert")
|
||||
.withEntry("options", "--cluster=routing-id&opt=val1");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with sslmode and crdb option")
|
||||
void testJdbcWithSsl() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretSsl)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database?sslmode=verify-full&sslrootcert=bindings/root.cert&options=--cluster=routing-id -c opt=val1")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
|
||||
private final FluentMap secretInvalidCrdbOption = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("sslmode", "verify-full")
|
||||
.withEntry("sslrootcert", "root.cert")
|
||||
.withEntry("options", "-cluster=routing-id&opt=val1");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with sslmode and crdb option")
|
||||
void testJdbcWithInvalidCrdbOption() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretInvalidCrdbOption)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database?sslmode=verify-full&sslrootcert=bindings/root.cert&options=-c -cluster=routing-id -c opt=val1")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("composes r2dbc url from host port and database with sslmode and crdb option")
|
||||
void testR2dbcWithSsl() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretSsl)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.r2dbc.password", "test-password")
|
||||
.containsEntry("spring.r2dbc.url", "r2dbc:postgresql://test-host:test-port/test-database?sslmode=verify-full&sslrootcert=bindings/root.cert&options=--cluster=routing-id -c opt=val1")
|
||||
.containsEntry("spring.r2dbc.username", "test-username");
|
||||
}
|
||||
|
||||
private final FluentMap secretSslDisable = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("sslmode", "disable");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with sslmode disable")
|
||||
void testJdbcWithSslDisable() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretSslDisable)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database?sslmode=disable")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
|
||||
private final FluentMap secretWithDBoptions = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("options", "opt1=val1&opt2=val2");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with DB options")
|
||||
void testJdbcWithDBoptions() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretWithDBoptions)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database?options=-c opt1=val1 -c opt2=val2")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
|
||||
private final FluentMap secretWithInvalidDBOptions = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("options", "opt1=val1&opt");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with invalid DB options")
|
||||
void testJdbcWithInvaildDBoptions() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretWithInvalidDBOptions)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database?options=-c opt1=val1")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
|
||||
private final FluentMap secretWithEmptyDBOptions = new FluentMap()
|
||||
.withEntry(Binding.TYPE, TYPE)
|
||||
.withEntry("database", "test-database")
|
||||
.withEntry("host", "test-host")
|
||||
.withEntry("password", "test-password")
|
||||
.withEntry("port", "test-port")
|
||||
.withEntry("username", "test-username")
|
||||
.withEntry("options", "");
|
||||
|
||||
@Test
|
||||
@DisplayName("composes jdbc url from host port and database with empty DB options")
|
||||
void testJdbcWithEmptyDBoptions() {
|
||||
Bindings bindings = new Bindings(
|
||||
new Binding("test-name", Paths.get("bindings"), secretWithEmptyDBOptions)
|
||||
);
|
||||
new PostgreSqlBindingsPropertiesProcessor().process(environment, bindings, properties);
|
||||
assertThat(properties)
|
||||
.containsEntry("spring.datasource.driver-class-name", "org.postgresql.Driver")
|
||||
.containsEntry("spring.datasource.password", "test-password")
|
||||
.containsEntry("spring.datasource.url", "jdbc:postgresql://test-host:test-port/test-database")
|
||||
.containsEntry("spring.datasource.username", "test-username");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user