View | Details | Raw Unified | Return to bug 47330
Collapse All | Expand All

(-)webapps/docs/config/valve.xml (+82 lines)
Lines 624-629 Link Here
624
</section>
624
</section>
625
625
626
626
627
<section name="Remote IP Valve">
628
629
  <subsection name="Introduction">
630
  
631
    <p>Tomcat port of
632
    <a href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>,
633
    this valve replaces the apparent client remote IP address and hostname for
634
    the request with the IP address list presented by a proxy or a load balancer
635
    via a request headers (e.g. &quot;X-Forwarded-For&quot;).</p>
636
637
    <p>Another feature of this valve is to replace the apparent scheme
638
    (http/https) and server port with the scheme presented by a proxy or a load
639
    balancer via a request header (e.g. &quot;X-Forwarded-Proto&quot;).</p>
640
 
641
    <p>This Valve may be used at the <code>Engine</code>, <code>Host</code> or
642
    <code>Context</code> level as required. Normally, this Valve would be used
643
    at the <code>Engine</code> level.</p>
644
645
  </subsection>
646
647
  <subsection name="Attributes">
648
649
    <p>The <strong>Remote IP Valve</strong> supports the
650
    following configuration attributes:</p>
651
652
    <attributes>
653
654
      <attribute name="className" required="true">
655
        <p>Java class name of the implementation to use.  This MUST be set to
656
        <strong>org.apache.catalina.valves.RemoteIpValve</strong>.</p>
657
      </attribute>
658
659
      <attribute name="remoteIPHeader" required="false">
660
        <p>Name of the HTTP Header read by this valve that holds the list of
661
        traversed IP addresses starting from the requesting client. If not
662
        specified, the default of <code>x-forwarded-for</code> is used.</p>
663
      </attribute>
664
665
      <attribute name="internalProxies" required="false">
666
        <p>List of internal proxies' IP addresses as comma separated regular
667
        expressions. If they appear in the <strong>remoteIpHeader</strong>
668
        value, they will be trusted and will not appear in the
669
        <strong>proxiesHeader</strong> value. If not specified the default value
670
        of <code>10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3},
671
        169\.254\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3}</code> will
672
        be used.</p>
673
      </attribute>
674
675
      <attribute name="proxiesHeader" required="false">
676
        <p>Name of the HTTP header created by this valve to hold the list of
677
        proxies that have been processed in the incoming
678
        <strong>remoteIpHeader</strong>. If not specified, the default of
679
        <code>x-forwarded-by</code> is used.</p>
680
      </attribute>
681
682
      <attribute name="trustedProxies" required="false">
683
        <p>List of trusted proxies' IP addresses as comma separated regular
684
        expressions. If they appear in the <strong>remoteIpHeader</strong>
685
        value, they will be trusted and will appear in the
686
        <strong>proxiesHeader</strong> value. If not specified, no proxies will
687
        be trusted.</p>
688
      </attribute>
689
690
      <attribute name="protocolHeader" required="false">
691
        <p>Name of the HTTP Header read by this valve that holds the protocol
692
        used by the client to connect to the proxy. If not specified, the
693
        default of <code>null</code> is used.</p>
694
      </attribute>
695
696
      <attribute name="protocolHeaderHttpsValue" required="false">
697
        <p>Value of the <strong>protocolHeader</strong> to indicate that it is
698
        an HTTPS request. If not specified, the default of <code>https</code> is
699
        used.</p>
700
      </attribute>
701
702
    </attributes>
703
704
  </subsection>
