Русский
Русский
English
Статистика
Реклама

Safe

SafetyNet Attestation описание и реализация проверки на PHP

11.02.2021 20:09:20 | Автор: admin

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

После многочасовых поисков и скрупулёзного изучения официальной документации Google решил поделиться полученным опытом. Потому что, кроме официальной документации, я нашел только отрывочные описания частных примеров реализации на разных ЯП. И ни намека на комплексное объяснение особенностей проверки по SafetyNet на сервере.

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

Дисклеймер: материал в явном виде содержит перевод официальной документации от Google с разъяснениями и описанием особенностей реализации, с которыми я столкнулся.

О технологии

Технология SafetyNet Attestation разработана Google как средство предоставления разработчикам мобильных приложений информации о надёжности приложения клиента при взаимодействии с сервером, который обслуживает мобильное приложение. Для этого в протоколе взаимодействия предусмотрен удостоверяющий сервис от Google, обеспечивающий верификацию, и представлены рекомендации по проверке ответа от удостоверяющего центра на стороне сервера.

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

Что позволяет проверить технология:

  1. Что именно вы являетесь автором приложения, которое сейчас взаимодействует с сервером.

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

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

В каких случаях механизм не применим или не имеет смысла:

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

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

  3. Если требуется детальное понимание статусов модификации системы, на которой работает мобильное приложение. В протокол заложен механизм однозначного определения модификации устройства. Он состоит из двух переменных: ctsProfileMatch и basicIntegrity. Об их назначении чуть ниже.

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

Схематично процесс проверки клиента можно представить в виде схемы:

Рассмотрим поэтапно процесс верификации устройств по протоколу:

  1. Инициация процесса проверки со стороны клиента.Отправка запроса от клиента на Backend на генерацию уникального идентификатора проверки (nonce) сессии. В процессе выполнения запроса на сервере генерируется ключ (nonce) сессии, сохраняется и передаётся на клиент для последующей проверки.

  2. Генерация JSW-токена на стороне удостоверяющего центра.Клиент, получив nonce, отправляет его на удостоверяющий центр вместе со служебной информацией. Затем в качестве ответа клиенту возвращается JWS, содержащий информацию о клиенте, время генерации токена, информацию о приложении (хеши сертификатов, которыми подписывается приложение в процессе публикации в Google Store), информацию о том, чем был подписан ответ (сигнатуру). О JWS, его структуре и прочих подробностях расскажу дальше в статье.

  3. Затем клиент передаёт JWS в неизменном виде на Backend для проверки. А на стороне сервера формируется ответ с информацией о результате прохождения аттестации.После получения статуса проверки JWS на стороне сервера обычно сохраняют факт проверки и, опираясь на него, влияют на функциональность положительным или негативным образом. Например, клиентам, не прошедшим аттестацию, можно отключить возможность писать комментарии, голосовать за рейтинг, совершать иные активности, которые могут повлиять на качество контента приложения или создавать угрозу и неудобство другим пользователям.

Описание процесса верификации на стороне сервера JWS от удостоверяющего центра

Документация Google в рамках тестирования на сервере предлагает организовать online-механизм верификации JWS, при котором с сервера приложения отправляется запрос с JWS на удостоверяющий сервис Google. А в ответе от сервиса Google содержится полный результат проверки JWS.

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

Далее расскажу обо всём алгоритме верификации JWS, в том числе о верификации самих сертификатов (проверке цепочки сертификатов).

Подробнее о JWS

JWS представляет собой три текстовых (base64 зашифрованных) выражения, разделенные точками (header.body.signature):

Например:

eyJhbGciOiJSUzI1NiIsICJ4NWMiOiBbInZlcnlzZWN1cmVwdWJsaWNzZXJ0Y2hhaW4xIiwgInZlcnlzZWN1cmVwdWJsaWNzZXJ0Y2hhaW4yIl19.ewogICJub25jZSI6ICJ2ZXJ5c2VjdXJlbm91bmNlIiwKICAidGltZXN0YW1wTXMiOiAxNTM5ODg4NjUzNTAzLAogICJhcGtQYWNrYWdlTmFtZSI6ICJ2ZXJ5Lmdvb2QuYXBwIiwKICAiYXBrRGlnZXN0U2hhMjU2IjogInh5eHl4eXh5eHl4eXh5eHl5eHl4eXg9IiwKICAiY3RzUHJvZmlsZU1hdGNoIjogdHJ1ZSwKICAiYXBrQ2VydGlmaWNhdGVEaWdlc3RTaGEyNTYiOiBbCiAgICAieHl4eXh5eHl4eXh5eHl4eXh5eD09PT09Lz0iCiAgXSwKICAiYmFzaWNJbnRlZ3JpdHkiOiB0cnVlCn0=.c2lnbmF0dXJl

В данном примере после расшифровки base64 получим:

Header :

json_decode(base64_decode(eyJhbGciOiJSUzI1NiIsICJ4NWMiOiBbInZlcnlzZWN1cmVwdWJsaWNzZXJ0Y2hhaW4xIiwgInZlcnlzZWN1cmVwdWJsaWNzZXJ0Y2hhaW4yIl19))={"alg":"RS256","x5c":["verysecurepublicsertchain1","verysecurepublicsertchain2"]}

Body:

json_decode(base64_decode(ewogICJub25jZSI6ICJ2ZXJ5c2VjdXJlbm91bmNlIiwKICAidGltZXN0YW1wTXMiOiAxNTM5ODg4NjUzNTAzLAogICJhcGtQYWNrYWdlTmFtZSI6ICJ2ZXJ5Lmdvb2QuYXBwIiwKICAiYXBrRGlnZXN0U2hhMjU2IjogInh5eHl4eXh5eHl4eXh5eHl5eHl4eXg9IiwKICAiY3RzUHJvZmlsZU1hdGNoIjogdHJ1ZSwKICAiYXBrQ2VydGlmaWNhdGVEaWdlc3RTaGEyNTYiOiBbCiAgICAieHl4eXh5eHl4eXh5eHl4eXh5eD09PT09Lz0iCiAgXSwKICAiYmFzaWNJbnRlZ3JpdHkiOiB0cnVlCn0=))={"nonce":"verysecurenounce","timestampMs":1539888653503,"apkPackageName":"very.good.app","apkDigestSha256":"xyxyxyxyxyxyxyxyyxyxyx=","ctsProfileMatch":true,"apkCertificateDigestSha256":["xyxyxyxyxyxyxyxyxyx=====/="],"basicIntegrity":true}

Signature

json_decode(base64_decode(c2lnbmF0dXJl))= signature

Остановимся на том, что именно содержится во всем JWS.

Header:

  • alg алгоритм, которым зашифрованы Header и Body JWS. Нужен для проверки сигнатуры.

  • x5c публичная часть сертификата (или цепочка сертификатов). Также нужен для проверки сигнатуры.

Body:

  • nonce произвольная строка полученная с сервера и сохранённая на нём же.

  • timestampMs время начала аттестации.

  • apkPackageName название приложения, которое запросило аттестацию.

  • apkDigestSha256 хеш подписи приложения, которое загружено в Google Play.

  • ctsProfileMatch флаг, показывающий прошло ли устройство пользователя верификацию в системе безопасности Google (основной и самый жёсткий критерий, по которому можно понять было ли устройство заручено и прошло ли оно сертификацию в Google).

  • apkCertificateDigestSha256 хеш сертификата (цепочки сертификатов), которыми подписано приложение в Google Play.

  • basicIntegrity более мягкий (по сравнению с ctsProfileMatch) критерий целостности установки.

Signature

Бинарная сигнатура, с помощью которой можно сделать заключение, что тело сообщения JWS было подписано с использованием сертификатов (цепочки сертификатов) указанных в Header, и с использованием известного нам приватного ключа. Ключевое позволяет понять, что в цепочке взаимодействия нет никого, кроме нас и удостоверяющего центра Google.

