Apple, боль и сертификаты

    Знакомьтесь, Боб — матёрый ios разработчик, Алиса — не менее матёрая тестировщица. Дело было вечером дело было в пятницу. Боб дофиксил багу, вроде бы протестил на своих девайсах. Затем Боб запускает уже отточенные до автоматизма команды:

    git checkout develop
    git merge bug_fix_#999
    git checkout master && git merge develop --no-ff ....
    git push ....
    


    На пуш на сервере срабатывает jenkins/teamcity/travis, который запускает билд. В это же самое время наш Боб пишет Алисе, что скоро пойдет домой, и хочет, чтобы аппа ушла сегодня в app store на апрув, дабы выиграть лишние пару дней, так как на носу выходные, если, конечно, приложение пройдет ручное тестирование Алисы.

    Приложение Боба довольно обычное: пару сотен компилируемых класс файлов, еще с десяток cocoapods зависимостей ну и кучка сторибордов — Боб ценит своё время и время коллег поэтому не пишет UI в коде. Боб знает, что его приложение с чистого старта на сервере собирается за 4 минуты для develop версии, которое идет на тест Алисе, и столько же или чуть больше для production версии. Боб также знает, что ему нужно около 10 минут, чтобы дождаться окончания полной сборки и затем сообщить Алисе, что она может приступать к тестированию. Боб человек ответственный, поэтому по истечении 10 минут после пуша проверяет статус билда, так как знает, что сервер — это отдельный параллельный мир со своими правилами, законами и странностями.

    Пятница, вечер, Боба отделяет от долгожданных выходных только 10 минут, после которых передаст эстафету Алисе. Боб вбивает в сафари bobcompany.ci/dashboard, где видит красную лампочку напротив своего приложения, глаза Боба потускнели, разочарованию не было предела. Боб жмет на show more, где его встречает ошибка:

    Code Sign error: No codesigning identities found: No codesigning identities (i.e. certificate and private key pairs) that match the provisioning profile specified in your build settings (“com.company.bob”) were found.
    


    Тут нервы Боба совсем сдают:





    *Кратко об ошибке, она проявляется, когда мы пытаемся подписать приложение несуществующим сертификатом, под несуществующим понимается или он не установлен на машине, или он устарел и mobileprovision заведен на более свежую версию сертификата того же аккаунта для того же бандла.

    И самое обидное, что эта ошибка только для production версии билда, который запускается вторым после develop версии, причем, сначала xcode компилирует зависимости (cocoapods) и уже только после проверяет валидность подписи, то есть только когда собирает основное приложение. Поэтому ошибка проявляется примерно во второй половине процесса сборки, из-за чего первые 6-7 минут потрачены в пустую, от чего Боб расстроен еще больше.

    Наш разработчик Боб не первый раз сталкивается с этой проблемой, ведь он работает в большой компании, где несколько команд ios разработчиков, где нормальная практика для разработки использовать один Apple аккаунт на несколько человек. Да, Боб знает про Xcode плагин https://github.com/neonichu/FixCode, но ведь не заставлять же его насильно всем ставить, разработчики нежные создания, не все любят, когда их заставляют что-то делать против их воли, Боб сам такой.

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

    Ну окей. Первым делом Боб проверяет какие сертификаты, вообще, есть на машине:

    security find-identity -v login.keychain
    


    Что выдает

      1) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike ... (KSDA3C3QF2)"
      2) 0279CB81AEAD8CE015282DD1FA76CE520A815C4D "iPhone Developer: Bob .. (4WT74HLM2M)"
      3) 79A2544B1A63C3F9D3DA3FFAB199FEAADB7EC306 "iPhone Developer: Alica ... (VJ53F2J4EK)"
      ....
         24 valid identities found
    


    Так, как минимум, в keychain, который используется по дэфолту на сервере, есть 24 сертификата. Боб знает, что каждый mobileprovision файл создается на какой-то один конкретный сертификат. Нужно выяснить на какой сертификат создан mobileprovision файл для которого упал билд и понять, что же произошло с сертификатом. Нужно, вообще, понять как mobileprovision файл связан с сертификатом. Боб знает, что develop версия приложения собралась, поэтому на сервере точно есть этот сертификат и сейчас нужно понять как он соотносится с mobileprovision файлом. Для этого Бобу нужно найти mobileprovision файл и данные о сертификате, чтобы начать искать какие-то соответствия между ними.

    Ищем mobileprovision файл по его бандлу (если ваш бандл wildcard, то можно искать по любым другим признакам):

    cd ~/Library/MobileDevice/Provisioning Profiles
    find . -name "*.mobileprovision" -type f -exec grep -H -n -a {} -e "com\.company\.bob" \;
    


    Отлично, мы нашли наш файл:

    ./f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision:30:		<string>4HUHB9J47M.com.company.bob</string>
    


    В файле есть префикс 4HUHB9J47M у бандла. Из ранее полученного списка сертификатов ничто с этим значением не совпадает. Поэтому Бобу приходится идти в developer.apple.com и искать на какой же аккаунт создан этот mobileprovision файл. Методом тыка он выясняет, что это сертификат Майка:

    2) 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C "iPhone Developer: Mike .. (KSDA3C3QF2)"
    


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

    Вытаскиваем данные в .pem файл:

    security find-certificate -p -c "iPhone Developer: Mike .. (KSDA3C3QF2)" > cert.pem
    


    В файле получаем следующее:

    -----BEGIN CERTIFICATE-----
    MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
    BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
    ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
    aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
    ...
    ....
    .....
    lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
    YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
    z34=
    -----END CERTIFICATE-----
    


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

    openssl x509 -noout -fingerprint -in cert.pem
    


    Выдает нам:

    SHA1 Fingerprint=40:94:8A:3C:A3:52:7F:58:0B:9E:CB:21:31:DE:6B:19:38:FB:3D:7C
    


    Если убрать двоеточия, то получим 40948A3CA3527F580B9ECB2131DE6B1938FB3D7C, это как раз sha1 сертификата Майка, который мы можем увидеть в UI в Keychain:



    Мы также можем получить срок действия сертификата, если нужно написать валидатор истёкших сертификатов:

    openssl x509 -noout -startdate -in cert.pem // Feb 27 07:13:41 2016 GMT
    openssl x509 -noout -enddate -in cert.pem // Feb 26 07:13:41 2017 GMT
    


    Это же мы видим и в keychain:



    В общем с сертификатом всё ясно. «Как же его привязать к нашему mobileprovision файлу?» — думает любопытный Боб. Давайте сначала посмотрим на mobileprovision файл поближе:



    security cms -D -i f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision 
    


    Вывод нам дает интересную информацию для поля data, а именно:

    <data>
    MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
    BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
    ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1
    ...
    ...
    YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
    z34=
    </data>
    


    Где-то это Боб уже видел, похоже на содержание ранее полученного .pem файла:

    -----BEGIN CERTIFICATE-----
    MIIFnjCCBIagAwIBAgIIN8GwnYhLQ/kwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV
    BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
    ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
    aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
    ...
    ....
    .....
    lD+ocFo6+mab/Ph6mTJOZkZu+hnqhzbTD9Q9dXKWkeXAwTqaESNfnhnuOdfCX3vu
    YAz0Hb46G9fkLa5lHjVydbtms685C+uz9Ss4GNRfji1cz5KyblAQAAsqQBUiCwnb
    z34=
    -----END CERTIFICATE-----
    




    Тут Боб понял, вот она зацепка mobileprovision файла на сертификат.

    Итого, нам нужно взять наш mobileprovision файл, вытащить из него значение для поля data, затем пробежать по всем валидным сертификатам и сравнить с данными из их .pem представления. Боб тут же накидал небольшой скрипт, который передал коллегам из бэкенда, чтобы они его запускали перед каждой сборкой ios проектов. Скрипт запускается достаточно просто:

    ruby cert_checker.rb f98a06f3-21c2-4de0-975f-5df74197c731.mobileprovision
    


    ruby скрипт
    require 'active_support/core_ext/hash'
    
    return if ARGV.empty?
    
    xmlString = `security cms -D -i #{ARGV.first}`
    data = Hash.from_xml(xmlString)
    
    # получили значение data из mobileprovision
    provision_cert_data = data['plist']['dict']['array'].map { |e| e['data'] }.compact.first
    
    # пробегаем по всем сертификатам и собираем их данные в .pem формате
    certs = `security find-identity -v login.keychain | grep -o "\\".*\\""`.split("\n").map { |e| e[1..-2] }
    pems = certs.map { |e| `security find-certificate -p -c "#{e}"`.split("\n")[1..-2].join('') }
    dict = Hash[pems.zip(certs)] 
    
    # сравниваем mobileprovision data с .pem данными сертификатов, если совпадет, значит нашли сертификат для mobileprovision, 
    # если не нашли, значит на машине не установлен сертификат
    
    if dict.keys.keep_if { |e| e == provision_cert_data }.empty? 
      puts "Have no certificate for file #{ARGV.first}"
    else
      puts "Your mobileprovision issued for #{dict[provision_cert_data]}"
    end
    



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

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

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 27
    • +22
      … и никаких релизов в пятницу!
      • 0
        Именно, кто ж релизит\чекинит вечером пятницы.
        • +2
          стандартная ios-практика. В пятницу «релизим» (заливаем билд на апрув в аппстор), в понедельник продолжаем тестировать и убеждаемся, что все ок (если не ок, то перезаливаем), в среду берут на ревью, в четверг апрувят и выпускаем юзерам.

          Так было раньше. Последний месяц ревью занимает день-два: статистика appreviewtimes
          • 0
            странная практика, в идеале тогда релизить нужно в среду, 2 дня на тест и перезаливку без потери 2 дополнительных дней (выходные)
            сами релизим в пятницу :) вот только не пытаемся это логически обосновать — логики нет

            кстати, в прошедшую пятницу релиз был на ревью 9 часов (девять, ДЕВЯТЬ часов. неужели мы наконец-то приехали туда, куда давно стремились)
            • 0
              Правило одно — есть возможность — надо заливать как можно раньше, несмотря на день недели.
              Как правило, перезаливка все же редкое явление, поэтому лучше залить в пятницу, чем не залить.
              Просто на финальное тестирование уходит много времени (мы очень беспокоимся за качество).
      • –22
        «и хочет, чтобы аппа ушла сегодня в стор на апрув, дабы выйграть лишние пару дней»

        Для получения хотя бы РВП _не_ гражданину РФ надо сдать экзамен по русика изык очин трудни.
        ФМС не туда смотрит, IMHO.
        • 0
          Один аккаунт разрешено использовать для нескольких разработчиков? И в чем смысл этого?
          • +1
            Экономика должна быть экономной.
          • 0
            Боб жмет на show more, где его встречает ошибка


            Простите, но зачем на сборочном сервере собирать продакшн-релиз и подписывать его сертификатом Developer'а?
            Что мешает сделать сертификат для подписи продакшн-релизов, положить его только на сборочный сервер, изменить одну строчку в .xcconfig и забыть про эту ошибку хотя бы на год?
            • 0
              Допустим в конторе несколько десятков аккаунтов. Разработчик всегда разрабатывает под своим одним аккаунтом. А уже под которым аккаунтом выложить приложение в стор решает бизнес, который заходит в админку приложения и из селекта выбирает на кого собрать и выложить данное приложение. Develop ipa собирается вместе с production, чтобы быть уверенным, что они собраны из одной кодовой базы.
              • 0
                Т.е. проблема в подписи develop ipa, собираемым на сервере?
                • 0
                  Не) Проблема в том, что куча сертификатов, кто-то их отзывает случайно (revoke), у некоторых истекает срок действия и забывают обновить. Это всё не так просто отследить, как я уже говорил выше, в разработке используются одни аккаунты, а в app store приложения уходят под другими. В данном примере у Боба develop ipa собралась, а production нет, потому что второй сертификат был кем-то отозван (revoked), а новый забыли поставить на машину, хотя mobileprovision был создан на новый сертификат.
                  • 0
                    Я и предлагаю решение — сертификат для продавлена всего один, подписывать релизные сборки на сборочном сервере надо им, а не каким-то пашей или машей, так больше порядка, имхо.
            • 0
              причем, сначала xcode компилирует зависимости (cocoapods) и уже только после проверяет валидность подписи, то есть только когда собирает основное приложение. Поэтому ошибка проявляется примерно во второй половине процесса сборки, из-за чего первые 6-7 минут потрачены в пустую, от чего Боб расстроен еще больше.

              Боб был бы меньше расстроен, если бы предсобранные зависимости статически линковались (динамические — подкладывались) вместо их полной сборки. Собранные ранее бинарники зависимостей — артефакты сборки проектов зависимостей, собираются только при изменении в репозитории.
              • 0
                Предлагаете все зависимости собирать в отдельные бинарники и затем их подключать руками к проекту вместе с кучей хедер файлов? Возможно, это кому-то удобно, я даже знаю ребят кто так делает, но мне удобнее когда всё динамически само разруливается. Дело вкуса)
                • 0
                  Предлагаете все зависимости собирать в отдельные бинарники и затем их подключать руками к проекту вместе с кучей хедер файлов?

                  Предлагаю собирать зависимости отдельно от проекта и хранить артефакты сборки для использования во время сборки проекта. Всё это автоматически без всяких рук умеет собирать TeamCity.
                  вместе с кучей хедер файлов?

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

                  Дело не только в удобстве, этот подход точно уменьшает время сборки и ваш Боб ждал бы не 6-7 минут — когда же соберутся сначала все зависимости, а меньше минуты, пока они скачаются и распакуются.
                  Но, конечно же, можно кататься на стульях, размахивая палками, пока компилируется — дело вкуса )
                  • 0
                    Предлагаете, например, ReactiveCocoa или AFNetworking собирать руками (или через carthage) в бинарник и его руками подключать в проект? А если я хочу исходники глянуть как там что работает? А такое бывает)
                    • 0
                      Ещё раз вам повторяю — собирать руками не надо, настройте сборку библиотек на сборочном агенте, помечайте тэгом коммит из которого собрались артефакты, подтягивайте артефакты из этой сборки в сборку самого проекта — сборочный агент умеет это делать автоматически без ваших рук.
                      Все ваши требования удовлетворены, плюс — счастливый Боб, не ожидающий у корыта по 7 минут сборки зависимостей.
              • 0
                просит админов дать доступ до сервера, коннектится туда по ssh и начинает выяснять, в чем же, именно, проблема и как её можно решить.

                Боб, похоже, ответственный и образованный программист, раз ему админы легко дают доступ на сборочный сервер по ssh.
                Да и админы не успели убежать пораньше в пятницу-то — тоже ответственные товарищи, хорошо знающие Боба и доверяющие ему.
                • 0
                  Всё верно) Боб работает не первый год в этой организации и у него сложились доверительные отношения с админами. Я так и не понял тут вашего сарказма(
                  • 0
                    Тут не в сарказме дело, ваш сценарий крайне оптимистичен, особенно, для вечера пятницы )
                    Посыл был в том, что не каждый программист — Боб, не все конторы разрешают лазить каким-то разработчикам по сборочным серверам и что-то там настраивать.
                    • 0
                      С этим Бобу повезло) Машинка, на которой собираются ios проекты, это просто отдельная физическая локальная тачка с xcode, cocoapods, и сертификатами. Куда затем с отдельного CI сервера прокидываются команды для сборки проектов и потом просто обратно выкачиваются результаты этого процесса. На сам же CI таким личностям как Боб доступ не положен)
                • 0
                  А вообще, могу порекомендовать уже готовых руби-скриптов для автоматизации сборок.
                  Раз у вас есть доверительная близость с админами, а сам агент полностью ваш — Fastlane вам в помощь.
                  • 0
                    Боб знает про fastline) тула крутая конечно, но он огорчен, что sigh resign работает только для простых ipa, если в ipa есть widget или framework, или даже часы, то он с этим не работает, поэтому Бобу пришлось немного допилить под себя.
                  • 0
                    Боб ценит время своих коллег, а потому не пишет UI в коде. Пусть гаденыши общий storyboard мерджат! Уахаха!

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