Пиши, сокращай

Три месяца назад сдал экзамен OSCP, а теперь получив бумажную копию могу раздавать советы как заправский пентестер. Какие инструменты использовать, как организовывать хранение файлов и как не выгореть в процессе обучения. Если же хорошо подумать, то таких статей десятки, а потому лучше расскажу о такой неочевидной теме как заполнение отчета.

Экзаменационный отчет OSCP

Экзамен занимает полные 48 часов, из которых первые 24 дается на взлом 5 серверов, а вторые 24 часа — на написание отчета о том, как был получен доступ к каждому из них. И если с первой частью еще понятно, то что же делать вторые 24 часа надо еще разобраться. Советов по курсу в интернете полно, а вот о том как же этот самый отчет написать никто не пишет.

TL;DR Экзаменационный отчет — это некая симуляция настоящего отчета, который специалист по ИБ отправляет в компанию, по завершению тестирования на проникновение. В случае с OSCP заказчик — это Offensive Security, а система которую тестируют — экзаменационные хосты.

В качестве примера дается такой вариант отчета и предлагается отталкиваться от него. Тут важно понять, что это именно пример и никто не запрещает использовать другие варианты оформления, главное, чтобы это смотрелось профессионально.

Так и поступим взяв вместо скучного стандартного, тот который можно заполнять в Markdown — Offensive Security Exam Report Template. Заполнять его намного проще, он сам позаботится о форматировании и все сделает автоматически (ну или почти все).

Строго говоря, это не один шаблон а набор шаблонов для семейства экзаменов: OSCP, OSWE, OSCE, OSEE и OSWP. И чтобы приготовить его для OSCP нужно клонировать к себе репозиторий и удалить из него все содержимое output, а в src оставить один файл OSCP-exam-report-template_whoisflynn_v3.2.md.

После этого директория src становится местом в которое будем складывать скриншоты, а OSCP-exam-report-template_whoisflynn_v3.2.md заготовкой для отчета. Чтобы получить PDF надо поставить Pandoc и Eisvogel Pandoc LaTeX PDF Template, но об этом итак написано в README, там вообще много полезного написано.

$ pandoc src/OSCP-exam-report-template_whoisflynn_v3.2.md \
    -o output/OSCP-OS-XXX-Exam-Report.pdf \
    --from markdown+yaml_metadata_block+raw_html \
    --template eisvogel \
    --table-of-contents \
    --toc-depth 6 \
    --number-sections \
    --top-level-division=chapter \
    --highlight-style breezedark \
    --resource-path src

В результате запуска в output должен появиться файл OSCP-OS-XXX-Exam-Report.pdf, где OS-XXX это личный номер студента. Только после таких приседаний стоит идти на экзамен. Времени будет не много, а тратить минуты или даже часы на попытки поставить Pandoc и Eisvogel это не лучшая идея.

Шаблон есть, что дальше?

Открываем его и в самом начале, где идет YAML metadata block, заменяем имя автора и OSID. Этот блок используется для титульного листа.

  • Разделы Introduction, Objective и Requirements так и оставляем без изменений.
  • В разделе High-Level Summary обновляем IP адреса экзаменационных машин и название уязвимостей, через которые был получен доступ. Например LFI или SQL Injection.
  • Далее идут Recommendations и Methodologies их также не трогаем, а в Information Gathering вставляем список IP адресов но уже без указания типов атак.
  • В начале раздела Penetration проставляем два числа, сколько машин было взломано и сколько их было всего.

