Skip to content

Commit bf9b359

Browse files
committed
wip...
1 parent 0937fe6 commit bf9b359

File tree

4 files changed

+251
-16
lines changed

4 files changed

+251
-16
lines changed

quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.keycloak.config;
22

3+
import java.io.File;
4+
import java.util.Arrays;
35
import java.util.HashMap;
46
import java.util.List;
57
import java.util.Map;
@@ -121,6 +123,55 @@ public class DatabaseOptions {
121123
.hidden()
122124
.build();
123125

126+
public static final Option<String> DB_TLS_MODE = new OptionBuilder<>("db-tls-mode", String.class)
127+
.category(OptionCategory.DATABASE)
128+
.expectedValues(Arrays.stream(DatabaseTlsMode.values()).map(DatabaseTlsMode::toCliValue).toList())
129+
.defaultValue(DatabaseTlsMode.DISABLED.toCliValue())
130+
.description("If the database tls mode is enabled at runtime.") // TODO
131+
.build();
132+
133+
public static final Option<File> DB_TLS_TRUSTSTORE_FILE = new OptionBuilder<>("db-tls-truststore-file", File.class)
134+
.category(OptionCategory.DATABASE)
135+
.description("If the database tls mode is enabled at runtime.") // TODO
136+
.build();
137+
138+
public static final Option<String> DB_TLS_RUSTSTORE_PASSWORD = new OptionBuilder<>("db-tls-truststore-password", String.class)
139+
.category(OptionCategory.DATABASE)
140+
.description("If the database tls mode is enabled at runtime.") // TODO
141+
.build();
142+
143+
// TLS hidden options, per vendor, to configure TLS in the driver
144+
public static final Option<String> DB_ORACLE_TLS_TRANSPORT = new OptionBuilder<>("db-oracle-protocol", String.class)
145+
.hidden()
146+
.build();
147+
public static final Option<String> DB_ORACLE_DN_MATCH = new OptionBuilder<>("db-oracle-dn-match", String.class)
148+
.defaultValue("true")
149+
.hidden()
150+
.build();
151+
public static final Option<String> DB_MSSQL_ENCRYPT = new OptionBuilder<>("db-mssql-encrypt", String.class)
152+
.defaultValue("true")
153+
.hidden()
154+
.build();
155+
public static final Option<String> DB_MSSQL_TRUST_CERTIFICATE = new OptionBuilder<>("db-mssql-trust-certificate", String.class)
156+
.defaultValue("false")
157+
.hidden()
158+
.build();
159+
public static final Option<String> DB_MYSQL_SSL_MODE = new OptionBuilder<>("db-mysql-ssl-mode", String.class)
160+
.defaultValue("VERIFY_IDENTITY")
161+
.hidden()
162+
.build();
163+
public static final Option<String> DB_MARIADB_SSL_MODE = new OptionBuilder<>("db-mariadb-ssl-mode", String.class)
164+
.defaultValue("verify-full")
165+
.hidden()
166+
.build();
167+
public static final Option<String> DB_POSTGRES_SSL_MODE = new OptionBuilder<>("db-postgres-ssl-mode", String.class)
168+
.defaultValue("verify-full")
169+
.hidden()
170+
.build();
171+
public static final Option<String> DB_POSTGRES_SSL_FACTORY = new OptionBuilder<>("db-postgres-ssl-factory", String.class)
172+
.hidden()
173+
.build();
174+
124175
public static final class Datasources {
125176
/**
126177
* Options that have their sibling for a named datasource
@@ -142,7 +193,10 @@ public static final class Datasources {
142193
DB_POOL_MIN_SIZE,
143194
DB_POOL_MAX_SIZE,
144195
DB_SQL_JPA_DEBUG,
145-
DB_SQL_LOG_SLOW_QUERIES
196+
DB_SQL_LOG_SLOW_QUERIES,
197+
DB_TLS_MODE,
198+
DB_TLS_TRUSTSTORE_FILE,
199+
DB_TLS_RUSTSTORE_PASSWORD
146200
);
147201

148202
/**
@@ -163,7 +217,10 @@ public static final class Datasources {
163217
.connectedOptions(TransactionOptions.TRANSACTION_XA_ENABLED_DATASOURCE,
164218
getDatasourceOption(DB_POOL_MAX_SIZE).orElseThrow(),
165219
getDatasourceOption(DB_SQL_JPA_DEBUG).orElseThrow(),
166-
getDatasourceOption(DB_SQL_LOG_SLOW_QUERIES).orElseThrow())
220+
getDatasourceOption(DB_SQL_LOG_SLOW_QUERIES).orElseThrow(),
221+
getDatasourceOption(DB_TLS_MODE).orElseThrow(),
222+
getDatasourceOption(DB_TLS_TRUSTSTORE_FILE).orElseThrow(),
223+
getDatasourceOption(DB_TLS_RUSTSTORE_PASSWORD).orElseThrow())
167224
);
168225

169226
private static final Map<String, Option<?>> cachedDatasourceOptions = new HashMap<>();
@@ -243,4 +300,13 @@ public static Optional<String> getNamedKey(Option<?> option, String namedPropert
243300
return getKeyForDatasource(option).map(key -> getWildcardNamedKey(key, namedProperty));
244301
}
245302
}
303+
304+
public enum DatabaseTlsMode {
305+
ENABLED,
306+
DISABLED;
307+
308+
public String toCliValue() {
309+
return name().toLowerCase();
310+
}
311+
}
246312
}

quarkus/config-api/src/main/java/org/keycloak/config/database/Database.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,10 @@ private String escapeReplacements(String snippet) {
210210
"oracle.jdbc.driver.OracleDriver",
211211
"org.hibernate.dialect.OracleDialect",
212212
// default URL looks like this: "jdbc:oracle:thin:@//${kc.db-url-host:localhost}:${kc.db-url-port:1521}/${kc.db-url-database:keycloak}"
213-
(namedProperty, alias) -> "jdbc:oracle:thin:@//%s:%s/%s".formatted(
213+
(namedProperty, alias) -> "jdbc:oracle:thin:%s//%s:%s/%s".formatted(
214+
getProperty(DatabaseOptions.DB_ORACLE_TLS_TRANSPORT, namedProperty, "@"),
214215
getProperty(DatabaseOptions.DB_URL_HOST, namedProperty, "localhost"),
215-
getProperty(DatabaseOptions.DB_URL_PORT, namedProperty, "1521"),
216+
getProperty(DatabaseOptions.DB_URL_PORT, namedProperty, "1521"), //TODO how to change default!?
216217
getProperty(DatabaseOptions.DB_URL_DATABASE, namedProperty, "keycloak")),
217218
"liquibase.database.core.OracleDatabase"
218219
);

quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Map;
66
import java.util.Objects;
77
import java.util.Optional;
8+
import java.util.function.BooleanSupplier;
89
import java.util.function.Consumer;
910
import java.util.function.Supplier;
1011

@@ -26,7 +27,19 @@
2627
import org.jboss.logging.Logger;
2728

2829
import static org.keycloak.config.DatabaseOptions.DB;
30+
import static org.keycloak.config.DatabaseOptions.DB_MARIADB_SSL_MODE;
31+
import static org.keycloak.config.DatabaseOptions.DB_MSSQL_ENCRYPT;
32+
import static org.keycloak.config.DatabaseOptions.DB_MSSQL_TRUST_CERTIFICATE;
33+
import static org.keycloak.config.DatabaseOptions.DB_MYSQL_SSL_MODE;
34+
import static org.keycloak.config.DatabaseOptions.DB_ORACLE_DN_MATCH;
35+
import static org.keycloak.config.DatabaseOptions.DB_ORACLE_TLS_TRANSPORT;
36+
import static org.keycloak.config.DatabaseOptions.DB_POOL_INITIAL_SIZE;
2937
import static org.keycloak.config.DatabaseOptions.DB_POOL_MAX_SIZE;
38+
import static org.keycloak.config.DatabaseOptions.DB_POSTGRES_SSL_FACTORY;
39+
import static org.keycloak.config.DatabaseOptions.DB_POSTGRES_SSL_MODE;
40+
import static org.keycloak.config.DatabaseOptions.DB_TLS_MODE;
41+
import static org.keycloak.config.DatabaseOptions.DB_TLS_RUSTSTORE_PASSWORD;
42+
import static org.keycloak.config.DatabaseOptions.DB_TLS_TRUSTSTORE_FILE;
3043
import static org.keycloak.config.DatabaseOptions.Datasources.OPTIONS_DATASOURCES;
3144
import static org.keycloak.config.DatabaseOptions.Datasources.getDatasourceOption;
3245
import static org.keycloak.config.DatabaseOptions.Datasources.getKeyForDatasource;
@@ -50,26 +63,26 @@ public final class DatabasePropertyMappers implements PropertyMapperGrouping {
5063
public List<PropertyMapper<?>> getPropertyMappers() {
5164
List<PropertyMapper<?>> mappers = List.of(
5265
fromOption(DatabaseOptions.DB_DIALECT)
53-
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::transformDialect)
66+
.mapFrom(DB, DatabasePropertyMappers::transformDialect)
5467
.build(),
5568
fromOption(DatabaseOptions.DB_DRIVER)
56-
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::getXaOrNonXaDriver)
69+
.mapFrom(DB, DatabasePropertyMappers::getXaOrNonXaDriver)
5770
.to("quarkus.datasource.jdbc.driver")
5871
.paramLabel("driver")
5972
.build(),
60-
fromOption(DatabaseOptions.DB)
73+
fromOption(DB)
6174
.to("quarkus.datasource.db-kind")
6275
.transformer(DatabasePropertyMappers::toDatabaseKind)
6376
.paramLabel("vendor")
6477
.build(),
6578
fromOption(DatabaseOptions.DB_URL)
6679
.to("quarkus.datasource.jdbc.url")
67-
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::getDatabaseUrl)
80+
.mapFrom(DB, DatabasePropertyMappers::getDatabaseUrl)
6881
.paramLabel("jdbc-url")
6982
.build(),
7083
fromOption(DatabaseOptions.DB_POSTGRESQL_TARGET_SERVER_TYPE)
7184
.to(PG_TARGET_SERVER_TYPE)
72-
.isEnabled(() -> isPostgresqlTargetServerTypeEnabled())
85+
.isEnabled(DatabasePropertyMappers::isPostgresqlTargetServerTypeEnabled)
7386
.build(),
7487
fromOption(DatabaseOptions.DB_URL_HOST)
7588
.paramLabel("hostname")
@@ -95,16 +108,16 @@ public List<PropertyMapper<?>> getPropertyMappers() {
95108
fromOption(DatabaseOptions.DB_SCHEMA)
96109
.paramLabel("schema")
97110
.build(),
98-
fromOption(DatabaseOptions.DB_POOL_INITIAL_SIZE)
111+
fromOption(DB_POOL_INITIAL_SIZE)
99112
.to("quarkus.datasource.jdbc.initial-size")
100113
.paramLabel("size")
101114
.build(),
102115
fromOption(DatabaseOptions.DB_POOL_MIN_SIZE)
103-
.mapFrom(DatabaseOptions.DB, DatabasePropertyMappers::transformMinPoolSize)
116+
.mapFrom(DB, DatabasePropertyMappers::transformMinPoolSize)
104117
.to("quarkus.datasource.jdbc.min-size")
105118
.paramLabel("size")
106119
.build(),
107-
fromOption(DatabaseOptions.DB_POOL_MAX_SIZE)
120+
fromOption(DB_POOL_MAX_SIZE)
108121
.to("quarkus.datasource.jdbc.max-size")
109122
.paramLabel("size")
110123
.build(),
@@ -122,20 +135,46 @@ public List<PropertyMapper<?>> getPropertyMappers() {
122135
.to("quarkus.datasource.\"<datasource>\".active")
123136
.build(),
124137
fromOption(DB_URL_PATH)
138+
.build(),
139+
// Database TLS configuration
140+
fromOption(DB_TLS_MODE)
141+
.paramLabel("mode")
142+
.build(),
143+
fromOption(DB_TLS_TRUSTSTORE_FILE)
144+
.paramLabel("path")
145+
.build(),
146+
fromOption(DB_TLS_RUSTSTORE_PASSWORD)
147+
.paramLabel("password")
148+
.isMasked(true)
149+
.build(),
150+
fromOption(DB_ORACLE_TLS_TRANSPORT)
151+
.isEnabled(isVendor(Database.Vendor.ORACLE))
152+
.mapFrom(DB_TLS_MODE, DatabasePropertyMappers::transformOracleProtocol)
153+
.build(),
154+
applyTLSJdbcProperty(DB_ORACLE_DN_MATCH, Database.Vendor.ORACLE, "ssl_server_dn_match"),
155+
applyTLSJdbcProperty(DB_MSSQL_ENCRYPT, Database.Vendor.MSSQL, "encrypt"),
156+
applyTLSJdbcProperty(DB_MSSQL_TRUST_CERTIFICATE, Database.Vendor.MSSQL, "trustServerCertificate"),
157+
applyTLSJdbcProperty(DB_MYSQL_SSL_MODE, List.of(Database.Vendor.MYSQL, Database.Vendor.TIDB), "sslMode"),
158+
applyTLSJdbcProperty(DB_MARIADB_SSL_MODE, Database.Vendor.MARIADB, "sslMode"),
159+
applyTLSJdbcProperty(DB_POSTGRES_SSL_MODE, Database.Vendor.POSTGRES, "sslmode"),
160+
fromOption(DB_POSTGRES_SSL_FACTORY)
161+
.isEnabled(() -> isTLSEnabledAndPropertyNotSet(Database.Vendor.POSTGRES, "sslfactory"))
162+
.mapFrom(DB_TLS_MODE, DatabasePropertyMappers::transformPostgresSSLFactory)
163+
.to("quarkus.datasource.jdbc.additional-jdbc-properties.sslfactory")
125164
.build()
126165
);
127166

128167
return appendDatasourceMappers(mappers, Map.of(
129168
// Inherit options from the DB mappers
130-
DatabaseOptions.DB, PropertyMapper.Builder::removeMapFrom,
131-
DatabaseOptions.DB_POOL_INITIAL_SIZE, mapper -> mapper.mapFrom(DatabaseOptions.DB_POOL_INITIAL_SIZE),
132-
DatabaseOptions.DB_POOL_MAX_SIZE, mapper -> mapper.mapFrom(DatabaseOptions.DB_POOL_MAX_SIZE)
169+
DB, PropertyMapper.Builder::removeMapFrom,
170+
DB_POOL_INITIAL_SIZE, mapper -> mapper.mapFrom(DB_POOL_INITIAL_SIZE),
171+
DB_POOL_MAX_SIZE, mapper -> mapper.mapFrom(DB_POOL_MAX_SIZE)
133172
));
134173
}
135174

136175
@Override
137176
public void validateConfig(Picocli picocli) {
138-
Configuration.getOptionalIntegerValue(DatabaseOptions.DB_POOL_MAX_SIZE).ifPresent(poolMaxSize -> {
177+
Configuration.getOptionalIntegerValue(DB_POOL_MAX_SIZE).ifPresent(poolMaxSize -> {
139178
if (poolMaxSize < JDBC_PING_MIN_POOL_MAX_SIZE && isJdbcPingStack()) {
140179
throw new PropertyException(
141180
"The JDBC_PING cache stack requires '%s' to be at least %d (current: %d). A higher value is recommended."
@@ -336,4 +375,49 @@ private static String transformDatasourceTo(String to) {
336375
return to;
337376
}
338377
}
378+
379+
private static String transformOracleProtocol(String key, String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
380+
var tlsMode = DatabaseOptions.DatabaseTlsMode.valueOf(value.toUpperCase());
381+
return tlsMode != DatabaseOptions.DatabaseTlsMode.DISABLED ? "@tcps" : "@";
382+
}
383+
384+
private static String transformPostgresSSLFactory(String key, String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
385+
// TODO check if db-truststore-file is present
386+
return "org.postgresql.ssl.DefaultJavaSSLFactory";
387+
}
388+
389+
private static PropertyMapper<?> applyTLSJdbcProperty(Option<String> option, Database.Vendor vendor, String key) {
390+
return applyTLSJdbcProperty(option, List.of(vendor), key);
391+
}
392+
393+
private static PropertyMapper<?> applyTLSJdbcProperty(Option<String> option, List<Database.Vendor> vendors, String key) {
394+
return fromOption(option)
395+
.isEnabled(() -> vendors.stream().anyMatch(vendor -> isTLSEnabledAndPropertyNotSet(vendor, key)))
396+
.to("quarkus.datasource.jdbc.additional-jdbc-properties." + key)
397+
.build();
398+
}
399+
400+
private static boolean isTLSEnabledAndPropertyNotSet(Database.Vendor expectedVendor, String key) {
401+
var tlsModeValue = Configuration.getConfigValue(DatabaseOptions.DB_TLS_MODE).getValue();
402+
if (tlsModeValue == null) {
403+
return false;
404+
}
405+
if (DatabaseOptions.DatabaseTlsMode.valueOf(tlsModeValue.toUpperCase()) == DatabaseOptions.DatabaseTlsMode.DISABLED) {
406+
return false;
407+
}
408+
var configuredVendor = Database.getVendor(Configuration.getConfigValue(DB).getValue()).orElseThrow();
409+
if (configuredVendor != expectedVendor) {
410+
return false;
411+
}
412+
String dbUrl = Configuration.getConfigValue(DatabaseOptions.DB_URL).getValueOrDefault("");
413+
// oracle requires tcps protocol for TLS - if not present, TLS is disabled
414+
return (expectedVendor != Database.Vendor.ORACLE || dbUrl.toLowerCase().contains("tcps")) &&
415+
!dbUrl.contains(key);
416+
}
417+
418+
private static BooleanSupplier isVendor(Database.Vendor vendor) {
419+
return () -> Database.getVendor(Configuration.getConfigValue(DB).getValue())
420+
.map(vendor::equals)
421+
.orElse(Boolean.FALSE);
422+
}
339423
}

0 commit comments

Comments
 (0)