вторник, 27 марта 2018 г.

opnwrt: nginx with https

Захотели поставить nginx на роутер c ssl сертификатами? А фигушки:
`the "ssl" parameter requires ngx_http_ssl_module in /etc/nginx/nginx.conf`
Ну и вторая важная проблема wrt-шников, которая будет решена:
`LuCI on Nginx is not possible, as Nginx does not support plain cgi.`

Что может быть круче, чем иметь что то одно, способное управлять всеми. Одно кольечко нагнуть всех назгулов, один маршрутизатор роутить трафик инициированный снаружи DMZ.
К примеру, у нас есть LuCi на роутере, Shell-in-a-box в виртуалке на компе и JPython c Jupiter Note в другой. Можно конечно поизголяться с проброской портов, ну у меня и так уже 2 десятка правил, которые я пухну восстанавливать каждый раз если что то пойдет не так. Будем делать проксирование вызовов на основе целевого URI.

Аналитика

Если бы нужен был защищенный доступ просто до LuCi, то можно было бы обойтись установкой пакета luci-ssl и uhttpd бы сам бы сконфигурировался бы скриптом на https.
Проблема в том, что штатный веб-сервер не умеет проксировать запросы на другие хосты, его только ради https и можно ставить
Я посматривал в сторону haproxy, он довольно крут, но есть одно простое правило: использовать инструменты по их прямому назначению, иначе огребешься ссаными костылями. А в данной задаче нужен не балансировщик, а веб сервер.

План

И во ссславу Сысоеву, у nginx есть опция reverse proxy
Т.о. нам потребуется
- скомпилировать nginx c поддержкой ssl, так как штатный nginx version 1.12.1 поставляется без него
- сконфигурировать uhttpd, что бы он не занимал стандартные порты, иначе он все запросы будет заворачивать на себя
- сконфигурировать nginx, что бы он проксировал http запросы
- выпустить ssl сертификат (done!)
- сконфигурировать nginx для использования ssl

Лирика

Важно примерно понимать, как это работает: раз два. У меня было куча проблем и я даже не мог понять что я делаю не так.
Тулчейн при сборке gcc для родной архитектуры роутера довольно подробно пишет лог. Но вот он ниразу не может определить, что место в вируалке кончилось, узнал случайно, какой то проект клонил. Пришлось судорожно искать как это сделать.

Но самый отпад в том, что сборщик openwrt не может сбилдить одну оч важную зависимость `gcc-5.4.0-final` Это реально смешно, вот полный лог событий. Я мудохался 3 дня и 2 ночи, а потом, вспомнил, что есть менее мертвый форк lede и на нем я делал кастомную сборку прошивки для mr3020. Господи, как быстро летит время, это было уже 4 года назад.
Но с тех пор я зарекся связываться с такими хилыми железками.

Ну да ладно, если у вас будет время, попробуйте в корень проекта еще качнуть ядро линукса 3.18 `wget -4 https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.23.tar.xz`, это помогло одному индусу (заметка, архив). Хотя веры ему мало, индус же)

Зависимости системные

основные
sudo su
apt-get install asciidoc  bc fastjar libgtk2.0-dev intltool jikespg genisoimage openjdk-8-jre openjdk-8-jdk -y
apt-get install binutils build-essential gcc-multilib flex git-core gettext sharutils bcc bin86 libusb-dev libxml-parser-perl libboost-all-dev libncurses5-dev gawk git subversion libssl-dev gettext zlib1g-dev unzip ccache xsltproc zip libssl-dev patch libz-dev -y

Зависимости для проекта

собственно, выкачиваем все исхода
git clone https://github.com/openwrt/openwrt.git # может быть когда-нибудь починят
# git clone https://git.lede-project.org/source.git openwrt # но пока только так
cd openwrt
git tag
# git checkout `tag_name` # можно сделать если хочется собрать стабильный снэпшот
./scripts/feeds update -a
#./scripts/feeds install -a # нам все пакеты нахер не нужны
./scripts/feeds install nginx # ставим только один, но там будут зависимости
./scripts/feeds install libpam libgnutls libopenldap libidn libssh2 liblzma libnetsnmp
make menuconfig

Прописываем архитектуру потребителя пакета. То бишь своего роутера.
Target System  : (MediaTek Ralink MIPS)
Subtarget: (MT7621 based boards)
Target Profile: (ZBT WG3526 (16MB flash))
Build the OpenWRT SDK : true
Exit → Save

делаем `make menuconfig` и идем
Network  → Web Servers/Proxies → nginx  → hit `M` → hit `ENTER`

жмем по `Save`, оставляем дофолтное имя конфига `.config`
кликабельный menuconfig

make tools/install
make toolchain/install
./script/feeds install nginx

выбираем пробелом следующие модули
- Enable SSL module
- Enable Lua module

а теперь, собираем один отдельный модуль
make package/feeds/packages/nginx/prepare
make package/feeds/packages/nginx/compile V=s

и ищем зависимости здесь:
/lede/bin/packages/mipsel_24kc/packages/

Подготовка тестового сервиса