Теперь начинается мясо. Заполнение четырех однотипных разделов посвященных экзаменационным хостам. Buffer overflow описывается отдельно но об этом еще расскажу. Каждый такой раздел состоит из нескольких подразделов.

  • Service Enumeration посвящен сбору информации. Какие инструменты были использованы для первичного сбора информации о хосте и какие результаты они принесли. Алгоритм заполнения такой: команда для запуска, затем скриншот вывода.
  • Далее вставляем скриншоты и краткие пояснения о том как получить непривилегированный доступ в систему. Для SQL инъекций стоит написать куда и какой payload вставить. Для RFI какую ссылку сформировать и т.д все это надо подтверждать скриншотами, что это работает.
  • Еще бывает так, что можно найти публичный эксплойт, тогда добавляем раздел Exploit Code содержащий ссылку на него.
  • Vulnerability Explanation — один из немногих разделов где надо много писать, а не ограничиваться скриншотами. В этом разделе описывается какой тип атаки был использован. Выдумывать не нужно, берем описание из OWASP и готово.
  • Vulnerability Fix — это продолжение прошлого раздела, в нем пишем как эту уязвимость устранить. Например обновить программное обеспечение или разогнать бездельников которые пишут такой код.
  • В Severity указываем насколько уязвимость опасна. Как правило это Critical.
  • Local.txt Proof Screenshot — доказательство получения доступа. Тут важно не ошибиться и приложить скриншот на котором видно: имя пользователя, IP адрес хоста и полный путь до файла local.txt c содержимым:
    $ id
    $ ip a
    $ cat /полный/путь/до/local.txt
    
  • Privilege Escalation — этот раздел аналогичен разделу Service Enumeration, с той лишь разницей, что в итоге будут получены права администратора и содержимое файла proof.txt.
  • За ним идут все подразделы аналогичные тем, что были необходимы для получения непривилегированного доступа.
  • Если Privilege Escalation не требуется этот раздел исключается.

Можно выдохнуть, это было самое сложное. После завершения этих секций, заполняем хост с Buffer overflow. Он тоже есть в шаблоне и идет в самом конце. Там все много проще.

  • Vulnerability Exploited: bof — наполняем заметками о том, как был написан эксплойт. Скриншотов из отладчика и вырезок из Proof of concept, с кратким описанием того, как от одного шага перейти к следующему, будет достаточно.
  • Завершающим этапом прикрепляем скриншоты с запуском готового эксплойта и последующим чтением файла proof.txt.
  • Completed Buffer Overflow Code, тут ничего не трогаем, там написано что полное содержимое готового эксплойта будет в приложении. Именно туда его и поместим.

Мы на финишной прямой. После описания хоста с Buffer overflow идут разделы Maintaining Access и House Cleaning, которые можно оставить как есть. А вот заполнить нужно только два или три приложения идущие следом, в секции Additional Items.

  • Appendix - Proof and Local Contents — в таблицу вставляем IP адреса экзаменационных машин и содержимое файлов local.txt и proof.txt
  • Appendix - Metasploit/Meterpreter Usage — заполняем только если в процессе экзамена пришлось воспользоваться Metasploit или Meterpreter, тогда указываем какой именно хост был взломан с их помощью.
  • Appendix - Completed Buffer Overflow Code — то самое место куда вставляют полный исходный код готового эксплойта на переполнение буфера.

Последний этап

Проверяем, что готовый PDF выглядит хорошо. Если выглядит не очень, то можно сделать две вещи. Во-первых, добавить разделители страниц. Делается это добавлением команды \newpage на той строке .md файла, где нужно сделать разрыв на новую страницу.

Во-вторых, скриншоты могут быть светлыми, а фон у отчета белый и в итоге все сливается. Делаем красивые рамочки у скриншотов однострочником на imagemagick.

$ for name in $(ls *.png); do convert $name -shave 2x2 -bordercolor black -border 2 $name; done

Осталось упаковать в 7z без пароля. Когда-то пароль был нужен, теперь нет.

$ cd output
$ 7z a OSCP-OS-00000-Exam-Report.7z OSCP-OS-00000-Exam-Report.pdf

Вуаля! Отправляем архив на проверку и спать.

О чем еще не написано в правилах?

Правила экзамена написаны таким образом, что сложно понять ограничения. А потому вопрос который возникает у тех кто сдает курс OSCP — какие инструменты, кроме Kali, разрешены и могут понадобится на этом курсе. И такой список есть, а называется он Unofficial OSCP Approved Tools и похоже, что это лучший список из тех, что можно найти. Ссылку на шаблон для отчета я взял как раз из него.

Вместо заключения

В лаборатории 66 машин, а на экзамене 5 и для каждой из них нужно хранить заметки, эксплойты, логи, скриншоты и тд. Подходов к организации хранения этой груды файлов много, кто-то использует структуру которую создает AutoRecon, кто-то не заморачивается и скидывает все в одну директорию.

Мне понравился подход который я подсмотрел у burmat в его oscp-pwk-random-tips-and-tricks, там же можно подсмотреть другие лайфхаки. Это такое оправдание того, что я не стал писать собственный пост на эту тему. На этом заканчиваю и как говорится, try harder.

Время, деньги, инфобез

За окном опять сугробы 2020 год, а у меня $999 и желание прокачаться в информационной безопасности. Похоже, что это отличное время чтобы инвестировать его и деньги в обучение. Именно так я и купил курс Penetration Testing with Kali Linux (OSCP).

OSCP (Offensive Security Certified Professional) это самый известный экзамен по информационной безопасности.

Я много читал про OSCP и только волшебный пинок локдауна заставил меня купить курс. Он отличается от других сертификаций по информационной безопасности неоправданно сложным экзаменом. Ребята из Offensive Security бегают по интернету и пристально следит за тем, чтобы никакие подсказки или готовые решения не были доступны публично. И, похоже, им это успешно удается делать более 10 лет подряд.

Минимальная стоимость курса — $999. За эти деньги ты получишь «стартер кит молодого пентестера», который включает в себя:

  • Книжку на 850 страниц
  • 17+ часов видео лекций
  • 30 дней доступа к лаборатории
  • 1 попытку сдачи экзамена

Сложности начинаются уже при покупке, так как нельзя купить курс и сразу начать заниматься, можно только выбрать дату начала обучения в будущем. В моем случае такой датой стало начало октября 2020, через месяц после покупки.

Небольшая ремарка. Уже в процессе обучения я решил прочитать блоги людей, которые проходили курс, и оказалось, что большая часть из них подошли к обучению основательно и готовились за полгода/год. А для тренировки перед OSCP покупали доступы HTB и VulnHub. Я же ничего из этого не делал и курс был для меня черной коробкой.

После оплаты мне скинули ссылки на кастомизированный образ Kali (чем конкретно он отличается от обычного я не понял), а также архив для проверки VPN соединения, который действует 3 дня. За эти дни нужно было скачать VMware, поставить туда этот Kali и проверить свое подключение к VPN. Говорят, раньше все это присылали в день начала занятий, а теперь присылают заранее.

Вообще в 2020 году курс изрядно обновили и, как они сами пишут, увеличили в 2 раза размер материалов — раньше в книжке было всего 400 страниц, а также на 30% число машин в лаборатории. А еще добавили новые типы атак, вроде атаки на Active Directory или лекций по PowerShell Empire, репозиторий которого уже закрыт владельцем.

Курс состоит из 3х частей

Первая часть — это теория. За нее отвечает та самая книжка на 850 страниц и набор видеороликов. А чтобы не было соблазна выкладывать это в открытый доступ, все страницы книги и видео материалы содержат водяные знаки с личным номером и именем покупателя курса.

Вторая часть курса — это время работы в лаборатории. Что это значит? Ты получаешь доступ в закрытый интранет, где располагается 4 виртуальных сети, связанных через шлюзы. На картинке из официального гайда можно посмотреть, как эти сети связаны между собой. Кроме того, в этой сети развернуто два полноценных Active Directory домена по 5 машин в каждом. Это, кстати, интересный момент, его я опишу чуть ниже.

Изначально доступ есть только в Student Network (она же Public), но по мере захвата машин можно попасть и в другие сети. В сети 66 хостов, на каждом из которых лежит файл proof.txt, который необходимо сдать в панели управления. Принцип чем-то похож на CTF, с той лишь разницей, что сам файл не так важен как понимание уязвимости через которую можно получить права администратора.

