Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions server/configs/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ mail.smtpUser=@@smtpUser@@
#mail.smtpStartTlsEnable=@@smtpStartTlsEnable@@
#mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@
#mail.smtpAuth=@@smtpAuth@@
#mail.smtpConnectionTimeout=@@smtpConnectionTimeout@@
#mail.smtpTimeout=@@smtpTimeout@@
#mail.smtpWriteTimeout=@@smtpWriteTimeout@@

## Microsoft Graph configuration - if using this, comment out SMTP configuration
#mail.graph.tenantId=@@graphTenantId@@
Expand Down
3 changes: 3 additions & 0 deletions server/configs/webapps/embedded/config/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ mail.smtpUser=Anonymous
#mail.smtpStartTlsEnable=@@smtpStartTlsEnable@@
#mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@
#mail.smtpAuth=@@smtpAuth@@
#mail.smtpConnectionTimeout=@@smtpConnectionTimeout@@
#mail.smtpTimeout=@@smtpTimeout@@
#mail.smtpWriteTimeout=@@smtpWriteTimeout@@

####################################################################################
## Microsoft Graph Configuration - if using this, comment out SMTP configuration
Expand Down
189 changes: 112 additions & 77 deletions server/embedded/src/org/labkey/embedded/LabKeyServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ public class LabKeyServer
private static final String TERMINATE_ON_STARTUP_FAILURE = "terminateOnStartupFailure";
private static final String JARS_TO_SKIP = "tomcat.util.scan.StandardJarScanFilter.jarsToSkip";
private static final String JARS_TO_SCAN = "tomcat.util.scan.StandardJarScanFilter.jarsToScan";
private static final String SERVER_GUID = "serverGUID";
public static final String SERVER_GUID_PARAMETER_NAME = "org.labkey.mothership." + SERVER_GUID;
public static final String SERVER_SSL_KEYSTORE = "org.labkey.serverSslKeystore";
public static final String CUSTOM_LOG4J_CONFIG = "org.labkey.customLog4JConfig";
public static final String CORS_PREFIX = "cors.";

