Безопасно by design - Дэн Берг Джонсон - E-Book

Безопасно by design E-Book

Дэн Берг Джонсон

0,0
13,99 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.
Mehr erfahren.
Beschreibung

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

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB
MOBI

Seitenzahl: 566

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Дэн Берг Джонсон, Дэниел Деоган, Дэниел Савано
Безопасно by design

Научный редактор М. Сагалович

Переводчик С. Черников

Литературный редактор Н. Рощина

Художник В. Мостипан

Корректоры О. Андриевич, Е. Павлович

Дэн Берг Джонсон, Дэниел Деоган, Дэниел Савано

Безопасно by design. — СПб.: Питер, 2021.

ISBN 978-5-4461-1507-5

© ООО Издательство "Питер", 2021

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

Оглавление

Предисловие
Введение
Благодарности
О книге
Для кого эта книга
Структура книги
О коде
От издательства
Об авторах
Об иллюстрации на обложке
Часть I. Введение
1. Роль проектирования в безопасности
1.1. Безопасность как неотъемлемое свойство системы
1.2. Что такое проектирование
1.3. Традиционный подход к безопасности ПО и его недостатки
1.4. Обеспечение безопасности за счет проектирования
1.5. Строки, XML и атака billion laughs
Резюме
2. Антракт: анти-«Гамлет»
2.1. Книжный интернет-магазин с нарушением бизнес-целостности
2.2. Поверхностное моделирование
2.3. Глубокое моделирование
Резюме
Часть II. Основы
3. Основные концепции предметно-ориентированного проектирования
3.1. Модели как средства обеспечения более глубокого понимания
3.2. Составные элементы модели
3.3. Ограниченные контексты
3.4. Взаимодействие между контекстами
Резюме
4. Концепции программирования, способствующие безопасности
4.1. Неизменяемость
4.2. Быстрое прекращение работы с использованием контрактов
4.3. Проверка корректности
Резюме
5. Доменные примитивы
5.1. Доменные примитивы и инварианты
5.2. Объекты одноразового чтения
5.3. Опираясь на доменные примитивы
5.4. Анализ помеченных данных
Резюме
6. Обеспечение целостности состояния
6.1. Управление состоянием с помощью сущностей
6.2. Согласованность в момент создания
6.3. Целостность сущностей
Резюме
7. Упрощение состояния
7.1. Частично неизменяемые сущности
7.2. Объекты состояния сущностей
7.3. Снимки сущностей
7.4. Эстафета сущностей
Резюме
8. Роль процесса доставки кода в безопасности
8.1. Использование конвейера доставки кода
8.2. Безопасное проектирование с использованием модульных тестов
8.3. Проверка переключателей функциональности
8.4. Автоматизированные тесты безопасности
8.5. Тестирование доступности
8.6. Проверка корректности конфигурации
Резюме
9. Безопасная обработка сбоев
9.1. Использование исключений для обработки сбоев
9.2. Обработка сбоев без использования исключений
9.3. Проектирование с расчетом на доступность
9.4. Работа с некорректными данными
Резюме
10. Преимущества облачного мышления
10.1. Концепции двенадцатифакторного приложения и облачной ориентированности
10.2. Хранение конфигурации на уровне окружения
10.3. Отдельные процессы
10.4. Не сохраняйте журнальные записи в файл
10.5. Администраторские процессы
10.6. Обнаружение сервисов и балансировка нагрузки
10.7. Три составляющие корпоративной безопасности
Резюме
11. Перерыв: страховой полис задаром
11.1. Продажа страховых полисов
11.2. Разделение сервисов
11.3. Новый тип платежей
11.4. Разбитая машина, запоздавший платеж и судебный иск
11.5. Что пошло не так?
11.6. Взгляд на общую картину происходящего
11.7. Замечание о микросервисной архитектуре
Резюме
Часть III. Применение основ на практике
12. Руководство по устаревшему коду
12.1. В какие участки старого кода следует внедрять доменные примитивы
12.2. Неоднозначные списки параметров
12.3. Сохранение в журнал непроверенных строк
12.4. Защитные конструкции в коде
12.5. Неправильное применение принципа DRY, когда во главе угла текст, а не идеи
12.6. Недостаточная проверка корректности в доменных типах
12.7. Тестирование на приемлемом уровне
12.8. Частичные доменные примитивы
Резюме
13. Руководство по микросервисам
13.1. Что такое микросервис
13.2. Каждый сервис — это ограниченный контекст
13.3. Передача конфиденциальных данных между сервисами
13.4. Ведение журнала в микросервисах
Резюме
14. В заключение: не забывайте о безопасности!
14.1. Анализируйте код на предмет безопасности
14.2. Следите за своим стеком технологий
14.3. Проводите тестирование на проникновение
14.4. Изучайте сферу безопасности
14.5. Выработайте процедуру на случай нарушения безопасности
Резюме

Посвящается нашим семьям

Предисловие

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

Свою вторую работу в качестве программиста я получил в рекламной фирме. Там активно обменивались документами Microsoft Word, защищенными паролями, зачастую с конфиденциальной коммерческой информацией внутри. Я указал на то, насколько слабым было шифрование этих файлов и как их можно было легко прочитать, используя инструмент, находящийся в свободном доступе в Usenet (древнем аналоге Google Groups). Никто меня не слушал, пока я не начал возвращать эти файлы отправителям, предварительно убрав шифрование.

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