Последняя часть программы — это экзамен. О его сложности я прочитал только когда имел на руках дату начала обучения. Целые форумы посвящены плачам людей, которые его пытаются сдать. Читать подобные пассажи строго не рекомендую.

День Х

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

До 2020 года книжка и видео дублировали друг друга, но сейчас это не так. Некоторые векторы атак, показанные в видео, не раскрываются в книге, а дополнительных заданий из книги нет в видео. Я выбрал путь стримера: смотреть в консоль на экране и слушать закадровый голос было проще чем читать все 850 страниц.

Я смотрел лекции по 2-3 часа в день и потому мне потребовалась целая неделя для просмотра всех 17 часов от начала и до конца, попутно листая PDF.

Лабы

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

Большинство хостов в лаборатории как-то связаны между собой. Письмо в почтовом ящике на одной машине, может содержать доступ к другой. Некая симуляция реальной сети. Но бывают и незапланированные сценарии. Например, чтобы получить доступ к контроллеру домена Active Directory, нужно получить доступ к дочерним узлам и только потом получить администратора контроллера. Именно так задумали эту атаку, но если хорошо погуглить выясняется, что есть довольно свежий эксплойт позволяющий атаковать сразу контроллер, игнорируя всю цепочку. Сделано это намеренно или нет, я не знаю.

Подсказок, как ломать, нет в принципе. Можно пойти на закрытый форум, только для студентов, но это отдельный цирк. Обсуждать можно все, но давать прямые подсказки запрещено. А потому местные посетители упражняются в аллегориях, например вместо упоминания smb пишут brazilian dance, а вместо dirtycow используют well-know exploit.

На форумах ходит поверье о 4 машинах (pain, sufferance, humble, gh0st) которые не обязательно ломать, так как они какие-то особенно сложные. Забавно, что узнал я об этом много позже, получения доступа к каждой из них. По мне, так это обычные, ничем не примечательные машины, которые встречаются на любом CTF-соревновании.

Спустя 3 недели я закрыл всего 23 из 66 машин. В этот момент у меня заканчивался 30-дневный доступ в лабораторию, и я купил дополнительные 30 дней (покупать дополнительные дни можно бесконечно). За следующие 30 дней я закрыл все машины, получив доступ ко всем подсетям.

Исходя из тредов на форуме можно сделать вывод, что типичный студент закрывает около 40 машин, так как взлом машин через SSH-туннелирование — это развлечение не для всех.

Экзамен

Эндшпилем этой движухи стал экзамен, о котором пишут статьи, наполненные либо слезами, либо советами, как к нему лучше готовиться морально и физически. Я попробую не ударяться в такие крайности.

Экзамен занимает полные 48 часов, из которых первые 24 дается на взлом 5 серверов, а вторые 24 часа — на написание отчета о том, как был получен доступ к каждому из них.

А чтобы было совсем весело, первые 24 часа за тобой через веб-камеру будут наблюдать специально обученные люди. Твой экран они тоже будут видеть. А потому, даже если захочется отойти в туалет, нужно будет написать об этом в специальном чате.

В день экзамена, за 15 минут до начала, на почту падает ссылка для проведения сессии подтверждения личности. Я использовал загранпаспорт. После это приходит уже новое письмо с доступами для подключение к VPN и ссылка на экзаменационную панель управления.

Для успешной сдачи экзамена нужно набрать 75 очков. Для этого есть 5 машин, они каждый раз разные, но тип каждой из них всегда одинаковый:

  • 1 машина на написание эксплойта с переполнением буфера за 25 очков
  • 1 комплексная машина со множеством ложных векторов атаки за 25 очков
  • 2 обычные машины по 20 очков
  • 1 совсем простая за 10

Максимум можно получить 100 очков. Плюс еще можно заработать 5 очков, конспектируя работу в лаборатории, но это вариант для параноиков. Лишние 5 очков точно не спасут, а выполнение и конспектирование всех заданий занимает очень много времени.

Так как экзамен — это рулетка, то и соотношение Windows/Linux может быть любым. Мне попались 3 машины на Linux и 2 на Windows.

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

