Build command should not allow runtime options

Closes #9618
This commit is contained in:
Pedro Igor 2022-01-21 11:35:22 -03:00
parent 873a44459a
commit b53c5d5eee
13 changed files with 95 additions and 266 deletions

View file

@ -17,11 +17,9 @@
package org.keycloak.quarkus.deployment;
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames;
import static org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
@ -70,6 +68,7 @@ import io.quarkus.runtime.LaunchMode;
import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler;
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.smallrye.config.ConfigValue;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import org.hibernate.cfg.AvailableSettings;
@ -311,14 +310,16 @@ class KeycloakProcessor {
Properties properties = new Properties();
for (String name : getPropertyNames()) {
if (isNotPersistentProperty(name)) {
PropertyMapper mapper = PropertyMappers.getMapper(name);
if (mapper == null) {
continue;
}
Optional<String> value = Configuration.getOptionalValue(name);
ConfigValue value = Configuration.getConfigValue(mapper.getFrom());
if (value.isPresent()) {
properties.put(name, value.get());
if (mapper.isBuildTime() && value != null && value.getValue() != null) {
properties.put(name, value.getValue());
}
}
@ -334,11 +335,6 @@ class KeycloakProcessor {
}
}
private boolean isNotPersistentProperty(String name) {
// these properties are ignored from the build time properties as they are runtime-specific
return !name.startsWith(NS_KEYCLOAK) || "kc.home.dir".equals(name) || CLI_ARGS.equals(name);
}
/**
* This will cause quarkus tu include specified modules in the jandex index. For example keycloak-services is needed as it includes
* most of the JAX-RS resources, which are required to register Resteasy builtin providers. See {@link ResteasyDeployment#isRegisterBuiltin()}.

View file

@ -87,17 +87,13 @@ public class KeycloakRecorder {
@Override
public String resolve(String feature) {
if (feature.startsWith("keycloak.profile.feature")) {
feature = feature.replaceAll("keycloak\\.profile\\.feature", "kc\\.features");
feature = feature.replaceFirst("keycloak\\.profile\\.feature.", "kc\\.features-");
} else {
feature = "kc.features";
}
Optional<String> value = getBuildTimeProperty(feature);
if (value.isEmpty()) {
value = getBuildTimeProperty(feature.replaceAll("\\.features\\.", "\\.features-"));
}
if (value.isPresent()) {
return value.get();
}

View file

@ -123,13 +123,6 @@ public final class Picocli {
if(!isDevMode()) {
if (cmd != null) {
cmd.getOut().println("Changes detected in configuration. Updating the server image.");
List<String> cliInput = getSanitizedCliInput();
cmd.getOut()
.printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n%n",
Build.NAME,
Environment.getCommand(),
Build.NAME,
String.join(" ", cliInput) + "\n");
}
}
return true;
@ -144,12 +137,18 @@ public final class Picocli {
* @return a list of potentially masked properties in CLI format, e.g. `--db-password=*******`
* instead of the actual passwords value.
*/
private static List<String> getSanitizedCliInput() {
private static List<String> getSanitizedRuntimeCliOptions() {
List<String> properties = new ArrayList<>();
parseConfigArgs(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
PropertyMapper mapper = PropertyMappers.getMapper(key);
if (mapper != null && mapper.isBuildTime()) {
return;
}
properties.add(key + "=" + formatValue(key, value));
}
});
@ -176,7 +175,7 @@ public final class Picocli {
cmd.execute(configArgsList.toArray(new String[0]));
if(!isDevMode()) {
cmd.getOut().printf("Next time you run the server, just run:%n%n\t%s %s%n%n", Environment.getCommand(), Start.NAME);
cmd.getOut().printf("Next time you run the server, just run:%n%n\t%s %s %s%n%n", Environment.getCommand(), Start.NAME, String.join(" ", getSanitizedRuntimeCliOptions()));
}
}
@ -271,9 +270,9 @@ public final class Picocli {
.build());
}
addOption(spec, Start.NAME, hasAutoBuildOption(cliArgs));
addOption(spec, StartDev.NAME, true);
addOption(spec, Build.NAME, true);
addOption(spec, Start.NAME, hasAutoBuildOption(cliArgs), true);
addOption(spec, StartDev.NAME, true, true);
addOption(spec, Build.NAME, true, hasAutoBuildOption(cliArgs));
CommandLine cmd = new CommandLine(spec);
@ -286,9 +285,13 @@ public final class Picocli {
return cmd;
}
private static void addOption(CommandSpec spec, String command, boolean includeBuildTime) {
private static void addOption(CommandSpec spec, String command, boolean includeBuildTime, boolean includeRuntime) {
CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec();
List<PropertyMapper> mappers = new ArrayList<>(PropertyMappers.getRuntimeMappers());
List<PropertyMapper> mappers = new ArrayList<>();
if (includeRuntime) {
mappers.addAll(PropertyMappers.getRuntimeMappers());
}
if (includeBuildTime) {
mappers.addAll(PropertyMappers.getBuildTimeMappers());

View file

@ -66,9 +66,6 @@ public final class Build extends AbstractCommand implements Runnable {
public static final String NAME = "build";
@Mixin
HelpAllMixin helpAllMixin;
@Override
public void run() {
exitWithErrorIfDevProfileIsSetAndNotStartDev();

View file

@ -53,6 +53,7 @@ import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
public class ConfigArgsConfigSource extends PropertiesConfigSource {
public static final String CLI_ARGS = "kc.config.args";
public static final String NAME = "CliConfigSource";
private static final String ARG_SEPARATOR = ";;";
private static final Pattern ARG_SPLIT = Pattern.compile(";;");
private static final Pattern ARG_KEY_VALUE_SPLIT = Pattern.compile("=");
@ -64,7 +65,7 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
}
protected ConfigArgsConfigSource() {
super(parseArgument(), "CliConfigSource", 600);
super(parseArgument(), NAME, 600);
}
public static void setCliArgs(String[] args) {

View file

@ -78,12 +78,4 @@ public class HelpCommandTest {
CLIResult cliResult = (CLIResult) result;
cliResult.assertHelp();
}
@Test
@Launch({ Build.NAME, "--help-all" })
void testBuildHelpAll(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertHelp();
}
}

View file

@ -39,13 +39,13 @@ import io.quarkus.test.junit.main.LaunchResult;
public class BuildAndStartDistTest {
@Test
@Launch({ "build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" })
@Launch({ "build", "--cache=local" })
@Order(1)
void firstYouBuild(LaunchResult result) {
}
@Test
@Launch({ "start" })
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false" })
@Order(2)
void thenYouStart(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;

View file

@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import io.quarkus.test.junit.main.Launch;
@ -53,4 +54,11 @@ class BuildCommandDistTest {
() -> "The Error Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
assertEquals(4, result.getErrorStream().size());
}
@Test
@Launch({ "build", "--db=postgres", "--db-username=myuser", "--db-password=mypassword", "--http-enabled=true" })
void testFailRuntimeOptions(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Unknown options: '--db-username=myuser', '--db-password=mypassword', '--http-enabled=true'");
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.cli.dist;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@DistributionTest
class EnableFeatureDistTest {
@Test
@Launch({ "build", "--features=preview" })
void testEnablePreviewFeatures(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Preview feature enabled: admin_fine_grained_authz");
cliResult.assertMessage("Preview feature enabled: openshift_integration");
cliResult.assertMessage("Preview feature enabled: scripts");
cliResult.assertMessage("Preview feature enabled: token_exchange");
}
@Test
@Launch({ "build", "--features-token_exchange=enabled" })
void testEnableSinglefeature(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Preview feature enabled: token_exchange");
}
}

View file

@ -18,6 +18,7 @@
package org.keycloak.it.cli.dist;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
@ -27,7 +28,6 @@ import org.keycloak.it.junit5.extension.DistributionTest;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
@DistributionTest
public class StartCommandDistTest extends StartCommandTest {
@ -48,10 +48,17 @@ public class StartCommandDistTest extends StartCommandTest {
}
@Test
@Launch({ "start", "--auto-build", "--db-password=secret", "--https-key-store-password=secret"})
void testStartWithAutoBuildDoesntShowCredentialsInConsole(LaunchResult result) {
@Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" })
void testStartUsingAutoBuild(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
assertTrue(cliResult.getOutput().contains("--db-password=" + PropertyMappers.VALUE_MASK));
assertTrue(cliResult.getOutput().contains("--https-key-store-password=" + PropertyMappers.VALUE_MASK));
cliResult.assertMessage("Changes detected in configuration. Updating the server image.");
cliResult.assertMessage("Updating the configuration and installing your custom providers, if any. Please wait.");
cliResult.assertMessage("Server configuration updated and persisted. Run the following command to review the configuration:");
cliResult.assertMessage("kc.sh show-config");
cliResult.assertMessage("Next time you run the server, just run:");
cliResult.assertMessage("kc.sh start --http-enabled=true --hostname-strict=false");
assertFalse(cliResult.getOutput().contains("--cache"));
cliResult.assertStarted();
}
}

View file

@ -21,7 +21,6 @@ optimal runtime.
Options:
-h, --help This help message.
--help-all This same help message but with additional options.
Cluster:

View file

@ -1,215 +0,0 @@
Creates a new and optimized server image.
Usage:
kc.sh build [OPTIONS]
Creates a new and optimized server image based on the configuration options
passed to this command. Once created, the configuration will be persisted and
read during startup without having to pass them over again.
Some configuration options require this command to be executed in order to
actually change a configuration. For instance
- Change database vendor
- Enable/disable features
- Enable/Disable providers or set a default
Consider running this command before running the server in production for an
optimal runtime.
Options:
-h, --help This help message.
--help-all This same help message but with additional options.
Cluster:
--cache <type> Defines the cache mechanism for high-availability. By default, a 'ispn' cache
is used to create a cluster between multiple server nodes. A 'local' cache
disables clustering and is intended for development and testing purposes.
Default: ispn.
--cache-config-file <file>
Defines the file from which cache configuration should be loaded from.
--cache-stack <stack>
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Database:
--db <vendor> The database vendor. Possible values are: h2-file, h2-mem, mariadb, mssql,
mssql-2012, mysql, oracle, postgres, postgres-95
--db-password <password>
The password of the database user.
--db-pool-initial-size <size>
The initial size of the connection pool.
--db-pool-max-size <size>
The maximum size of the connection pool. Default: 100.
--db-pool-min-size <size>
The minimal size of the connection pool.
--db-schema <schema> The database schema to be used.
--db-url <jdbc-url> The full database JDBC URL. If not provided, a default URL is set based on the
selected database vendor. For instance, if using 'postgres', the default
JDBC URL would be 'jdbc:postgresql://localhost/keycloak'.
--db-url-database <dbname>
Sets the database name of the default JDBC URL of the chosen vendor. If the
`db-url` option is set, this option is ignored.
--db-url-host <hostname>
Sets the hostname of the default JDBC URL of the chosen vendor. If the
`db-url` option is set, this option is ignored.
--db-url-properties <properties>
Sets the properties of the default JDBC URL of the chosen vendor. If the
`db-url` option is set, this option is ignored.
--db-username <username>
The username of the database user.
Feature:
--features-account2 <enabled|disabled>
Enables the ACCOUNT2 feature.
--features-account_api <enabled|disabled>
Enables the ACCOUNT_API feature.
--features-admin2 <enabled|disabled>
Enables the ADMIN2 feature.
--features-admin_fine_grained_authz <enabled|disabled>
Enables the ADMIN_FINE_GRAINED_AUTHZ feature.
--features-authorization <enabled|disabled>
Enables the AUTHORIZATION feature.
--features-ciba <enabled|disabled>
Enables the CIBA feature.
--features-client_policies <enabled|disabled>
Enables the CLIENT_POLICIES feature.
--features-declarative_user_profile <enabled|disabled>
Enables the DECLARATIVE_USER_PROFILE feature.
--features-docker <enabled|disabled>
Enables the DOCKER feature.
--features-dynamic_scopes <enabled|disabled>
Enables the DYNAMIC_SCOPES feature.
--features-impersonation <enabled|disabled>
Enables the IMPERSONATION feature.
--features-map_storage <enabled|disabled>
Enables the MAP_STORAGE feature.
--features-openshift_integration <enabled|disabled>
Enables the OPENSHIFT_INTEGRATION feature.
--features-par <enabled|disabled>
Enables the PAR feature.
--features-scripts <enabled|disabled>
Enables the SCRIPTS feature.
--features-token_exchange <enabled|disabled>
Enables the TOKEN_EXCHANGE feature.
--features-upload_scripts <enabled|disabled>
Enables the UPLOAD_SCRIPTS feature.
--features-web_authn <enabled|disabled>
Enables the WEB_AUTHN feature.
-ft, --features <preview>
Enables all tech preview features.
Hostname:
--hostname <hostname>
Hostname for the Keycloak server.
--hostname-admin <url>
Overrides the hostname for the admin console and APIs.
--hostname-path <path>
This should be set if proxy uses a different context-path for Keycloak.
--hostname-strict <true|false>
Disables dynamically resolving the hostname from request headers. Should
always be set to true in production, unless proxy verifies the Host header.
Default: true.
--hostname-strict-backchannel <true|false>
By default backchannel URLs are dynamically resolved from request headers to
allow internal an external applications. If all applications use the public
URL this option should be enabled. Default: false.
HTTP/TLS:
--http-enabled <true|false>
Enables the HTTP listener. Default: false.
--http-host <host> The used HTTP Host. Default: 0.0.0.0.
--http-port <port> The used HTTP port. Default: 8080.
--http-relative-path <path>
Set the path relative to '/' for serving resources. Default: /.
--https-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file <file>
The file path to a private key in PEM format.
--https-cipher-suites <ciphers>
The cipher suites to use. If none is given, a reasonable default is selected.
--https-client-auth <auth>
Configures the server to require/request client authentication. Possible
Values: none, request, required. Default: none.
--https-key-store-file <file>
The key store which holds the certificate information instead of specifying
separate files.
--https-key-store-password <password>
The password of the key store file. Default: password.
--https-key-store-type <type>
The type of the key store file. If not given, the type is automatically
detected based on the file name.
--https-port <port> The used HTTPS port. Default: 8443.
--https-protocols <protocols>
The list of protocols to explicitly enable. Default: TLSv1.3.
--https-trust-store-file <file>
The trust store which holds the certificate information of the certificates to
trust.
--https-trust-store-password <password>
The password of the trust store file.
--https-trust-store-type <type>
The type of the trust store file. If not given, the type is automatically
detected based on the file name.
Metrics:
--metrics-enabled <true|false>
If the server should expose metrics and healthcheck. If enabled, metrics are
available at the '/metrics' endpoint and healthcheck at the '/health'
endpoint. Default: false.
Proxy:
--proxy <mode> The proxy address forwarding mode if the server is behind a reverse proxy.
Possible values are: none,edge,reencrypt,passthrough Default: none.
Vault:
--vault-file-path <dir>
If set, secrets can be obtained by reading the content of files within the
given path.
--vault-hashicorp-paths <paths>
A set of one or more paths that should be used when looking up secrets.
Examples:
Optimize the server based on a profile configuration:
$ kc.sh --profile=prod build
Change database settings:
$ kc.sh build --db=postgres [--db-url][--db-username][--db-password]
Enable a feature:
$ kc.sh build --features-<feature_name>=[enabled|disabled]
Or alternatively, enable all tech preview features:
$ kc.sh build --features=preview
Enable metrics:
$ kc.sh build --metrics-enabled=true
Change the relative path:
$ kc.sh build --http-relative-path=/auth
You can also use the "--auto-build" option when starting the server to avoid
running this command every time you change a configuration:
$ kc.sh start --auto-build <OPTIONS>
By doing that you have an additional overhead when the server is starting.
Use 'kc.sh build --help-all' to list all available options, including the start
options.

View file

@ -5,7 +5,6 @@
<echo>Re-augmenting...</echo>
<exec osfamily="unix" dir="${auth.server.home}/bin" executable="./kc.sh" failonerror="true">
<arg value="build"/>
<arg value="--http-enabled=true"/>
<arg value="--http-relative-path=/auth"/>
<arg value="--cache=local"/>
</exec>