Проверка сертификатов

Перейдём к непосредственной проверки каждой части полученного JWS. Начнём с сертификатов и алгоритма шифрования:

1. Проверяем, что алгоритм, с помощью которого подписано тело, нами поддерживается:

[$checkMethod, $algorithm] = JWT::$supported_algs[$statement->getHeader()->getAlgorithm()];if ($checkMethod != 'openssl') {   throw new CheckSignatureException('Not supported algorithm function');}

2. Проверяем, что сертификат (цепочка сертификатов), содержащиеся в Header (поле x5c), удовлетворяют нас по содержимому (загружаются в качестве публичных ключей):

private function extractAlgorithm(array $headers): string{   if (empty($headers['alg'])) {       throw new EmptyAlgorithmField('Empty alg field in headers');   }   return $headers['alg'];}private function extractCertificateChain(array $headers): X509{   if (empty($headers['x5c'])) {       throw new MissingCertificates('Missing certificates');   }   $x509 = new X509();   if ($x509->loadX509(array_shift($headers['x5c'])) === false) {       throw new CertificateLoadError('Failed to load certificate');   }   while ($textCertificate = array_shift($headers['x5c'])) {       if ($x509->loadCA($textCertificate) === false) {           throw new CertificateCALoadError('Failed to load certificate');       }   }   if ($x509->loadCA(RootGoogleCertService::rootCertificate()) === false) {       throw new RootCertificateError('Failed to load Root-CA certificate');   }   return $x509;}

3. Валидируем сигнатуру сертификата (цепочки сертификатов):

private function guardCertificateChain(StatementHeader $header): bool{   if (!$header->getCertificateChain()->validateSignature()) {       throw new CertificateChainError('Certificate chain signature is not valid');   }   return true;}

4. Сверяем hostname подписавшего сервера с сервером аттестации Google (ISSUINGHOSTNAME = 'attest.android.com'):

private function guardAttestHostname(StatementHeader $header): bool{   $commonNames = $header->getCertificateChain()->getDNProp('CN');   $issuingHostname = $commonNames[0] ?? null;   if ($issuingHostname !== self::ISSUING_HOSTNAME) {       throw new CertificateHostnameError(           'Certificate isn\'t issued for the hostname ' . self::ISSUING_HOSTNAME       );   }   return true;}

Верификация тела JWS

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

1. Проверка nounce.

Тут все просто. Распаковали JWS, получили в Body nonce и сверили с тем, что у нас сохранено на сервере:

private function guardNonce(Nonce $nonce, StatementBody $statementBody): bool{   $statementNonce = $statementBody->getNonce();   if (!$statementNonce->isEqual($nonce)) {       throw new WrongNonce('Invalid nonce');   }   return true;}

2. Проверяем заручено ли устройство, с которого происходит запрос.

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

Есть два параметра, на основе которых можно принимать решение о надежности устройства: ctsProfileMatch и basicIntegrity. ctsProfileMatch более строгий критерий, он определяет сертифицировано ли устройство в Google Play и верифицировано ли устройство в сервисе проверки безопасности Google. basicIntegrity определяет, что устройство не было скомпрометировано.

private function guardDeviceIsNotRooted(StatementBody $statementBody): bool{   $ctsProfileMatch = $statementBody->getCtsProfileMatch();   $basicIntegrity = $statementBody->getBasicIntegrity();   if (empty($ctsProfileMatch) || !$ctsProfileMatch) {       throw new ProfileMatchFieldError('Device is rooted');   }   if (empty($basicIntegrity) || !$basicIntegrity) {       throw new BasicIntegrityFieldError('Device can be rooted');   }   return true;}

3. Проверяем время начала прохождения аттестации.

