package org.apereo.cas.config; import com.google.common.base.Throwables; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.ExtendedAccessLogValve; import org.apache.catalina.valves.SSLValve; import org.apache.catalina.valves.rewrite.RewriteValve; import org.apache.commons.lang3.StringUtils; import org.apache.coyote.http2.Http2Protocol; import org.apereo.cas.CasEmbeddedContainerUtils; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.core.CasServerProperties; import org.apereo.cas.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.io.Resource; import org.springframework.util.SocketUtils; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * This is {@link CasEmbeddedContainerTomcatConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("casEmbeddedContainerTomcatConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) @ConditionalOnProperty(name = CasEmbeddedContainerUtils.EMBEDDED_CONTAINER_CONFIG_ACTIVE, havingValue = "true") @AutoConfigureBefore(EmbeddedServletContainerAutoConfiguration.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class CasEmbeddedContainerTomcatConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(CasEmbeddedContainerTomcatConfiguration.class); @Autowired private ServerProperties serverProperties; @Autowired private CasConfigurationProperties casProperties; @ConditionalOnClass(value = {Tomcat.class, Http2Protocol.class}) @Bean public EmbeddedServletContainerFactory servletContainer() { final TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory(); configureAjp(tomcat); configureHttp(tomcat); configureHttpProxy(tomcat); configureExtendedAccessLogValve(tomcat); configureRewriteValve(tomcat); configureSSLValve(tomcat); return tomcat; } private void configureRewriteValve(final TomcatEmbeddedServletContainerFactory tomcat) { final Resource res = casProperties.getServer().getRewriteValveConfigLocation(); if (ResourceUtils.doesResourceExist(res)) { final RewriteValve valve = new RewriteValve() { @Override protected synchronized void startInternal() throws LifecycleException { super.startInternal(); try (InputStream is = res.getInputStream(); InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader buffer = new BufferedReader(isr)) { parse(buffer); } catch (final Exception e) { throw Throwables.propagate(e); } } }; valve.setAsyncSupported(true); valve.setEnabled(true); LOGGER.debug("Creating Rewrite valve configuration for the embedded tomcat container..."); tomcat.addContextValves(valve); } } private void configureExtendedAccessLogValve(final TomcatEmbeddedServletContainerFactory tomcat) { final CasServerProperties.ExtendedAccessLog ext = casProperties.getServer().getExtAccessLog(); if (ext.isEnabled() && StringUtils.isNotBlank(ext.getPattern())) { LOGGER.debug("Creating extended access log valve configuration for the embedded tomcat container..."); final ExtendedAccessLogValve valve = new ExtendedAccessLogValve(); valve.setPattern(ext.getPattern()); if (StringUtils.isBlank(ext.getDirectory())) { valve.setDirectory(serverProperties.getTomcat().getAccesslog().getDirectory()); } else { valve.setDirectory(ext.getDirectory()); } valve.setPrefix(ext.getPrefix()); valve.setSuffix(ext.getSuffix()); valve.setAsyncSupported(true); valve.setEnabled(true); valve.setRotatable(true); valve.setBuffered(true); tomcat.addContextValves(valve); tomcat.addEngineValves(valve); } } private void configureHttp(final TomcatEmbeddedServletContainerFactory tomcat) { final CasServerProperties.Http http = casProperties.getServer().getHttp(); if (http.isEnabled()) { LOGGER.debug("Creating HTTP configuration for the embedded tomcat container..."); final Connector connector = new Connector(http.getProtocol()); int port = http.getPort(); if (port <= 0) { LOGGER.warn("No explicit port configuration is provided to CAS. Scanning for available ports..."); port = SocketUtils.findAvailableTcpPort(); } LOGGER.info("Activated embedded tomcat container HTTP port to [{}]", port); connector.setPort(port); LOGGER.debug("Configuring embedded tomcat container for HTTP2 protocol support"); connector.addUpgradeProtocol(new Http2Protocol()); http.getAttributes().forEach(connector::setAttribute); tomcat.addAdditionalTomcatConnectors(connector); } } private void configureHttpProxy(final TomcatEmbeddedServletContainerFactory tomcat) { final CasServerProperties.HttpProxy proxy = casProperties.getServer().getHttpProxy(); if (proxy.isEnabled()) { LOGGER.debug("Customizing HTTP proxying for connector listening on port [{}]", tomcat.getPort()); tomcat.getTomcatConnectorCustomizers().add(connector -> { connector.setSecure(proxy.isSecure()); connector.setScheme(proxy.getScheme()); if (StringUtils.isNotBlank(proxy.getProtocol())) { LOGGER.debug("Setting HTTP proxying protocol to [{}]", proxy.getProtocol()); connector.setProtocol(proxy.getProtocol()); } if (proxy.getRedirectPort() > 0) { LOGGER.debug("Setting HTTP proxying redirect port to [{}]", proxy.getRedirectPort()); connector.setRedirectPort(proxy.getRedirectPort()); } if (proxy.getProxyPort() > 0) { LOGGER.debug("Setting HTTP proxying proxy port to [{}]", proxy.getProxyPort()); connector.setProxyPort(proxy.getProxyPort()); } proxy.getAttributes().forEach(connector::setAttribute); LOGGER.info("Configured connector listening on port [{}]", tomcat.getPort()); }); } else { LOGGER.debug("HTTP proxying is not enabled for CAS; Connector configuration for port [{}] is not modified.", tomcat.getPort()); } } private void configureAjp(final TomcatEmbeddedServletContainerFactory tomcat) { final CasServerProperties.Ajp ajp = casProperties.getServer().getAjp(); if (ajp.isEnabled() && ajp.getPort() > 0) { LOGGER.debug("Creating AJP configuration for the embedded tomcat container..."); final Connector ajpConnector = new Connector(ajp.getProtocol()); ajpConnector.setProtocol(ajp.getProtocol()); ajpConnector.setPort(ajp.getPort()); ajpConnector.setSecure(ajp.isSecure()); ajpConnector.setAllowTrace(ajp.isAllowTrace()); ajpConnector.setScheme(ajp.getScheme()); if (ajp.getAsyncTimeout() > 0) { ajpConnector.setAsyncTimeout(ajp.getAsyncTimeout()); } ajpConnector.setEnableLookups(ajp.isEnableLookups()); if (ajp.getMaxPostSize() > 0) { ajpConnector.setMaxPostSize(ajp.getMaxPostSize()); } ajpConnector.addUpgradeProtocol(new Http2Protocol()); if (ajp.getProxyPort() > 0) { LOGGER.debug("Set AJP proxy port to [{}]", ajp.getProxyPort()); ajpConnector.setProxyPort(ajp.getProxyPort()); } if (ajp.getRedirectPort() > 0) { LOGGER.debug("Set AJP redirect port to [{}]", ajp.getRedirectPort()); ajpConnector.setRedirectPort(ajp.getRedirectPort()); } ajp.getAttributes().forEach(ajpConnector::setAttribute); tomcat.addAdditionalTomcatConnectors(ajpConnector); } } private void configureSSLValve(final TomcatEmbeddedServletContainerFactory tomcat) { final CasServerProperties.SslValve valveConfig = casProperties.getServer().getSslValve(); if (valveConfig.isEnabled()) { LOGGER.debug("Adding SSLValve to engine of the embedded tomcat container..."); final SSLValve valve = new SSLValve(); valve.setSslCipherHeader(valveConfig.getSslCipherHeader()); valve.setSslCipherUserKeySizeHeader(valveConfig.getSslCipherUserKeySizeHeader()); valve.setSslClientCertHeader(valveConfig.getSslClientCertHeader()); valve.setSslSessionIdHeader(valveConfig.getSslSessionIdHeader()); tomcat.addEngineValves(valve); } } }