Я подумываю о разработке приложения для Google App Engine, которое не должно получать слишком много трафика. Я действительно предпочел бы не платить за превышение бесплатных квот. Однако похоже, что было бы довольно легко вызвать атаку отказа в обслуживании, перегрузив приложение и превысив квоты. Есть ли способы предотвратить или усложнить превышение бесплатных квот? Я знаю, что могу, например, ограничить количество запросов с IP (что затрудняет превышение квоты ЦП), но есть ли способ усложнить превышение квот запросов или пропускной способности?
Можно ли предотвратить DoSing в Google App Engine?
Ответы (5)
Нет встроенных инструментов для предотвращения DoS. Если вы пишете Google Apps с использованием java, вы можете использовать фильтр service.FloodFilter
. Следующий фрагмент кода выполнится раньше, чем любой из ваших сервлетов.
package service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
*
* This filter can protect web server from simple DoS attacks
* via request flooding.
*
* It can limit a number of simultaneously processing requests
* from one ip and requests to one page.
*
* To use filter add this lines to your web.xml file in a <web-app> section.
*
<filter>
<filter-name>FloodFilter</filter-name>
<filter-class>service.FloodFilter</filter-class>
<init-param>
<param-name>maxPageRequests</param-name>
<param-value>50</param-value>
</init-param>
<init-param>
<param-name>maxClientRequests</param-name>
<param-value>5</param-value>
</init-param>
<init-param>
<param-name>busyPage</param-name>
<param-value>/busy.html</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>JSP flood filter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
*
* PARAMETERS
*
* maxPageRequests: limits simultaneous requests to every page
* maxClientRequests: limits simultaneous requests from one client (ip)
* busyPage: busy page to send to client if the limit is exceeded
* this page MUST NOT be intercepted by this filter
*
*/
public class FloodFilter implements Filter
{
private Map <String, Integer> pageRequests;
private Map <String, Integer> clientRequests;
private ServletContext context;
private int maxPageRequests = 50;
private int maxClientRequests = 10;
private String busyPage = "/busy.html";
public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
{
String page = null;
String ip = null;
try {
if ( request instanceof HttpServletRequest ) {
// obtaining client ip and page URI without parameters & jsessionid
HttpServletRequest req = (HttpServletRequest) request;
page = req.getRequestURI();
if ( page.indexOf( ';' ) >= 0 )
page = page.substring( 0, page.indexOf( ';' ) );
ip = req.getRemoteAddr();
// trying & registering request
if ( !tryRequest( page, ip ) ) {
// too many requests in process (from one client or for this page)
context.log( "Flood denied from "+ip+" on page "+page );
page = null;
// forwarding to busy page
context.getRequestDispatcher( busyPage ).forward( request, response );
return;
}
}
// requesting next filter or servlet
chain.doFilter( request, response );
} finally {
if ( page != null )
// unregistering the request
releaseRequest( page, ip );
}
}
private synchronized boolean tryRequest( String page, String ip )
{
// checking page requests
Integer pNum = pageRequests.get( page );
if ( pNum == null )
pNum = 1;
else {
if ( pNum > maxPageRequests )
return false;
pNum = pNum + 1;
}
// checking client requests
Integer cNum = clientRequests.get( ip );
if ( cNum == null )
cNum = 1;
else {
if ( cNum > maxClientRequests )
return false;
cNum = cNum + 1;
}
pageRequests.put( page, pNum );
clientRequests.put( ip, cNum );
return true;
}
private synchronized void releaseRequest( String page, String ip )
{
// removing page request
Integer pNum = pageRequests.get( page );
if ( pNum == null ) return;
if ( pNum <= 1 )
pageRequests.remove( page );
else
pageRequests.put( page, pNum-1 );
// removing client request
Integer cNum = clientRequests.get( ip );
if ( cNum == null ) return;
if ( cNum <= 1 )
clientRequests.remove( ip );
else
clientRequests.put( ip, cNum-1 );
}
public synchronized void init( FilterConfig config ) throws ServletException
{
// configuring filter
this.context = config.getServletContext();
pageRequests = new HashMap <String,Integer> ();
clientRequests = new HashMap <String,Integer> ();
String s = config.getInitParameter( "maxPageRequests" );
if ( s != null )
maxPageRequests = Integer.parseInt( s );
s = config.getInitParameter( "maxClientRequests" );
if ( s != null )
maxClientRequests = Integer.parseInt( s );
s = config.getInitParameter( "busyPage" );
if ( s != null )
busyPage = s;
}
public synchronized void destroy()
{
pageRequests.clear();
clientRequests.clear();
}
}
Если вы используете python, возможно, вам придется использовать собственный фильтр.
Я не уверен, возможно ли это, но в часто задаваемых вопросах по App Engine указано что, если вы докажете, что это DOS-атака, они вернут все сборы, связанные с атакой.
Кажется, что у них есть фильтр на основе IP-адреса, доступный как для Python, так и для Java (я знаю, что это старый поток, но он все еще занимает высокое место в поиске Google).
https://developers.google.com/appengine/docs/python/config/dos
Всегда можно использовать службу, которая предоставляет функции защиты от отказа в обслуживании, перед приложением App Engine. Например, Cloudflare предоставляет уважаемый сервис https://www.cloudflare.com/waf/, и есть другие. Насколько я понимаю (отказ от ответственности: я лично не пользовался услугой), эти функции доступны в бесплатном плане.
Также довольно легко создать реализацию ограничения скорости на основе кэша памяти в самом приложении. Вот первый результат поиска этого метода в Google: http://blog.simonwillison.net/post/57956846132/ratelimitcache. Этот механизм надежен и может быть рентабельным, поскольку использование общего кэша памяти может быть достаточным и бесплатным. Кроме того, этот маршрут дает вам возможность управлять ручками. Недостатком является то, что приложение само должно обрабатывать HTTP-запрос и принимать решение разрешить или отклонить его, поэтому может возникнуть необходимость в расходах (или исчерпании [бесплатной] квоты).
Полное раскрытие информации: я работаю в Google над App Engine и не имею никакого отношения к Cloudflare или Саймону Уиллисону.
Недавно был выпущен брандмауэр GAE, призванный заменить предыдущая, довольно ограниченная, служба защиты от DoS.
Он поддерживает программное обновление правил брандмауэра через (REST) Admin API: apps.firewall.ingressRules, которые можно комбинировать с частью логики в приложении для обнаружения DoS, как описано в других ответах. Разница будет в том, что после развертывания правила с проблемных запросов больше не будет взиматься плата, поскольку они больше не доходят до приложения, поэтому сама фильтрация в приложении не требуется.