Тоже ничего сложного. Нужно проверить, что с момента ответа от сервера Google прошло немного времени. По сути, нет чётких критериев прохождения теста с реферальным значением нужно определиться самим.

private function guardTimestamp(StatementBody $statementBody): bool{   $timestampDiff = $this->config->getTimeStampDiffInterval();   $timestampMs = $statementBody->getTimestampMs();   if (abs(microtime(true) * 1000 - $timestampMs) > $timestampDiff) {       throw new TimestampFieldError('TimestampMS and the current time is more than ' . $timestampDiff . ' MS');   }   return true;}

4. Проверяем подпись приложения.

Здесь тоже два параметра: apkDigestSha256 и apkCertificateDigestSha256. Но apkDigestSha256 самой Google помечен как нерекомендуемый способ проверки. С марта 2018 года они начали добавлять мета-информацию в приложения из-за чего ваш хеш подписи приложения может не сходиться с тем, который будет приходить в JWS (подробнее здесь).

Поэтому единственным способом проверки остается проверка хеша подписи приложения apkCertificateDigestSha256. Фактически этот параметр нужно сравнить с теми sha1 ключа, которым подписываете apk при загрузке в Google Play.

private function guardApkCertificateDigestSha256(StatementBody $statementBody): bool{   $apkCertificateDigestSha256 = $this->config->getApkCertificateDigestSha256();   $testApkCertificateDigestSha256 = $statementBody->getApkCertificateDigestSha256();   if (empty($testApkCertificateDigestSha256)) {       throw new ApkDigestShaError('Empty apkCertificateDigestSha256 field');   }   $configSha256 = [];   foreach ($apkCertificateDigestSha256 as $sha256) {       $configSha256[] = base64_encode(hex2bin($sha256));   }   foreach ($testApkCertificateDigestSha256 as $digestSha) {       if (in_array($digestSha, $configSha256)) {           return true;       }   }   throw new ApkDigestShaError('apkCertificateDigestSha256 is not valid');}

5. Проверяем имя приложения, запросившего аттестацию.

Сверяем название приложения в JWS с известным названием нашего приложения.

private function guardApkPackageName(StatementBody $statementBody): bool{   $apkPackageName = $this->config->getApkPackageName();   $testApkPackageName = $statementBody->getApkPackageName();   if (empty($testApkPackageName)) {       throw new ApkNameError('Empty apkPackageName field');   }   if (!in_array($testApkPackageName, $apkPackageName)) {       throw new ApkNameError('apkPackageName ' . $testApkPackageName. ' not equal ' . join(", ", $apkPackageName));   }   return true;}

Верификация сигнатуры

Здесь нужно совершить одно действие, которое даст нам понимание того, что Header и Body ответа JWS подписаны сервером авторизации Google. Для этого в исходном виде склеиваем Header c Body (с разделителем в виде ".") и проверяем сигнатуру:

protected function guardSignature(Statement $statement): bool{   $jwsHeaders = $statement->getRawHeaders();   $jwsBody = $statement->getRawBody();   $signData = $jwsHeaders . '.' . $jwsBody;   $stringPublicKey = (string)$statement->getHeader()->getCertificateChain()->getPublicKey();   [$checkMethod, $algorithm] = JWT::$supported_algs[$statement->getHeader()->getAlgorithm()];   if ($checkMethod != 'openssl') {       throw new CheckSignatureException('Not supported algorithm function');   }   if (openssl_verify($signData, $statement->getSignature(), $stringPublicKey, $algorithm) < 1) {       throw new CheckSignatureException('Signature is invalid');   }   return true;}

Вместо заключения. Библиотека на PHP

Уже после решения задачи и отдельно от нашей кодовой базы, я разработал библиотеку на PHP, которая обеспечивает полный цикл верификации JWS.

Её можно скачать из Packagist и использовать в своих прое

Подробнее..

Пример реального проекта на F

26.01.2021 18:12:26 | Автор: admin

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

Диаграмма контейнеровДиаграмма контейнеров

