Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Term vs match query

Term query ищет документы по точному совпадению запроса с токенами в индексе, а match query — по совпадению токенизированого запроса. Т.е. к значению в запросе применяются те же преобразования, что и при индексации, или даже какой-то другой анализатор. Дополнительная плюшка match query еще в том, что он, как и term query, универсален, и позволяет искать не только по текстовым полям, но и по keyword/числовым/датам и т.п.

Возникает вопрос: а зачем тогда вообще нужен term query? Для текстового поля логично использовать тот же анализатор (разве что кроме каких-то очень экзотических случаев или дебага). Для прочих типов полей — разница будет крошечной: потратится только немного времени на определение анализатора, который надо применить (чтобы выяснить, что его нет). Даже разработчики говорят — используйте match для единообразия. И в старом гайде пишут примерно то же самое. Я нашел только один аргумент — скоринг: в term query игнорируется IDF. Но это подходит под “экзотический случай”.

Альтернативный вариант — использовать для всего, кроме текста terms query. Это ясно дает понять, что ищем в индексе “как есть”, намекая на то, что это поле в индексе тоже хранится “как есть”.

Ссылка

OWASP CheatSheet

Случайно наткнулся на сборник шпаргалок по OWASP.

Если кто вдруг не знает, OWASP (Open Web Application Security Project) — это онлайн-сообщество, которое рожает бесплатные советы про то, как безопасно писать преобразователи sql в json веб-приложения. Самое известное и старое творчество — OWASP TOP 10.

Возможно, в каких-то компаниях от круд-формошлепов старших разработчиков будут требовать знания этих рекомендаций. Правда есть нюанс — большая часть проблем, описанных в них, покрывается нормальными библиотеками/фреймворками и правилом “не реализуй безопасность самостоятельно”. А если и накосячить в этом — проверять вас скорее всего будет такой же разработчик, как и вы, потому что большинству компаний насрать на ИБ в целом, а на безопасный процесс разработки — тем более. Даже если не насрать — то должен быть как минимум специальный человек, ответственный за это, который в том числе отвечает за такие вещи, как построение модели нарушителя, обучение специалистов, организацию проверки кода на соответствие требованиям ИБ.

А сами шпаргалки среднего качества и довольно разнородны. Но пригодятся для того, чтобы проверить себя на совсем очевидные ляпы или начать погружаться в тему.

Ссылка

GitHub Actions

Решил попробовать на прошлой неделе, после закрытия долга с задачей про заявки.

Думаю, найду сейчас какой-нибудь туториал, пройду его и буду корпеть над составлением yaml для корректной работы.

Оказалось, что для того, чтобы подключить прогон тестов нужно просто зайти на вкладку с Actions, нажать одну кнопку для подключения

потом при желании подредактировать файлик (я заменил java 1.8 на 11) и жахнуть коммит.

Т.е. три клика и все. Я в шоке.

What a great time to be alive!

Ссылка

Hexdiff

Простенькая утилита для сравнения двух файлов в hex.

hexdiff 1.bin 2.bin

Разницу подсвечивает не очень хорошо, но удобно посмотреть, где она начинается. По крайней мере удобнее, чем открыть 2 параллельных hex-редактора или делать diff по двум выводам hexdump. Мне это пригодилось, когда нужно было понять, почему один из двух “одинаковых” URL’ов работал, а другой нет: проверил наличие невидимых символов, которые там действительно оказались.

Обнаружил случайно: смотрел на diff, который из полезного смог только сказать, что две строки разные. Подумал — как можно сделать бинарный diff? Ну, наверно, diff -b или какой-нибудь hexdiff. И на вторую команду система предложила sudo apt install hexdiff. Люблю такой UX :)

Ссылка

Миграция approle между двумя Vault-хранилищами

Секреты смигрировать много ума не надо — это практически key-value хранилище. А вот с ролями приложений (которые представляют собой пару role_id + secret_id и по сути мало отличаются от логина-пароля) сложнее. Нельзя просто так взять и записать

vault write /auth/approle/role/ROLE_NAME role_id=SOME_UUID secret_id=WANTED_UUID

— параметр secret_id будет проигнорирован. Т.е.

vault read /auth/approle/role/ROLE_NAME/role-id

вернет нужный role_id, а вот

vault list /auth/approle/role/ROLE_NAME/secret-id

вернет ошибку, т.к. secret_id не был создан. Причем нужный secret_id не получится прописать, только сгенерировать новый через

vault write /auth/approle/role/ROLE_NAME/secret-id

Но есть способ это обойти — создать custom-secret-id.

write /auth/approle/role/ROLE_NAME/custom-secret-id secret_id=WANTED_UUID

Для приложения все будет работать “как раньше”, роль/секрет ему можно не менять.