По прошествии более чем десяти лет, когда я занимался разработкой крупномасштабной платформы энерготрейдинга в ThoughtWorks, мне попался отчет об ошибке, который по сей день остается моим любимым. Одна из наших тестировщиц заметила, что поле ввода пароля не предусматривало проверку длины, хотя сам пароль должен был быть не длиннее 30 символов. Но вместо того, чтобы оформить это как «не проверяется 30-символьный лимит на пароль», она написала: «Интересно, сколько текста я могла бы всунуть в поле ввода пароля?» Путем проб и ошибок в заключительном отчете был сделан следующий вывод: «Если в поле пароля ввести больше 32 000 символов, приложение падает». Она превратила простую ошибку проверки корректности в эксплойт для DoS-атаки, который позволял вывести из строя все приложение путем ввода пароля, сформированного подходящим образом. (Спустя несколько лет я посетил конференцию по тестированию программного обес­печения, на которой для регистрации было решено использовать планшеты iPad с самописным приложением. Когда моя подруга попыталась зарегистрироваться под именем Julie undefined и сломала тем самым систему, я понял, что с тестировщиками ПО лучше не шутить.)

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

Почему все так плохо? В мире бесплатной многофакторной аутентификации, биометрической безопасности, физических токенов, пакетов управления паролями вроде 1Password (https://1password.com/) и LastPass (https://www.lastpass.com/) и таких сервисов уведомлений, как Have I Been Pwned (https://haveibeenpwned.com), легко поверить в то, что проблемы безопасности уже решены. Но, как отмечают во введении Дэн, Дэниел и Дэниел (я был просто обязан написать предисловие к этой книге, так как над ней работало слишком мало людей по имени Дэниел), надежные замки и крепкие двери не помогут, если злоумышленник может просто снять их с метафорических петель и уйти с добычей.

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

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

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

Эта книга — одна из моих любимых. Во-первых, она объединяет два моих любимых направления: программную и информационную безопасность (которым я увлекаюсь как любитель) и предметно-ориентированное проектирование (в котором я имею кое-какую квалификацию). Во-вторых, это действенное практическое руководство. Это не просто призыв воспринимать безопасность как часть проектирования (что уже само по себе достойная цель), но и целый ряд примеров, охватывающих как архитектурные вопросы, так и листинги с реальным кодом, что придает книге конкретику.

Хотел бы отметить несколько примеров, которые мне больше всего запомнились. Один был посвящен поверхностному проектированию и иллюстрировал использование стандартных типов вроде целых чисел и строк для представления сложных бизнес-концепций. Это создает такие угрозы безопасности, как взлом пароля (тип Password, в отличие от строки, сам может проверить свою длину) или заказ отрицательного количества книг (тип BookCount не допустил бы ввода отрицательных значений, как это произошло с int). Я занимаюсь профессиональной разработкой программного обеспечения больше 30 лет, но во время чтения этого раздела хотел вернуться в прошлое и треснуть себя молодого по голове этой книгой или по крайней мере оставить ее на своем столе с загадочной пометкой: «Прочти меня» в стиле «Алисы в Стране чудес».

Еще одним примером была тема о плохой обработке ошибок, что является источником огромного числа потенциальных нарушений безопасности. У большинства современных языков есть два пути выполнения кода: один, где все идет хорошо, и другой, на котором случаются разные неприятности. Второй в основном существует в «серой» зоне из блоков catch и обработчиков исключений или робких инструкций-ограничителей. Программистам свойственно искажение восприятия, которое убеждает нас в том, что мы обо всем позаботились. У нас даже иногда хватает наглости оставлять комментарии вроде //этогоникогданеслучится. И мы оказываемся не правы снова и снова.

Покойный Джо Армстронг, потрясающий системный инженер и создатель языка Erlang, приговаривал, что единственный надежный способ обработать ошибку — «позволить ей (системе. — Примеч. авт.) упасть!». Ухищрения, на которые мы идем, лишь бы этого не допустить, включают нулевые указатели (известные как «ошибка на миллиард») и их хитрые исключения, вложенные инструкции if-else, логику блоков switch вида «повезет — не повезет» и привычку доверять нашей IDE работу по генерации загадочного шаблонного кода для интерполяции строк и проверки равенства.

Всем известно, что мелкие компоненты легче тестировать, чем крупные. В них на порядок меньше мест, где могут таиться программные дефекты, поэтому их проще анализировать с точки зрения безопасности. Тем не менее мы только начинаем осознавать, что влечет за собой выполнение сотен и тысяч мелких компонентов (в микросервисной или бессерверной архитектуре), а новые области, такие как наблюдаемость и проектирование хаоса (chaos engineering), начинают привлекать к себе внимание подобно тому, как это происходило с DevOps и непрерывной доставкой.

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

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

Дэниел Терхорст-Норт, начинающий специалист в сфере безопасности, июль 2019 года

Введение

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

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

Среди людей, которые оказали на нас самое большое влияние, был Эрик Эванс. Его идеи о предметно-ориентированном проектировании (Domain-Driven De­sign, DDD) сформировали терминологию для обсуждения того, как код должен наполняться смыслом. В 2008 году исследователь в области безопасности Джон Уиландер и энтузиаст DDD Дэн Берг Джонсон начали работать вместе. Принципы DDD послужили основой для их дискуссий о безопасности и коде. В 2009 году они предложили выражение «предметно-ориентированная безопасность», которое стало одним из первых предвестников безопасности на уровне проектирования. Во время своего выступления на конференции OWASP European в 2010 году они обнаружили, что Эрленд Офтедаль из Осло тоже экспериментировал с похожими идеями, что позволило расширить дискуссию. Это, в свою очередь, привело к более глубокому пониманию способов минимизировать такие риски, как атаки внедрения и межсайтовый скриптинг (XSS). В 2011 году к команде присоединились Дэниел Деоган и Дэниел Савано, что положило начало активному применению этих идей на практике. Мы развивали свои методики, используя проектирование для улучшения безопасности, и испытывали их в реальных крупномасштабных системах. К нашему восторгу, они работали на удивление хорошо. Например, наш клиент тайно заказал аудит безопасности для проверки одного из наших проектов, результатом стало одно-единственное замечание, тогда как другой сопоставимый проект получил список из 3000 замечаний!

Популяризируя свои мысли с помощью проектов, статей в блогах и выступлений на конференциях, мы продолжали распространять идею о том, что со слабыми местами в безопасности можно бороться на уровне проектирования. Так продолжалось до тех пор, пока в 2015 году с Дэниелом Деоганом не связались представители издательства Manning и не предложили оформить все это в виде книги. На момент написания данных строк в 2019 году мы рассмотрели довольно много тем, и книга стала толще и полнее, чем мы ожидали. Но мы старались включить в нее только тот материал, который, по нашему мнению, важен для безопасности. А также позаботились о том, чтобы написанное здесь не было привязано к каким-то конкретным языкам или фреймворкам. Надеемся, что наши идеи окажутся универсальными и не устареют в ближайшее время. Мы рады, что вы приобрели экземпляр этой книги, и хотим, чтобы она помогла вам сделать этот чудесный цифровой мир чуточку лучше, стабильнее и безопаснее.

Благодарности

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

Мы бы также хотели поблагодарить тех, кто сделал эту книгу реальностью. Спасибо нашим терпеливым редакторам, Синтии Кейн, Тони Арртиоле и Дженнифер Стаут, которые сделали отличные замечания по содержимому и стилю. Спасибо замечательному редактору текста, Рейчел Хед, которая отшлифовала наш грубый неродной английский. И спасибо производственному отделу Manning, который помог превратить нашу рукопись в готовое издание. Глубоко признательны Дэниелу Терхорст-Норту за предисловие и отзывы, которые он оставил в процессе его написания. Спасибо Гойко Аджичу, Эрленду Офтедалю, Питеру Магнуссону, Джимми Нилсону, Луи Атенцио и Джону Гатри за техническую экспертизу и отзывы. Всем рецензентам: Адриану Ситу, Александру Зенгеру, Андреа Барисоне, Арналдо Габриелю Мейеру, Кристоферу Финку, Доту Морине, Дэвиду Рэймонду, Дагу Спарлингу, Эросу Педрини, Генрику Герингу, Яну Гойвертсу, Джереми Ланжу, Джиму Амрхейну, Джону Касевичу, Джонатану Шерли, Джозефу Престону, Пьетро Маффи, Ричарду Вогану, Роберту Кильти, Стиву Экманну и Зороджаю Макайе — ваши советы помогли сделать эту книгу лучше. Спасибо издательству, которое поверило в нас и в тему, которая здесь освещается. Мы также хотели бы выразить признательность всем, кто участвовал в создании этой книги, но с кем мы не общались напрямую. Поразительно, сколько всего требуется для появления на свет такого издания, как это.

Прежде всего я хочу поблагодарить свою любимую жену Фиа и замечательных сыновей Карла и Антона. Спасибо за чай и поддержку. Вы свет моей жизни. В более профессиональном смысле хотел бы сказать спасибо Консу Ахсу, который научил меня программированию, Эрику Эвансу — за то, что показал мне строгость предметно-ориентированного проектирования, и Джону Уиландеру, который помог понять связь между хорошим программированием и безопасностью. Спасибо специалистам по безопасности, которые должны оставаться анонимными. И наконец, спасибо духу, живущему в компьютере.

Дэн Берг Джонсон

Я бы хотел поблагодарить свою прекрасную жену Айду и любимых детей Лукаса и Айзека. Спасибо за ваши поддержку, любовь и понимание в подчас напряженные времена написания этой книги. Без вас у меня бы ничего не получилось — спасибо вам. Я также благодарен всем, кто за прошедшие годы испытывал мои идеи на прочность: ваши вопросы, комментарии и интересные дискуссии были по-настоящему полезны во время работы над этой книгой.

Дэниел Деоган

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

Дэниел Савано

О книге

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

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

Для кого эта книга

Книга написана в первую очередь для разработчиков ПО, но она может понравиться любому человеку с интересом к теме безопасности. Важно, чтобы читатель был знаком с C-подобным синтаксисом и имел базовые навыки программирования на языках Java или C#. Все примеры и рекомендации представлены здесь так, чтобы быть полезными независимо от уровня ваших знаний, поскольку понимать, как писать безопасный код, должен любой — от младшего разработчика до опытного архитектора. Поэтому, если вы хотите улучшить свои навыки программирования в целом или пытаетесь сделать имеющуюся кодовую базу безопаснее, эта книга вам поможет. Ее можно использовать также в качестве лекционного материала в университетах или для чтения в учебных группах.

Структура книги

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

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

• Глава 1. Здесь демонстрируется, как проектирование может способствовать безопасности ПО и как оно позволяет с легкостью создавать защищенные системы. На закуску приводится пример того, как обеспечение безопасности на уровне проектирования дает возможность предотвратить угрозы безопасности.

• Глава 2. Это отступление о том, как плохая архитектура ПО привела к существенным финансовым потерям. В этом примере безопасности ничто бы не угрожало, если бы использовались концепции, представленные в части II.

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

• Глава 3. Здесь вы познакомитесь с одними из важнейших концепций предметно-ориентированного проектирования (DDD), необходимыми для понимания многих идей, связанных с безопасным проектированием. Мысли и понятия, которые вы усвоите, прочитав ее, активно используются по всей книге, поэтому, если вы плохо ориентируетесь в DDD, советуем начать с этой главы.

• Глава 4. Эта глава познакомит вас с несколькими конструкциями программирования, важными для безопасности. В ней рассказывается о преимуществах неизменяемости и быстрого прекращения работы и демонстрируется безопасная проверка корректности данных.

• Глава 5. Здесь обсуждаются доменные примитивы и то, как они составляют основу безопасного кода. Вы узнаете о преимуществах объектов одноразового чтения и о том, как доменные примитивы становятся фундаментом для создания безопасных сущностей.

• Глава 6. Здесь мы обсудим основы создания безопасных сущностей: как обеспечить их согласованность в момент создания и поддерживать в целостном состоянии на протяжении жизненного цикла.

• Глава 7. Продолжение темы сущностей. Вы познакомитесь с разными подходами к минимизации их сложности.

• Глава 8. Здесь вы увидите, как с помощью конвейера доставки можно улучшить и проверить безопасность программного обеспечения. Мы также обсудим некоторые трудности, возникающие при автоматизации тестирования безопасности.

• Глава 9. В этой главе вы научитесь справляться со сбоями и ошибками, не нарушая безопасность. Кроме того, мы исследуем некоторые пути борьбы с неполадками на уровне архитектуры.

• Глава 10. Глава описывает, как принципы проектирования, распространенные в облачных окружениях, можно использовать для повышения безопасности даже тех систем, на которые они не были изначально рассчитаны.

• Глава 11. Еще одно отступление, посвященное тому, как система с сервис-ориентированной архитектурой вышла из строя, хотя каждый отдельный ее сервис был исправен. Это реальный пример уникальных проблем с безопасностью, возникающих при создании системы, состоящей из других систем. Обсуждение этих проблем продолжается в части III.

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

• Глава 12. Здесь рассматриваются шаблоны проектирования и конструкции в коде, которые часто встречаются в старых проектах и оказываются проблематичными с точки зрения безопасности. Вы узнаете, как их находить и исправлять.

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

• Глава 14. Здесь мы обсудим, почему время от времени безопасности нужно уделять особое внимание. Мы также подскажем, о каких направлениях следует позаботиться при создании безопасных программных систем.

О коде

Концепции, представленные в книге, не привязаны к конкретному языку, но во всех примерах кода в качестве языка программирования мы решили использовать Java — отчасти потому, что это один из самых распространенных языков. Еще одна причина в том, что его C-подобный синтаксис должен быть понятен любому разработчику. Этот код нужен для представления определенных концепций, а не для создания полностью рабочих примеров. Мы попытались максимально приблизить его к тому, что вы бы написали в реальных условиях, но в то же время всегда избавлялись от любых элементов, которые могут отвлечь вас от обучения. Это означает, что для ясности некоторые фрагменты методов и классов были опущены.

В этой книге принято использовать так называемый змеиный регистр (snake case) в именах тестовых методов (как в методах с аннотациями @Test из JUnit). Это сделано для удобства чтения. Когда для создания тестов применяется стиль BDD (behavior-driven development — разработка через поведение), как мы любим делать, имена тестовых методов зачастую превращаются в длинные грамматически корректные предложения, которые смогут прочитать не только разработчики. При использовании верблюжьего регистра (camel case) слишком длинные имена методов становятся практически неразборчивыми. Змеиный регистр решает эту проблему. Верблюжий регистр является стандартным стилем именования в Java и применяется в именах всех остальных методов и классов.

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

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

Код представленных здесь примеров доступен для загрузки на веб-сайте Manning по адресу https://www.manning.com/books/secure-by-design.

От издательства

Ваши замечания, предложения, вопросы отправляйте по адресу [email protected] (издательство «Питер», компьютерная редакция).

Мы будем рады узнать ваше мнение!

На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.

Об авторах

Дэн Берг Джонсон

Дэниел Деоган

Дэниел Савано

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

Об иллюстрации на обложке

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

На обложке изображена Sultana, or Kaddin, что в переводе с турецкого означает «жена». Эта иллюстрация позаимствована из коллекции костюмов Османской империи, опубликованной 1 января 1802 года Уильямом Миллером с Бонд-стрит, Лондон. Заглавная страница издания утеряна, и найти ее не удается до сих пор. В оглавлении книги рисунки подписаны как на английском, так и на французском, возле каждого из них указаны имена двух художников, которые над ним работали. Они, несомненно, были бы удивлены, обнаружив свою работу на обложке книги по программированию… 200 лет спустя.

Коллекция была куплена редактором издательства Manning на антикварном блошином рынке в «Гараже» на 26-й Западной улице на Манхэттене. Продавец был американцем, проживавшим в Анкаре, столице Турции, и покупка произошла, когда он уже собирал товары в конце рабочего дня. У редактора Manning не было при себе достаточной суммы наличными, а на предложение заплатить кредитной картой или чеком он получил вежливый отказ. Продавец возвращался в Анкару тем же вечером, и ситуация казалась безнадежной. Как же все получилось? Сделку удалось утрясти с помощью старого доброго устного соглашения, скрепленного рукопожатием. Продавец просто предложил оплатить покупку с помощью банковского перевода, и вдобавок к коллекции иллюстраций редактор получил банковские реквизиты. Мы, конечно же, на следующий день перевели нужную сумму и по сей день остаемся благодарны и потрясены оказанным нам доверием. Это в духе давно прошедших времен.

Рисунки из Османской коллекции, как и другие иллюстрации, размещенные на наших обложках, показывают, насколько богатой и разнообразной была традиционная одежда 200 лет назад. Они напоминают об атмосфере изоляции и удаленности тех времен — и любого другого исторического периода, за исключением гиперпо­движного настоящего. Мода с тех пор поменялась, а региональное многообразие испарилось. Сейчас нередко сложно различить жителей разных континентов. Если смотреть на это с оптимизмом, можно сказать, что взамен культурного и визуального многообразия мы получили более разнообразную личную жизнь. Или, например, что культура и техника стали более пестрыми и интересными.

Мы в издательстве Manning превозносим изобретательность и инициативу компьютерного бизнеса. И чтобы это подчеркнуть, размещаем на обложках наших книг рисунки из этой коллекции, которые напоминают о богатом многообразии жизни, царившей 200 лет назад в разных концах света.

Часть I. Введение

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

Глава 2, заключительная в этой части, позволит вам сделать передышку — читать ее будет проще. Мы познакомимся с некоторыми темами, о которых пойдет речь в следующей части, и сделаем это на примере клиента, с которым работали. Итак, рассмотрим для начала, каким образом сочетаются безопасность и разработка, и обсудим базовые идеи, стоящие за проектированием безопасного ПО.

1. Роль проектирования в безопасности

В этой главе

• Безопасность как неотъемлемое свойство системы.

• Проектирование и его роль в обеспечении безопасности.

• Повышение безопасности за счет хорошей архитектуры.

• Защита от атаки Billion Laughs.

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

Вы понимаете, что вам всегда нужно заботиться обезопасности, но это мешает сосредоточиться на других задачах. Кроме того, вы все равно тратите бо'льшую часть времени на код, который не вызывается непосредственно через Интернет, поэтому библиотеки для веб-безопасности, которые вы планировали задействовать, не совсем подходят. Вместе с тем задачи, относящиеся к безопасности, получают все меньший приоритет, в отличие от бизнес-функций. В конце концов, время поджимает, и, если вам не удастся реализовать те возможности, которые нужны пользователям, безопасность системы не будет иметь никакого значения. Прибыль приносят бизнес-функции, и ни один пользователь не поблагодарит вас за добавление CSRF-токенов1 в форму входа. К тому же менее приоритетные задачи всегда можно отложить на потом.

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

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

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

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

• Почему задачи, связанные с безопасностью, всегда получают пониженный прио­ритет?

• Почему разработчиков в целом мало интересует безопасность?

• Специалисты не устают напоминать разработчикам о безопасности, так почему же не все ею занимаются?

• Почему руководители не понимают, что их команда нуждается в специалистах по безопасности не меньше, чем в тестировщиках?

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

ВАЖНО

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

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

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

1.1. Безопасность как неотъемлемое свойство системы

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

1.1.1. Ограбление банка Öst-Götha, 1854 год

На дворе 25 мая 1854 года, ночь незадолго до ограбления шведского банка O..st-Go..tha. Капрал и бывший фермер по имени Нильс Стрид и его компаньон, кузнец Ларс Экстрём, молча идут в направлении банка. Входная дверь банковского отделения заперта, но ключ висит снаружи на гвозде — просто нужно знать, где искать. Руководство банка также потратилось на высококачественные замки для хранилища, которые почти невозможно взломать. Но кузнецу не составляет труда вырвать петли и открыть дверь хранилища с другой стороны. Этим двум преступникам удалось унести все, что хранилось в банке, — 900 000 риксдалеров (официальная шведская валюта того времени)3.

На протяжении многих лет это было одно из крупнейших ограблений за всю историю. Аналогичную сумму удалось похитить только в 1963 году, во время ограб­ления поезда на железнодорожном мосту Брайдгоу-Бридж в Букингемшире, Англия. В Швеции грабители оставили после себя одну купюру номиналом в три риксдалера4 и записку с нелепым рифмованным стихом:

Vi länsat haver Öst-Götha Bank och mången rik knös torde blivit pank. Vi lämna dock en tredaler kvar ty hundar pissar på den som inget har.

(Мы ограбили банк Öst-Götha, и многие толстосумы разорятся. Однако мы оставляем после себя три далера, потому что на тех, у кого за душой ничего нет, мочатся псы.)

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

Эта история является ярким примером организации безопасности в виде набора возможностей — замков и петель. Замки высокого качества давали ощущение защищенности, но сами по себе не обеспечивали никакой безопасности. Хорошего замка недостаточно, если ключ висит на гвозде или если дверь в хранилище закреплена на слабых петлях. Вместо того чтобы рассматривать безопасность как набор отдельных элементов, лучше относиться к ней как к неотъемлемому свойству системы.

Если бы руководство считало безопасность неотъемлемым свойством своего учреждения, оно бы задалось вопросом: «Как не дать людям унести деньги из банка?» И ответом был бы не просто замок — в число мер безопасности входили бы хранение ключа в другом месте или проверка наличия других способов взлома двери в хранилище. Владельцы банка могли бы придумать что-то новое, например сигнализацию. Они могли бы изобрести механизмы предотвращения ограблений, которые появились уже в следующем веке, но не стали бы полагаться на один лишь дверной замок.

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

1.1.2. Элементы безопасности и безопасность в целом

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

Во многих методологиях основное внимание уделяется тому, что должна делать система, то есть функциональной стороне. Rational Unified Process (RUP) до сих пор во многом влияет на разработку ПО и концентрируется на функциональности в виде сценариев использования. Другие соображения, такие как время ответа или необходимая емкость, попадают в категорию второстепенных — дополнительные спецификации. В сообществе agile доминирующим форматом для описания того, что нужно будет сделать в ходе следующего спринта (или его аналога), является пользовательская история примерно такого вида: «Я, пользователь такой-то, хочу такую-то возможность, чтобы получить такую-то выгоду». Учитывая такой акцент на возможностях (на том, что система делает), неудивительно, что подобным образом зачастую описывается и безопасность: нам нужна страница входа, нам требуется модуль для обнаружения мошенничества, у нас должен вестись журнал.

Специалисты по безопасности Джон Уиландер и Дженс Густавссон провели исследование того, как люди описывают и определяют безопасность. Они отобрали крупные инициативы в сфере программного обеспечения, финансируемые из бюджета. Обнаружилось, что в 78 % случаев безопасность напрямую относилась к возможностям5.

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

Представьте себе сайт для хранения фотографий с механизмом аутентификации. Если попытаться втиснуть этот механизм в формат функциональной пользовательской истории, получится нечто вроде следующего: «Мне, как пользователю, нужна страница входа, которая позволит мне получить доступ к своим загруженным фотографиям».

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

Представьте, что вы вместе со своей командой создаете страницу входа. После аутентификации пользователь перенаправляется к своим фотографиям, в числе которых есть очень-неловкая-поза.jpg. Пользователь может щелкнуть на ссылке, чтобы загрузить изображение. Усложним ситуацию и представим, что у другого пользователя есть прямая ссылка на данную фотографию (рис. 1.1). Как вам это нравится? Вы реализовали задуманное, так как у вас есть страница входа с описанной функциональностью, но упустили из виду что-то важное, не так ли?

Рис. 1.1. Страницы входа как таковой недостаточно

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

Теперь свое желание можно перефразировать так: «Мне, как пользователю, нужно, чтобы доступ к моим изображениям можно было получить через страницу входа и чтобы эти изображения оставались конфиденциальными». Такая формулировка отражает ту озабоченность, которую заинтересованные лица изначально выражали относительно страницы входа.

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

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

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

1.1.3. Категории требований к безопасности: CIA-T

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

В классической информационной безопасности обычно выделяют три основных требования: конфиденциальность, целостность и доступность (confidentiality, integrity, availability — CIA).

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

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

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

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

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

Позже к этим трем факторам присоединился еще один: отслеживаемость (traceability, T) — это необходимость знать, кто и когда получал доступ к данным или изменял их. После некоторых скандалов в финансовом секторе и здравоохранении это стало важным вопросом. Такого рода аудит является существенной частью постановления Европейского союза, которое называется «Общий регламент защиты персональных данных» (General Data Protection Regulation, GDPR) и которое вступило в силу в 2018 году. Например, согласно GDPR обращения к личным данным должны отслеживаться и сохраняться в постоянном журнале аудита. Мы будем использовать термины «конфиденциальность», «целостность», «доступность» и «отслеживаемость» на страницах этой книги для уточнения того, какого рода безопасность на кону.

Забота о вопросах безопасности, а не об отдельных ее элементах существенно повышает качество системы, но при этом ставит разработчиков в непростое положение: как обеспечить безопасность в программном обеспечении, которое вы пишете? Гарантировать отсутствие каких-либо ошибок непросто. Для этого разработчики должны непрерывно заботиться о безопасности. Но есть и другой путь — внедрить безопасность в ваш рабочий процесс и процесс проектирования.

1.2. Что такое проектирование

Написание программного обеспечения — это далеко не тривиальная задача. Разработчик должен иметь навыки в широком спектре направлений. От вас ждут знаний в разных сферах, начиная с языков программирования и алгоритмов и заканчивая архитектурой систем и методологиями agile. Эти дисциплины пронизывают разные области знаний и могут заметно отличаться друг от друга, но при их обсуждении постоянно упоминается один и тот же термин — «проектирование». Какое же значение мы вкладываем в это слово?

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

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

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

По мере развития своей кодовой базы вы разбиваете код на пакеты или модули, чтобы сделать его более понятным и простым в использовании и получить при этом такие желанные свойства, как слаженность и слабое связывание. Вы можете применять такие методики и концепции, как интерфейсы, принцип инверсии зависимостей8 и неизменяемость, одновременно пытаясь не нарушить принцип подстановки Лисков9. Можете также попробовать вынести и изолировать часть функциональности, чтобы сделать ее более наглядной или чтобы ее было легче тестировать. Таким образом, вы пишете код и выполняете рефакторинг — перепроектирование, приводящее к улучшению вашего приложения.

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

Все виды деятельности, описанные ранее, относятся к написанию кода. Мы сказали, что все они — часть процесса проектирования, но если немного подумать, какие из них связаны с проектированием, а какие — нет?

• Являются ли планирование API и анализ архитектуры системы типичными примерами проектирования?

• Можно ли моделирование предметной области считать проектированием?

• Относится ли к проектированию выбор между тем, использовать или нет ключевое слово final в объявлении поля объекта?

Если спросить у десяти человек, какие виды деятельности, относящейся к разработке ПО, считаются проектированием, вы, скорее всего, получите десять разных ответов. В качестве явных примеров многие, наверное, назовут моделирование предметной области, планирование API, применение шаблонов проектирования и формирование архитектуры системы — отчасти потому, что это более традиционный взгляд на то, что такое проектирование. Лишь немногие скажут, что к процессу проектирования ПО относятся серьезные размышления о том, как написать выражение if или цикл for.

Так что же считать частью процесса проектирования? Ответ прост: все, что имеет отношение к разработке. Систему или программный компонент как результат проектирования можно назвать стабильным (то есть функционирующим, и это не означает, что он прекращает развиваться) только после его реализации и развертывания в реальных условиях. Иными словами, модели предметной области, модули, API и шаблоны проектирования так же важны в проектировании, как и объявления полей и методов, выражения if и хеш-таблицы. Все это влияет на стабильность итогового продукта.

Все эти аспекты разработки имеют кое-что общее: они требуют сознательного принятия решений. Любой вид деятельности, связанный с сознательным принятием решений, является частью процесса проектирования ПО. Это, в свою очередь, означает, что проектирование играет роль руководящего принципа, по которому создается система, и применять его можно на всех уровнях, от кода до архитектуры.

ОБРАТИТЕ ВНИМАНИЕ

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

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

1.3. Традиционный подход к безопасности ПО и его недостатки

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

Рис. 1.2. Традиционно к безопасности ПО подходят как к конкретным видам деятельности и концепциям

Разработчики должны быть знакомы с межсайтовым скриптингом (cross-site scripting, XSS), иметь представление об уязвимостях в низкоуровневых протоколах и знать наизусть список рисков безопасности OWASP10. Тестировщики должны владеть базовыми методами проверки на проникновение, а специалисты в предметной области — уметь обсуждать соответствующие проблемы и принимать решения относительно безопасности программного обеспечения.

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

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

Листинг 1.1. Простой класс User

Если взглянуть на это представление пользователя, можно заметить потенциальные проблемы с безопасностью. Например, в качестве имени пользователя принимается любое строковое значение, что делает возможными атаки XSS. XSS-атака возникает, когда злоумышленник использует веб-приложение для отправки вредоносного кода другому пользователю. Этот код, к примеру, может иметь вид клиентского скрипта на языке JavaScript. Если во время регистрации в сервисе злоумышленник введет в качестве имени пользователя что-то вроде <script>alert(42);</script>, позже, когда это имя отобразится на какой-то веб-странице приложения, браузер может отобразить окно с числом 4211.

Чтобы устранить эту уязвимость с помощью традиционного подхода, можно добавить явную проверку корректности входных данных, ориентированную на безопасность. Проверку данных, к примеру, можно было бы реализовать в виде фильт­ров, которые анализируют данные всех форм в веб-приложении и следят за тем, чтобы они не содержали никакого вредоносного XSS-кода. Но то же самое можно было бы делать прямо в доменном классе. Если вы предпочитаете проверять ввод в классе User, посмотрите в листинге 1.2, как это может выглядеть.

Листинг 1.2. Класс User с проверкой корректности ввода

В этом листинге видно, как мы подключаем гипотетическую библиотеку безопасности, которая позволяет проверять строки на предмет возможных XSS-атак. Мы также решили убедиться в том, что ни один из параметров конструктора не равен null, чтобы сделать проверку еще лучше. Такой способ обеспечения безопасности ПО широко распространен, однако ему свойственны несколько проблем, включая следующие.

• Разработчику приходится напрямую заниматься уязвимостями и в то же время сосредотачиваться на решении бизнес-задач.

• Каждый разработчик должен быть специалистом по безопасности.

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

Разберем каждый из этих пунктов и посмотрим, что с ними не так.

1.3.1. Безопасность требует к себе отдельного внимания

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

1.3.2. Все должны быть специалистами по безопасности

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

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

1.3.3. Нужно знать обо всех уязвимостях, даже о неизвестных в данный момент

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

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

1.4. Обеспечение безопасности за счет проектирования

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

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

Рис. 1.3. Делая акцент на проектировании, а не на безопасности, вы можете избежать проблем, присущих традиционному подходу

1.4.1. Делаем пользователя изначально защищенным

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

Обсудив этот вопрос, вы приходите к выводу о том, что имя пользователя должно содержать только символы [A-Za-z0-9_-] и иметь длину от 4 до 40 символов. Это продиктовано тем, что считается нормальным именем пользователя в создаваемом приложении. Вы исключаете символы < и > не потому, что они могут быть частью XSS-атаки в случае, если имя пользователя выводится в браузере. Скорее, отвечаете на вопрос: «Как должно выглядеть имя пользователя в этом контексте?» В данном примере вы решили, что символы < и > не могут быть частью корректного имени, поэтому исключили их.

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

Рис. 1.4. Обсуждение концепций со специалистами в предметной области

Листинг 1.3. Класс User с ограничениями предметной области

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

Понимая это, вы можете вынести логику в отдельный класс Username, инкапсулирующий все ваши знания об имени пользователя. Новый класс обеспечивает соблюдение всех правил предметной области на этапе создания. Этот новый объект называется примитивом предметной области (или доменным примитивом, подробнее о нем — в главе 5). В листинге 1.4 показано, как будет выглядеть класс User после вынесения кода в новый класс Username.

Листинг 1.4. Класс User с объектом-значением предметной области

Сосредоточившись на проектировании, вы смогли выяснить дополнительные подробности о пользователе и его имени. Это позволило создать более точную модель предметной области. Вы также сумели заметить, что концепции текущей модели стали настолько важными, что их следовало вынести в отдельные объекты. В итоге вы стали глубже понимать свою предметную область и в то же время защитились от уязвимости XSS, которую мы обсудили ранее: больше нельзя ввести <script>alert(42);</script>, так как это некорректное имя пользователя. И вы еще даже не начали думать о безопасности! Если уделить этому вопросу какое-то внимание, ограничения, касающиеся имени пользователя, скорее всего, можно будет сделать еще жестче, но во главе угла будет оставаться проектирование.

Обратите внимание

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

Итак, мы познакомились с недостатками традиционного подхода и увидели, как применять проектирование для создания безопасного ПО. Некоторые концепции, затронутые в этом разделе, подробно рассмотрим в главе 3. Там будут представлены основные понятия предметно-ориентированного проектирования (Domain-Driven Design), имеющие отношение к данной книге. Затем в главах 4 и 5 опишем фундаментальные конструкции программирования, помогающие добиться безопасности. Теперь посмотрим на преимущества обеспечения безопасности через проектирование и объясним, почему этот метод, по нашему мнению, дает лучшие результаты по сравне­нию с традиционным подходом к безопасности программного обеспечения.

1.4.2. Преимущества подхода, основанного на проектировании

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

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

• Проектирование ПО — главное, что интересует разработчиков, оно лежит в основе их умений. Это позволяет легко внедрить концепции безопасности через проектирование.

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

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

• Если сосредоточиться на предметной области, можно автоматически устранить множество проблем с безопасностью.

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

Проектирование — естественный аспект разработки ПО

Разработчиков программного обеспечения с первых дней учат тому, что хорошее проектирование крайне важно. Создание хорошо спроектированного приложения, которое как следует справляется с поставленными перед ним задачами, — повод для гордости. Это делает проектирование естественным аспектом создания ПО.

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

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

Вопросы бизнеса и безопасности получают одинаковый приоритет

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

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

• И разработчики, и бизнес-специалисты плохо понимают безопасность.

• По причинам, рассмотренным ранее, разработчики зачастую считают, что безопасность — это не их дело.

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

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

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

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

Специалисты, не имеющие отношения к безопасности, пишут безопасный код без дополнительных усилий

Еще одно интересное преимущество подхода к безопасности, основанного на проектировании, таково: любые специалисты могут писать безопасный код без дополнительных усилий. И дело вовсе не в том, что они анализируют векторы атак и размышляют над тем, как вредоносные данные могут повлиять на систему, скорее, небезопасные концепции просто не попадают в архитектуру. Чтобы это проиллюстрировать, рассмотрим класс Username из листинга 1.4, инварианты которого гарантируют, что приниматься будут только корректные имена пользователей. Оправданно ли использование этого сложного типа вместо обычной строки?

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

Упор на предметную область позволяет автоматически избежать дыр в безопасности

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

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

1.4.3. Сочетание разных подходов

Как упоминалось ранее, если к акценту на проектирование добавить более традиционную и целенаправленную заботу о безопасности, код станет еще более защищенным. Это важное замечание, так как повышенное внимание к проектированию хоть и обеспечивает высокую степень защищенности, но никогда не охватывает все требования к безопасности системы (и это не цель данного подхода). При создании ПО всегда нужно провести проверку на проникновение и активный анализ конкретных векторов атаки и уязвимостей12. Даже если сосредоточенность на предметной области сделала пример с Username безопасным, вы все равно должны выполнить надлежащее кодирование вывода при отображении его на веб-странице. Уделяя основное внимание проектированию и в то же время применяя разносторонний подход к безопасности, вы можете создавать по-настоящему защищенные продукты.

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

1.5. Строки, XML и атака billion laughs