IPAccessHandler.java revision 03928aee4356845252ac6b662d5c72c29903813e
1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.server.handler;
20
21import java.io.IOException;
22import java.util.Collections;
23import java.util.List;
24import java.util.Map;
25
26import javax.servlet.ServletException;
27import javax.servlet.http.HttpServletRequest;
28import javax.servlet.http.HttpServletResponse;
29
30import org.eclipse.jetty.http.HttpStatus;
31import org.eclipse.jetty.http.PathMap;
32import org.eclipse.jetty.io.EndPoint;
33import org.eclipse.jetty.server.AbstractHttpConnection;
34import org.eclipse.jetty.server.Request;
35import org.eclipse.jetty.util.IPAddressMap;
36import org.eclipse.jetty.util.log.Log;
37import org.eclipse.jetty.util.log.Logger;
38
39
40/**
41 * IP Access Handler
42 * <p>
43 * Controls access to the wrapped handler by the real remote IP. Control is provided
44 * by white/black lists that include both internet addresses and URIs. This handler
45 * uses the real internet address of the connection, not one reported in the forwarded
46 * for headers, as this cannot be as easily forged.
47 * <p>
48 * Typically, the black/white lists will be used in one of three modes:
49 * <ul>
50 * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
51 * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
52 * <li>Allowing a general range of IPs/URLs by specifying several general white list
53 * entries, that are then further refined by several specific black list exceptions
54 * </ul>
55 * <p>
56 * An empty white list is treated as match all. If there is at least one entry in
57 * the white list, then a request must match a white list entry. Black list entries
58 * are always applied, so that even if an entry matches the white list, a black list
59 * entry will override it.
60 * <p>
61 * Internet addresses may be specified as absolute address or as a combination of
62 * four octet wildcard specifications (a.b.c.d) that are defined as follows.
63 * </p>
64 * <pre>
65 * nnn - an absolute value (0-255)
66 * mmm-nnn - an inclusive range of absolute values,
67 *           with following shorthand notations:
68 *           nnn- => nnn-255
69 *           -nnn => 0-nnn
70 *           -    => 0-255
71 * a,b,... - a list of wildcard specifications
72 * </pre>
73 * <p>
74 * Internet address specification is separated from the URI pattern using the "|" (pipe)
75 * character. URI patterns follow the servlet specification for simple * prefix and
76 * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
77 * <p>
78 * Earlier versions of the handler used internet address prefix wildcard specification
79 * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
80 * They also used the first "/" character of the URI pattern to separate it from the
81 * internet address. Both of these features have been deprecated in the current version.
82 * <p>
83 * Examples of the entry specifications are:
84 * <ul>
85 * <li>10.10.1.2 - all requests from IP 10.10.1.2
86 * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
87 * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
88 * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
89 * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
90 * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
91 * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
92 *                                  to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
93 * </ul>
94 * <p>
95 * Earlier versions of the handler used internet address prefix wildcard specification
96 * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
97 * They also used the first "/" character of the URI pattern to separate it from the
98 * internet address. Both of these features have been deprecated in the current version.
99 */
100public class IPAccessHandler extends HandlerWrapper
101{
102    private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
103
104    IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>();
105    IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>();
106
107    /* ------------------------------------------------------------ */
108    /**
109     * Creates new handler object
110     */
111    public IPAccessHandler()
112    {
113        super();
114    }
115
116    /* ------------------------------------------------------------ */
117    /**
118     * Creates new handler object and initializes white- and black-list
119     *
120     * @param white array of whitelist entries
121     * @param black array of blacklist entries
122     */
123    public IPAccessHandler(String[] white, String []black)
124    {
125        super();
126
127        if (white != null && white.length > 0)
128            setWhite(white);
129        if (black != null && black.length > 0)
130            setBlack(black);
131    }
132
133    /* ------------------------------------------------------------ */
134    /**
135     * Add a whitelist entry to an existing handler configuration
136     *
137     * @param entry new whitelist entry
138     */
139    public void addWhite(String entry)
140    {
141        add(entry, _white);
142    }
143
144    /* ------------------------------------------------------------ */
145    /**
146     * Add a blacklist entry to an existing handler configuration
147     *
148     * @param entry new blacklist entry
149     */
150    public void addBlack(String entry)
151    {
152        add(entry, _black);
153    }
154
155    /* ------------------------------------------------------------ */
156    /**
157     * Re-initialize the whitelist of existing handler object
158     *
159     * @param entries array of whitelist entries
160     */
161    public void setWhite(String[] entries)
162    {
163        set(entries, _white);
164    }
165
166    /* ------------------------------------------------------------ */
167    /**
168     * Re-initialize the blacklist of existing handler object
169     *
170     * @param entries array of blacklist entries
171     */
172    public void setBlack(String[] entries)
173    {
174        set(entries, _black);
175    }
176
177    /* ------------------------------------------------------------ */
178    /**
179     * Checks the incoming request against the whitelist and blacklist
180     *
181     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
182     */
183    @Override
184    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
185    {
186        // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
187        AbstractHttpConnection connection = baseRequest.getConnection();
188        if (connection!=null)
189        {
190            EndPoint endp=connection.getEndPoint();
191            if (endp!=null)
192            {
193                String addr = endp.getRemoteAddr();
194                if (addr!=null && !isAddrUriAllowed(addr,baseRequest.getPathInfo()))
195                {
196                    response.sendError(HttpStatus.FORBIDDEN_403);
197                    baseRequest.setHandled(true);
198                    return;
199                }
200            }
201        }
202
203        getHandler().handle(target,baseRequest, request, response);
204    }
205
206
207    /* ------------------------------------------------------------ */
208    /**
209     * Helper method to parse the new entry and add it to
210     * the specified address pattern map.
211     *
212     * @param entry new entry
213     * @param patternMap target address pattern map
214     */
215    protected void add(String entry, IPAddressMap<PathMap> patternMap)
216    {
217        if (entry != null && entry.length() > 0)
218        {
219            boolean deprecated = false;
220            int idx;
221            if (entry.indexOf('|') > 0 )
222            {
223                idx = entry.indexOf('|');
224            }
225            else
226            {
227                idx = entry.indexOf('/');
228                deprecated = (idx >= 0);
229            }
230
231            String addr = idx > 0 ? entry.substring(0,idx) : entry;
232            String path = idx > 0 ? entry.substring(idx) : "/*";
233
234            if (addr.endsWith("."))
235                deprecated = true;
236            if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
237                path=path.substring(1);
238
239            PathMap pathMap = patternMap.get(addr);
240            if (pathMap == null)
241            {
242                pathMap = new PathMap(true);
243                patternMap.put(addr,pathMap);
244            }
245            if (path != null && !"".equals(path))
246                pathMap.put(path,path);
247
248            if (deprecated)
249                LOG.debug(toString() +" - deprecated specification syntax: "+entry);
250        }
251    }
252
253    /* ------------------------------------------------------------ */
254    /**
255     * Helper method to process a list of new entries and replace
256     * the content of the specified address pattern map
257     *
258     * @param entries new entries
259     * @param patternMap target address pattern map
260     */
261    protected void set(String[] entries,  IPAddressMap<PathMap> patternMap)
262    {
263        patternMap.clear();
264
265        if (entries != null && entries.length > 0)
266        {
267            for (String addrPath:entries)
268            {
269                add(addrPath, patternMap);
270            }
271        }
272    }
273
274    /* ------------------------------------------------------------ */
275    /**
276     * Check if specified request is allowed by current IPAccess rules.
277     *
278     * @param addr internet address
279     * @param path context path
280     * @return true if request is allowed
281     *
282     */
283    protected boolean isAddrUriAllowed(String addr, String path)
284    {
285        if (_white.size()>0)
286        {
287            boolean match = false;
288
289            Object whiteObj = _white.getLazyMatches(addr);
290            if (whiteObj != null)
291            {
292                List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
293
294                for (Object entry: whiteList)
295                {
296                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
297                    if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
298                        break;
299                }
300            }
301
302            if (!match)
303                return false;
304        }
305
306        if (_black.size() > 0)
307        {
308            Object blackObj = _black.getLazyMatches(addr);
309            if (blackObj != null)
310            {
311                List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
312
313                for (Object entry: blackList)
314                {
315                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
316                    if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
317                        return false;
318                }
319            }
320        }
321
322        return true;
323    }
324
325    /* ------------------------------------------------------------ */
326    /**
327     * Dump the white- and black-list configurations when started
328     *
329     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
330     */
331    @Override
332    protected void doStart()
333        throws Exception
334    {
335        super.doStart();
336
337        if (LOG.isDebugEnabled())
338        {
339            System.err.println(dump());
340        }
341    }
342
343    /* ------------------------------------------------------------ */
344    /**
345     * Dump the handler configuration
346     */
347    public String dump()
348    {
349        StringBuilder buf = new StringBuilder();
350
351        buf.append(toString());
352        buf.append(" WHITELIST:\n");
353        dump(buf, _white);
354        buf.append(toString());
355        buf.append(" BLACKLIST:\n");
356        dump(buf, _black);
357
358        return buf.toString();
359    }
360
361    /* ------------------------------------------------------------ */
362    /**
363     * Dump a pattern map into a StringBuilder buffer
364     *
365     * @param buf buffer
366     * @param patternMap pattern map to dump
367     */
368    protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
369    {
370        for (String addr: patternMap.keySet())
371        {
372            for (Object path: ((PathMap)patternMap.get(addr)).values())
373            {
374                buf.append("# ");
375                buf.append(addr);
376                buf.append("|");
377                buf.append(path);
378                buf.append("\n");
379            }
380        }
381    }
382 }
383