705
706
</section>
707
708
627
</body>
709
</body>
628
710
629
711
(-)test/org/apache/catalina/valves/RemoteIpValveTest.java (+386 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.catalina.valves;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.Arrays;
23
import java.util.List;
24
25
import javax.servlet.ServletException;
26
27
import junit.framework.TestCase;
28
29
import org.apache.catalina.connector.Request;
30
import org.apache.catalina.connector.Response;
31
import org.apache.catalina.valves.ValveBase;
32
33
/**
34
 * {@link RemoteIpValve} Tests
35
 */
36
public class RemoteIpValveTest extends TestCase {
37
    
38
    static class RemoteAddrAndHostTrackerValve extends ValveBase {
39
        private String remoteAddr;
40
        private String remoteHost;
41
        
42
        public String getRemoteAddr() {
43
            return remoteAddr;
44
        }
45
        
46
        public String getRemoteHost() {
47
            return remoteHost;
48
        }
49
        
50
        @Override
51
        public void invoke(Request request, Response response) throws IOException, ServletException {
52
            this.remoteHost = request.getRemoteHost();
53
            this.remoteAddr = request.getRemoteAddr();
54
        }
55
    }
56
    
57
    public void testCommaDelimitedListToStringArray() {
58
        List<String> elements = Arrays.asList("element1", "element2", "element3");
59
        String actual = RemoteIpValve.listToCommaDelimitedString(elements);
60
        assertEquals("element1, element2, element3", actual);
61
    }
62
    
63
    public void testCommaDelimitedListToStringArrayEmptyList() {
64
        List<String> elements = new ArrayList<String>();
65
        String actual = RemoteIpValve.listToCommaDelimitedString(elements);
66
        assertEquals("", actual);
67
    }
68
    
69
    public void testCommaDelimitedListToStringArrayNullList() {
70
        String actual = RemoteIpValve.listToCommaDelimitedString(null);
71
        assertEquals("", actual);
72
    }
73
    
74
    public void testInvokeAllowedRemoteAddrWithNullRemoteIpHeader() throws Exception {
75
        // PREPARE
76
        RemoteIpValve remoteIpValve = new RemoteIpValve();
77
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
78
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
79
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
80
        remoteIpValve.setProxiesHeader("x-forwarded-by");
81
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
82
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
83
        
84
        Request request = new Request();
85
        request.setCoyoteRequest(new org.apache.coyote.Request());
86
        request.setRemoteAddr("192.168.0.10");
87
        request.setRemoteHost("remote-host-original-value");
88
        
89
        // TEST
90
        remoteIpValve.invoke(request, null);
91
        
92
        // VERIFY
93
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
94
        assertNull("x-forwarded-for must be null", actualXForwardedFor);
95
        
96
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
97
        assertNull("x-forwarded-by must be null", actualXForwardedBy);
98
        
99
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
100
        assertEquals("remoteAddr", "192.168.0.10", actualRemoteAddr);
101
        
102
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
103
        assertEquals("remoteHost", "remote-host-original-value", actualRemoteHost);
104
        
105
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
106
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
107
        
108
        String actualPostInvokeRemoteHost = request.getRemoteHost();
109
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
110
        
111
    }
112
    
113
    public void testInvokeAllProxiesAreTrusted() throws Exception {
114
        
115
        // PREPARE
116
        RemoteIpValve remoteIpValve = new RemoteIpValve();
117
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
118
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
119
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
120
        remoteIpValve.setProxiesHeader("x-forwarded-by");
121
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
122
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
123
        
124
        Request request = new Request();
125
        request.setCoyoteRequest(new org.apache.coyote.Request());
126
        request.setRemoteAddr("192.168.0.10");
127
        request.setRemoteHost("remote-host-original-value");
128
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
129
        
130
        // TEST
131
        remoteIpValve.invoke(request, null);
132
        
133
        // VERIFY
134
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
135
        assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
136
        
137
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
138
        assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
139
        
140
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
141
        assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
142
        
143
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
144
        assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
145
        
146
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
147
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
148
        
149
        String actualPostInvokeRemoteHost = request.getRemoteHost();
150
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
151
    }
152
    
153
    public void testInvokeAllProxiesAreTrustedOrInternal() throws Exception {
154
        
155
        // PREPARE
156
        RemoteIpValve remoteIpValve = new RemoteIpValve();
157
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
158
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
159
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
160
        remoteIpValve.setProxiesHeader("x-forwarded-by");
161
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
162
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
163
        
164
        Request request = new Request();
165
        request.setCoyoteRequest(new org.apache.coyote.Request());
166
        request.setRemoteAddr("192.168.0.10");
167
        request.setRemoteHost("remote-host-original-value");
168
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for")
169
            .setString("140.211.11.130, proxy1, proxy2, 192.168.0.10, 192.168.0.11");
170
        
171
        // TEST
172
        remoteIpValve.invoke(request, null);
173
        
174
        // VERIFY
175
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
176
        assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
177
        
178
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
179
        assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
180
        
181
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
182
        assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
183
        
184
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
185
        assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
186
        
187
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
188
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
189
        
190
        String actualPostInvokeRemoteHost = request.getRemoteHost();
191
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
192
    }
193
    
194
    public void testInvokeAllProxiesAreInternal() throws Exception {
195
        
196
        // PREPARE
197
        RemoteIpValve remoteIpValve = new RemoteIpValve();
198
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
199
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
200
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
201
        remoteIpValve.setProxiesHeader("x-forwarded-by");
202
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
203
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
204
        
205
        Request request = new Request();
206
        request.setCoyoteRequest(new org.apache.coyote.Request());
207
        request.setRemoteAddr("192.168.0.10");
208
        request.setRemoteHost("remote-host-original-value");
209
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, 192.168.0.10, 192.168.0.11");
210
        
211
        // TEST
212
        remoteIpValve.invoke(request, null);
213
        
214
        // VERIFY
215
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
216
        assertNull("all proxies are internal, x-forwarded-for must be null", actualXForwardedFor);
217
        
218
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
219
        assertNull("all proxies are internal, x-forwarded-by must be null", actualXForwardedBy);
220
        
221
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
222
        assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
223
        
224
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
225
        assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
226
        
227
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
228
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
229
        
230
        String actualPostInvokeRemoteHost = request.getRemoteHost();
231
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
232
    }
233
    
234
    public void testInvokeAllProxiesAreTrustedAndRemoteAddrMatchRegexp() throws Exception {
235
        
236
        // PREPARE
237
        RemoteIpValve remoteIpValve = new RemoteIpValve();
238
        remoteIpValve.setInternalProxies("127\\.0\\.0\\.1, 192\\.168\\..*, another-internal-proxy");
239
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
240
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
241
        remoteIpValve.setProxiesHeader("x-forwarded-by");
242
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
243
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
244
        
245
        Request request = new Request();
246
        request.setCoyoteRequest(new org.apache.coyote.Request());
247
        request.setRemoteAddr("192.168.0.10");
248
        request.setRemoteHost("remote-host-original-value");
249
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
250
        
251
        // TEST
252
        remoteIpValve.invoke(request, null);
253
        
254
        // VERIFY
255
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
256
        assertNull("all proxies are trusted, x-forwarded-for must be null", actualXForwardedFor);
257
        
258
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
259
        assertEquals("all proxies are trusted, they must appear in x-forwarded-by", "proxy1, proxy2", actualXForwardedBy);
260
        
261
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
262
        assertEquals("remoteAddr", "140.211.11.130", actualRemoteAddr);
263
        
264
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
265
        assertEquals("remoteHost", "140.211.11.130", actualRemoteHost);
266
        
267
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
268
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
269
        
270
        String actualPostInvokeRemoteHost = request.getRemoteHost();
271
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
272
    }
273
    
274
    public void testInvokeNotAllowedRemoteAddr() throws Exception {
275
        // PREPARE
276
        RemoteIpValve remoteIpValve = new RemoteIpValve();
277
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
278
        remoteIpValve.setTrustedProxies("proxy1,proxy2,proxy3");
279
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
280
        remoteIpValve.setProxiesHeader("x-forwarded-by");
281
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
282
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
283
        
284
        Request request = new Request();
285
        request.setCoyoteRequest(new org.apache.coyote.Request());
286
        request.setRemoteAddr("not-allowed-internal-proxy");
287
        request.setRemoteHost("not-allowed-internal-proxy-host");
288
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for").setString("140.211.11.130, proxy1, proxy2");
289
        
290
        // TEST
291
        remoteIpValve.invoke(request, null);
292
        
293
        // VERIFY
294
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
295
        assertEquals("x-forwarded-for must be unchanged", "140.211.11.130, proxy1, proxy2", actualXForwardedFor);
296
        
297
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
298
        assertNull("x-forwarded-by must be null", actualXForwardedBy);
299
        
300
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
301
        assertEquals("remoteAddr", "not-allowed-internal-proxy", actualRemoteAddr);
302
        
303
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
304
        assertEquals("remoteHost", "not-allowed-internal-proxy-host", actualRemoteHost);
305
        
306
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
307
        assertEquals("postInvoke remoteAddr", "not-allowed-internal-proxy", actualPostInvokeRemoteAddr);
308
        
309
        String actualPostInvokeRemoteHost = request.getRemoteHost();
310
        assertEquals("postInvoke remoteAddr", "not-allowed-internal-proxy-host", actualPostInvokeRemoteHost);
311
    }
312
    
313
    public void testInvokeUntrustedProxyInTheChain() throws Exception {
314
        // PREPARE
315
        RemoteIpValve remoteIpValve = new RemoteIpValve();
316
        remoteIpValve.setInternalProxies("192\\.168\\.0\\.10, 192\\.168\\.0\\.11");
317
        remoteIpValve.setTrustedProxies("proxy1, proxy2, proxy3");
318
        remoteIpValve.setRemoteIpHeader("x-forwarded-for");
319
        remoteIpValve.setProxiesHeader("x-forwarded-by");
320
        RemoteAddrAndHostTrackerValve remoteAddrAndHostTrackerValve = new RemoteAddrAndHostTrackerValve();
321
        remoteIpValve.setNext(remoteAddrAndHostTrackerValve);
322
        
323
        Request request = new Request();
324
        request.setCoyoteRequest(new org.apache.coyote.Request());
325
        request.setRemoteAddr("192.168.0.10");
326
        request.setRemoteHost("remote-host-original-value");
327
        request.getCoyoteRequest().getMimeHeaders().addValue("x-forwarded-for")
328
            .setString("140.211.11.130, proxy1, untrusted-proxy, proxy2");
329
        
330
        // TEST
331
        remoteIpValve.invoke(request, null);
332
        
333
        // VERIFY
334
        String actualXForwardedFor = request.getHeader("x-forwarded-for");
335
        assertEquals("ip/host before untrusted-proxy must appear in x-forwarded-for", "140.211.11.130, proxy1", actualXForwardedFor);
336
        
337
        String actualXForwardedBy = request.getHeader("x-forwarded-by");
338
        assertEquals("ip/host after untrusted-proxy must appear in  x-forwarded-by", "proxy2", actualXForwardedBy);
339
        
340
        String actualRemoteAddr = remoteAddrAndHostTrackerValve.getRemoteAddr();
341
        assertEquals("remoteAddr", "untrusted-proxy", actualRemoteAddr);
342
        
343
        String actualRemoteHost = remoteAddrAndHostTrackerValve.getRemoteHost();
344
        assertEquals("remoteHost", "untrusted-proxy", actualRemoteHost);
345
        
346
        String actualPostInvokeRemoteAddr = request.getRemoteAddr();
347
        assertEquals("postInvoke remoteAddr", "192.168.0.10", actualPostInvokeRemoteAddr);
348
        
349
        String actualPostInvokeRemoteHost = request.getRemoteHost();
350
        assertEquals("postInvoke remoteAddr", "remote-host-original-value", actualPostInvokeRemoteHost);
351
    }
352
    
353
    public void testListToCommaDelimitedString() {
354
        String[] actual = RemoteIpValve.commaDelimitedListToStringArray("element1, element2, element3");
355
        String[] expected = new String[] {
356
            "element1", "element2", "element3"
357
        };
358
        assertArrayEquals(expected, actual);
359
    }
360
    
361
    public void testListToCommaDelimitedStringMixedSpaceChars() {
362
        String[] actual = RemoteIpValve.commaDelimitedListToStringArray("element1  , element2,\t element3");
363
        String[] expected = new String[] {
364
            "element1", "element2", "element3"
365
        };
366
        assertArrayEquals(expected, actual);
367
    }
368
    
369
    private void assertArrayEquals(String[] expected, String[] actual) {
370
        if (expected == null) {
371
            assertNull(actual);
372
            return;
373
        }
374
        assertNotNull(actual);
375
        assertEquals(expected.length, actual.length);
376
        List<String> e = new ArrayList<String>();
377
        e.addAll(Arrays.asList(expected));
378
        List<String> a = new ArrayList<String>();
379
        a.addAll(Arrays.asList(actual));
380
        
381
        for (String entry : e) {
382
            assertTrue(a.remove(entry));
383
        }
384
        assertTrue(a.isEmpty());
385
    }
386
}
(-)java/org/apache/catalina/valves/RemoteIpValve.java (+711 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.catalina.valves;
19
20
import java.io.IOException;
21
import java.util.ArrayList;
22
import java.util.Iterator;
23
import java.util.LinkedList;
24
import java.util.List;
25
import java.util.regex.Pattern;
26
import java.util.regex.PatternSyntaxException;
27
28
import javax.servlet.ServletException;
29
30
import org.apache.tomcat.util.res.StringManager;
31
import org.apache.catalina.connector.Request;
32
import org.apache.catalina.connector.Response;
33
import org.apache.catalina.valves.Constants;
34
import org.apache.catalina.valves.RequestFilterValve;
35
import org.apache.catalina.valves.ValveBase;
36
import org.apache.juli.logging.Log;
37
import org.apache.juli.logging.LogFactory;
38
39
/**
40
 * <p>
41
 * Tomcat port of <a href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this valve replaces the apparent
42
 * client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request
43
 * headers (e.g. "X-Forwarded-For").
44
 * </p>
45
 * <p>
46
 * Another feature of this valve is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a
47
 * load balancer via a request header (e.g. "X-Forwarded-Proto").
48
 * </p>
49
 * <p>
50
 * This valve proceeds as follows:
51
 * </p>
52
 * <p>
53
 * If the incoming <code>request.getRemoteAddr()</code> matches the valve's list of internal proxies :
54
 * <ul>
55
 * <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http
56
 * header named <code>$remoteIPHeader</code> (default value <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li>
57
 * <li>For each ip/host of the list:
58
 * <ul>
59
 * <li>if it matches the internal proxies list, the ip/host is swallowed</li>
60
 * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li>
61
 * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li>
62
 * </ul>
63
 * </li>
64
 * <li>If the request http header named <code>$protocolHeader</code> (e.g. <code>x-forwarded-for</code>) equals to the value of
65
 * <code>protocolHeaderHttpsValue</code> configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>,
66
 * <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the
67
 * <code>$httpsServerPort</code> configuration parameter.</li>
68
 * </ul>
69
 * </p>
70
 * <p>
71
 * <strong>Configuration parameters:</strong>
72
 * <table border="1">
73
 * <tr>
74
 * <th>RemoteIpValve property</th>
75
 * <th>Description</th>
76
 * <th>Equivalent mod_remoteip directive</th>
77
 * <th>Format</th>
78
 * <th>Default Value</th>
79
 * </tr>
80
 * <tr>
81
 * <td>remoteIPHeader</td>
82
 * <td>Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting client</td>
83
 * <td>RemoteIPHeader</td>
84
 * <td>Compliant http header name</td>
85
 * <td>x-forwarded-for</td>
86
 * </tr>
87
 * <tr>
88
 * <td>internalProxies</td>
89
 * <td>List of internal proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, they will be trusted and will not appear
90
 * in the <code>proxiesHeader</code> value</td>
91
 * <td>RemoteIPInternalProxy</td>
92
 * <td>Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library)</td>
93
 * <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3} <br/>
94
 * By default, 10/8, 192.168/16, 169.254/16 and 127/8 are allowed ; 172.16/12 has not been enabled by default because it is complex to
95
 * describe with regular expressions</td>
96
 * </tr>
97
 * </tr>
98
 * <tr>
99
 * <td>proxiesHeader</td>
100
 * <td>Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming
101
 * <code>remoteIPHeader</code></td>
102
 * <td>RemoteIPProxiesHeader</td>
103
 * <td>Compliant http header name</td>
104
 * <td>x-forwarded-by</td>
105
 * </tr>
106
 * <tr>
107
 * <td>trustedProxies</td>
108
 * <td>List of trusted proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, they will be trusted and will appear
109
 * in the <code>proxiesHeader</code> value</td>
110
 * <td>RemoteIPTrustedProxy</td>
111
 * <td>Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library)</td>
112
 * <td>&nbsp;</td>
113
 * </tr>
114
 * <tr>
115
 * <td>protocolHeader</td>
116
 * <td>Name of the http header read by this valve that holds the flag that this request </td>
117
 * <td>N/A</td>
118
 * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> or <code>Front-End-Https</code></td>
119
 * <td><code>null</code></td>
120
 * </tr>
121
 * <tr>
122
 * <td>protocolHeaderHttpsValue</td>
123
 * <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td>
124
 * <td>N/A</td>
125
 * <td>String like <code>https</code> or <code>ON</code></td>
126
 * <td><code>https</code></td>
127
 * </tr>
128
 * <tr>
129
 * </table>
130
 * </p>
131
 * <p>
132
 * <p>
133
 * This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform.
134
 * </p>
135
 * <p>
136
 * <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to use address blocks (e.g.
137
 * <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> and <code>RemoteIPTrustedProxy</code> ; as Tomcat doesn't have a
138
 * library similar to <a
139
 * href="http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d">apr_ipsubnet_test</a>,
140
 * <code>RemoteIpValve</code> uses regular expression to configure <code>internalProxies</code> and <code>trustedProxies</code> in the same
141
 * fashion as {@link RequestFilterValve} does.
142
 * </p>
143
 * <hr/>
144
 * <p>
145
 * <strong>Sample with internal proxies</strong>
146
 * </p>
147
 * <p>
148
 * RemoteIpValve configuration:
149
 * </p>
150
 * <code><pre>
151
 * &lt;Valve 
152
 *   className="org.apache.catalina.connector.RemoteIpValve"
153
 *   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
154
 *   remoteIPHeader="x-forwarded-for"
155
 *   remoteIPProxiesHeader="x-forwarded-by"
156
 *   protocolHeader="x-forwarded-proto"
157
 *   /&gt;</pre></code>
158
 * <p>
159
 * Request values:
160
 * <table border="1">
161
 * <tr>
162
 * <th>property</th>
163
 * <th>Value Before RemoteIpValve</th>
164
 * <th>Value After RemoteIpValve</th>
165
 * </tr>
166
 * <tr>
167
 * <td>request.remoteAddr</td>
168
 * <td>192.168.0.10</td>
169
 * <td>140.211.11.130</td>
170
 * </tr>
171
 * <tr>
172
 * <td>request.header['x-forwarded-for']</td>
173
 * <td>140.211.11.130, 192.168.0.10</td>
174
 * <td>null</td>
175
 * </tr>
176
 * <tr>
177
 * <td>request.header['x-forwarded-by']</td>
178
 * <td>null</td>
179
 * <td>null</td>
180
 * </tr>
181
 * <tr>
182
 * <td>request.header['x-forwarded-proto']</td>
183
 * <td>https</td>
184
 * <td>https</td>
185
 * </tr>
186
 * <tr>
187
 * <td>request.scheme</td>
188
 * <td>http</td>
189
 * <td>https</td>
190
 * </tr>
191
 * <tr>
192
 * <td>request.secure</td>
193
 * <td>false</td>
194
 * <td>true</td>
195
 * </tr>
196
 * <tr>
197
 * <td>request.serverPort</td>
198
 * <td>80</td>
199
 * <td>443</td>
200
 * </tr>
201
 * </table>
202
 * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request.
203
 * <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
204
 * </p>
205
 * <hr/>
206
 * <p>
207
 * <strong>Sample with trusted proxies</strong>
208
 * </p>
209
 * <p>
210
 * RemoteIpValve configuration:
211
 * </p>
212
 * <code><pre>
213
 * &lt;Valve 
214
 *   className="org.apache.catalina.connector.RemoteIpValve"
215
 *   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
216
 *   remoteIPHeader="x-forwarded-for"
217
 *   remoteIPProxiesHeader="x-forwarded-by"
218
 *   trustedProxies="proxy1, proxy2"
219
 *   /&gt;</pre></code>
220
 * <p>
221
 * Request values:
222
 * <table border="1">
223
 * <tr>
224
 * <th>property</th>
225
 * <th>Value Before RemoteIpValve</th>
226
 * <th>Value After RemoteIpValve</th>
227
 * </tr>
228
 * <tr>
229
 * <td>request.remoteAddr</td>
230
 * <td>192.168.0.10</td>
231
 * <td>140.211.11.130</td>
232
 * </tr>
233
 * <tr>
234
 * <td>request.header['x-forwarded-for']</td>
235
 * <td>140.211.11.130, proxy1, proxy2</td>
236
 * <td>null</td>
237
 * </tr>
238
 * <tr>
239
 * <td>request.header['x-forwarded-by']</td>
240
 * <td>null</td>
241
 * <td>proxy1, proxy2</td>
242
 * </tr>
243
 * </table>
244
 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
245
 * are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
246
 * </p>
247
 * <hr/>
248
 * <p>
249
 * <strong>Sample with internal and trusted proxies</strong>
250
 * </p>
251
 * <p>
252
 * RemoteIpValve configuration:
253
 * </p>
254
 * <code><pre>
255
 * &lt;Valve 
256
 *   className="org.apache.catalina.connector.RemoteIpValve"
257
 *   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
258
 *   remoteIPHeader="x-forwarded-for"
259
 *   remoteIPProxiesHeader="x-forwarded-by"
260
 *   trustedProxies="proxy1, proxy2"
261
 *   /&gt;</pre></code>
262
 * <p>
263
 * Request values:
264
 * <table border="1">
265
 * <tr>
266
 * <th>property</th>
267
 * <th>Value Before RemoteIpValve</th>
268
 * <th>Value After RemoteIpValve</th>
269
 * </tr>
270
 * <tr>
271
 * <td>request.remoteAddr</td>
272
 * <td>192.168.0.10</td>
273
 * <td>140.211.11.130</td>
274
 * </tr>
275
 * <tr>
276
 * <td>request.header['x-forwarded-for']</td>
277
 * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td>
278
 * <td>null</td>
279
 * </tr>
280
 * <tr>
281
 * <td>request.header['x-forwarded-by']</td>
282
 * <td>null</td>
283
 * <td>proxy1, proxy2</td>
284
 * </tr>
285
 * </table>
286
 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
287
 * are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in
288
 * <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
289
 * </p>
290
 * <hr/>
291
 * <p>
292
 * <strong>Sample with an untrusted proxy</strong>
293
 * </p>
294
 * <p>
295
 * RemoteIpValve configuration:
296
 * </p>
297
 * <code><pre>
298
 * &lt;Valve 
299
 *   className="org.apache.catalina.connector.RemoteIpValve"
300
 *   internalProxies="192\.168\.0\.10, 192\.168\.0\.11"
301
 *   remoteIPHeader="x-forwarded-for"
302
 *   remoteIPProxiesHeader="x-forwarded-by"
303
 *   trustedProxies="proxy1, proxy2"
304
 *   /&gt;</pre></code>
305
 * <p>
306
 * Request values:
307
 * <table border="1">
308
 * <tr>
309
 * <th>property</th>
310
 * <th>Value Before RemoteIpValve</th>
311
 * <th>Value After RemoteIpValve</th>
312
 * </tr>
313
 * <tr>
314
 * <td>request.remoteAddr</td>
315
 * <td>192.168.0.10</td>
316
 * <td>untrusted-proxy</td>
317
 * </tr>
318
 * <tr>
319
 * <td>request.header['x-forwarded-for']</td>
320
 * <td>140.211.11.130, untrusted-proxy, proxy1</td>
321
 * <td>140.211.11.130</td>
322
 * </tr>
323
 * <tr>
324
 * <td>request.header['x-forwarded-by']</td>
325
 * <td>null</td>
326
 * <td>proxy1</td>
327
 * </tr>
328
 * </table>
329
 * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds
330
 * <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we can not trust that
331
 * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> that is an IP
332
 * verified by <code>proxy1</code>.
333
 * </p>
334
 */
335
public class RemoteIpValve extends ValveBase {
336
    
337
    /**
338
     * {@link Pattern} for a comma delimited string that support whitespace characters
339
     */
340
    private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
341
    
342
    /**
343
     * The descriptive information related to this implementation.
344
     */
345
    private static final String info = "org.apache.catalina.connector.RemoteIpValve/1.0";
346
    
347
    /**
348
     * Logger
349
     */
350
    private static Log log = LogFactory.getLog(RemoteIpValve.class);
351
    
352
    /**
353
     * The StringManager for this package.
354
     */
355
    protected static StringManager sm = StringManager.getManager(Constants.Package);
356
    
357
    /**
358
     * Convert a given comma delimited list of regular expressions into an array of compiled {@link Pattern}
359
     * 
360
     * @return array of patterns (not <code>null</code>)
361
     */
362
    protected static Pattern[] commaDelimitedListToPatternArray(String commaDelimitedPatterns) {
363
        String[] patterns = commaDelimitedListToStringArray(commaDelimitedPatterns);
364
        List<Pattern> patternsList = new ArrayList<Pattern>();
365
        for (String pattern : patterns) {
366
            try {
367
                patternsList.add(Pattern.compile(pattern));
368
            } catch (PatternSyntaxException e) {
369
                throw new IllegalArgumentException(sm.getString("remoteIpValve.syntax", pattern), e);
370
            }
371
        }
372
        return patternsList.toArray(new Pattern[0]);
373
    }
374
    
375
    /**
376
     * Convert a given comma delimited list of regular expressions into an array of String
377
     * 
378
     * @return array of patterns (non <code>null</code>)
379
     */
380
    protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) {
381
        return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern
382
            .split(commaDelimitedStrings);
383
    }
384
    
385
    /**
386
     * Convert an array of strings in a comma delimited string
387
     */
388
    protected static String listToCommaDelimitedString(List<String> stringList) {
389
        if (stringList == null) {
390
            return "";
391
        }
392
        StringBuilder result = new StringBuilder();
393
        for (Iterator<String> it = stringList.iterator(); it.hasNext();) {
394
            Object element = it.next();
395
            if (element != null) {
396
                result.append(element);
397
                if (it.hasNext()) {
398
                    result.append(", ");
399
                }
400
            }
401
        }
402
        return result.toString();
403
    }
404
    
405
    /**
406
     * Return <code>true</code> if the given <code>str</code> matches at least one of the given <code>patterns</code>.
407
     */
408
    protected static boolean matchesOne(String str, Pattern... patterns) {
409
        for (Pattern pattern : patterns) {
410
            if (pattern.matcher(str).matches()) {
411
                return true;
412
            }
413
        }
414
        return false;
415
    }
416
    
417
    /**
418
     * @see #setHttpsServerPort(int)
419
     */
420
    private int httpsServerPort = 443;
421
    
422
    /**
423
     * @see #setInternalProxies(String)
424
     */
425
    private Pattern[] internalProxies = new Pattern[] {
426
        Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"),
427
        Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")
428
    };
429
    
430
    /**
431
     * @see #setProtocolHeader(String)
432
     */
433
    private String protocolHeader = null;
434
    
435
    /**
436
     * @see #setProtocolHeaderHttpsValue(String)
437
     */
438
    private String protocolHeaderHttpsValue = "https";
439
    
440
    /**
441
     * @see #setProxiesHeader(String)
442
     */
443
    private String proxiesHeader = "X-Forwarded-By";
444
    
445
    /**
446
     * @see #setRemoteIpHeader(String)
447
     */
448
    private String remoteIpHeader = "X-Forwarded-For";
449
    
450
    /**
451
     * @see RemoteIpValve#setTrustedProxies(String)
452
     */
453
    private Pattern[] trustedProxies = new Pattern[0];
454
    
455
    public int getHttpsServerPort() {
456
        return httpsServerPort;
457
    }
458
    
459
    /**
460
     * Return descriptive information about this Valve implementation.
461
     */
462
    public String getInfo() {
463
        return info;
464
    }
465
    
466
    /**
467
     * @see #setInternalProxies(String)
468
     * @return comma delimited list of internal proxies
469
     */
470
    public String getInternalProxies() {
471
        List<String> internalProxiesAsStringList = new ArrayList<String>();
472
        for (Pattern internalProxyPattern : internalProxies) {
473
            internalProxiesAsStringList.add(String.valueOf(internalProxyPattern));
474
        }
475
        return listToCommaDelimitedString(internalProxiesAsStringList);
476
    }
477
    
478
    /**
479
     * @see #setProtocolHeader(String)
480
     * @return the protocol header (e.g. "X-Forwarded-Proto")
481
     */
482
    public String getProtocolHeader() {
483
        return protocolHeader;
484
    }
485
    
486
    /**
487
     * @see RemoteIpValve#setProtocolHeaderHttpsValue(String)
488
     * @return the value of the protocol header for incoming https request (e.g. "https")
489
     */
490
    public String getProtocolHeaderHttpsValue() {
491
        return protocolHeaderHttpsValue;
492
    }
493
    
494
    /**
495
     * @see #setProxiesHeader(String)
496
     * @return the proxies header name (e.g. "X-Forwarded-By")
497
     */
498
    public String getProxiesHeader() {
499
        return proxiesHeader;
500
    }
501
    
502
    /**
503
     * @see #setRemoteIpHeader(String)
504
     * @return the remote IP header name (e.g. "X-Forwarded-For")
505
     */
506
    public String getRemoteIpHeader() {
507
        return remoteIpHeader;
508
    }
509
    
510
    /**
511
     * @see #setTrustedProxies(String)
512
     * @return comma delimited list of trusted proxies
513
     */
514
    public String getTrustedProxies() {
515
        List<String> trustedProxiesAsStringList = new ArrayList<String>();
516
        for (Pattern trustedProxy : trustedProxies) {
517
            trustedProxiesAsStringList.add(String.valueOf(trustedProxy));
518
        }
519
        return listToCommaDelimitedString(trustedProxiesAsStringList);
520
    }
521
    
522
    /**
523
     * {@inheritDoc}
524
     */
525
    @Override
526
    public void invoke(Request request, Response response) throws IOException, ServletException {
527
        final String originalRemoteAddr = request.getRemoteAddr();
528
        final String originalRemoteHost = request.getRemoteHost();
529
        final String originalScheme = request.getScheme();
530
        final boolean originalSecure = request.isSecure();
531
        final int originalServerPort = request.getServerPort();
532
        
533
        if (matchesOne(originalRemoteAddr, internalProxies)) {
534
            String remoteIp = null;
535
            // In java 6, proxiesHeaderValue should be declared as a java.util.Deque
536
            LinkedList<String> proxiesHeaderValue = new LinkedList<String>();
537
            
538
            String[] remoteIPHeaderValue = commaDelimitedListToStringArray(request.getHeader(remoteIpHeader));
539
            int idx;
540
            // loop on remoteIPHeaderValue to find the first trusted remote ip and to build the proxies chain
541
            for (idx = remoteIPHeaderValue.length - 1; idx >= 0; idx--) {
542
                String currentRemoteIp = remoteIPHeaderValue[idx];
543
                remoteIp = currentRemoteIp;
544
                if (matchesOne(currentRemoteIp, internalProxies)) {
545
                    // do nothing, internalProxies IPs are not appended to the
546
                } else if (matchesOne(currentRemoteIp, trustedProxies)) {
547
                    proxiesHeaderValue.addFirst(currentRemoteIp);
548
                } else {
549
                    idx--; // decrement idx because break statement doesn't do it
550
                    break;
551
                }
552
            }
553
            // continue to loop on remoteIPHeaderValue to build the new value of the remoteIPHeader
554
            LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>();
555
            for (; idx >= 0; idx--) {
556
                String currentRemoteIp = remoteIPHeaderValue[idx];
557
                newRemoteIpHeaderValue.addFirst(currentRemoteIp);
558
            }
559
            if (remoteIp != null) {
560
                
561
                request.setRemoteAddr(remoteIp);
562
                request.setRemoteHost(remoteIp);
563
                
564
                // use request.coyoteRequest.mimeHeaders.setValue(str).setString(str) because request.addHeader(str, str) is no-op in Tomcat
565
                // 6.0
566
                if (proxiesHeaderValue.size() == 0) {
567
                    request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader);
568
                } else {
569
                    String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
570
                    request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader).setString(commaDelimitedListOfProxies);
571
                }
572
                if (newRemoteIpHeaderValue.size() == 0) {
573
                    request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader);
574
                } else {
575
                    String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
576
                    request.getCoyoteRequest().getMimeHeaders().setValue(remoteIpHeader).setString(commaDelimitedRemoteIpHeaderValue);
577
                }
578
            }
579
            
580
            if (protocolHeader != null) {
581
                String protocolHeaderValue = request.getHeader(protocolHeader);
582
                if (protocolHeaderValue != null && protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) {
583
                    request.setSecure(true);
584
                    // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0
585
                    request.getCoyoteRequest().scheme().setString("https");
586
                    
587
                    request.setServerPort(httpsServerPort);
588
                }
589
            }
590
            
591
            if (log.isDebugEnabled()) {
592
                log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + originalRemoteAddr
593
                          + "', originalRemoteHost='" + originalRemoteHost + "', originalSecure='" + originalSecure + "', originalScheme='"
594
                          + originalScheme + "' will be seen as newRemoteAddr='" + request.getRemoteAddr() + "', newRemoteHost='"
595
                          + request.getRemoteHost() + "', newScheme='" + request.getScheme() + "', newSecure='" + request.isSecure() + "'");
596
            }