Затем, чтобы расслабится, я получил доступ к машине за 10 очков, часто ее называют low-hanging fruit. Для атаки требовалось просто найти один публичный эксплойт, который дает доступ к учетной записи с правами администратора.

Большинство экзаменуемых начинают с переполнения буфера, а я оставил его на середину, чтобы разгрузить мозги. Кому-то это может показаться сложным, но по факту дают примитивное приложение и заранее подготовленный PoC в котором нужно дописать только «мясо». Берем любую инструкцию и следуем всем шагам.

После этих упражнений у меня было 60 очков в кармане. Оставшееся от экзамена время я потратил на одновременную атаку на 2 оставшиеся машины. На одной из них я быстро получил непривилегированного пользователя, а вот с эскалацией привилегий вышло сложнее. На второй же кроме LFI не получалось прощупать ничего. Ближе к вечеру я получил привилегированный доступ на первой из машин, а на вторую решил просто забить и лечь спать.

Я читал о смельчаках, которые не спали по 24 часа и все равно проваливали экзамен. Примыкать к их числу я не хотел. Проснувшись за 3 часа до окончания экзамена, я проверил хватает ли мне скриншотов и логов для заполнения отчета. А после этого попытался еще раз копнуть оставшуюся машину, но так ничего и не вышло.

Отчет

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

Написание такого отчета может отпугнуть неподготовленного русскоязычного обывателя. Но и у англоязычных студентов это вызывало приступы паники. Некоторые «счастливчики» писали отчеты по 200+ страниц. Сказу скажу, что мой отчет содержал всего 68 и это с учетом того, что я вставлял по одному скриншоту на лист.

Вообще бояться этого смысла нет, писать эссе там точно не надо. Достаточно взять шаблон и вставлять в нужные места скриншоты с одним предложением что-же скриншот значит. Использовать стандартный шаблон тоже не обязательно. Для своего отчета я использовал альтернативный вариант в Markdown.

После заполнения отчета его нужно было запаковать в 7z и отправить на специальной странице. До 2020 надо было еще и паролем шифровать, но теперь от этой практики отказались.

Результат

Спустя неделю мне пришло заветное письмо, где сообщалось, что я сдал экзамен и теперь мне необходимо заполнить информацию о моем местонахождении для отправки физической копии сертификата.

На этом заканчиваю свой рассказ, а о каких-то технических хаках, которые могут помочь на этом курсе, напишу в отдельной заметке. Надеюсь что к этому времени DHL уже пришлет мне мой сертификат. А на вопрос, стоит ли сдавать этот экзамен ответ однозначный — да, если есть много свободного времени и лишние $999.

Как отказаться от RSS в 2020 году

— Я решил отказаться от RSS…

— Ок, бумер

За несколько лет получение и потребление контента изменилось. Twitter и Telegram заняли первое место среди каналов доставки информации, а RSS как был так и остался чем-то маргинальным.

В RSS меня держало несколько лент которых не было нигде, и это были не только «местечковые блоги» но информационные сайты обновления которых получать через Twitter было неудобно.

Идея трансляции RSS в Telegram занимала мои мысли давно, а беглый поиск по Github’у давал десятки готовых скриптов. Но во всех этих скриптах был фатальный недостаток - их надо было заворачивать в какие-то контейнеры, куда-то ставить, а потом еще и поддерживать. К счастью Вастрик написал лонгрид о движении NoCode после прочтения которого я понял, что время пришло. Если авторы контента не идут в Telegram, то их можно туда принести.

Первым делом зарегистрировал себе бесплатный аккаунт в Integromat, это такой IFTTT для нормальных людей. Затем создал бота в Telegram и открыл по одному каналу на каждую из RSS лент. Можно было бы все ленты доставлять в один канал, но я не пытался из мессенджера сделать RSS ридер.

