Blob Blame History Raw
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>