597
        }
598
        try {
599
            getNext().invoke(request, response);
600
        } finally {
601
            request.setRemoteAddr(originalRemoteAddr);
602
            request.setRemoteHost(originalRemoteHost);
603
            
604
            request.setSecure(originalSecure);
605
            
606
            // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0
607
            request.getCoyoteRequest().scheme().setString(originalScheme);
608
            
609
            request.setServerPort(originalServerPort);
610
        }
611
    }
612
    
613
    /**
614
     * <p>
615
     * Server Port value if the {@link #protocolHeader} indicates HTTPS
616
     * </p>
617
     * <p>
618
     * Default value : 443
619
     * </p>
620
     */
621
    public void setHttpsServerPort(int httpsServerPort) {
622
        this.httpsServerPort = httpsServerPort;
623
    }
624
    
625
    /**
626
     * <p>
627
     * Comma delimited list of internal proxies. Can be expressed with regular expressions.
628
     * </p>
629
     * <p>
630
     * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3}
631
     * </p>
632
     */
633
    public void setInternalProxies(String commaDelimitedInternalProxies) {
634
        this.internalProxies = commaDelimitedListToPatternArray(commaDelimitedInternalProxies);
635
    }
636
    
637
    /**
638
     * <p>
639
     * Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If <code>null</code>, request.scheme and
640
     * request.secure will not be modified.
641
     * </p>
642
     * <p>
643
     * Default value : <code>null</code>
644
     * </p>
645
     */
