Make it possible to switch between BC and BC-FIPS libraries

Closes #12424
This commit is contained in:
Pedro Igor 2022-09-15 22:56:25 -03:00
parent 4016dd95d2
commit 00e4c3567a
19 changed files with 399 additions and 30 deletions

View file

@ -26,13 +26,13 @@ public class CryptoIntegration {
if (cryptoProvider == null) {
cryptoProvider = detectProvider(classLoader);
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
if (logger.isTraceEnabled()) {
logger.tracef(dumpJavaSecurityProviders());
}
}
}
}
if (logger.isTraceEnabled()) {
logger.tracef(dumpJavaSecurityProviders());
}
}
public static CryptoProvider getProvider() {
@ -67,4 +67,7 @@ public class CryptoIntegration {
return builder.append("]").toString();
}
public static void setProvider(CryptoProvider provider) {
cryptoProvider = provider;
}
}

View file

@ -0,0 +1,17 @@
package org.keycloak.crypto.fips;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
/**
* <p>A {@link FIPS1402Provider} that forces BC to run in FIPS approve mode by default.
*
* <p>In order to set the default mode the {@code org.bouncycastle.fips.approved_only} must be set. Otherwise,
* calling {@link CryptoServicesRegistrar#setApprovedOnlyMode(boolean)} the mode is set on a per thread-basis and does not work
* well when handling requests using multiple threads.
*/
public class Fips1402StrictCryptoProvider extends FIPS1402Provider {
static {
System.setProperty("org.bouncycastle.fips.approved_only", Boolean.TRUE.toString());
}
}

View file

