fix: fail to start if the admin user can't be added (#31207)

also allowing the bootstrap options to be used by the cli, which
requires hidden options to stay hidden

and a minor refactoring for clarity

closes: #31160

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2024-07-18 10:27:48 -04:00 committed by GitHub
parent 14a9927e29
commit d970521415
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 43 additions and 112 deletions

View file

@ -157,10 +157,14 @@ public final class Help extends CommandLine.Help {
}
private boolean isVisible(OptionSpec option) {
if (option.description().length == 0) {
// do not show options without a description
if (option.description().length == 0 || option.hidden()) {
// do not show options without a description nor hidden
return false;
}
if (ALL_OPTIONS) {
return true;
}
String optionName = undecorateDuplicitOptionName(option.longestName());
@ -172,30 +176,13 @@ public final class Help extends CommandLine.Help {
if (mapper == null) {
final var disabledMapper = PropertyMappers.getDisabledMapper(optionName);
final var isDisabledMapper = disabledMapper.isPresent();
// Show disabled mappers, which do not have a description when they're enabled
final var isEnabledWhenEmpty = isDisabledMapper && disabledMapper.get().getEnabledWhen().isEmpty();
if (isEnabledWhenEmpty) {
return true;
}
if (ALL_OPTIONS && isDisabledMapper) {
return true;
}
// only filter mapped options, defaults to the hidden marker
return !option.hidden() && !isDisabledMapper;
return disabledMapper.flatMap(PropertyMapper::getEnabledWhen).isEmpty();
}
boolean isUnsupportedOption = !PropertyMappers.isSupported(mapper);
if (isUnsupportedOption) {
// unsupported options removed from help if all options are not requested
return !option.hidden() && ALL_OPTIONS;
}
return !option.hidden();
// unsupported options removed from help if all options are not requested
return PropertyMappers.isSupported(mapper);
}
public static void setAllOptions(boolean allOptions) {

View file

@ -18,9 +18,12 @@
package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.common.util.IoUtils;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
@ -97,7 +100,9 @@ public class BootstrapAdminService extends AbstractNonServerCommand {
@Override
public void onStart(QuarkusKeycloakApplication application) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
application.createTemporaryMasterRealmAdminService(clientId, clientSecret, /*bootstrap.expiration,*/ null);
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminService(clientId, clientSecret, /* bootstrap.expiration, */ session));
}
}

View file

@ -18,9 +18,12 @@
package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.common.util.IoUtils;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
@ -97,7 +100,9 @@ public class BootstrapAdminUser extends AbstractNonServerCommand {
@Override
public void onStart(QuarkusKeycloakApplication application) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
application.createTemporaryMasterRealmAdminUser(username, password, /*bootstrap.expiration,*/ null);
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminUser(username, password, /* bootstrap.expiration, */ session));
}
}

View file

