Можно ли предотвратить DoSing в Google App Engine?

Я подумываю о разработке приложения для Google App Engine, которое не должно получать слишком много трафика. Я действительно предпочел бы не платить за превышение бесплатных квот. Однако похоже, что было бы довольно легко вызвать атаку отказа в обслуживании, перегрузив приложение и превысив квоты. Есть ли способы предотвратить или усложнить превышение бесплатных квот? Я знаю, что могу, например, ограничить количество запросов с IP (что затрудняет превышение квоты ЦП), но есть ли способ усложнить превышение квот запросов или пропускной способности?


person Zifre    schedule 04.05.2009    source источник


Ответы (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, возможно, вам придется использовать собственный фильтр.

person Achille    schedule 04.05.2009
comment
Я, вероятно, собираюсь использовать Java для дополнительной скорости, так что это может помочь. - person Zifre; 05.05.2009
comment
В App Engine уже некоторое время поддерживается фильтр DoS. - person Nick Johnson; 16.05.2012
comment
Фильтр DOS работает только для известных IP-адресов, верно? Он не может справиться с DDOS-атаками, IP-адреса которых неизвестны до начала атаки. Кроме того, приведенный выше пример не может защитить использование полосы пропускания статических ресурсов. - person user1431972; 14.04.2014

Я не уверен, возможно ли это, но в часто задаваемых вопросах по App Engine указано что, если вы докажете, что это DOS-атака, они вернут все сборы, связанные с атакой.

person Jeremy Logan    schedule 04.05.2009
comment
Спасибо ... если бы я заплатил за это, я бы почувствовал себя намного лучше в связи с этой проблемой. - person Zifre; 05.05.2009
comment
Если вы не включите выставление счетов, превышение бесплатных квот просто отключит ваш сайт на короткий период (гораздо меньше, чем на целый день). Он будет выставлять вам счет только в том случае, если вы явно включили это, и вы можете установить собственное ограничение на оплату. - person Nick Johnson; 05.05.2009
comment
Я испытал DOS-атаку при загрузке статического файла (что-то вроде 20MB x 600 раз за 2 часа) с одного IP-адреса, я попросил возмещение, и они отказались, заявив, что это не считается DOS-атакой. И они говорят, что если услуга остановлена ​​из-за достижения установленного вами дневного бюджета, это не считается отказом. Я бы сказал, что нам лучше придумать наш собственный способ защиты от DOS-атак, пока Google не исправит их проблему. - person user1431972; 11.11.2013

Кажется, что у них есть фильтр на основе IP-адреса, доступный как для Python, так и для Java (я знаю, что это старый поток, но он все еще занимает высокое место в поиске Google).

https://developers.google.com/appengine/docs/python/config/dos

person Alex    schedule 15.05.2012
comment
Это бесполезно при любой DDoS-атаке, количество пользователей намного превышает 1000 IP-адресов, которые вы можете заблокировать с помощью этого инструмента. Не говоря уже о том, что вам нужно повторно загружать свой веб-сайт каждые несколько минут, поскольку новые злоумышленники участвуют в атаке. - person Igor Jerosimić; 18.11.2012

Всегда можно использовать службу, которая предоставляет функции защиты от отказа в обслуживании, перед приложением App Engine. Например, Cloudflare предоставляет уважаемый сервис https://www.cloudflare.com/waf/, и есть другие. Насколько я понимаю (отказ от ответственности: я лично не пользовался услугой), эти функции доступны в бесплатном плане.

Также довольно легко создать реализацию ограничения скорости на основе кэша памяти в самом приложении. Вот первый результат поиска этого метода в Google: http://blog.simonwillison.net/post/57956846132/ratelimitcache. Этот механизм надежен и может быть рентабельным, поскольку использование общего кэша памяти может быть достаточным и бесплатным. Кроме того, этот маршрут дает вам возможность управлять ручками. Недостатком является то, что приложение само должно обрабатывать HTTP-запрос и принимать решение разрешить или отклонить его, поэтому может возникнуть необходимость в расходах (или исчерпании [бесплатной] квоты).

Полное раскрытие информации: я работаю в Google над App Engine и не имею никакого отношения к Cloudflare или Саймону Уиллисону.

person Tad Hunt    schedule 28.07.2016

Недавно был выпущен брандмауэр GAE, призванный заменить предыдущая, довольно ограниченная, служба защиты от DoS.

Он поддерживает программное обновление правил брандмауэра через (REST) ​​Admin API: apps.firewall.ingressRules, которые можно комбинировать с частью логики в приложении для обнаружения DoS, как описано в других ответах. Разница будет в том, что после развертывания правила с проблемных запросов больше не будет взиматься плата, поскольку они больше не доходят до приложения, поэтому сама фильтрация в приложении не требуется.

person Dan Cornilescu    schedule 26.08.2017
comment
как мы можем вызвать этот API REST с нашего сервера, если они просят Oauth2 использовать его? облако. google.com/appengine/docs/admin-api/reference/rest/v1beta/ - person Ulises CT; 30.12.2019