SAFe

На выбор ингредиентов, определяющее влияние, оказал SAFe Stack. SAFe представляет собой шаблон dotnet CLI, в котором подобраны необходимые компоненты для гомогенной разработки SPA в связке с бэкэндом. Сайт проекта содержит много обучающих материалов и примеров.

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

Первая буква акронима - S - означает Saturn - идиоматический фреймворк над Giraffe, который, в свою очередь, функциональная обертка над Asp.net.

Вторая буква - A - означает Azure. Здесь мне было сразу не по-пути с SAFe, а тем, кто использует Ажур, как платформу, пригодится библиотека Farmer, которую пилят те же люди, что и SAFe.

Третья буква - F - означает Fable - транспайлер из F# в JavaScript - настоящая сдобная булочка в экосистеме фарша.

Бэкенд

Для создания API используется библиотека Fable.Remoting. Fable.Remoting скрывает абстракции веб-сервера. Типы, определяющие контракт, помещаются в общий для бэкенда и фронтенда файл (или сборку). Реализовываете API на сервере, все остальное (создание прокси, сериализацию, обработку ошибок, логирование) делает за вас библиотека. Помимо JSON, поддерживается передача бинарных данных.

Сейчас, имея такой удобный инструмент, как Fable.Remoting, я не вижу смысла тянуть в бэкенд колбасу Saturn - Giraffe - Asp.Net. Но, по историческим причинам, в проекте остался Giraffe.

Если нужно использовать спецификацию OpenAPI, можно посмотреть на GiraffeGenerator.

В качестве хранилища данных, в проекте используется NoSql база DynamoDB. За основу реализации слоя доступа к данным была взята библиотека DynamoDb.Ok. В ней используется идиоматический подход на основе монады Reader. В итоге, могу сказать, что этот подход мне не понравился. В будущем хотелось бы вообще не отвлекаться на код в слое доступа к данным. Есть идеи, возможно, об этом выйдет отдельная статья.

C реляционными базами из F# работать не приходилось. В чате сообщества замечал нарекания на ограниченную поддержку типов F# в Entity Framework и рекомендации использовать Dapper.

В прошлом году сообщество обогатилось серией годных статей по теме внедрения зависимостей: статья 1, статья 2, статья 3. Подход, базирующийся на Flexible Types, применяется и в данном проекте.

Для логирования используется библиотека Serilog, у которой есть расширение для Giraffe.

Для авторизации используется самописная реализация JWT.

Для связи с AWS частично используется дотнетовский AWSSDK, частично HTTP, так как SDK не покрывает весь функционал облака.

Фронтенд

Основа фронтендов на F# - Fable, Ваш код и большая часть стандартной библиотеки переводится в JS. Можно легко взаимодействовать с библиотеками JS. Существует множество обвязок (binding) для популярных библиотек, в т.ч. React и его компонент.

Для управления состоянием используется Elmish - реализация Elm-архитектуры. Рендеринг страницы производится с помощью Fable.React и Bulma.

Разработка фронта в этой экосистеме доставляет.

пример кода
let quizView (dispatch : Msg -> unit) (settings:Settings) (quiz:QuizRecord) l10n = [   br []   figure [ Class "image is-128x128"; Style [Display DisplayOptions.InlineBlock] ] [ img [ Src <| Infra.urlForMediaImgSafe settings.MediaHost quiz.ImgKey ] ]   br []   h3 [Class "title is-3"] [str quiz.Name]    div [Class "notification is-white"][       p [Class "subtitle is-5"][           match quiz.StartTime with           | Some dt -> str (dt.ToString("yyyy-MM-dd HH:mm"))           | None -> str "???"            if quiz.Status = Live then               str " "               span [Class "tag is-danger is-light"][str "live"]           br[]       ]        p [] (splitByLines quiz.Description)        if quiz.EventPage <> "" then           a[Href quiz.EventPage][str l10n.Details]    ]]

