Make it possible to switch between BC and BC-FIPS libraries
Closes #12424
This commit is contained in:
parent
4016dd95d2
commit
00e4c3567a
19 changed files with 399 additions and 30 deletions
|
@ -26,13 +26,13 @@ public class CryptoIntegration {
|
||||||
if (cryptoProvider == null) {
|
if (cryptoProvider == null) {
|
||||||
cryptoProvider = detectProvider(classLoader);
|
cryptoProvider = detectProvider(classLoader);
|
||||||
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
|
logger.debugv("BouncyCastle provider: {0}", BouncyIntegration.PROVIDER);
|
||||||
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.tracef(dumpJavaSecurityProviders());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef(dumpJavaSecurityProviders());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CryptoProvider getProvider() {
|
public static CryptoProvider getProvider() {
|
||||||
|
@ -67,4 +67,7 @@ public class CryptoIntegration {
|
||||||
return builder.append("]").toString();
|
return builder.append("]").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setProvider(CryptoProvider provider) {
|
||||||
|
cryptoProvider = provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.crypto.fips;
|
package org.keycloak.crypto.fips;
|
||||||
|
|
||||||
|
import static org.bouncycastle.crypto.CryptoServicesRegistrar.isInApprovedOnlyMode;
|
||||||
|
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
|
||||||
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
||||||
|
@ -17,11 +19,10 @@ public class KeycloakFipsSecurityProvider extends Provider {
|
||||||
private final BouncyCastleFipsProvider bcFipsProvider;
|
private final BouncyCastleFipsProvider bcFipsProvider;
|
||||||
|
|
||||||
public KeycloakFipsSecurityProvider(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;
|
this.bcFipsProvider = bcFipsProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized final Service getService(String type, String algorithm) {
|
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
|
// Using 'SecureRandom.getInstance("SHA1PRNG")' will delegate to BCFIPS DEFAULT provider instead of returning SecureRandom based on potentially unsecure SHA1PRNG
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ public enum OptionCategory {
|
||||||
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
|
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
|
||||||
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
|
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
|
||||||
LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED),
|
LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED),
|
||||||
|
SECURITY("Security", 120, ConfigSupportLevel.EXPERIMENTAL),
|
||||||
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
|
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
|
||||||
|
|
||||||
private String heading;
|
private String heading;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -89,6 +89,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.config.SecurityOptions;
|
||||||
import org.keycloak.config.StorageOptions;
|
import org.keycloak.config.StorageOptions;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionSpi;
|
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)
|
@BuildStep(onlyIf = IsDevelopment.class)
|
||||||
void configureDevMode(BuildProducer<HotDeploymentWatchedFileBuildItem> hotFiles) {
|
void configureDevMode(BuildProducer<HotDeploymentWatchedFileBuildItem> hotFiles) {
|
||||||
hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.conf"));
|
hotFiles.produce(new HotDeploymentWatchedFileBuildItem("META-INF/keycloak.conf"));
|
||||||
|
|
|
@ -148,7 +148,17 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<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>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
|
|
|
@ -38,6 +38,9 @@ import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.common.Profile;
|
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.Configuration;
|
||||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||||
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
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<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
|
||||||
Map<Class<? extends Provider>, String> defaultProviders,
|
Map<Class<? extends Provider>, String> defaultProviders,
|
||||||
Map<String, ProviderFactory> preConfiguredProviders,
|
Map<String, ProviderFactory> preConfiguredProviders,
|
||||||
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, Boolean reaugmented) {
|
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes, boolean reaugmented) {
|
||||||
Config.init(new MicroProfileConfigProvider());
|
Config.init(new MicroProfileConfigProvider());
|
||||||
Profile.setInstance(new QuarkusProfile());
|
Profile.setInstance(new QuarkusProfile());
|
||||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes, reaugmented));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,8 @@ public final class PropertyMappers {
|
||||||
MAPPERS.addAll(LoggingPropertyMappers.getMappers());
|
MAPPERS.addAll(LoggingPropertyMappers.getMappers());
|
||||||
MAPPERS.addAll(TransactionPropertyMappers.getTransactionPropertyMappers());
|
MAPPERS.addAll(TransactionPropertyMappers.getTransactionPropertyMappers());
|
||||||
MAPPERS.addAll(StoragePropertyMappers.getMappers());
|
MAPPERS.addAll(StoragePropertyMappers.getMappers());
|
||||||
|
MAPPERS.addAll(ClassLoaderPropertyMappers.getMappers());
|
||||||
|
MAPPERS.addAll(SecurityPropertyMappers.getMappers());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
private void updateProperties(Consumer<Properties> propertiesConsumer, File propertiesFile) {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
|
||||||
|
|
74
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java
vendored
Normal file
74
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java
vendored
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -279,6 +279,12 @@ Logging:
|
||||||
categories and their levels. For the root category, you don't need to
|
categories and their levels. For the root category, you don't need to
|
||||||
specify a category. Default: info.
|
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.
|
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
|
Use 'kc.sh start-dev --help-all' to list all available options, including build
|
||||||
|
|
|
@ -128,18 +128,18 @@ Transaction:
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||||
account2, account-api, admin-fine-grained-authz, admin2, docker,
|
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||||
impersonation, openshift-integration, scripts, token-exchange, web-authn,
|
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||||
client-policies, ciba, map-storage, par, declarative-user-profile,
|
web-authn, client-policies, ciba, map-storage, par,
|
||||||
dynamic-scopes, client-secret-rotation, step-up-authentication,
|
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||||
recovery-codes, update-email, preview.
|
step-up-authentication, recovery-codes, update-email, preview.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: authorization,
|
||||||
account2, account-api, admin-fine-grained-authz, admin2, docker,
|
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||||
impersonation, openshift-integration, scripts, token-exchange, web-authn,
|
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||||
client-policies, ciba, map-storage, par, declarative-user-profile,
|
web-authn, client-policies, ciba, map-storage, par,
|
||||||
dynamic-scopes, client-secret-rotation, step-up-authentication,
|
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||||
recovery-codes, update-email, preview.
|
step-up-authentication, recovery-codes, update-email, preview.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -174,7 +174,8 @@ HTTP/TLS:
|
||||||
--http-host <host> The used HTTP Host. Default: 0.0.0.0.
|
--http-host <host> The used HTTP Host. Default: 0.0.0.0.
|
||||||
--http-port <port> The used HTTP port. Default: 8080.
|
--http-port <port> The used HTTP port. Default: 8080.
|
||||||
--http-relative-path <path>
|
--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>
|
--https-certificate-file <file>
|
||||||
The file path to a server certificate or certificate chain in PEM format.
|
The file path to a server certificate or certificate chain in PEM format.
|
||||||
--https-certificate-key-file <file>
|
--https-certificate-key-file <file>
|
||||||
|
@ -278,6 +279,12 @@ Logging:
|
||||||
categories and their levels. For the root category, you don't need to
|
categories and their levels. For the root category, you don't need to
|
||||||
specify a category. Default: info.
|
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.
|
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
|
Use 'kc.bat start-dev --help-all' to list all available options, including
|
||||||
|
|
|
@ -285,6 +285,12 @@ Logging:
|
||||||
categories and their levels. For the root category, you don't need to
|
categories and their levels. For the root category, you don't need to
|
||||||
specify a category. Default: info.
|
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
|
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
|
'build' before starting the server. You can disable this behavior by using the
|
||||||
'--optimized' option:
|
'--optimized' option:
|
||||||
|
|
|
@ -134,18 +134,18 @@ Transaction:
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
--features <feature> Enables a set of one or more features. Possible values are: authorization,
|
||||||
account2, account-api, admin-fine-grained-authz, admin2, docker,
|
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||||
impersonation, openshift-integration, scripts, token-exchange, web-authn,
|
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||||
client-policies, ciba, map-storage, par, declarative-user-profile,
|
web-authn, client-policies, ciba, map-storage, par,
|
||||||
dynamic-scopes, client-secret-rotation, step-up-authentication,
|
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||||
recovery-codes, update-email, preview.
|
step-up-authentication, recovery-codes, update-email, preview.
|
||||||
--features-disabled <feature>
|
--features-disabled <feature>
|
||||||
Disables a set of one or more features. Possible values are: authorization,
|
Disables a set of one or more features. Possible values are: authorization,
|
||||||
account2, account-api, admin-fine-grained-authz, admin2, docker,
|
account2, account-api, admin-fine-grained-authz, admin-api, admin, admin2,
|
||||||
impersonation, openshift-integration, scripts, token-exchange, web-authn,
|
docker, impersonation, openshift-integration, scripts, token-exchange,
|
||||||
client-policies, ciba, map-storage, par, declarative-user-profile,
|
web-authn, client-policies, ciba, map-storage, par,
|
||||||
dynamic-scopes, client-secret-rotation, step-up-authentication,
|
declarative-user-profile, dynamic-scopes, client-secret-rotation,
|
||||||
recovery-codes, update-email, preview.
|
step-up-authentication, recovery-codes, update-email, preview.
|
||||||
|
|
||||||
Hostname:
|
Hostname:
|
||||||
|
|
||||||
|
@ -180,7 +180,8 @@ HTTP/TLS:
|
||||||
--http-host <host> The used HTTP Host. Default: 0.0.0.0.
|
--http-host <host> The used HTTP Host. Default: 0.0.0.0.
|
||||||
--http-port <port> The used HTTP port. Default: 8080.
|
--http-port <port> The used HTTP port. Default: 8080.
|
||||||
--http-relative-path <path>
|
--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>
|
--https-certificate-file <file>
|
||||||
The file path to a server certificate or certificate chain in PEM format.
|
The file path to a server certificate or certificate chain in PEM format.
|
||||||
--https-certificate-key-file <file>
|
--https-certificate-key-file <file>
|
||||||
|
@ -284,6 +285,12 @@ Logging:
|
||||||
categories and their levels. For the root category, you don't need to
|
categories and their levels. For the root category, you don't need to
|
||||||
specify a category. Default: info.
|
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
|
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
|
'build' before starting the server. You can disable this behavior by using the
|
||||||
'--optimized' option:
|
'--optimized' option:
|
||||||
|
|
Loading…
Reference in a new issue