This is an automated email from the ASF dual-hosted git repository. schultz pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 3ab883aa746a5c577efa39d9080858e53ca77da6 Author: Christopher Schultz <ch...@christopherschultz.net> AuthorDate: Mon Mar 11 17:38:01 2024 -0400 Add checking for the age of the Tomcat version running and warn if it's getting old. --- .../catalina/security/LocalStrings.properties | 3 ++ .../apache/catalina/security/SecurityListener.java | 63 ++++++++++++++++++++++ java/org/apache/catalina/util/ServerInfo.java | 18 +++++++ webapps/docs/changelog.xml | 5 ++ webapps/docs/config/listeners.xml | 5 ++ 5 files changed, 94 insertions(+) diff --git a/java/org/apache/catalina/security/LocalStrings.properties b/java/org/apache/catalina/security/LocalStrings.properties index 49dace925f..03906637c3 100644 --- a/java/org/apache/catalina/security/LocalStrings.properties +++ b/java/org/apache/catalina/security/LocalStrings.properties @@ -18,6 +18,9 @@ SecurityListener.checkUmaskNone=No umask setting was found in system property [{ SecurityListener.checkUmaskParseFail=Failed to parse value [{0}] as a valid umask. SecurityListener.checkUmaskSkip=Unable to determine umask. It appears Tomcat is running on Windows so skip the umask check. SecurityListener.checkUserWarning=Start attempted while running as user [{0}]. Running Tomcat as this user has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml) +SecurityListener.buildDateAgeUnreadable=Unable to read configured buildDateWarningAgeDays [{0}], using default of [{1}] days. +SecurityListener.buildDateUnreadable=Server build date [{0}] is unreadable as an ISO-8601 date. +SecurityListener.buildDateIsOld=This version of Tomcat was built more than {0} days ago. You should consider upgrading to the current version. listener.notServer=This listener must only be nested within Server elements, but is in [{0}]. diff --git a/java/org/apache/catalina/security/SecurityListener.java b/java/org/apache/catalina/security/SecurityListener.java index 2371e30f7d..0fd20933b4 100644 --- a/java/org/apache/catalina/security/SecurityListener.java +++ b/java/org/apache/catalina/security/SecurityListener.java @@ -16,6 +16,10 @@ */ package org.apache.catalina.security; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -24,6 +28,7 @@ import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; import org.apache.catalina.Server; +import org.apache.catalina.util.ServerInfo; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.StringUtils; @@ -42,11 +47,18 @@ public class SecurityListener implements LifecycleListener { private static final String UMASK_FORMAT = "%04o"; + private static final int DEFAULT_BUILD_DATE_WARNING_AGE_DAYS = 180; + /** * The list of operating system users not permitted to run Tomcat. */ private final Set<String> checkedOsUsers = new HashSet<>(); + /** + * The number of days this Tomcat build can go without warning upon startup. + */ + private int buildDateWarningAgeDays = DEFAULT_BUILD_DATE_WARNING_AGE_DAYS; + /** * The minimum umask that must be configured for the operating system user running Tomcat. The umask is handled as * an octal. @@ -126,6 +138,33 @@ public class SecurityListener implements LifecycleListener { return String.format(UMASK_FORMAT, minimumUmask); } + /** + * Sets the number of days that may pass between the build-date of this + * Tomcat instance before warnings are printed. + * + * @param ageDays The number of days a Tomcat build is allowed to age + * before logging warnings. + */ + public void setBuildDateWarningAgeDays(String ageDays) { + try { + buildDateWarningAgeDays = Integer.parseInt(ageDays); + } catch (NumberFormatException nfe) { + // Just use the default and warn the user + log.warn(sm.getString("SecurityListener.buildDateAgeUnreadable", + ageDays, DEFAULT_BUILD_DATE_WARNING_AGE_DAYS)); + } + } + + /** + * Gets the number of days that may pass between the build-date of this + * Tomcat instance before warnings are printed. + * + * @return The number of days a Tomcat build is allowed to age + * before logging warnings. + */ + public int getBuildDateWarningAgeDays() { + return buildDateWarningAgeDays; + } /** * Execute the security checks. Each check should be in a separate method. @@ -133,6 +172,7 @@ public class SecurityListener implements LifecycleListener { protected void doChecks() { checkOsUser(); checkUmask(); + checkServerBuildAge(); } @@ -179,4 +219,27 @@ public class SecurityListener implements LifecycleListener { getMinimumUmask())); } } + + protected void checkServerBuildAge() { + String buildDateString = ServerInfo.getServerBuiltISO(); + + if (null == buildDateString || buildDateString.length() < 1 || !Character.isDigit(buildDateString.charAt(0))) { + log.warn(sm.getString("SecurityListener.buildDateUnreadable", buildDateString)); + } else { + try { + Date buildDate = new SimpleDateFormat("yyyy-MM-dd").parse(buildDateString); + + int allowedAgeDays = getBuildDateWarningAgeDays(); + + Calendar old = Calendar.getInstance(); + old.add(Calendar.DATE, -allowedAgeDays); // Subtract X days from today + + if(buildDate.before(old.getTime())) { + log.warn(sm.getString("SecurityListener.buildDateIsOld", allowedAgeDays)); + } + } catch (ParseException pe) { + log.warn(sm.getString("SecurityListener.buildDateUnreadable", buildDateString)); + } + } + } } diff --git a/java/org/apache/catalina/util/ServerInfo.java b/java/org/apache/catalina/util/ServerInfo.java index 7f8f1748ce..adf0c4e565 100644 --- a/java/org/apache/catalina/util/ServerInfo.java +++ b/java/org/apache/catalina/util/ServerInfo.java @@ -45,6 +45,11 @@ public class ServerInfo { */ private static final String serverBuilt; + /** + * The server built String, in ISO-8604 date format. + */ + private static final String serverBuiltIso; + /** * The server's version number String. */ @@ -54,6 +59,7 @@ public class ServerInfo { String info = null; String built = null; + String builtIso = null; String number = null; Properties props = new Properties(); @@ -62,6 +68,7 @@ public class ServerInfo { props.load(is); info = props.getProperty("server.info"); built = props.getProperty("server.built"); + builtIso = props.getProperty("server.built.iso"); number = props.getProperty("server.number"); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); @@ -72,12 +79,16 @@ public class ServerInfo { if (built == null || built.equals("@VERSION_BUILT@")) { built = "unknown"; } + if (builtIso == null || builtIso.equals("@VERSION_BUILT_ISO@")) { + builtIso = "unknown"; + } if (number == null || number.equals("@VERSION_NUMBER@")) { number = "11.0.x"; } serverInfo = info; serverBuilt = built; + serverBuiltIso = builtIso; serverNumber = number; } @@ -99,6 +110,13 @@ public class ServerInfo { return serverBuilt; } + /** + * @return the server built date for this version of Tomcat in ISO-8601 date format. + */ + public static String getServerBuiltISO() { + return serverBuiltIso; + } + /** * @return the server's version number. */ diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 538a0b60de..b921c7ccfe 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -113,6 +113,11 @@ error status code to the client when a permit cannot be acquired from the semaphore. (remm) </update> + <add> + Add checking of the "age" of the running Tomcat instance since its + build-date to the SecurityListener, and log a warning if the server + is old. (schultz) + </add> </changelog> </subsection> </section> diff --git a/webapps/docs/config/listeners.xml b/webapps/docs/config/listeners.xml index c58f45ea9f..f26537db43 100644 --- a/webapps/docs/config/listeners.xml +++ b/webapps/docs/config/listeners.xml @@ -416,6 +416,11 @@ is not performed on Windows platforms.</p> </attribute> + <attribute name="buildDateWarningAgeDays" required="false"> + <p>The maximim number of days between the build-date of this instance + of Tomcat and its startup date can be before warnings will be logged. + If not specified, the default value of <b>180</b> is used.</p> + </attribute> </attributes> </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org