diff -up ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java.orig ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java
--- ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java.orig 2020-03-03 14:24:23.418779786 -0500
+++ ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java 2020-03-03 14:26:28.555466694 -0500
@@ -23,7 +23,12 @@ import java.net.InetAddress;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.servlet.http.HttpServletResponse;
@@ -80,6 +85,9 @@ public abstract class AbstractAjpProcess
protected static final byte[] pongMessageArray;
+ private static final Set<String> javaxAttributes;
+
+
static {
// Allocate the end message array
AjpMessage endMessage = new AjpMessage(16);
@@ -120,6 +128,14 @@ public abstract class AbstractAjpProcess
pongMessageArray = new byte[pongMessage.getLen()];
System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
0, pongMessage.getLen());
+
+ // Build the Set of javax attributes
+ Set<String> s = new HashSet<String>();
+ s.add("javax.servlet.request.cipher_suite");
+ s.add("javax.servlet.request.key_size");
+ s.add("javax.servlet.request.ssl_session");
+ s.add("javax.servlet.request.X509Certificate");
+ javaxAttributes= Collections.unmodifiableSet(s);
}
@@ -307,9 +323,16 @@ public abstract class AbstractAjpProcess
/**
* Required secret.
*/
+ @Deprecated
protected String requiredSecret = null;
+ protected String secret = null;
+ public void setSecret(String secret) {
+ this.secret = secret;
+ this.requiredSecret = secret;
+ }
+ @Deprecated
public void setRequiredSecret(String requiredSecret) {
- this.requiredSecret = requiredSecret;
+ setSecret(requiredSecret);
}
@@ -326,9 +349,15 @@ public abstract class AbstractAjpProcess
public String getClientCertProvider() { return clientCertProvider; }
public void setClientCertProvider(String s) { this.clientCertProvider = s; }
- // --------------------------------------------------------- Public Methods
+
+ private Pattern allowedRequestAttributesPatternPattern;
+ public void setAllowedRequestAttributesPatternPattern(Pattern allowedRequestAttributesPatternPattern) {
+ this.allowedRequestAttributesPatternPattern = allowedRequestAttributesPatternPattern;
+ }
+ // --------------------------------------------------------- Public Methods
+
/**
* Send an action to the connector.
*
@@ -827,7 +856,7 @@ public abstract class AbstractAjpProcess
}
// Decode extra attributes
- boolean secret = false;
+ boolean secretPresentInRequest = false;
byte attributeCode;
while ((attributeCode = requestHeaderMessage.getByte())
!= Constants.SC_A_ARE_DONE) {
@@ -856,8 +885,25 @@ public abstract class AbstractAjpProcess
}
} else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) {
request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
+ } else if (n.equals("JK_LB_ACTIVATION")) {
+ request.setAttribute(n, v);
+ } else if (javaxAttributes.contains(n)) {
+ request.setAttribute(n, v);
} else {
- request.setAttribute(n, v );
+ // All 'known' attributes will be processed by the previous
+ // blocks. Any remaining attribute is an 'arbitrary' one.
+ if (allowedRequestAttributesPatternPattern == null) {
+ response.setStatus(403);
+ setErrorState(ErrorState.CLOSE_CLEAN, null);
+ } else {
+ Matcher m = allowedRequestAttributesPatternPattern.matcher(n);
+ if (m.matches()) {
+ request.setAttribute(n, v);
+ } else {
+ response.setStatus(403);
+ setErrorState(ErrorState.CLOSE_CLEAN, null);
+ }
+ }
}
break;
@@ -930,9 +976,9 @@ public abstract class AbstractAjpProcess
case Constants.SC_A_SECRET:
requestHeaderMessage.getBytes(tmpMB);
- if (requiredSecret != null) {
- secret = true;
- if (!tmpMB.equals(requiredSecret)) {
+ if (secret != null) {
+ secretPresentInRequest = true;
+ if (!tmpMB.equals(secret)) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
@@ -948,7 +994,7 @@ public abstract class AbstractAjpProcess
}
// Check if secret was submitted if required
- if ((requiredSecret != null) && !secret) {
+ if ((secret != null) && !secretPresentInRequest) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
diff -up ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java.orig ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java
--- ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java.orig 2020-03-03 14:24:23.422779776 -0500
+++ ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java 2020-03-03 14:39:25.219521977 -0500
@@ -16,6 +16,8 @@
*/
package org.apache.coyote.ajp;
+import java.util.regex.Pattern;
+
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
@@ -85,8 +87,61 @@ public abstract class AbstractAjpProtoco
* Required secret.
*/
protected String requiredSecret = null;
+ private String secret = null;
+ /**
+ * Set the secret that must be included with every request.
+ *
+ * @param secret The required secret
+ */
+ public void setSecret(String secret) {
+ this.secret = secret;
+ this.requiredSecret = secret;
+ }
+ protected String getSecret() {
+ return secret;
+ }
+ /**
+ * Set the required secret that must be included with every request.
+ *
+ * @param requiredSecret The required secret
+ *
+ * @deprecated Replaced by {@link #setSecret(String)}.
+ * Will be removed in Tomcat 11 onwards
+ */
+ @Deprecated
public void setRequiredSecret(String requiredSecret) {
- this.requiredSecret = requiredSecret;
+ setSecret(requiredSecret);
+ }
+ /**
+ * @return The current secret
+ *
+ * @deprecated Replaced by {@link #getSecret()}.
+ * Will be removed in Tomcat 11 onwards
+ */
+ @Deprecated
+ protected String getRequiredSecret() {
+ return getSecret();
+ }
+
+
+ private boolean secretRequired = false;
+ public void setSecretRequired(boolean secretRequired) {
+ this.secretRequired = secretRequired;
+ }
+ public boolean getSecretRequired() {
+ return secretRequired;
+ }
+
+
+ private Pattern allowedRequestAttributesPatternPattern;
+ public void setAllowedRequestAttributesPattern(String allowedRequestAttributesPattern) {
+ this.allowedRequestAttributesPatternPattern = Pattern.compile(allowedRequestAttributesPattern);
+ }
+ public String getAllowedRequestAttributesPattern() {
+ return allowedRequestAttributesPatternPattern.pattern();
+ }
+ protected Pattern getAllowedRequestAttributesPatternPattern() {
+ return allowedRequestAttributesPatternPattern;
}
@@ -136,4 +191,16 @@ public abstract class AbstractAjpProtoco
return null;
}
}
+
+
+ @Override
+ public void start() throws Exception {
+ if (getSecretRequired()) {
+ String secret = getSecret();
+ if (secret == null || secret.length() == 0) {
+ throw new IllegalArgumentException(sm.getString("ajpprotocol.nosecret"));
+ }
+ }
+ super.start();
+ }
}
diff -up ./java/org/apache/coyote/ajp/AjpAprProtocol.java.orig ./java/org/apache/coyote/ajp/AjpAprProtocol.java
--- ./java/org/apache/coyote/ajp/AjpAprProtocol.java.orig 2020-03-03 14:24:23.426779766 -0500
+++ ./java/org/apache/coyote/ajp/AjpAprProtocol.java 2020-03-03 14:26:28.556466692 -0500
@@ -148,10 +148,11 @@ public class AjpAprProtocol extends Abst
processor.setAjpFlush(proto.getAjpFlush());
processor.setTomcatAuthentication(proto.tomcatAuthentication);
processor.setTomcatAuthorization(proto.getTomcatAuthorization());
- processor.setRequiredSecret(proto.requiredSecret);
+ processor.setSecret(proto.getSecret());
processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
processor.setClientCertProvider(proto.getClientCertProvider());
processor.setMaxCookieCount(proto.getMaxCookieCount());
+ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern());
register(processor);
return processor;
}
diff -up ./java/org/apache/coyote/ajp/AjpNioProtocol.java.orig ./java/org/apache/coyote/ajp/AjpNioProtocol.java
--- ./java/org/apache/coyote/ajp/AjpNioProtocol.java.orig 2020-03-03 14:24:23.430779756 -0500
+++ ./java/org/apache/coyote/ajp/AjpNioProtocol.java 2020-03-03 14:26:28.556466692 -0500
@@ -178,10 +178,11 @@ public class AjpNioProtocol extends Abst
processor.setAjpFlush(proto.getAjpFlush());
processor.setTomcatAuthentication(proto.tomcatAuthentication);
processor.setTomcatAuthorization(proto.getTomcatAuthorization());
- processor.setRequiredSecret(proto.requiredSecret);
+ processor.setSecret(proto.getSecret());
processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
processor.setClientCertProvider(proto.getClientCertProvider());
processor.setMaxCookieCount(proto.getMaxCookieCount());
+ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern());
register(processor);
return processor;
}
diff -up ./java/org/apache/coyote/ajp/AjpProtocol.java.orig ./java/org/apache/coyote/ajp/AjpProtocol.java
--- ./java/org/apache/coyote/ajp/AjpProtocol.java.orig 2020-03-03 14:24:23.435779743 -0500
+++ ./java/org/apache/coyote/ajp/AjpProtocol.java 2020-03-03 14:26:28.556466692 -0500
@@ -136,10 +136,11 @@ public class AjpProtocol extends Abstrac
processor.setAjpFlush(proto.getAjpFlush());
processor.setTomcatAuthentication(proto.tomcatAuthentication);
processor.setTomcatAuthorization(proto.getTomcatAuthorization());
- processor.setRequiredSecret(proto.requiredSecret);
+ processor.setSecret(proto.getSecret());
processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
processor.setClientCertProvider(proto.getClientCertProvider());
processor.setMaxCookieCount(proto.getMaxCookieCount());
+ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern());
register(processor);
return processor;
}
diff -up ./java/org/apache/coyote/ajp/LocalStrings.properties.orig ./java/org/apache/coyote/ajp/LocalStrings.properties
--- ./java/org/apache/coyote/ajp/LocalStrings.properties.orig 2020-03-03 14:24:23.439779733 -0500
+++ ./java/org/apache/coyote/ajp/LocalStrings.properties 2020-03-03 14:26:13.057505470 -0500
@@ -14,6 +14,7 @@
# limitations under the License.
ajpprotocol.endpoint.starterror=Error starting endpoint
ajpprotocol.init=Initializing Coyote AJP/1.3 on {0}
+ajpprotocol.nosecret=The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid.
ajpprotocol.start=Starting Coyote AJP/1.3 on {0}
ajpprotocol.failedwrite=Socket write failed
ajpprotocol.request.register=Error registering request processor in JMX
diff -up ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java.orig ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
--- ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java.orig 2020-03-03 14:24:23.442779726 -0500
+++ ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java 2020-03-03 14:26:28.556466692 -0500
@@ -30,14 +30,26 @@ import javax.servlet.http.HttpServletReq
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.apache.catalina.Context;
+import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
public class TestAbstractAjpProcessor extends TomcatBaseTest {
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ Connector c = getTomcatInstance().getConnector();
+ c.setProperty("secretRequired", "false");
+ c.setProperty("allowedRequestAttributesPattern", "MYATTRIBUTE.*");
+ }
+
@Override
protected String getProtocol() {
/*
diff -up ./webapps/docs/changelog.xml.orig ./webapps/docs/changelog.xml
--- ./webapps/docs/changelog.xml.orig 2020-03-03 14:24:23.446779716 -0500
+++ ./webapps/docs/changelog.xml 2020-03-03 14:41:14.526247697 -0500
@@ -57,6 +57,25 @@
They eventually become mixed with the numbered issues. (I.e., numbered
issues do not "pop up" wrt. others).
-->
+<section name="Tomcat 7.0.76-11 (csutherl)">
+ <subsection name="Coyote">
+ <changelog>
+ <add>
+ Rename the <code>requiredSecret</code> attribute of the AJP/1.3
+ Connector to <code>secret</code> and add a new attribute
+ <code>secretRequired</code> that defaults to <code>true</code>. When
+ <code>secretRequired</code> is <code>true</code> the AJP/1.3 Connector
+ will not start unless the <code>secret</code> attribute is configured to
+ a non-null, non-zero length String. (markt)
+ </add>
+ <add>
+ Add a new attribute, <code>allowedRequestAttributesPattern</code> to
+ the AJP/1.3 Connector. Requests with unreconised attributes will be
+ blocked with a 403. (markt)
+ </add>
+ </changelog>
+ </subsection>
+</section>
<section name="Tomcat 7.0.76-9 (csutherl)">
<subsection name="Catalina">
<changelog>
diff -up ./webapps/docs/config/ajp.xml.orig ./webapps/docs/config/ajp.xml
--- ./webapps/docs/config/ajp.xml.orig 2020-03-03 14:24:23.448779711 -0500
+++ ./webapps/docs/config/ajp.xml 2020-03-03 14:39:44.506473588 -0500
@@ -312,6 +312,25 @@
interface.</p>
</attribute>
+ <attribute name="allowedRequestAttributesPattern" required="false">
+ <p>The AJP protocol passes some information from the reverse proxy to the
+ AJP connector using request attributes. These attributes are:</p>
+ <ul>
+ <li>javax.servlet.request.cipher_suite</li>
+ <li>javax.servlet.request.key_size</li>
+ <li>javax.servlet.request.ssl_session</li>
+ <li>javax.servlet.request.X509Certificate</li>
+ <li>AJP_LOCAL_ADDR</li>
+ <li>AJP_REMOTE_PORT</li>
+ <li>AJP_SSL_PROTOCOL</li>
+ <li>JK_LB_ACTIVATION</li>
+ </ul>
+ <p>The AJP protocol supports the passing of arbitrary request attributes.
+ Requests containing arbitrary request attributes will be rejected with a
+ 403 response unless the entire attribute name matches this regular
+ expression. If not specified, the default value is <code>null</code>.</p>
+ </attribute>
+
<attribute name="bindOnInit" required="false">
<p>Controls when the socket used by the connector is bound. By default it
is bound when the connector is initiated and unbound when the connector is
@@ -436,8 +455,18 @@
expected concurrent requests (synchronous and asynchronous).</p>
</attribute>
- <attribute name="requiredSecret" required="false">
+ <attribute name="secret" required="false">
<p>Only requests from workers with this secret keyword will be accepted.
+ The default value is <code>null</code>. This attrbute must be specified
+ with a non-null, non-zero length value unless
+ <strong>secretRequired</strong> is explicitly configured to be
+ <code>false</code>.</p>
+ </attribute>
+
+ <attribute name="secretRequired" required="false">
+ <p>If this attribute is <code>true</code>, the AJP Connector will only
+ start if the <strong>secret</strong> attribute is configured with a
+ non-null, non-zero length value. The default value is <code>false</code>.
</p>
</attribute>