@ -1,5 +1,7 @@
package org.keycloak.crypto.fips;
import static org.bouncycastle.crypto.CryptoServicesRegistrar.isInApprovedOnlyMode;
import java.security.Provider;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
@ -17,11 +19,10 @@ public class KeycloakFipsSecurityProvider extends Provider {
private final BouncyCastleFipsProvider bcFipsProvider;
public KeycloakFipsSecurityProvider(BouncyCastleFipsProvider bcFipsProvider) {
super("KC", 1, "Keycloak pseudo provider");
super("KC(" + bcFipsProvider.toString() + (isInApprovedOnlyMode() ? " Approved Mode" : "") + ")", 1, "Keycloak pseudo provider");
this.bcFipsProvider = bcFipsProvider;
}
@Override
public synchronized final Service getService(String type, String algorithm) {
// Using 'SecureRandom.getInstance("SHA1PRNG")' will delegate to BCFIPS DEFAULT provider instead of returning SecureRandom based on potentially unsecure SHA1PRNG

View file

@ -0,0 +1,9 @@
package org.keycloak.config;
public class ClassLoaderOptions {
public static final Option<String> IGNORE_ARTIFACTS = new OptionBuilder<>("class-loader-ignore-artifacts", String.class)
.category(OptionCategory.GENERAL)
.hidden()
.build();
}

View file

@ -14,6 +14,7 @@ public enum OptionCategory {
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED),
SECURITY("Security", 120, ConfigSupportLevel.EXPERIMENTAL),
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
private String heading;

View file

@ -0,0 +1,31 @@
package org.keycloak.config;
public class SecurityOptions {
public enum FipsMode {
enabled("org.keycloak.crypto.fips.FIPS1402Provider"),
strict("org.keycloak.crypto.fips.Fips1402StrictCryptoProvider"),
disabled("org.keycloak.crypto.def.DefaultCryptoProvider");
private String providerClassName;
FipsMode(String providerClassName) {
this.providerClassName = providerClassName;
}
public boolean isFipsEnabled() {
return this.equals(enabled) || this.equals(strict);
}
public String getProviderClassName() {
return providerClassName;
}
}
public static final Option<FipsMode> FIPS_MODE = new OptionBuilder<>("fips-mode", FipsMode.class)
.category(OptionCategory.SECURITY)
.buildTime(true)
.description("Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on non-approved mode. For full FIPS compliance, set 'strict' to run on approved mode.")
.defaultValue(FipsMode.disabled)
.build();
}

View file

@ -89,6 +89,7 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config;
import org.keycloak.config.SecurityOptions;
import org.keycloak.config.StorageOptions;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionSpi;
@ -563,6 +564,17 @@ class KeycloakProcessor {
}));
}
@Consume(KeycloakSessionFactoryPreInitBuildItem.class)
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void setCryptoProvider(KeycloakRecorder recorder) {
SecurityOptions.FipsMode fipsMode = Configuration.getOptionalValue(
MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey()).map(
SecurityOptions.FipsMode::valueOf).orElse(SecurityOptions.FipsMode.disabled);
recorder.setCryptoProvider(fipsMode);
}
@BuildStep(onlyIf = IsDevelopment.class)
void configureDevMode(BuildProducer<HotDeploymentWatchedFileBuildItem> hotFiles) {
hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.conf"));

View file

@ -148,7 +148,17 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>${keycloak.crypto.artifactId}</artifactId>
<artifactId>keycloak-crypto-default</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-crypto-fips1402</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>

View file

@ -38,6 +38,9 @@ import io.vertx.ext.web.RoutingContext;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.config.SecurityOptions;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
@ -70,7 +73,7 @@ public class KeycloakRecorder {
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<Class<? extends Provider>, String> defaultProviders,
Map<String, ProviderFactory> preConfiguredProviders,
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, Boolean reaugmented) {
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, boolean reaugmented) {
Config.init(new MicroProfileConfigProvider());
Profile.setInstance(new QuarkusProfile());
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented));
@ -162,4 +165,20 @@ public class KeycloakRecorder {
}
};
}
public void setCryptoProvider(SecurityOptions.FipsMode fipsMode) {
String cryptoProvider = fipsMode.getProviderClassName();
try {
CryptoIntegration.setProvider(
(CryptoProvider) Thread.currentThread().getContextClassLoader().loadClass(cryptoProvider).getDeclaredConstructor().newInstance());
} catch (ClassNotFoundException | NoClassDefFoundError cause) {
if (fipsMode.isFipsEnabled()) {
throw new RuntimeException("Failed to configure FIPS. Make sure you have added the Bouncy Castle FIPS dependencies to the 'providers' directory.");
}
throw new RuntimeException("Unexpected error when configuring the crypto provider: " + cryptoProvider, cause);
} catch (Exception cause) {
throw new RuntimeException("Unexpected error when configuring the crypto provider: " + cryptoProvider, cause);
}
}
}

View file

@ -0,0 +1,44 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
import org.keycloak.config.ClassLoaderOptions;
import org.keycloak.config.SecurityOptions;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
final class ClassLoaderPropertyMappers {
private ClassLoaderPropertyMappers(){}
public static PropertyMapper[] getMappers() {
return new PropertyMapper[] {
fromOption(ClassLoaderOptions.IGNORE_ARTIFACTS)
.to("quarkus.class-loading.removed-artifacts")
.transformer(ClassLoaderPropertyMappers::resolveIgnoredArtifacts)
.build()
};
}
private static Optional<String> resolveIgnoredArtifacts(Optional<String> value, ConfigSourceInterceptorContext context) {
if (Environment.isRebuildCheck() || Environment.isRebuild()) {
ConfigValue fipsEnabled = Configuration.getConfigValue(
MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + SecurityOptions.FIPS_MODE.getKey());
if (fipsEnabled != null && SecurityOptions.FipsMode.valueOf(fipsEnabled.getValue()).isFipsEnabled()) {
return Optional.of(
"org.bouncycastle:bcprov-jdk15on,org.bouncycastle:bcpkix-jdk15on,org.keycloak:keycloak-crypto-default");
}
return Optional.of(
"org.keycloak:keycloak-crypto-fips1402,org.bouncycastle:bc-fips,org.bouncycastle:bctls-fips,org.bouncycastle:bcpkix-fips");
}
return value;
}
}

View file

@ -38,6 +38,8 @@ public final class PropertyMappers {
MAPPERS.addAll(LoggingPropertyMappers.getMappers());
MAPPERS.addAll(TransactionPropertyMappers.getTransactionPropertyMappers());
MAPPERS.addAll(StoragePropertyMappers.getMappers());
MAPPERS.addAll(ClassLoaderPropertyMappers.getMappers());
MAPPERS.addAll(SecurityPropertyMappers.getMappers());
}
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {

View file

@ -0,0 +1,43 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
import org.keycloak.config.SecurityOptions;
import io.smallrye.config.ConfigSourceInterceptorContext;
final class SecurityPropertyMappers {
private SecurityPropertyMappers() {
}
public static PropertyMapper[] getMappers() {
return new PropertyMapper[] {
fromOption(SecurityOptions.FIPS_MODE).transformer(SecurityPropertyMappers::resolveFipsMode)
.paramLabel("mode")
.build()
};
}
private static Optional<String> resolveFipsMode(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.isEmpty()) {
return of(SecurityOptions.FipsMode.disabled.toString());
}
return of(SecurityOptions.FipsMode.valueOf(value.get()).toString());
}
private static Optional<String> resolveSecurityProvider(Optional<String> value,
ConfigSourceInterceptorContext configSourceInterceptorContext) {
SecurityOptions.FipsMode fipsMode = value.map(SecurityOptions.FipsMode::valueOf)
.orElse(SecurityOptions.FipsMode.disabled);
if (fipsMode.isFipsEnabled()) {
return of("BCFIPS");
}
return value;
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2022 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.utils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactRequest;
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
import io.quarkus.bootstrap.utils.BuildToolHelper;
public final class Maven {
public static Path resolveArtifact(String groupId, String artifactId) {
try {
Path projectDir = BuildToolHelper.getProjectDir(Paths.get(Maven.class.getResource(".").toURI()));
BootstrapMavenContext ctx = new BootstrapMavenContext(
BootstrapMavenContext.config().setPreferPomsFromWorkspace(true).setWorkspaceModuleParentHierarchy(true)
.setCurrentProject(projectDir.toString()));
LocalProject project = ctx.getCurrentProject();
RepositorySystem repositorySystem = ctx.getRepositorySystem();
List<RemoteRepository> remoteRepositories = ctx.getRemoteRepositories();
ArtifactDescriptorResult descrResult = repositorySystem.readArtifactDescriptor(
ctx.getRepositorySystemSession(),
new ArtifactDescriptorRequest()
.setArtifact(new DefaultArtifact(project.getGroupId(), project.getArtifactId(), "pom", project.getVersion()))
.setRepositories(remoteRepositories));
for (org.eclipse.aether.graph.Dependency dependency : descrResult.getManagedDependencies()) {
Artifact artifact = dependency.getArtifact();
if (artifact.getGroupId().equals(groupId) && artifact.getArtifactId().equals(artifactId)) {
return repositorySystem.resolveArtifact(
ctx.getRepositorySystemSession(),
new ArtifactRequest().setArtifact(artifact)
.setRepositories(remoteRepositories))
.getArtifact().getFile().toPath();
}
}
} catch (Exception cause) {
throw new RuntimeException("Failed to resolve artifact", cause);
}
return null;
}
}

View file

@ -467,6 +467,14 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
public void copyProvider(String groupId, String artifactId) {
try {
Files.copy(Maven.resolveArtifact(groupId, artifactId), getDistPath().resolve("providers").resolve(artifactId + ".jar"));
} catch (IOException cause) {
throw new RuntimeException("Failed to copy JAR file to 'providers' directory", cause);
}
}
private void updateProperties(Consumer<Properties> propertiesConsumer, File propertiesFile) {
Properties properties = new Properties();

View file

@ -0,0 +1,74 @@
/*
* Copyright 2022 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 java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST)
@RawDistOnly(reason = "Containers are immutable")
public class FipsDistTest {
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=enabled", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
@BeforeStartDistribution(FipsDistTest.InstallBcFipsDependencies.class)
void testFipsNonApprovedMode(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertStarted();
cliResult.assertMessage("Java security providers: [ \n"
+ " KC(BCFIPS version 1.000203) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
}
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=strict", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
@BeforeStartDistribution(FipsDistTest.InstallBcFipsDependencies.class)
void testFipsApprovedMode(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertStarted();
cliResult.assertMessage("org.bouncycastle.crypto.fips.FipsUnapprovedOperationError: password must be at least 112 bits");
cliResult.assertMessage("Java security providers: [ \n"
+ " KC(BCFIPS version 1.000203 Approved Mode) version 1.0 - class org.keycloak.crypto.fips.KeycloakFipsSecurityProvider");
}
@Test
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--fips-mode=enabled", "--cache=local", "--log-level=org.keycloak.common.crypto.CryptoIntegration:trace" })
void failStartDueToMissingFipsDependencies(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertError("Failed to configure FIPS. Make sure you have added the Bouncy Castle FIPS dependencies to the 'providers' directory.");
}
public static class InstallBcFipsDependencies implements Consumer<KeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
RawKeycloakDistribution rawDist = (RawKeycloakDistribution) distribution;
rawDist.copyProvider("org.bouncycastle", "bc-fips");
rawDist.copyProvider("org.bouncycastle", "bctls-fips");
rawDist.copyProvider("org.bouncycastle", "bcpkix-fips");
}
}
}

View file

@ -279,6 +279,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
Security (Experimental):
--fips-mode <mode> Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
non-approved mode. For full FIPS compliance, set 'strict' to run on approved
mode. Possible values are: enabled, strict, disabled. Default: disabled.
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

View file

@ -128,18 +128,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:
@ -174,7 +174,8 @@ HTTP/TLS:
--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: /.
Set the path relative to '/' for serving resources. The path must start with a
'/'. Default: /.
--https-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file <file>
@ -278,6 +279,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
Security (Experimental):
--fips-mode <mode> Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
non-approved mode. For full FIPS compliance, set 'strict' to run on approved
mode. Possible values are: enabled, strict, disabled. Default: disabled.
Do NOT start the server using this command when deploying to production.
Use 'kc.bat start-dev --help-all' to list all available options, including

View file

@ -285,6 +285,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
Security (Experimental):
--fips-mode <mode> Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
non-approved mode. For full FIPS compliance, set 'strict' to run on approved
mode. Possible values are: enabled, strict, disabled. Default: disabled.
By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option:

View file

@ -134,18 +134,18 @@ Transaction:
Feature:
--features <feature> Enables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
--features-disabled <feature>
Disables a set of one or more features. Possible values are: authorization,
account2, account-api, admin-fine-grained-authz, admin2, docker,
impersonation, openshift-integration, scripts, token-exchange, web-authn,
client-policies, ciba, map-storage, par, declarative-user-profile,
dynamic-scopes, client-secret-rotation, step-up-authentication,
recovery-codes, update-email, preview.
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
docker, impersonation, openshift-integration, scripts, token-exchange,
web-authn, client-policies, ciba, map-storage, par,
declarative-user-profile, dynamic-scopes, client-secret-rotation,
step-up-authentication, recovery-codes, update-email, preview.
Hostname:
@ -180,7 +180,8 @@ HTTP/TLS:
--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: /.
Set the path relative to '/' for serving resources. The path must start with a
'/'. Default: /.
--https-certificate-file <file>
The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file <file>
@ -284,6 +285,12 @@ Logging:
categories and their levels. For the root category, you don't need to
specify a category. Default: info.
Security (Experimental):
--fips-mode <mode> Experimental: Sets the FIPS mode. If 'enabled' is set, FIPS is enabled but on
non-approved mode. For full FIPS compliance, set 'strict' to run on approved
mode. Possible values are: enabled, strict, disabled. Default: disabled.
By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option: