Обновить

В логе адрес ::ffff:192.0.2.5, в белом списке 192.0.2.5. Это один адрес, и он не совпадает

Уровень сложностиСредний
Время на прочтение5 мин
Охват и читатели9.4K
Всего голосов 7: ↑4 и ↓3+3
Комментарии7

Комментарии 7

А в чем минус приложению работать на ipv4 как обычно, а проброс адресов выполнять на nginx:

server {
    server_tokens off;
    listen 443 ssl;
    listen [2001:111:11:111::1]:443 ssl;
    server_name test.ru;
    ssl_certificate fullchain.pem;
    ssl_certificate_key privkey.pem;
    location @backend {
	proxy_pass http://127.0.0.1:2000;
    }

    proxy_redirect     off;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
}

Таким образом абстрагирован прикладной уровень от сетевого, там nginx разберется что и куда.

Минуса по сути нет, вариант рабочий — это как раз одна из тех развязок, о которых речь в статье («развести слушателей»). Вы вынесли разбор обоих стеков на nginx, а бэкенд общается с ним по чистому IPv4 через 127.0.0.1 и mapped-адрес не увидит вовсе. Хороший подход, мне нравится.

Проблема при этом никуда не девается, просто стягивается в одну точку — в сам nginx. Пока listen раздельные, как у вас (443 и конкретный [2001:…]), всё чисто. Но стоит где-то появиться listen [::]:443 без ipv6only=on — и в логах самого nginx $remote_addr поедет в форме ::ffff:…, теперь уже на уровне прокси.

И второе: реальный адрес клиента теперь приезжает в приложение не из сокета, а из X-Forwarded-For, который вы прокидываете. Если перед nginx окажется ещё один прокси на дуал-стек сокете, mapped-форма может прилететь и в этот заголовок — а его обычно разбирают руками. Так что приём отличный, просто точка, где держать нормализацию адреса, смещается с сокета приложения на границу прокси.

Я почему указывал `listen [2001:111:11:111::1]:443 ssl;`, потому что выше стоит ipv6 firewall, который валидирует ipv6 трафик на пограничном шлюзе. Что можно, а что нельзя, дабы ipv6 адреса внутри вверенной инфры жили спокойно.

Правильный ответ достаётся только через .ipv4_mapped, о котором нужно знать заранее

Можно не знать о нем. Просто адреса нужно разбирать по версии протокола в коде (библиотека это умеет), а не считать все как v4.

Новые приложения вообще лучше писать так, чтобы они понимали v6, а v4 хранить как mapped.

Хороший разбор, взгляд зацепился:

Код отрабатывает штатно, тест на «адрес не из сети — отказать» проходит, ревью проходит тоже.


Есть небольшой опыт в тестировании, это классика: проверку вайтлиста тестируют как чистую функцию - подали строку "192.0.2.5", получили allow, подали "10.0.0.1" - deny, все зеленое. А в проде адрес приходит не строкой из теста, а из сокета, и баг живет ровно на этом стыке. Юнит-тестом его не поймать в принципе, хоть 100% покрытия сделай.

Ловится только интеграционным тестом через реальный сокет: поднимаем сервис на "::", подключаемся по 127.0.0.1 и проверяем, что вайтлист со 127.0.0.1 пропускает. Один такой тест в CI - и вся эта история падает на пулл-реквесте с переездом на dual-stack, а не на заявке от клиента. Но так почти никто не делает: проверка адреса выглядит слишком простой, чтобы гонять ее не юнитом.

А разве описанный вами вариант не является просто случаем "некорректный юнит-тест"? Если адрес приходит строкой, в нем вообще что-угодно может быть.

Отчасти да - если знать про mapped-адреса, то "::ffff:192.0.2.5" просто добавляется в юнит-кейсы, и после инцидента именно так и делается, это правильный регрессионный тест. Вопрос в том, откуда этот кейс возьмется в списке до инцидента.

Юнит-тест проверяет функцию против нашего представления о входах - что придет строка с чем угодно - да, но "что угодно" в тестах всегда конечный список, который мы сами и придумали. Если про mapped-форму не знаешь, ее в списке не будет, и тест честно зеленый. А интеграционный через сокет проверяет само представление: он приносит в проверку тот вход, который реально отдает ядро, без нашего участия. Баг ведь не в функции сравнения - она корректно сравнивает то, что дали. Баг на стыке "что отдает сокет" и "что ожидает проверка", а стыки юнитами не проверяются по определению, на то они и юниты.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации