fastlane: Create temporary Keychain for iOS signing

This is a new approach to signing the iOS app by using a temporary
Keychain created only for that specific build and unlocked in advance.

By doing it this way we can avoid issues with `errSecInternalComponent`
appearing when there is no UI to open a Keychain password prompt when
running build in CI. I've described this problem in details in:
https://github.com/fastlane/fastlane/issues/15185

Thanks to `codesign:` partition ID being added to key partition list by
Fastlane `match` when importing a Keychain this approach now works:
https://github.com/fastlane/fastlane/pull/17456

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2020-10-27 13:18:10 +01:00
parent d8eb33e330
commit 3b780f4ff2
No known key found for this signature in database
GPG Key ID: 4EF064D0E6D63020
7 changed files with 117 additions and 91 deletions

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label 'linux' }

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label 'linux' }

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label 'macos-xcode-11.5' }

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label params.AGENT_LABEL }

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label 'macos' }

View File

@ -1,4 +1,4 @@
library 'status-react-jenkins@v1.2.5'
library 'status-react-jenkins@v1.2.6'
pipeline {
agent { label 'linux' }

View File

@ -11,18 +11,6 @@
# There are a few env variables defined in the .env file in
# this directory (fastlane/.env)
# unlocks keychain if KEYCHAIN_PASSWORD variable is present
# (to be used on CI machines)
def unlock_keychain_if_needed
return unless ENV['KEYCHAIN_PASSWORD']
unlock_keychain(
path: 'login.keychain',
password: ENV['KEYCHAIN_PASSWORD'],
set_default: true
)
end
def curl_upload(url, file, auth, conn_timeout = 5, timeout = 60, retries = 3)
rval = sh(
'curl',
@ -81,6 +69,38 @@ def upload_to_saucelabs(file)
end
end
# Helper which acts like a Python context manager
def with(ctx)
yield ctx.setup
ensure
ctx.teardown
end
# Creates temporary keychain for build duration
class Keychain
attr_accessor :name, :path, :pass
def initialize(name)
# We use epoch time to void clashes with CI builds
@name = "#{name}_#{Time.now.to_f}.keychain-db"
@path = "~/Library/Keychains/#{@name}"
@pass = rand().to_s
Fastlane::Actions::CreateKeychainAction.run(
name: @name,
password: @pass,
unlock: true,
# Lock it up after 25 minutes just in case we don't delete it.
timeout: 1500,
# To make Fastlane import WWDR certificate into it.
default_keychain: true,
)
end
# for use in with()
def setup; self end
def teardown; Fastlane::Actions::DeleteKeychainAction.run(name: @name) end
end
# builds an ios app with ad-hoc configuration and put it
# to "status-ios" output folder
# `readonly`:
@ -95,12 +115,14 @@ def build_ios_adhoc(readonly: false, pr_build: false)
scheme = pr_build ? 'StatusImPR' : 'StatusIm'
app_id = pr_build ? 'im.status.ethereum.pr' : 'im.status.ethereum'
with Keychain.new('adhoc') do |kc|
match(
type: 'adhoc',
force_for_new_devices: true,
readonly: readonly,
app_identifier: app_id,
keychain_name: 'login.keychain'
force_for_new_devices: true,
keychain_name: kc.name,
keychain_password: kc.pass
)
build_ios_app(
@ -120,6 +142,7 @@ def build_ios_adhoc(readonly: false, pr_build: false)
}
)
end
end
# builds an ios app with e2e configuration and put it
# to "status-ios" output folder
@ -128,11 +151,13 @@ def build_ios_e2e
showsdks_output = sh('xcodebuild', '-showsdks')
simulator_sdk = showsdks_output.scan(/iphonesimulator\d\d?\.\d\d?/).first
with Keychain.new('adhoc') do |kc|
match(
type: 'adhoc',
force_for_new_devices: true,
readonly: true,
keychain_name: 'login.keychain'
force_for_new_devices: true,
keychain_name: kc.name,
keychain_password: kc.pass
)
build_ios_app(
@ -156,6 +181,7 @@ def build_ios_e2e
# ...and we don't need an .ipa file for them, because we use .app directly
skip_package_ipa: true
)
end
zip(
path: 'status-ios/Build/Products/Release-iphonesimulator/StatusIm.app',
@ -181,7 +207,6 @@ platform :ios do
desc 'It creates an .ipa that can be used by a list of devices, registeded in the App Store Connect.'
desc 'This .ipa is ready to be distibuted through diawi.com'
lane :adhoc do
unlock_keychain_if_needed
build_ios_adhoc(readonly: true)
end
@ -189,31 +214,30 @@ platform :ios do
desc 'This lane is used for SauceLabs end-to-end testing.'
desc 'It creates an .app that can be used inside of a iPhone simulator.'
lane :e2e do
unlock_keychain_if_needed
build_ios_e2e
end
desc '`fastlane ios pr` - makes a new pr build'
desc 'This lane builds a new adhoc build and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)'
lane :pr do
unlock_keychain_if_needed
build_ios_adhoc(pr_build: true)
end
desc '`fastlane ios nightly` - makes a new nightly'
desc 'This lane builds a new nightly and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)'
lane :nightly do
unlock_keychain_if_needed
build_ios_adhoc()
end
desc '`fastlane ios release` builds a release & uploads it to TestFlight'
lane :release do
with Keychain.new('adhoc') do |kc|
match(
type: 'appstore',
readonly: true,
app_identifier: 'im.status.ethereum',
keychain_name: 'login.keychain'
keychain_name: kc.name,
keychain_password: kc.pass
)
build_ios_app(
@ -230,6 +254,8 @@ platform :ios do
"ITSAppUsesNonExemptEncryption": false
}
)
end
upload_to_testflight(
ipa: 'status-ios/StatusIm.ipa',
skip_waiting_for_build_processing: true