Ограничение частоты запросов по API_KEY с использованием OpenResty и Docker

· 5 минуты на чтение

Предыдущий вариант ограничения был статичен, для внесения изменений в список API-KEY  требовался перезапуск Nginx. Нужно решение с динамическими ключами.  OpenResty - это комплекс полезных расширений для сервера Nginx, на его  базе можно реализовать требуемое. Используем Docker, официальный образ OpenResty и официальный образ сервера Redis. Redis будет хранить в себе API-ключи.

Задача: все запросы, поступающие на адрес http://192.168.0.71:8080/ проверять на наличие заголовка API-KEY и на его базе принимать решение о фильтрации запросов. Запросы с правильными API-KEY перенаправлять на адрес http://192.168.0.72/.

Внимание! Все указанные операции проводятся на хосте 192.168.0.71!

Установить Docker. Для CentOS:

yum install docker

Установить контейнер OpenResty. Здесь мы запускаем контейнер, подключаем  в него с хоста папку с конфигами nginx, чтобы было удобнее их  редактировать:

docker run -d \
--name openresty \
-p 8080:8080 \
-v /media/docker-data/openresty/etc/nginx/conf.d:/etc/nginx/conf.d \
openresty/openresty:alpine

Установить контейнер Redis. Здесь также подключаем папку хоста, для  хранения данных на случай восстановления при аварийных ситуациях:

docker run -d \
--name redis \
-p 6379:6379 \
-v /media/docker-data/redis/data:/data \
redis redis-server --appendonly yes

Создаем конфиг nginx:

#/media/docker-data/openresty/etc/nginx/conf.d/default.conf:

# Выделение памяти для функции расчета частоты
lua_shared_dict my_limit_req_store 100m;

server {

    listen       8080;
    server_name  192.168.0.71;

    location / {

        access_by_lua_file /etc/nginx/conf.d/apikey_module.lua;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://192.168.0.72/;
    }

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    # Этот путь изменен на корректный для OpenResty, иначе в логах будет ошибка 404 вместо 503,
    # просто потому, что файл описания 503-й ошибки не найден...
    location = /50x.html {
        root /usr/local/openresty/nginx/html;
    }
}

Создаем скрипт LUA, подключаемый в конфиге выше (строка access_by_lua_file…):

#/media/docker-data/openresty/etc/nginx/conf.d/apikey_module.lua:

-- Функции в начале файла, т.к. интерпретатор LUA имеет последовательную обработку скрипта и должен видеть вызывающийся
-- код до его вызова.

-- Проверка наличия ключа в базе Redis
function check_key(key)
    local redis = require "resty.redis"
    local red = redis:new()
    red:set_timeout(1000) -- 1 second
    - Здесь укажите собственные настройки сервера Redis
    local ok, err = red:connect("192.168.0.71", 16379)

    -- Если не удалось подключиться к серверу Redis - пишем в лог
    if not ok then
        ngx.log(ngx.ERR, "failed to connect to Redis server: ", err)
        return false
    end

    -- Запрос ключа в сервере Redis
    local value, err = red:get(key)

    -- Если не удалось выполнить запрос - пишем в лог
    if not value then
        ngx.log(ngx.ERR, "failed to get key in Redis server: ", err)
        return false
    end

    -- Если для ключа нет значения - то возвращаем false
    -- иначе - возвращаем true
    if value == ngx.null then
        ngx.log(ngx.ERR, "no value found for key ", key)
        return false
    else
        -- Ключ найден в базе Redis, пишем в лог. Убрать потом.
        --ngx.log(ngx.ERR, "key:", key, " value:", value)
        return true
    end
end

--Функция ограничения частоты запросов
function limit_requests()
    local limit_req = require "resty.limit.req"
    -- my_limit_req_store прописывается в перед блоком server конфига Nginx (default.conf)
    -- 200 запросов в секунду с одного IP (исключительно для тестов)
    local lim, err = limit_req.new("my_limit_req_store", 200, 0)
    -- Если ограничитель не инициирован, то смиренно отдадим контент, но с задержкой
    if not lim then
        ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
        -- Спим 1 сек
        ngx.sleep(1)
        return ngx.exit(200)
    end

    -- Ключ счетчика частоты - IP запрашивающего
    local key = ngx.var.binary_remote_addr
    local delay, err = lim:incoming(key, true)
    if not delay then
        -- Если запрос не пролазит по частоте - то даем ответ 503
        if err == "rejected" then
            ngx.log(ngx.ERR, "delay rejected: ", err)
            return ngx.exit(503)
        end
        ngx.log(ngx.ERR, "failed to limit req: ", err)
        return ngx.exit(503)
    end

    if delay>= 0.001 then
        local excess = err
        ngx.sleep(delay)
    end
end

-- Ищем ключ API_KEY в заголовках
local key = ngx.var.http_api_key

-- Если ключ есть, то нужно проверить его по базе Redis
if key then
    -- Проверка на наличие ключа в базе Redis
    local key_exist = check_key(key)
    -- Если ключа нет - передаем управление на функцию ограничения частоты запросов
    if not key_exist then
        ngx.log(ngx.ERR, "check_key() returned false")
        limit_requests()
    end
else
    limit_requests()
end

Для добавления ключей в Redis используем консоль:

docker run -it --link redis:redis --rm redis redis-cli -h redis -p 6379
#Добавляем ключи:
redis:6379> set 111 ""
redis:6379> set 222 ""
redis:6379> set 333 ""
quit

Испытания.

# Установка нагрузочного инструмента для испытаний
yum install siege

Коды результатов: (200 - запрос ушел на следующий сервер, 503 - запрос отброшен).
10 запросов с неверным ключом:

[root@localhost ~]# siege -b -r 1 -c 10 -H "API-KEY: 1aaa111" http://192.168.0.71:8080/icons/ubuntu-logo.png
** SIEGE 4.0.2
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.02 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.03 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png

10 запросов без ключа:

[root@localhost ~]# siege -b -r 1 -c 10 http://192.168.0.71:8080/icons/ubuntu-logo.png
** SIEGE 4.0.2
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.01 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.02 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.02 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.02 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 503     0.02 secs:     541 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png

10 запросов с правильным ключом:

[root@localhost ~]# siege -b -r 1 -c 10 -H "API-KEY: 111" http://192.168.0.71:8080/icons/ubuntu-logo.png
** SIEGE 4.0.2
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     0.01 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.01 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.01 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.01 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png
HTTP/1.1 200     0.02 secs:    3338 bytes ==> GET  /icons/ubuntu-logo.png

Источники:

Related Articles

Установка Nominatim в Docker

Качаем репозиторий с гитхабаmkdir /home/user/nominatim cd /home/user/nominatim git clone https://github.com/merlinnot/nominatim-docker.git cd

· 2 минуты на чтение

Ограничение частоты запросов по API-KEY с использованием Nginx

Хотим добавить ограничение частоты запросов для какого-то ресурса. Пример запроса: http://test.one/somefile_105_907.pngИспользуем nginx. Примеры на

· 1 минута на чтение

Установка и настройка движка Ghost в контейнере Docker.

Пробросил внешний порт 57443 на порт сервера 57443. Порт сервера слушает nginx. Конфиг nginx обеспечивает шифрование SSL, заворачивает HTTP трафик

· 1 минута на чтение