Вообще говоря, это не очень хороший подход, правильнее и безопаснее будет сгенерировать новый secret_id и прокидывать его приложению вместо этой чехарды. Но прокидывание роли контролирует обычно CI/CD (иначе зачем нужна вообще эта безопасность), а там две роли сразу (старую и новую) для всех фиче-веток не прокинешь. Дешевле на время переходного периода мигрировать старую роль приложения, а после окончания миграции — удалить ее.

Ссылка

ConditionalOnMissingBean

Неприятным сюрпризом оказалось поведение аннотации @ConditionalOnMissingBean. Казалось бы, название говорит о том, что бин добавляется в контекст только тогда, когда нет бина с таким же классом (или с классом, который указан в параметре аннотации).

Т.е. если есть

@Configuration
class MainConfiguration

@ConditionalOnMissingBean(MainConfiguration::class)
@Configuration
class TestConfiguration

то TestConfiguration загрузится только в случае, если в контексте нет MainConfiguration. Однако если переименовать TestConfigurationConfigurationTest, то будут загружены обе конфигурации. WTF, переименование класса ломает функциональность?

На самом деле, все очень просто: загрузка бинов в контекст по умолчанию производится в лексикографическом порядке, а @ConditionalOnMissingBean смотрит только на контекст, загруженный на данный момент, и во втором случае бин MainConfiguration действительно пока еще не создан при загрузке ConfigurationTest. Конечно, документация явно говорит об этом и предлагает использовать @ConditionalOnMissingBean только для автоконфигураций. Решить эту проблему можно явной аннотацией @Order или профилями, но сделать это можно только для контролируемых классов. И ради тестов такое делать — перебор. Но ни через какой @Conditional это сделать не получится, т.к. он не влияет на порядок загрузки бинов.

Ссылка

Авторизация на уровне запросов к БД

Наткнулся на интересный прием для авторизации SQL-запросов: добавлять к каждому запросу join к табличкам из RBAC. Т.е. в каждый запрос прокидывается пользователь, и результаты выборки дополнительно фильтруются с учетом имеющихся у пользователя привилегий. Например, если идет запрос в таблицу с публикациями, то добавляется условие, что в таблице привилегий у пользователя либо полный доступ, либо есть доступ к таблице публикаций. Если доступ есть — то будет нормальный ответ с публикациями, если нет — пустой результат.

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

Есть как минимум три нюанса, при возникновении которых эта схема будет иметь смысл.

  1. Экономия количества походов в таблицы. Можно привести такой пример: с публикациями мы должны проверить, что есть право публиковать вообще (пользователь не read-only), что человек состоит в канале, что у него есть роль админа-публикатора. Если авторизацию контролировать на более высоком уровне, то в примере, в зависимости от организации БД, могут быть запросы к трем и более таблицам.
  2. Устаревание настроек привилегий из-за неатомарности операций. Есть транзакции, да, но не всегда получится все делать в рамках одной транзакции. А тут все права проверяются в рамках одного запроса.
  3. Мультитенантные системы (одна база с кучей пользователей). Конечно, это обычно звоночек про то, что данные организованы/изолированы не очень хорошо, но обстоятельства разные бывают, и иногда data access control необходим.

Вообще, приятнее конечно работать с простой моделью доступа. Она тупо удобнее и проще в реализации. Если цена ошибочного действия не очень высока и/или оно относительно легко поправимо, то достаточно будет просто добавить аудит. Применение супергранулярным системам доступа я вижу только в критичных системах (банки, ИБ) и бигдате. Да и даже там это не гарантия качества и безопасности — кожаные мешки умеют ошибаться назло всем ограничениям.

Ссылка

Медленный printf

В продолжение поста про оптимизацию буферов. Можно выжать еще больше из скорости записи, если отказаться от printf, о чем написано в топ-статье. Вообще довольно интересная статья про оптимизацию FizzBuzz, и автор еще там разворачивает циклы, использует векторные инструкции и заполняет буфера справа налево — рекомендую почитать наискосок хотя бы.

Еще один интересный факт — printf, оказывается, Тьюринг-полный. Почитать подробнее можно в статье про атаки на поток управления.

Ссылка

Rollout restart в kubernetes

Для обновления конфигов бывает полезна команда

kubectl rollout restart deployment your_service_name --namespace your_namespace

после которой экземпляры сервиса будут перезапущены по одному. Но иногда настройки прав доступа могут быть кривыми и не разрешать эту операцию.

Чтобы то же самое сделать вручную, нужно:

  1. Масштабировать сервис вверх на 1, чтобы не было алертов и/или проблем с нагрузкой.
  2. Удалить один из старых подов (UI обычно показывает дату создания).
  3. Дождаться, пока не будет поднят автоматически новый под на замену старого.
  4. Повторить пп. 2-3 пока не останутся только новые поды.
  5. Масташбировать сервис вниз на 1, чтобы вернуть все как было.
Ссылка