de.trustable.ca3s.core.Ca3SApp Maven / Gradle / Ivy
Show all versions of ca-3-s Show documentation
package de.trustable.ca3s.core;
import de.trustable.ca3s.cert.bundle.TimedRenewalCertMap;
import de.trustable.ca3s.core.config.ApplicationProperties;
import de.trustable.ca3s.core.config.DefaultProfileUtil;
import de.trustable.ca3s.core.security.provider.*;
import de.trustable.ca3s.core.service.util.KeyUtil;
import de.trustable.util.JCAManager;
import io.undertow.Undertow;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.xnio.Options;
import org.xnio.SslClientAuthMode;
import tech.jhipster.config.JHipsterConstants;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Security;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@SpringBootApplication
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
public class Ca3SApp implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Ca3SApp.class);
public static final String SERVER_TLS_PREFIX = "ca3s.tlsAccess.";
public static final String SERVER_TLS_CLIENT_AUTH_PREFIX = "ca3s.tlsClientAuth.";
public static final String SERVER_ADMIN_PREFIX = "ca3s.adminAccess.";
public static final String SERVER_RA_PREFIX = "ca3s.raAccess.";
public static final String SERVER_ACME_PREFIX = "ca3s.acmeAccess.";
public static final String SERVER_SCEP_PREFIX = "ca3s.scepAccess.";
public static final String DEFAULT_BINDING_HOST = "0.0.0.0";
public static final String HTTPS_CERTIFICATE_DN_SUFFIX = "ca3s.https.certificate.dnSuffix";
public static final String O_TRUSTABLE_SOLUTIONS_C_DE = "O=trustable solutions, C=DE";
private final Environment env;
@Autowired
KeyUtil keyUtil;
@Autowired
Ca3sTrustManager ca3sTrustManager;
public Ca3SApp(Environment env) {
this.env = env;
}
/**
* Initializes ca3s.
*
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
*
* You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/.
*/
@Override
public void afterPropertiesSet() {
Collection activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
log.error("You have misconfigured your application! It should not run " +
"with both the 'dev' and 'prod' profiles at the same time.");
}
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
log.error("You have misconfigured your application! It should not " +
"run with both the 'dev' and 'cloud' profiles at the same time.");
}
}
/**
* Main method, used to run the application.
*
* @param args the command line arguments.
*/
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Ca3SApp.class);
DefaultProfileUtil.addDefaultProfile(app);
app.addListeners(new PropertiesLogger());
ApplicationContext ctx = app.run(args);
logApplicationStartup(ctx);
}
private static void logApplicationStartup(ApplicationContext ctx) {
if( log.isDebugEnabled()) {
String[] allBeanNames = ctx.getBeanDefinitionNames();
for (String beanName : allBeanNames) {
log.debug("bean name: " + beanName);
}
}
Environment env = ctx.getEnvironment();
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
String serverPort = env.getProperty("server.port");
String contextPath = env.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(contextPath)) {
contextPath = "/";
}
String hostAddress = "localhost";
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.warn("The host name could not be determined, using `localhost` as fallback");
}
log.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}{}\n\t" +
"External: \t{}://{}:{}{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
serverPort,
contextPath,
protocol,
hostAddress,
serverPort,
contextPath,
env.getActiveProfiles());
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer propsConfig
= new PropertySourcesPlaceholderConfigurer();
propsConfig.setLocation(new ClassPathResource("git.properties"));
propsConfig.setIgnoreResourceNotFound(true);
propsConfig.setIgnoreUnresolvablePlaceholders(true);
return propsConfig;
}
@Bean
public TimedRenewalCertMapHolder registerJCEProvider() {
JCAManager.getInstance();
Security.addProvider(new BouncyCastlePQCProvider());
String dnSuffix = env.getProperty(HTTPS_CERTIFICATE_DN_SUFFIX, O_TRUSTABLE_SOLUTIONS_C_DE);
TimedRenewalCertMap certMap = new TimedRenewalCertMap(null, new Ca3sFallbackBundleFactory(dnSuffix, keyUtil));
Security.addProvider(new Ca3sKeyStoreProvider(certMap, "ca3s"));
Security.addProvider(new Ca3sKeyManagerProvider(certMap));
TimedRenewalCertMapHolder trcmh = new TimedRenewalCertMapHolder();
trcmh.setCertMap(certMap);
log.info("JCAManager and Provider initialized ..." );
return trcmh;
}
@Bean
public UndertowServletWebServerFactory embeddedServletContainerFactory() {
registerJCEProvider();
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
EndpointConfigs endpointConfigs = getEndpointConfigs();
factory.addBuilderCustomizers(new UndertowBuilderCustomizer() {
@Override
public void customize(Undertow.Builder builder) {
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(Ca3sKeyManagerProvider.SERVICE_NAME);
KeyStore ks = KeyStore.getInstance("ca3s");
ks.load(null, null);
keyManagerFactory.init(ks, "password".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
SSLContext sslContext;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, null, null);
TrustManager[] trustManagers = {ca3sTrustManager};
SSLContext sslContextClientAuth;
sslContextClientAuth = SSLContext.getInstance("TLS");
sslContextClientAuth.init(keyManagers, trustManagers, null);
for( EndpointConfig epc : endpointConfigs.getPortConfigMap().values()) {
if( epc.isHttps()) {
if( epc.isClientAuth()) {
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUESTED);
builder.addHttpsListener(epc.getPort(), epc.getBindingHost(), sslContextClientAuth);
log.debug("added TLS client auth listen port {} for {}", epc.port, epc.getUsageDescription());
}else{
builder.addHttpsListener(epc.getPort(), epc.getBindingHost(), sslContext);
log.debug("added TLS listen port {} for {}", epc.port, epc.getUsageDescription());
}
} else {
log.debug("added plain text listen port {} for {}", epc.port, epc.getUsageDescription());
builder.addHttpListener(epc.getPort(), epc.getBindingHost());
}
}
} catch(GeneralSecurityException | IOException gse) {
log.error("problem configuring listen ports ", gse);
}
}
});
return factory;
}
EndpointConfigs getEndpointConfigs() {
EndpointConfigs epc = new EndpointConfigs();
epc.addConfig(getPortForUsage(SERVER_TLS_CLIENT_AUTH_PREFIX, 8442),
true, true,
getBindingHostForUsage( SERVER_TLS_CLIENT_AUTH_PREFIX, DEFAULT_BINDING_HOST), "TLS Client Auth Port");
epc.addConfig(getPortForUsage(SERVER_TLS_PREFIX, 8443),
getHTTPSForUsage(SERVER_TLS_PREFIX, true),
getBindingHostForUsage( SERVER_TLS_PREFIX, DEFAULT_BINDING_HOST), "TLS Port");
epc.addConfig(getPortForUsage(SERVER_ADMIN_PREFIX, 8443),
getHTTPSForUsage(SERVER_ADMIN_PREFIX, true),
getBindingHostForUsage( SERVER_ADMIN_PREFIX, DEFAULT_BINDING_HOST), "Admin Port");
epc.addConfig(getPortForUsage(SERVER_RA_PREFIX, 8443),
getHTTPSForUsage(SERVER_RA_PREFIX, true),
getBindingHostForUsage( SERVER_RA_PREFIX, DEFAULT_BINDING_HOST),"RA Port");
epc.addConfig(getPortForUsage(SERVER_ACME_PREFIX, 8443),
getHTTPSForUsage(SERVER_ACME_PREFIX, true),
getBindingHostForUsage( SERVER_ACME_PREFIX, DEFAULT_BINDING_HOST), "ACME Port");
int httpPort = getPortForUsage("server.", 8080);
int scepPort = getPortForUsage(SERVER_SCEP_PREFIX, 8081);
if( scepPort != httpPort) {
epc.addConfig(scepPort,
getHTTPSForUsage(SERVER_SCEP_PREFIX, false),
getBindingHostForUsage( SERVER_SCEP_PREFIX, DEFAULT_BINDING_HOST), "SCEP Port");
}
return epc;
}
int getPortForUsage(final String usage, int defaultPort) {
int port = defaultPort;
String item = usage + "port";
String envPort = env.getProperty(item);
if( envPort == null) {
log.debug("Port for usage '{}' undefined, using default port #{}", item, defaultPort);
}else {
port = Integer.parseUnsignedInt(envPort);
}
return port;
}
boolean getHTTPSForUsage(final String usage, boolean defaultHTTPS) {
boolean isHttps = defaultHTTPS;
String item = usage + "https";
String envHttpsUsage = env.getProperty(item);
if( envHttpsUsage == null) {
log.debug("Use HTTPS for usage '{}' undefined, using default mode {}", item, defaultHTTPS);
}else {
isHttps = Boolean.parseBoolean(envHttpsUsage);
}
return isHttps;
}
String getBindingHostForUsage(final String usage, String defaultBindingHost) {
String bindingHost = defaultBindingHost;
String item = usage + "bindingHost";
String envBindingHost = env.getProperty(item);
if( envBindingHost == null) {
log.debug("Binding host for usage '{}' undefined, using default '{}'", item, defaultBindingHost);
}else {
bindingHost = envBindingHost;
}
return bindingHost;
}
}
class EndpointConfigs{
private static final Logger log = LoggerFactory.getLogger(EndpointConfigs.class);
HashMap portConfigMap = new HashMap<>();
public void addConfig(int port, boolean isHttps, String bindingHost, String usageDescription) {
addConfig( port, isHttps, false, bindingHost, usageDescription);
}
public void addConfig(int port, boolean isHttps, boolean isClientAuth, String bindingHost, String usageDescription) {
if( portConfigMap.containsKey(port)) {
EndpointConfig existingConfig = portConfigMap.get(port);
if( existingConfig.isHttps() != isHttps ) {
log.warn("Https redefinition for port {}, ignoring definition for '{}'", port, usageDescription);
}
if( !existingConfig.getBindingHost().equalsIgnoreCase(bindingHost)) {
log.warn("Binding Host redefinition for port {}, ignoring definition for '{}'", port, usageDescription);
}
existingConfig.usageDescription += ", " + usageDescription;
}else {
portConfigMap.put(port, new EndpointConfig(port, isHttps, isClientAuth, bindingHost, usageDescription));
}
}
public HashMap getPortConfigMap() {
return portConfigMap;
}
}
class EndpointConfig{
int port;
boolean isHttps;
boolean isClientAuth;
String bindingHost;
String usageDescription;
public EndpointConfig(int port, boolean isHttps, boolean isClientAuth, String bindingHost, String usageDescription) {
this.port = port;
this.isHttps = isHttps;
this.bindingHost = bindingHost;
this.usageDescription = usageDescription;
this.isClientAuth = isClientAuth;
}
public int getPort() {
return port;
}
public boolean isHttps() {
return isHttps;
}
public String getBindingHost() {
return bindingHost;
}
public String getUsageDescription() {
return usageDescription;
}
public boolean isClientAuth() {
return isClientAuth;
}
}