Осталось собрать вместе части конструктора. Для этого я взял готовый рецепт, вставил адрес первой RSS ленты и канал доставки, а также поставил запуск каждый 15 минут. После этого клонировал получившийся сценарий меняя настройки. Через пару минут все каналы ожили и стали доставлять обновления из RSS, а я удалил все ленты из Feedly и закрыл аккаунт навсегда.

А теперь к плохим новостям. Через 5 дней Integromat радостно сообщил, что я по потратил весь бесплатный лимит. Ведь каждый сценарий при 15-минутных запусках будет тратить по ~2880 операций в месяц. Чтобы не заводить новый аккаунт заплатил за базовый план, а для настроек выбрал запуск рецептов раз в час, таким образом снизив число запусков до 720 в месяц.

Кибер настольный календарь

Типичный день в энтерпрайзе чуть менее, чем полностью состоит из встреч. В офисе об этих встречах напоминает не только Microsoft Exchange но и члены команды.

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

Raspberry Pi

Для этого взял Raspberry Pi Zero W, прицепил экран и написал скрипт который раз в 5 минут заходит на корпоративный сервер Exchange, достанет ближайшую встречу и выводит на экран ее название и человекопонятное описание когда она начнется.

Raspberry Pi

Календарь я хотел сделать приближенным к аналоговым предметам, а потому взял E Ink экран, который не светится в темноте и не раздражает глаза.

Waveshare 2.13inch e-Paper HAT я купил год назад на Amazon и он все это время лежал без дела. Китайцы из Waveshare делают экраны совместимые с Raspberry Pi и любезно предоставляют готовый SDK на Python.

Raspberry Pi

Итоговый проект и инструкция по установке выложил на github и подробно рассматривать не буду. Если кратко, то функция next_meeting достает ближайшую встречу, а функция get_image формирует изображение, которое выводится на экран.

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

Как работает встраивание ошибок в Go

Обработка ошибок в Go концептуально прозрачна но вызывает массу вопросов и негодования у людей переходящих на Go с ООП языков. И первое с чем такие люди сталкиваются это отсутствие вложенности ошибок. Чтобы как-то смягчить эту боль в заднице Dave Cheney написал библиотеку для оборачивания ошибок и реализуя некое подобие наследования.

К версии 1.13 разработчики устали слушать нытье и насыпали немного синтаксического сахара добавив новую структуру для оборачивания ошибок и две новых функции для работы с ошибками. Вот как это работает под капотом. Когда мы создаем обычную ошибку, то фактически инициализируем следующую структуру.

type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

А если оборачиваем ошибку используя вызов fmt.Errorf("%w", err), то возвращаемая ошибка получит уже другую структуру, содержащую указатель на оборачиваемую ошибку и метод Unwrap() для доступа к ней.

type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

func (e *wrapError) Unwrap() error {
	return e.err
}

С такими ошибками можно делать ровно два действия, сравнить со значением и с типом (не совсем). Понять как это работает проще всего на примере. Возьмем абстрактный микросервис, которых ходит в другой микросервис через RPC клиент по http.

Handler → Middleware → RPC Client → HTTP Client

Ошибка может произойти как на уровне http клиента, так и в постобработке полученного ответа. При этом логирование ошибок происходит на уровне handler’а, а остальные уровни только оборачивают ошибки и пробрасывают наверх. Хватит теории, давай код.

package main

import (
	"fmt"
	"net/http"
)

type httpError struct {
	Code    int
	Message string
}

func (e *httpError) Error() string {
	return fmt.Sprintf("%d: %s", e.Code, e.Message)
}

type rpcError struct {
	Method string
	err    error
}

func (e *rpcError) Error() string {
	return fmt.Sprintf("%s error: %s", e.Method, e.err.Error())
}

func (e *rpcError) Unwrap() error {
	return e.err
}

func main() {
	httpErr := &httpError{
		Code:    http.StatusInternalServerError,
		Message: http.StatusText(http.StatusInternalServerError),
	}
	rpcErr := &rpcError{
		Method: "getEnterpriseResourceV1",
		err:    httpErr,
	}
	middlewareErr := fmt.Errorf("middleware: %w", rpcErr)
	handlerErr := fmt.Errorf("handler: %w", middlewareErr)

	fmt.Printf("%q\n", handlerErr)
	// handler: middleware: getEnterpriseResourceV1 error: 500: Internal Server Error
}

Тут происходит имитация ошибки на уровне http клиента с последующим оборачиванием в rpc ошибку, а потом в middleware и handler. Конечная ошибка выглядит как набор вложенных друг в друга структур.

fmt.wrapError{
    msg: "handler: middleware: getEnterpriseResourceV1 error: 500: Internal Server Error",
    err: &fmt.wrapError{
        msg: "middleware: getEnterpriseResourceV1 error: 500: Internal Server Error",
        err: &rpcError{
            Method: "getEnterpriseResourceV1",
            err: &httpError{
                Code:    500,
                Message: "Internal Server Error",
            },
        },
    },
}

Имея такую матрешку попробуем выяснить какая ошибка была изначально и какие этапы оборачивания она прошла. Для этого воспользуемся функцией сравнения ошибок по значению errors.Is(err, target error). Это функция рекурсивно сравнивает значение ошибки с target вызывая .Unwrap() для продвижения вглубь матрешки.

errors.Is(handlerErr, handlerErr)
// true т.к эквивалент ==
errors.Is(handlerErr, middlewareErr)
// true т.к handlerErr.Unwrap() == middlewareErr
errors.Is(handlerErr, rpcErr)
// true т.к handlerErr.Unwrap().Unwrap() == rpcErr
errors.Is(handlerErr, httpErr)
// true т.к handlerErr.Unwrap().Unwrap().Unwrap() == httpErr

errors.Is(handlerErr, fmt.Errorf("generic")) // false
errors.Is(handlerErr, &unknownError{})       // false
errors.Is(handlerErr, &rpcError{})           // false т.к совпадает тип, но не значение
errors.Is(handlerErr, &httpError{})          // false т.к совпадает тип, но не значение

Выглядит логично и удобно, но в реальном мире это не будет работать. Переменные вложенных ошибок будут в другом скоупе и их невозможно будет достать чтобы сравнить. Правда можно добавить немного магии, реализовав метод Is(target error) у httpError.

func (e *httpError) Is(target error) bool {
	return reflect.ValueOf(target).Elem().Type() == reflect.TypeOf(httpError{})
}

В таком случае сработает проверка по-типу даже если значения не будут совпадать.

errors.Is(handlerErr, &httpError{})
// true

Но это хак, т.к для проверки по типу в Go добавили вторую функцию errors.As(err error, target interface{}). Она работает аналогично функции errors.Is, рекурсивно обходя матрешку, но сравнивает не значения, а тип target и вызывая .Unwrap() для продвижения вглубь. Если же совпадение найдено, но значения типа совпавшей ошибки копируется в target.

var s *rpcError
if errors.As(handlerErr, &s) {
    fmt.Printf("handlerErr as rpcError: %s\n", s.Method)
}
// handlerErr as rpcError: getEnterpriseResourceV1

var h *httpError
if errors.As(handlerErr, &h) {
    fmt.Printf("handlerErr as httpError: %d\n", h.Code)
}
// handlerErr as httpError: 500

var e error
if errors.As(handlerErr, &e) {
    fmt.Printf("handlerErr as error: %s\n", e)
}
// handlerErr as error: handler: middleware: getEnterpriseResourceV1 error: 500: Internal Server Error

var u *unknownError
if errors.As(handlerErr, &u) {
    fmt.Printf("handlerErr as unknownError: %v\n", u)
}
// не выведет ничего

У такого подхода один очевидный плюс, разработчики убили сразу двух зайцев, и сравнение по типу и распаковка ошибки. Но есть и минусы, в ситуации когда нужно просто понять что за тип ошибки нет варианта использовать конструкцию switch, как это можно сделать используя пакет pkg/errors.

В целом новый механизм работы с ошибками решает часть болей и потенциально помогает отказаться от еще одной внешней зависимости в проекте.