static final String MAX_TOTAL_CONNECTIONS_DEFAULT = "50";
static final String MAX_IDLE_DEFAULT = "10";
static final String MAX_WAIT_MILLIS_DEFAULT = "120000";
Expand Down Expand Up @@ -108,83 +104,89 @@ public static void main(String[] args)
""";

application.setDefaultProperties(new HashMap<>()
{{
{{
// GitHub Issue 796: JSON logging stopped after Tomcat/Spring update
// Propagate log4j configuration to Spring Boot config, which is necessary with Spring Boot 4.x
String log4JConfig = System.getProperty("log4j.configurationFile");
if (log4JConfig != null)
{
String[] log4JConfigParts = log4JConfig.split(",");
if (log4JConfigParts.length > 0)
{
if ("log4j2.xml".equals(log4JConfigParts[0]))
{
// Assume this is the one packaged with our embedded build and on the classpath
put("logging.config", "classpath:log4j2.xml");
}
else
{
put("logging.config", log4JConfigParts[0]);
}
if (log4JConfigParts.length > 1)
{
// Propagate log4j configuration to Spring Boot config, which is necessary with Spring Boot 4.x
String log4JConfig = System.getProperty("log4j.configurationFile");
if (log4JConfig != null)
{
String[] log4JConfigParts = log4JConfig.split(",");
if (log4JConfigParts.length > 0)
{
if ("log4j2.xml".equals(log4JConfigParts[0]))
{
// Assume this is the one packaged with our embedded build and on the classpath
put("logging.config", "classpath:log4j2.xml");
}
else
{
put("logging.config", log4JConfigParts[0]);
}
if (log4JConfigParts.length > 1)
{
put("logging.log4j2.config.override", String.join(",", Arrays.asList(log4JConfigParts).subList(1, log4JConfigParts.length)));
}
}
}
}
}
}

put("server.tomcat.basedir", ".");
put("server.tomcat.accesslog.directory", logHome);
put("server.tomcat.basedir", ".");
put("server.tomcat.accesslog.directory", logHome);

// Boost limits imposed by Tomcat v10.1.42
put("server.tomcat.max-part-count", 500);
put("server.tomcat.max-part-header-size", 1024); // GitHub Issue 161: LKS insert forms can't handle long file field names
put("server.tomcat.max-connections", 250);
// Boost limits imposed by Tomcat v10.1.42
put("server.tomcat.max-part-count", 500);
put("server.tomcat.max-part-header-size", 1024); // GitHub Issue 161: LKS insert forms can't handle long file field names
put("server.tomcat.max-connections", 250);
// Boost limit back to Tomcat 10 level
put("server.tomcat.max-parameter-count", 10_000);

// Enable HTTP compression for response content
put("server.compression.enabled", "true");
// Spring Boot compresses HTML, JSON and other types by default, but not TSV, CSV, or SVG.
// We have to duplicate the defaults and add those types
put("server.compression.mime-types", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml,text/tab-separated-values,text/csv,image/svg+xml");

put("server.tomcat.accesslog.enabled", "true");
put("server.tomcat.accesslog.pattern", "%h %l %u %t \"%r\" %s %b %D %S %I \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i");
put("jsonaccesslog.pattern", "%h %t %m %U %s %b %D %S \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i");

// Issue 52415: Omit stack traces from Tomcat error pages by default, but propagate error messages
put("server.error.include-stacktrace", "never");
put("server.error.include-message", "always");

put("csp.enforce", enforceCsp);
put("csp.report", reportCsp);

// GitHub Issue 692: Stop using CBC in HTTPS ciphers
// These settings configure HTTPS. Admins must opt in with additional settings
// in application.properties, like the key store. Without those other settings,
// HTTP-only startup fails unless "server.ssl.enabled" is explicitly set to false here
put("server.ssl.enabled", "false");
put("server.ssl.protocol", "TLS");
put("server.ssl.enabled-protocols", "TLSv1.3,TLSv1.2");
// Use explicit JSSE cipher suite names to avoid CBC-mode suites
put("server.ssl.ciphers",
String.join(",",
// TLS 1.3
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
// TLS 1.2 (AEAD only, no CBC)
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
)
);
put("server.ssl.use-cipher-suites-order", "true");
}}
put("server.tomcat.max-parameter-count", 10_000);

// Enable HTTP compression for response content
put("server.compression.enabled", "true");
// Spring Boot compresses HTML, JSON and other types by default, but not TSV, CSV, or SVG.
// We have to duplicate the defaults and add those types
put("server.compression.mime-types", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml,text/tab-separated-values,text/csv,image/svg+xml");

put("server.tomcat.accesslog.enabled", "true");
put("server.tomcat.accesslog.pattern", "%h %l %u %t \"%r\" %s %b %D %S %I \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i");
put("jsonaccesslog.pattern", "%h %t %m %U %s %b %D %S \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i");

// Issue 52415: Omit stack traces from Tomcat error pages by default, but propagate error messages
put("server.error.include-stacktrace", "never");
put("server.error.include-message", "always");

put("csp.enforce", enforceCsp);
put("csp.report", reportCsp);

// GitHub Issue 692: Stop using CBC in HTTPS ciphers
// These settings configure HTTPS. Admins must opt in with additional settings
// in application.properties, like the key store. Without those other settings,
// HTTP-only startup fails unless "server.ssl.enabled" is explicitly set to false here
put("server.ssl.enabled", "false");
put("server.ssl.protocol", "TLS");
put("server.ssl.enabled-protocols", "TLSv1.3,TLSv1.2");
// Use explicit JSSE cipher suite names to avoid CBC-mode suites
put("server.ssl.ciphers",
String.join(",",
// TLS 1.3
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
// TLS 1.2 (AEAD only, no CBC)
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
)
);
put("server.ssl.use-cipher-suites-order", "true");

// GitHub Issue #1416 - default values for SMTP timeouts
put("mail.smtpConnectionTimeout", 10 * 1000);
put("mail.smtpTimeout", 60 * 1000);
// Unlike the socket-level timeouts above, JavaMail implements writetimeout with a ScheduledThreadPool per connection - one per message, since Transport.send() doesn't pool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude was insistent. It did inspire some ideas that I documented here: https://github.com/LabKey/internal-issues/issues/1438

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to tell Claude I find it irrelevant.

put("mail.smtpWriteTimeout", 60 * 1000);
}}
);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
Expand Down Expand Up @@ -818,6 +820,9 @@ public static class MailProperties
private String smtpStartTlsEnable;
private String smtpSocketFactoryClass;
private String smtpAuth;
private Integer smtpConnectionTimeout;
private Integer smtpTimeout;
private Integer smtpWriteTimeout;

public String getSmtpHost()
{
Expand Down Expand Up @@ -898,6 +903,36 @@ public void setSmtpAuth(String smtpAuth)
{
this.smtpAuth = smtpAuth;
}

public Integer getSmtpConnectionTimeout()
{
return smtpConnectionTimeout;
}

public void setSmtpConnectionTimeout(Integer smtpConnectionTimeout)
{
this.smtpConnectionTimeout = smtpConnectionTimeout;
}

public Integer getSmtpTimeout()
{
return smtpTimeout;
}

public void setSmtpTimeout(Integer smtpTimeout)
{
this.smtpTimeout = smtpTimeout;
}

public Integer getSmtpWriteTimeout()
{
return smtpWriteTimeout;
}

public void setSmtpWriteTimeout(Integer smtpWriteTimeout)
{
this.smtpWriteTimeout = smtpWriteTimeout;
}
}

@Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.logging.log4j.Logger;
import org.apache.tomcat.util.descriptor.web.ContextResource;
import org.labkey.bootstrap.ConfigException;
import org.labkey.embedded.LabKeyServer.MailProperties;
import org.springframework.boot.tomcat.TomcatWebServer;
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
Expand All @@ -39,14 +40,15 @@
import java.util.Map;
import java.util.Objects;

import static org.labkey.embedded.LabKeyServer.CORS_PREFIX;
import static org.labkey.embedded.LabKeyServer.CUSTOM_LOG4J_CONFIG;
import static org.labkey.embedded.LabKeyServer.SERVER_GUID_PARAMETER_NAME;
import static org.labkey.embedded.LabKeyServer.SERVER_SSL_KEYSTORE;

class LabKeyTomcatServletWebServerFactory extends TomcatServletWebServerFactory
{
private static final Logger LOG = LogManager.getLogger(LabKeyTomcatServletWebServerFactory.class);
private static final String SERVER_GUID = "serverGUID";
private static final String SERVER_GUID_PARAMETER_NAME = "org.labkey.mothership." + SERVER_GUID;
private static final String SERVER_SSL_KEYSTORE = "org.labkey.serverSslKeystore";
private static final String CUSTOM_LOG4J_CONFIG = "org.labkey.customLog4JConfig";
private static final String CORS_PREFIX = "cors.";

private final LabKeyServer _server;

public LabKeyTomcatServletWebServerFactory(LabKeyServer server)
Expand Down Expand Up @@ -423,42 +425,35 @@ private String getPropValue(Map<Integer, String> propValues, Integer resourceKey
private void addSmtpProperties(StandardContext context)
{
// Get session/mail properties
LabKeyServer.MailProperties mailProps = _server.smtpSource();
MailProperties mailProps = _server.smtpSource();

addSmtpProperty(context, "host", mailProps.getSmtpHost());
addSmtpProperty(context, "user", mailProps.getSmtpUser());
addSmtpProperty(context, "port", mailProps.getSmtpPort());
addSmtpProperty(context, "from", mailProps.getSmtpFrom());
addSmtpProperty(context, "password", mailProps.getSmtpPassword());
addSmtpProperty(context, "starttls.enable", mailProps.getSmtpStartTlsEnable());
addSmtpProperty(context, "socketFactory.class", mailProps.getSmtpSocketFactoryClass());
addSmtpProperty(context, "auth", mailProps.getSmtpAuth());
addSmtpProperty(context, "connectiontimeout", mailProps.getSmtpConnectionTimeout());
addSmtpProperty(context, "timeout", mailProps.getSmtpTimeout());
addSmtpProperty(context, "writetimeout", mailProps.getSmtpWriteTimeout());
}

if (mailProps.getSmtpHost() != null)
{
context.addParameter("mail.smtp.host", mailProps.getSmtpHost());
}
if (mailProps.getSmtpUser() != null)
{
context.addParameter("mail.smtp.user", mailProps.getSmtpUser());
}
if (mailProps.getSmtpPort() != null)
{
context.addParameter("mail.smtp.port", mailProps.getSmtpPort());
}
if (mailProps.getSmtpFrom() != null)
{
context.addParameter("mail.smtp.from", mailProps.getSmtpFrom());
}
if (mailProps.getSmtpPassword() != null)
{
context.addParameter("mail.smtp.password", mailProps.getSmtpPassword());
}
if (mailProps.getSmtpStartTlsEnable() != null)
{
context.addParameter("mail.smtp.starttls.enable", mailProps.getSmtpStartTlsEnable());
}
if (mailProps.getSmtpSocketFactoryClass() != null)
{
context.addParameter("mail.smtp.socketFactory.class", mailProps.getSmtpSocketFactoryClass());
}
if (mailProps.getSmtpAuth() != null)
private void addSmtpProperty(StandardContext context, String name, String value)
{
if (value != null)
{
context.addParameter("mail.smtp.auth", mailProps.getSmtpAuth());
context.addParameter("mail.smtp." + name, value);
}
}

private void addSmtpProperty(StandardContext context, String name, Integer value)
{
if (value != null)
addSmtpProperty(context, name, String.valueOf(value));
}

private void addGraphProperties(StandardContext context)
{
// Get Microsoft Graph mail properties
Expand Down