Проводить опыты будем на https://github.com/mcgr0g/rancho/tree/master/shellinabox
нужно будет дополнить параметры запуска так
`--disable-ssl --disable-ssl-menu`,
что бы не использовать встроенный самосгенеренный ssl серт. Ну и перезапускам
`sudo /etc/init.d/shellinabox restart`
Теперь сервис должен спокойно отзываться по адресу http://buben.lan:4200/
предполагаемый эндпоинт `box.now.io/buben/`

Подготовка LuCi

Старичка uhttpd необходимо перенести с дефолтных портов, что б освободить место для nginx. Ну и 81 порт наружу не должен торчать. Правим конфиг `vi /etc/config/uhttpd` так
config uhttpd 'main'
    list listen_http '127.0.0.1:81'
#   list listen_http '[::]:88'
#   list listen_https '192.168.1.12:443'
#   list listen_https '[::]:443'
    option redirect_https '0'
    option home '/www'
    option rfc1918_filter '0'
    option max_requests '3'
    option max_connections '100'
    option redirect_https '0'
#   option key '/etc/acme/box.now.io/box.now.io.key'
#   option cert '/etc/acme/box.now.io/box.now.io.cer'
    option cgi_prefix '/cgi-bin'
    option script_timeout '60'
    option network_timeout '30'
    option http_keepalive '20'
    option tcp_keepalive '1'
    option ubus_prefix '/ubus'

и перезапускаем `/etc/init.d/uhttpd restart` и смотрим переехал ли на новый порт `http://nethead.lan:81/cgi-bin/luci` и `box.now.io:81`

Ставим nginx

Ну вот мы и добрались до виновника торжества. Ставим пакет, предварительно положив его в /tmp
cd /tmp/
ls -la | grep nginx
opkg install nginx_1.10.2-1_mipsel_24kc.ipk
ls -la /etc/nginx/ | grep conf
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old

Конфигурируем nginx

Cразу открываем лог файл и смотрим че там будет сыпаться.
cd /var/log/nginx/
tail -f error.log

И это не просто рекомендация, я готов поспорить, что объебов будет много.
Что бы грамотно приготовить его пришлось хорошенько курнуть следующие маны:
https://nginx.org/ru/docs/http/request_processing.html
https://nginx.org/ru/docs/beginners_guide.html
https://nginx.org/ru/docs/http/ngx_http_core_module.html#location
https://nginx.org/ru/docs/http/ngx_http_ssl_module.html
https://serverfault.com/a/9717
https://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_ssl_certificate
https://serverfault.com/a/627309
в `vi /etc/nginx/nginx.conf` прописываем
user  root;
worker_processes  1;
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    #default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;
    gzip_buffers     4 16k;
    gzip_http_version 1.0;
    gzip_comp_level 2;
    gzip_types       text/plain application/x-javascript text/css application/xml;
    gzip_vary on;
    gzip_min_length  1k;

    server {
        listen 80;
        listen [::]:80;
        server_name box.now.io;
        location ^~ /.well-known/acme-challenge/ {
            #костылек для продления сертификатов
            root   /www;
            default_type "text/plain"; 
        }
        location / {
            # костылек для форварда на htps
            return 301 https://$host$request_uri;
        }

    }

    server {
        listen 80;
        listen [::]:80;
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name  192.168.1.12 nethead.lan box.now.io;
        #charset koi8-r;
        # access_log  logs/host.access.log  main;

        ssl on;
        ssl_certificate      /etc/acme/box.now.io/box.now.io.cer;
        ssl_certificate_key  /etc/acme/box.now.io/box.now.io.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        # if ($scheme = http) { # старый костылек для форварда на htps
        #     return 301 https://$host$request_uri;
        # }

        location / {
            root   /www;
            proxy_pass http://localhost:81;
            index  index.html index.htm;
            proxy_set_header X-Forwarded-Proto https;
        }

        location /buben/ {
            proxy_pass http://buben.lan:4200;
            default_type  application/octet-stream;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # proxy_set_header Host               $http_host;
            # proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
            # proxy_set_header X-Forwarded-Proto https;
            # proxy_redirect off;
        }
    }
}

Если вы ленивое чмо, как я и редактируете файл на винде и передаете по scp, то не забудьте выпилить кривые переносы строк `^M` командой
sed -i 's/\r$//' nginx.conf

Не забываем указать волшебные команды, что бы эта хреновина заработала
/etc/init.d/nginx enable
/etc/init.d/nginx start
/etc/init.d/nginx reload

Не забываем про acme

Сейчас токены должны валяются по адресу http://box.now.io:80/.well-known/acme-challenge/
Проверьте не объебалились ли вы и не переехали они на другой порт http://box.now.io:81/.well-known/acme-challenge/
Я не объебался, я молодец - посавил костылек в виде первого server{}, который слушает только 80 порт и редиректит на токены.

Дессерт

Если у вас есть потребность постоянно проверять что можно обновить, используйте
https://github.com/tavinus/opkg-upgrade
Главное исключите из него nginx.

Бонус

А вот скомпиленный бинарь nginx_1.10.2-1