646
    public void setProtocolHeader(String protocolHeader) {
647
        this.protocolHeader = protocolHeader;
648
    }
649
    
650
    /**
651
     * <p>
652
     * Case insensitive value of the protocol header to indicate that the incoming http request uses SSL.
653
     * </p>
654
     * <p>
655
     * Default value : <code>https</code>
656
     * </p>
657
     */
658
    public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
659
        this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
660
    }
661
    
662
    /**
663
     * <p>
664
     * The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP
665
     * addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
666
     * while any intermediate RemoteIPInternalProxy addresses are discarded.
667
     * </p>
668
     * <p>
669
     * Name of the http header that holds the list of trusted proxies that has been traversed by the http request.
670
     * </p>
671
     * <p>
672
     * The value of this header can be comma delimited.
673
     * </p>
674
     * <p>
675
     * Default value : <code>X-Forwarded-By</code>
676
     * </p>
677
     */
678
    public void setProxiesHeader(String proxiesHeader) {
679
        this.proxiesHeader = proxiesHeader;
680
    }
681
    
682
    /**
683
     * <p>
684
     * Name of the http header from which the remote ip is extracted.
685
     * </p>
686
     * <p>
687
     * The value of this header can be comma delimited.
688
     * </p>
689
     * <p>
690
     * Default value : <code>X-Forwarded-For</code>
691
     * </p>
692
     * 
693
     * @param remoteIPHeader
694
     */