Отличное введение в экосистему - книга The Elmish Book.

Однако, фронтдендеры такие фронтендеры, связка Elmish + Fable.React + Boolma уже вышла из моды. В 2021 году, чтобы быть в тренде, вам нужно освоить Feliz + Fable.React.WebComponent + Material UI и рассмотреть альтернативу - Fable.Svetle. Моя бэкендер страдать.

Для взаимодействия с Aws, а именно с брокером сообщений в AppSync, используется библиотека Aws Amplify.

Тесты

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

Модульные тесты не писались. Не могу не отметить то чувство защищенности, которое дает система типов F#. Глупые ошибки обычно отлавливаются компилятором. За все время эксплуатации проекта, словил всего один мажорный баг на проде. В других проектах использовал FsUnit и expecto. Первый проще встраивается в инструментарий, второй гибче в синтаксисе, но, как по мне, большой разницы между ними нет.

Из прочих решений для тестирования, в моем списке на попробовать:

  • FsCheck - для тестирование на основе свойств

  • Canopy - фреймворк и DSL для тестирования UI

  • NBomber - для нагрузочных тестов

Сборка и развертывание

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

Для обновления инфраструктуры и развертывания используется AWS Cloud Development Kit. Поддержка F# не заявлена, но идет из коробки, вслед за C#.

Инструментарий

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

Плохая новость. Инструментарий развивается, но он уступает тому, что есть в C#. Проект разрабатывается в VSCode с расширением Ionide. Подвисания, перезагрузки, регулярная необходимость удалять временные файлы - все это по-прежнему присутствует. Есть подозрение, что, в более крупных проектах, это может сильно испортить жизнь разработчику. Альтернативой Ionide является Rider. И там и там недавно вышли обновления, будем надеяться, что ситуация улучшится.

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

Также, работая с F# необходимо менять привычки отладки. Меньше пошаговой отладки, больше вывода в консоль. Использовать FSI.

Итого

По состоянию на начало 2021, F# пригоден для прикладных проектов небольшого и среднего размера. Для меня, преимуществами этого языка являются:

  • экосистема фронтенд-разработки,

  • система типов,

  • компактный синтаксис.

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

исходный код проекта

Картинка-поощрение для тех, кто дочитал эту статью до конца.Картинка-поощрение для тех, кто дочитал эту статью до конца.
Подробнее..
Категории: Net , F , Fsharp , Fable , Elmish , Fake , Paket , Safe , Fable.remoting

Масштабируем продакт-менеджмент как управлять продуктом, который разрабатывают 50 команд

24.02.2021 20:16:16 | Автор: admin
Всем привет!

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

image

Интересующимся и желающим пообщаться на эту тему добро пожаловать под кат.

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

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

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

Знакомая картина?

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

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


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

Не похоже на историю выше, верно?

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

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

  • Пользователей кастдевил продакт? Теперь ваши владельцы продукта о них не вспоминают, они носятся от одного стейкхолдера к другому, пытаясь понять за какие требования хвататься в первую очередь;
  • Была ясная и прозрачная юнит-экономика? Теперь владельцы продукта не считают даже PnL, загружая всю команду на пол спринта сторями с сомнительной пользой;
  • Были job story, customer journey map и прочие персоны? Теперь в сторе пишут Как продакт оунер я хочу чтобы это было сделано, потому что это нужно департаменту Operation and Maintanence;
  • Было понимание ответственности за результат и сроки? Теперь каждая команда сама за себя, ваши фичи доставят на релиз-другой позже.

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

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

Списочком

Являются ли они серебряной пулей?

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

И могу сказать следующее:

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

Единственно ли верен подобный подход? Давайте посмотрим, что еще можно сделать.

image

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

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

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

Пользователи. В сторону стейкхолдеров. Только пользователи могут дать верное представление о ценности продукта. Владелец продукта должен ориентироваться на результаты интервью и анализ тикетов поддержки в своих гипотезах.

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

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

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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru