- PVSM.RU - https://www.pvsm.ru -
Эта статья о том, как настроить Squid proxy с SSL bump в Docker и организовать realtime-мониторинг логов. В качестве практического примера я покажу open-source решение на современном стеке (Bun + Redis + Vue), которое решает проблему устаревших инструментов мониторинга.
Исходный код проекта: GitHub [1]
Что будет в статье:
Настройка Squid с SSL bump в Docker
UDP-приём логов (без ограничений syslog)
Realtime-анализ с использованием Redis RediSearch
Frontend на Vue для визуализации
Расскажите в комментариях, как вы организуете блок-листы и мониторинг прокси. Используете ли Squid или перешли на альтернативы? Какие инструменты применяете для анализа логов? Считаете ли squid архаичным?
├── Вступление [2]
├── История [3]
├── Легкая часть (для сис. админов)/
│---├── Установка squid docker + ssl_bump [4]
│---└── Установка анализатора [5]
└── Техническая часть (для nodejs/vue dev)/
-----├── Bun/Redis [6]
-----├── Elysia [7]
-----���── Frontend [8]
Давным-давно, когда дети находили реликты прошлого в виде буквы Ш, а сайты верстали в таблицах, самыми тяжелыми элементами сайта были фотографии вашего питомца и красивые анимированные кнопочки во flash… Интернета было всем мало. Так мало, что его раздавали по талонам карточкам.
Тяжелые времена, требовали тяжелых решений. Все мечтали о безлимитном интернете, о настоящем безлимитище. Но я как засланец (я не перепутал букву) из будущего могу сказать, пока эти времена не наступили, хоть маркетологи и называют его безлимитным.
Людям требовалось ограничение, контроль, кэширование. Им нужен был squid cache. Времена изменились. Теперь squid поддерживается энтузиастами за шаурму, а большая часть ПО для него написана в мохнатые годы.
SqStat [9] — это простое приложение для realtime-анализа логов cache manager. Squid отправлял данные по сокетам, и php сохранял данные в memcached, показывая простую таблицу хостов с метриками. Начиная со Squid v6.X.X cache manager перестал быть нужным, а логи, поступающие из него, были крайне скудны. Интернета стало больше, отпала необходимость в кэшировании на уровне прокси, большая часть трафика стала идти по https.
Большая часть приложений стала следить за log-файлами. И я думал, что это единс��венный способ, в интернете я часто натыкался на https://wiki.squid-cache.org/ [10]. Поэтому в первых версиях читал логи из папки. При помощи chokidar [11] я следил за обновлениями файлов, и это было неэффективно.
Задача была простая: сделать ремейк старого анализатора для возможности мониторинга в новых версиях squid. Мне нужно было быстро сохранять и быстро доставать логи, для этого как-никак подходит redis.
Для начала мы поднимем docker-версию squid с ssl_bump. Первое время я брал готовые access-логи и на базе них сделал скрипт-генератор. Это была ошибка.
Если зайти на официальную документацию, вы не найдете docker. Возможно компании, которые его используют, до сих пор не контейнеризировали его. В сети можно найти готовые решения, хоть и неофициальные. Я могу выделить два:
b4tman/docker-squid [12] — здесь на базе alpine, он простой и легкий. Не 7.X версия, но свежее и надёжнее нет.
Ajeris/squid-docker-source [13] — здесь на базе debian-slim. Более комплексное решение с Kerberos, ACL-листами и т.д. Ближе к реальному проду.
Я воспользуюсь первым вариантом, т.к. его проще тестировать.
squid:
user: "3128:3128"
image: ghcr.io/b4tman/squid-ssl-bump:latest
container_name: squid-proxy
restart: unless-stopped
ports:
- "3129:3129"
volumes:
- ./docker/ssl:/etc/squid/ssl/
- ./docker/squid.conf:/etc/squid/conf.d/squid.conf:ro
- ./docker/passwd:/etc/squid/passwords:ro # необязательно. Для тестов
- ./logs:/var/log/squid
environment:
- TZ=Asia/Almaty
- SQUID_CONFIG_FILE=/etc/squid/conf.d/squid.conf
extra_hosts:
- 'host.docker.internal:host-gateway' # это строчка для локальных экспериментов. Если у вас настроен macvlan, вам это не нужно
networks:
- squid-net
Нам требуется конфиг squid.conf (во втором репозитории есть примеры конфигов). Для получения детальных логов, а не просто одного лога с доменом, нам необходим ssl_bump. Для его работы необходимо сгенерировать корневые сертификаты.
Источник: https://github.com/Ajeris/squid-docker-source/blob/main/setup_project.sh [14] (я только поменял пути и права на key)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
SSL_DIR="${SSL_DIR:-${SCRIPT_DIR}/docker/ssl}"
CA_KEY="${CA_KEY:-$SSL_DIR/squid.key}"
CA_CRT="${CA_CRT:-$SSL_DIR/squid.crt}"
CA_SUBJECT="${CA_SUBJECT:-/CN=proxy.tp.oil/O=Squid/C=KZ}"
KEY_SIZE="${KEY_SIZE:-4096}"
CERT_DAYS="${CERT_DAYS:-3650}"
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
BLUE='33[0;34m'
NC='33[0m' # No Color
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
generate_ssl_cert() {
print_info "Checking SSL certificate setup..."
if [ ! -d "$SSL_DIR" ]; then
mkdir -p "$SSL_DIR"
print_info "Created: $SSL_DIR"
fi
# Set permissions for SSL certificate directory
if [ -d "$SSL_DIR" ]; then
chmod 755 "$SSL_DIR"
chmod 644 "$SSL_DIR"/* 2>/dev/null || true
print_success "SSL certificate directory permissions set"
fi
# Check if OpenSSL is available
if ! command -v openssl &> /dev/null; then
print_error "OpenSSL is not installed or not in PATH"
exit 1
fi
if [ ! -f "$CA_KEY" ] || [ ! -f "$CA_CRT" ]; then
print_info "Generating new SSL certificate..."
print_info " Key size: $KEY_SIZE bits"
print_info " Validity: $CERT_DAYS days"
print_info " Subject: $CA_SUBJECT"
# Generate private key
openssl genrsa -out "$CA_KEY" "$KEY_SIZE"
# Generate self-signed certificate
openssl req -new -x509 -days "$CERT_DAYS"
-extensions v3_ca
-key "$CA_KEY"
-out "$CA_CRT"
-subj "$CA_SUBJECT"
# Set correct permissions
chmod 644 "$CA_KEY"
chmod 644 "$CA_CRT"
print_success "SSL certificate generated:"
print_info " Private key: $CA_KEY"
print_info " Certificate: $CA_CRT"
# Show certificate info
echo ""
print_info "Certificate details:"
openssl x509 -in "$CA_CRT" -text -noout | grep -E "(Subject|Issuer|Not Before|Not After)" | sed 's/^/ /'
echo ""
print_warning "IMPORTANT: Install $CA_CRT as trusted CA in browsers to avoid SSL warnings"
else
print_success "SSL certificate already exists:"
print_info " Private key: $CA_KEY"
print_info " Certificate: $CA_CRT"
# Check expiration
if openssl x509 -checkend 86400 -noout -in "$CA_CRT" >/dev/null; then
print_success "Certificate is valid"
else
print_warning "Certificate is expiring soon or expired!"
fi
fi
# Show certificate fingerprint
echo ""
print_info "Certificate fingerprint (SHA256):"
openssl x509 -noout -fingerprint -sha256 -in "$CA_CRT" | sed 's/.*=/ /'
}
main() {
echo "Creating ssl certs..."
generate_ssl_cert
print_success "Certs created successfully!"
}
main "$@"
Обязательно читаем скрипты перед их выполнением. Если вы не понимаете содержимого скрипта, попросите чат агента. В данном скрипте необходимо задать стартовые переменные. В случае ошибки, проверьте права файлов и размер шифрования. Для старых версий squid используется комбинированный pem формат.
Включаем бампинг в squid.conf. В моём варианте всё упрощено, готовый конфиг с bump [15]
http_port 3129 ssl-bump generate-host-certificates=on dynamic_cert_mem_cache_size=4MB cert=/etc/squid/ssl/squid.crt key=/etc/squid/ssl/squid.key
sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/cache/squid/ssl_db -M 4MB
sslcrtd_children 8 startup=1 idle=1
ssl_bump server-first all
ssl_bump bump all
Для мониторинга логов в реальном времени разработал open-source решение на Elysia + Vue.
Технический стек:
Backend: Bun + Elysia + Redis (с модулем RediSearch)
Frontend: Vue 3 + NaiveUI + UnoCSS
Deploy: Docker Compose
Возможности:
UDP-приём логов
Realtime-анализ последнего часа работы
Поддержка нескольких Squid-инстансов одновременно
Алиасы пользователей
Настройка в squid.conf:
access_log udp://analyzer_host:5140 squid
Исходники и документация: GitHub [1]
Альтернативы: Если у вас уже развёрнут ELK/GrayLog stack, можете использовать их. Но потребуется написать grok-паттерны для парсинга, настроить aggregations для Squid-специфичных метрик (hit/miss ratio, hierarchy codes) и создать дашборды. Готовое решение экономит 2-3 дня работы.
Совет: Если используете Squid версии ниже 6.x, рекомендую обновиться. Разница между версиями 5 и 6 существенна, особенно в части логирования и производительности.
Если до этого был мануал, здесь больше мысли/записки разработчика. Я буду часто уходить в сторону
В squid.conf указываем адрес UDP-сервера для отправки логов:
access_log udp://analyzer_host:5140 squid
Сервер выступает в роли слушателя и может принимать логи с нескольких Squid-инстансов одновременно. В продакшене у нас работают два прокси-сервера, отправляющих логи на один анализатор.
Для приёма UDP-пакетов использую bun.udpSocket [16]. Кстати, эта функциональность появилась в Bun относительно недавно — видимо, после того как адвокат девелопер [17] из redis указал на её отсутствие [18].
Поначалу создал лишь базовый вариант логов, но потом мне показалось, что этого мало. На сайте squid нашёл форматы логов [19]. Поэтому я попросил crush сгенерировать большой json.
{
token: "%ts",
field: "timestamp",
redisType: "NUMERIC",
postgresType: "BIGINT",
transform: "timestampToMs",
}
...
87 различных типов полей для логов. Ко��ечно нет смысла прописывать в custom_logs все токены. Но я решил задать возможность на будущее использовать кастомные логи.
Стандартный формат логов:
%ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
К примеру, в дефолтных нет отображения User-Agent. Читать каждый раз огромный массив нет смысла, берём самое важное и сохраняем в кэш.
Токены squid могут содержать количество символов, мы это удаляем. Забавно, но redis не любит точки. Redis делит строку на токены по точкам, и поиск по ip перестаёт работать. Пришлось заменить точки на _
Несмотря на включенный SSL_Bump, нельзя быть на 100% уверенным в чистоте логов. Squid иногда подкидывает подлянку. Какие-то поля логов отдаются как -, где-то в serverIP попадает ::. Это приводило к ошибкам при попытке сложных агрегаций. Некоторые поля считаются числом, но всё равно могут быть -
Squid не всегда определяет MIME-типы, даже когда url оканчивается на .js. Конечно, я не могу со 100% уверенностью сказать, что файл с расширением .js — это действительно javascript. Однако решил подправить.
Планировалось собирать пачку логов за один час и отправлять потом в postgres. Поначалу не работало TLS-соединение (rediss), но там внесли строчки CIPHERS, заработало. Конечно, в Bun мы можем воспользоваться ORM, но оно было сделано для nodejs, поэтому решил оставить стандартный клиент для повышения скорости.
Несмотря на скорость разработчиков по интеграции nodejs в bun-среду, многое им приходится пропускать. В моём случае я использовал redis не просто для банального кэширования, а как полноценную БД. Drizzle ORM предназначен для реляционных БД, у него есть интеграция с Upstash redis, а с обычным нет.
Мне требовался поиск, агрегация. Собственно говоря, модуль RediSearch. Я бы мог написать провайдер для какой-нибудь популярной ORM. Но тогда бы я застрял на четверть века со своим приложением.
И не иди путём искушения. Дабы посмеешь написать сей труд, как официальная поддержка сразу появится
Для работы в Bun с модулями redis пришлось использовать
function send(command: string, args: string[]) {}
В redis есть возможность работы с JSON, однако процесс сериализации/десериализации может быть трудозатратным. А красивая работа с объектами добавлена недавно в hset. Ранее мне приходилось вручную переводить переменные в camelCase. Хочу напомнить некоторым: JavaScript и JSON используют camelCase. Исключения только для констант и классов. По возможности выполняйте преобразования имён переменных при работе с БД.
Мои попытки контролировать процесс транзакций приводили к ERROR MULTI calls not be nested. Я оставил попытки оптимизировать процессы в redis.
const [firstResult, lastResult] = await Promise.all([
redisClient.send("FT.SEARCH", firstArgs),
redisClient.send("FT.SEARCH", lastArgs),
]);
Оно само работает. Оно само оптимизирует pipeline. Не лезь оно тебя сожрёт. Возможно когда-нибудь я разберусь как правильно работать с транзакциями в redis, но не сегодня.
Сперва я создаю возможность валидации и парсинга env-переменных. По возможности желательно избегать в проектах использования env напрямую через process.env или Bun.env. Так как у меня Elysia, проще воспользоваться typebox. К сожалению, встроенный валидатор в typebox не очень удобен, поэтому добавляем ajv. Почему не Elysia Env [20]? Я использую env часто за пределами elysia, автор не догадался вынести функцию парсинга и поделить плагин на инициализацию, которая возвращает распарсенные переменные, и обёртку плагина для elysia.
Возможно, конфигуратор в стиле Bati [21] решил бы проблему. Выбирать пакеты через CLI крайне неудобно из-за кросс-пакетной совместимости.
Отдельно стоит упомянуть авто-документацию валидатора ответов сервера — это удобно. Тебе не нужно писать отдельные аннотации для swagger. Swagger здесь, кстати, багованный, но современный. Scalar UI выглядит красиво и может публиковать документацию у вас в профиле, делая её общедоступной.
Я не буду писать про удобный синтаксис в стиле fastify и как писать роуты, этого полно в интернете. А вот что реально важно — это выбор плагинов. Я советую использовать только официальные, т.к. они лучше всего дружат со всей экосистемой. Пакеты сообщества, как правило, работают не супер.
Также советую сразу компилировать ваше приложение в бинарный формат. Пример:
bun build src/index.ts --compile --target=bun-linux-x64-baseline --outfile=bunsqstat-backend-binary
В своём варианте использую baseline для совместимости с kvm (классический работает только при наличии AVX2). В вашем варианте хватит обычного bun-linux-x64
Работает только на x64, но при таком методе компиляции вы можете использовать кластерный режим для продакшена. Bun умеет создавать tcp с одним и тем же портом.
Компиляция в бинарник упрощает деплой приложения, вам не нужен будет bun на продакшене.
Также в своём репозитории я использую turborepo с серверным кэшем, rolldown-vite для ускорения компиляции фронта. Rolldown уже корректно работает с github CI.
Моё приложение представляет собой стек из
Caddy для публикации Vue SPA
Elysia server
redis-stack Redis (RediSearch module)
В теории я думал убрать из этой схемы Caddy и добавить на Elysia, но в начале с Caddy было проще, поэтому я оставил. Хотя в таком случае устанавливать было бы заметно проще, можно было бы избавиться от supervisor. Я даже думал попробовать Tauri, но боялся застрять надолго без знаний Rust.
К сожалению, eden является самой странной частью Elysia.
Eden - это плагин для elysia, позволяет использовать на фронтенде клиент с типизированными запросами. (по сути sdk без танцов с бубном)
Я по прежнему не понимаю почему из раза в раз некорректно импортировались типы. Иногда типы появлялись в процессе разработки, а порой исчезали после добавления/удаления плагинов. Поэтому я часто пользуюсь более простым вариантом, сгенирировать типы для клиента из swagger документации [22]. Есть конечно более схожий с Eden вариант, ts-rest [23] он создаёт общий контракт между бэком и фронтом. Кстати дело не в webstorm, проверял на vscode/helix.
Здесь на самом деле всё банально. NaiveUI в качестве кита, VueUse, UnoCSS. Кстати, по поводу UnoCSS меня позабавил факт, что разработчик ушёл делать NuxtUI на tailwind, а свой атомарный движок забросил, и теперь фанаты пилят UnoUI. Будет очередной новый кит с отсутствием кастомизации, и людям всё равно придётся делать свой kit на RekaUI или shadcdn. Зато добавили крутой тренд, теперь киты стали бесплатно публиковать дизайны в фигме.
Сам по себе фронт получился так себе. Хотя в целом я писал приложение весьма в расслабленном состоянии, получая порцию декаданса. Как вам такое: написать shared worker и принимать в нём веб-сокеты. Звучит весьма круто, ведь теперь при открытии в нескольких вкладках мы создаём одного клиента на сервере, а потом мы слушаем порт shared worker... и отправляем запрос на сервер для получения данных. (звук стрельбы дробовиком в ногу)
Самое первое — это таблица access data. Самое странное — нужно объяснить юзерам формат работы с redis. Если бы у нас был elastic или формат данных в json, мы бы могли позволить поиск по всем значениям таблицы. Но мы работаем с redis, дабы увеличить скорость, мы специально даём пользователю выбрать поле для поиска. Косяк моей архитектуры — я решил экранировать данные на фронте, и это плохой подход. Нет, меня не волнует безопасность, т.к. приложение для ограниченного круга и не будет публиковаться наружу. А то, что у пользователя будут возникать ошибки от redis.
Ещё я хорошо продумал такую вещь, как алиасы. В некоторых анализаторах есть возможность привязать алиас к IP клиента. Дело в том, что в squid есть авторизация, но пользоваться внутри сети ею не будут. Чтобы красиво помечать сотрудников, например, vitalik@osu.oil (после собаки — это название отдела) ну или просто по-дружески Витя_с_ОСУ.
Так вот, мы получаем из редиски имена пользователей через пробел user 192.168.1.1 user2 192.168.1.2, теперь создаём radix-дерево по ip. Зачем? Потому что Блиц — это скорость без границ. И по барабану, что в нашем кейсе пользователи внутри сети, а значит, два октета будут одинаковые, и в лучшем случае пользователи поделятся на две группы в 3 октете.
И это не всё, я решил не нагружать бэк, и вместо поиска алиаса на бэке я делаю его на фронте. Во вкладке пользователи у меня нет пагинации, и используется fuzzy search. В целом там всё норм. Но в остальных местах у меня пагинация реализована на бэке, соответственно, поиск по пользователю работать в первой таблице уже не будет. В теории я должен на бэке выполнить поиск по алиасу, если нахожу — сделать запрос по полю clientIP, хотя на фронте делаю запрос по имени пользователя.
Хотел сделать себе проще, а получилось как всегда. За лень приходится платить дважды.
Можно ли использовать GrayLog?
Да. Однако GrayLog будет лишь собирать данные. Вам все равно потребуется обрабатывать и выводить их в Grafana. Это не готовое решение.
Где и когда применим BunSqStat?
При первичной отладке Squid или для выявления аномалий в ближайшем временном промежутке. К примеру, зафиксировать блокировку важного ресурса.
Это замена SquidAnalyzer?
Нет. SquidAnalyzer читает логи по cron (раз в указанный промежуток), а не в реальном времени, однако способен обрабатывать большее количество логов. BunSqStat рассчитан на час работы, в дальнейшем планируется интегрировать postgres для сбора больших отчетов.
Почему 48 мб по умолчанию для Redis?
Это приблизительное значение, зависит от количества запросов. Количество логов можно регулировать изменением максимальной памяти Redis в настройках.
Логи бегут слишком быстро.
Можно регулировать интервал выпадения пачек логов в настройках. (только на фронтенде)
Что означает «продолжительность»?
Длительность запроса. Карточка в метриках отображает суммарную длительность всех запросов.
Я могу установить без Docker?
Да. На гитхабе можно скачать актуальный релиз в zip формате. В документации описан способ установки. В данный момент нет bash скрипта для установки всего стека. Требуются навыки деплоя веб-приложений. Вариант с Docker проще.
Вывод:
У проекта есть недостатки, большинство обнаруживаешь в конце (классика), но главное — получать удовольствие в процессе разработки. Даже несмотря на простоту задачи, я умудрился вставлять палки в колёса. Это был первый проект, где серьёзно использовал чат-агентов через warp и crush. И это добавило больше сложностей и рефакторинга. Схема -> Ядро -> Модули. В итоге получилось MVP, а не полноценное приложение.
Автор: francyfox
Источник [24]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/redis/438185
Ссылки в тексте:
[1] GitHub: https://github.com/francyfox/BunSqStat
[2] Вступление: #intro
[3] История: #history
[4] Установка squid docker + ssl_bump: #squid-docker
[5] Установка анализатора: #install
[6] Bun/Redis: #bun
[7] Elysia : #elysia
[8] Frontend: #frontend
[9] SqStat: https://github.com/alv1r/SqStat
[10] https://wiki.squid-cache.org/: https://wiki.squid-cache.org/
[11] chokidar: https://www.npmjs.com/package/chokidar
[12] b4tman/docker-squid: https://github.com/b4tman/docker-squid
[13] Ajeris/squid-docker-source: https://github.com/Ajeris/squid-docker-source
[14] https://github.com/Ajeris/squid-docker-source/blob/main/setup_project.sh: https://github.com/Ajeris/squid-docker-source/blob/main/setup_project.sh
[15] конфиг с bump: https://github.com/Ajeris/squid-docker-source/blob/main/config/squid-ssl-bump.conf
[16] bun.udpSocket: https://bun.com/docs/runtime/networking/udp
[17] адвокат девелопер: https://habr.com/ru/companies/haulmont/articles/555902/
[18] указал на её отсутствие: https://www.youtube.com/watch?v=aWJ9mfmHqfw
[19] форматы логов: https://www.squid-cache.org/Doc/config/logformat/
[20] Elysia Env: https://github.com/yolk-oss/elysia-env/tree/main
[21] Bati: https://github.com/vikejs/bati
[22] swagger документации: https://github.com/openapi-ts/openapi-typescript
[23] ts-rest: https://github.com/ts-rest/ts-rest
[24] Источник: https://habr.com/ru/articles/973934/?utm_source=habrahabr&utm_medium=rss&utm_campaign=973934
Нажмите здесь для печати.