695
    public void setRemoteIpHeader(String remoteIpHeader) {
696
        this.remoteIpHeader = remoteIpHeader;
697
    }
698
    
699
    /**
700
     * <p>
701
     * Comma delimited list of proxies that are trusted when they appear in the {@link #remoteIPHeader} header. Can be expressed as a
702
     * regular expression.
703
     * </p>
704
     * <p>
705
     * Default value : empty list, no external proxy is trusted.
706
     * </p>
707
     */
708
    public void setTrustedProxies(String commaDelimitedTrustedProxies) {
709
        this.trustedProxies = commaDelimitedListToPatternArray(commaDelimitedTrustedProxies);
710
    }
711
}
(-)java/org/apache/catalina/valves/LocalStrings.properties (+3 lines)
Lines 41-46 Link Here
41
errorReportValve.note=note
41
errorReportValve.note=note
42
errorReportValve.rootCauseInLogs=The full stack trace of the root cause is available in the {0} logs.
42
errorReportValve.rootCauseInLogs=The full stack trace of the root cause is available in the {0} logs.
43
43
44
# Remote IP valve
45
remoteIpValve.syntax=Invalid regular expressions [{0}] provided.
46
44
# HTTP status reports
47
# HTTP status reports
45
http.100=The client may continue ({0}).
48
http.100=The client may continue ({0}).
46
http.101=The server is switching protocols according to the "Upgrade" header ({0}).
49
http.101=The server is switching protocols according to the "Upgrade" header ({0}).
(-)java/org/apache/catalina/valves/mbeans-descriptors.xml (+37 lines)
Lines 355-358 Link Here
355
355
356
  </mbean>
356
  </mbean>
357
357
358
  <mbean name="RemoteIpValve"
359
         description="Valve that sets client information (eg IP address) based on data from a trusted proxy"
360
         domain="Catalina"
361
         group="Valve"
362
         type="org.apache.catalina.valves.RemoteIpValve">
363
    
364
    <attribute name="internalProxies"
365
               description="Comma delimited list of internal proxies"
366
               type="java.lang.String"
367
               writeable="false" />
368
               
369
    <attribute name="protocolHeader"
370
               description="The protocol header (e.g. &quot;X-Forwarded-Proto&quot;)"
371
               type="java.lang.String"
372
               writeable="false" />
373
               
374
    <attribute name="protocolHeaderHttpsValue"
375
               description="The value of the protocol header for incoming https request (e.g. &quot;https&quot;)"
376
               type="java.lang.String"
377
               writeable="false" />
378
               
379
    <attribute name="proxiesHeader"
380
               description="The proxies header name (e.g. &quot;X-Forwarded-By&quot;)"
381
               type="java.lang.String"
382
               writeable="false" />
383
               
384
    <attribute name="remoteIpHedaer"
385
               description="The remote IP header name (e.g. &quot;X-Forwarded-For&quot;)"
386
               type="java.lang.String"
387
               writeable="false" />
388
               
389
    <attribute name="trustedProxies"
390
               description="Comma delimited list of trusted proxies"
391
               type="java.lang.String"
392
               writeable="false" />
393
               
394
  </mbean>
358
</mbeans-descriptors>
395
</mbeans-descriptors>

Return to bug 47330