@ -64,6 +64,7 @@ public final class PropertyMappers {
MAPPERS.addAll(ExportPropertyMappers.getMappers());
MAPPERS.addAll(ImportPropertyMappers.getMappers());
MAPPERS.addAll(TruststorePropertyMappers.getMappers());
MAPPERS.addAll(BootstrapAdminPropertyMappers.getMappers());
}
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {

View file

@ -98,33 +98,12 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
}
}
public void createTemporaryMasterRealmAdminUser(String adminUserName, String adminPassword, /*Integer adminExpiration,*/ KeycloakSession existingSession) {
KeycloakSessionTask task = session -> {
new ApplianceBootstrap(session).createTemporaryMasterRealmAdminUser(adminUserName, adminPassword /*, adminExpiration*/, false);
};
runAdminTask(adminUserName, existingSession, task);
public void createTemporaryMasterRealmAdminUser(String adminUserName, String adminPassword, /*Integer adminExpiration,*/ KeycloakSession session) {
new ApplianceBootstrap(session).createTemporaryMasterRealmAdminUser(adminUserName, adminPassword /*, adminExpiration*/, false);
}
public void createTemporaryMasterRealmAdminService(String clientId, String clientSecret, /*Integer adminExpiration,*/ KeycloakSession existingSession) {
KeycloakSessionTask task = session -> {
new ApplianceBootstrap(session).createTemporaryMasterRealmAdminService(clientId, clientSecret /*, adminExpiration*/);
};
runAdminTask(clientId, existingSession, task);
}
private void runAdminTask(String adminUserName, KeycloakSession existingSession, KeycloakSessionTask task) {
try {
if (existingSession == null) {
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, task);
} else {
task.run(existingSession);
}
} catch (Throwable t) {
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
}
public void createTemporaryMasterRealmAdminService(String clientId, String clientSecret, /*Integer adminExpiration,*/ KeycloakSession session) {
new ApplianceBootstrap(session).createTemporaryMasterRealmAdminService(clientId, clientSecret /*, adminExpiration*/);
}
private String getEnvOrProp(String envKey, String propKey) {

View file

@ -63,6 +63,13 @@ public class BootstrapAdminDistTest {
void createAdmin(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
}
@Test
@Launch({ "start-dev", "--bootstrap-admin-password=MY_PASSWORD" })
void createAdminWithCliOptions(LaunchResult result) {
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
result.getOutput().contains("Created temporary admin user with username temp-admin");
}
@Test
@Launch({ "bootstrap-admin", "service", "--no-prompt" })

View file

@ -48,30 +48,19 @@ public class FipsDistTest {
}
@Test
void testFipsApprovedModePasswordFails(KeycloakDistribution dist) {
void testFipsApprovedMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", "admin");
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "admin");
CLIResult cliResult = dist.run("start", "--fips-mode=strict");
cliResult.assertStarted();
cliResult.assertMessage(
"org.bouncycastle.crypto.fips.FipsUnapprovedOperationError: password must be at least 112 bits");
cliResult.assertMessage("password must be at least 112 bits");
cliResult.assertMessage("Java security providers: [ \n"
+ " KC(" + BCFIPS_VERSION + " Approved Mode, FIPS-JVM: " + KeycloakFipsSecurityProvider.isSystemFipsEnabled() + ") version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
});
}
@Test
void testFipsApprovedModePasswordSucceeds(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", "admin");
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "adminadminadmin");
CLIResult cliResult = dist.run("start", "--fips-mode=strict");
cliResult = dist.run("start", "--fips-mode=strict");
cliResult.assertStarted();
cliResult.assertMessage("Java security providers: [ \n"
+ " KC(" + BCFIPS_VERSION + " Approved Mode, FIPS-JVM: " + KeycloakFipsSecurityProvider.isSystemFipsEnabled() + ") version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
cliResult.assertMessage("Created temporary admin user with username admin");
});
}

View file

@ -171,16 +171,10 @@ Logging:
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
when GELF is activated.
--log-gelf-version <version>
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
Available only when GELF is activated.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
--log-syslog-app-name <name>
The app name used when formatting the message in RFC5424 format. Default:
keycloak. Available only when Syslog is activated.
--log-syslog-endpoint <host:port>
The IP address and port of the syslog server. Default: localhost:514.
Available only when Syslog is activated.
@ -217,4 +211,4 @@ Export:
--users-per-file <number>
Set the number of users per file. It is used only if 'users' is set to
'different_files'. Increasing this number leads to exponentially increasing
export times. Default: 50.
export times. Default: 50.

View file

@ -171,16 +171,10 @@ Logging:
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
when GELF is activated.
--log-gelf-version <version>
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
Available only when GELF is activated.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
--log-syslog-app-name <name>
The app name used when formatting the message in RFC5424 format. Default:
keycloak. Available only when Syslog is activated.
--log-syslog-endpoint <host:port>
The IP address and port of the syslog server. Default: localhost:514.
Available only when Syslog is activated.
@ -211,4 +205,4 @@ Import:
--file <file> Set the path to a file that will be read.
--override <true|false>
Set if existing data should be overwritten. If set to false, data will be
ignored. Default: true.
ignored. Default: true.

View file

@ -191,10 +191,6 @@ Hostname v1 (Deprecated):
headers to allow internal and external applications. If all applications use
the public URL this option should be enabled. Default: false. Available only
when hostname:v1 feature is enabled.
--hostname-strict-https <true|false>
DEPRECATED. Forces frontend URLs to use the 'https' scheme. If set to false,
the HTTP scheme is inferred from requests. Default: true. Available only
when hostname:v1 feature is enabled.
--hostname-url <url> DEPRECATED. Set the base URL for frontend URLs, including scheme, host, port
and path. Available only when hostname:v1 feature is enabled.
@ -385,16 +381,10 @@ Logging:
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
when GELF is activated.
--log-gelf-version <version>
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
Available only when GELF is activated.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
--log-syslog-app-name <name>
The app name used when formatting the message in RFC5424 format. Default:
keycloak. Available only when Syslog is activated.
--log-syslog-endpoint <host:port>
The IP address and port of the syslog server. Default: localhost:514.
Available only when Syslog is activated.
@ -431,4 +421,4 @@ Security:
Do NOT start the server using this command when deploying to production.
Use 'kc.sh start-dev --help-all' to list all available options, including build
options.
options.

View file

@ -192,10 +192,6 @@ Hostname v1 (Deprecated):
headers to allow internal and external applications. If all applications use
the public URL this option should be enabled. Default: false. Available only
when hostname:v1 feature is enabled.
--hostname-strict-https <true|false>
DEPRECATED. Forces frontend URLs to use the 'https' scheme. If set to false,
the HTTP scheme is inferred from requests. Default: true. Available only
when hostname:v1 feature is enabled.
--hostname-url <url> DEPRECATED. Set the base URL for frontend URLs, including scheme, host, port
and path. Available only when hostname:v1 feature is enabled.
@ -386,16 +382,10 @@ Logging:
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
when GELF is activated.
--log-gelf-version <version>
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
Available only when GELF is activated.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
--log-syslog-app-name <name>
The app name used when formatting the message in RFC5424 format. Default:
keycloak. Available only when Syslog is activated.
--log-syslog-endpoint <host:port>
The IP address and port of the syslog server. Default: localhost:514.
Available only when Syslog is activated.
@ -436,4 +426,4 @@ By default, this command tries to update the server configuration by running a
$ kc.sh start '--optimized'
By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.
configuration you have set when manually running the 'build' command.

View file

@ -177,10 +177,6 @@ Hostname v1 (Deprecated):
headers to allow internal and external applications. If all applications use
the public URL this option should be enabled. Default: false. Available only
when hostname:v1 feature is enabled.
--hostname-strict-https <true|false>
DEPRECATED. Forces frontend URLs to use the 'https' scheme. If set to false,
the HTTP scheme is inferred from requests. Default: true. Available only
when hostname:v1 feature is enabled.
--hostname-url <url> DEPRECATED. Set the base URL for frontend URLs, including scheme, host, port
and path. Available only when hostname:v1 feature is enabled.
@ -337,16 +333,10 @@ Logging:
DEPRECATED. Set the format for the GELF timestamp field. Uses Java
SimpleDateFormat pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. Available only
when GELF is activated.
--log-gelf-version <version>
The GELF version to be used. Possible values are: 1.0, 1.1. Default: 1.1.
Available only when GELF is activated.
--log-level <category:level>
The log level of the root category or a comma-separated list of individual
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
--log-syslog-app-name <name>
The app name used when formatting the message in RFC5424 format. Default:
keycloak. Available only when Syslog is activated.
--log-syslog-endpoint <host:port>
The IP address and port of the syslog server. Default: localhost:514.
Available only when Syslog is activated.
@ -378,4 +368,4 @@ By default, this command tries to update the server configuration by running a
$ kc.sh start '--optimized'
By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.
configuration you have set when manually running the 'build' command.