Code signing iOS pada project React Native sering menjadi sumber kegagalan build saat berpindah dari mesin lokal developer ke server CI/CD. Masalah ini berakar pada model desentralisasi sertifikat: setiap developer membuat sertifikat distribusi dan provisioning profile mandiri melalui Xcode. Artikel ini membahas implementasi Fastlane Match untuk menstandarkan proses code signing menggunakan repository Git terenkripsi, konfigurasi non-interaktif pada CI/CD, dan penyesuaian project Xcode.
Akar Masalah: Certificate Sprawl dan Profile Mismatch
Secara default, opsi Automatically manage signing pada Xcode menginstruksikan Xcode mengunduh atau men-generate sertifikat baru langsung dari Apple Developer Portal. Pola ini memicu sejumlah kendala:
- Limit Sertifikat Distribusi: Akun Apple Developer Program membatasi jumlah maksimum sertifikat Apple Distribution aktif (biasanya 2-3 sertifikat). Ketika developer baru bergabung dan men-generate sertifikat baru, sertifikat lama sering kali ter-revoke. Akibatnya, runner CI/CD yang menyimpan sertifikat lama langsung gagal saat build.
- Ketiadaan Private Key di CI: Runner CI (seperti GitHub Actions macOS runner) adalah mesin ephemeral (dibuat dan dihapus secara dinamis). Runner tidak memiliki private key yang tersimpan di Keychain lokal developer.
- Provisioning Profile Mismatch: Profil provisi terikat pada UDID perangkat dan sertifikat spesifik. Jika profil di portal Apple tidak sinkron dengan sertifikat di Keychain CI,
xcodebuildakan melempar error:No matching provisioning profiles found.
Solusi: Pendekatan Centralized Signing via Fastlane Match
Fastlane Match mengadopsi konsep Git as a Single Source of Truth. Seluruh sertifikat (development dan distribution) serta provisioning profile di-generate satu kali oleh Match, kemudian dienkripsi menggunakan OpenSSL (AES-256) dan disimpan di repository Git privat terpisah. Seluruh developer dan mesin CI/CD hanya perlu mengakses repo tersebut dan mendekripsinya menggunakan passphrase yang sama (MATCH_PASSWORD).
1. Menyiapkan Repository Git Privat
Buat sebuah repository Git privat kosong (misalnya di GitHub/GitLab: org-certs-repo). Repository ini hanya akan menyimpan file sertifikat dan profile terenkripsi. Berikan akses baca (read-only deploy key) ke runner CI.
2. Inisialisasi dan Konfigurasi Matchfile
Jalankan inisialisasi di dalam folder root React Native:
cd ios
bundle exec fastlane match initPilih storage mode git dan masukkan URL SSH repository privat Anda. Fastlane akan membuat file ios/fastlane/Matchfile. Konfigurasikan file tersebut sebagai berikut:
# ios/fastlane/Matchfile
git_url("git@github.com:organisasi-anda/certificates-repo.git")
storage_mode("git")
type("appstore")
app_identifier(["com.perusahaan.appname"])
username("apple-developer@perusahaan.com") # Opsional jika menggunakan API KeyOtentikasi Non-Interaktif dengan App Store Connect API Key
Apple mewajibkan Two-Factor Authentication (2FA) untuk Apple ID biasa, yang menyebabkan sesi login pada runner CI kadaluarsa dan membutuhkan input OTP. Gunakan App Store Connect API Key untuk otentikasi headless.
Generate API Key di App Store Connect (Menu: Users and Access > Integrations > App Store Connect API). Unduh file private key .p8, lalu catat Key ID dan Issuer ID.
Konfigurasi Fastfile: Lane Development dan App Store
Tambahkan lane di ios/fastlane/Fastfile. Gunakan action setup_ci untuk membuat Keychain sementara yang terisolasi pada runner CI, mencegah prompt interaktif dari macOS Keychain.
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Load App Store Connect API Key"
lane :load_api_key do
app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"],
is_key_content_base64: true,
in_house: false
)
end
desc "Sync certificates for local development"
lane :sync_dev do
api_key = load_api_key
match(
type: "development",
readonly: true,
api_key: api_key
)
end
desc "Build and Sign App for App Store / TestFlight"
lane :build_release do
api_key = load_api_key
# Inisialisasi keychain temporer khusus CI
setup_ci if is_ci
match(
type: "appstore",
readonly: is_ci,
api_key: api_key
)
# Update project signing settings secara programatis sebelum build
update_code_signing_settings(
use_automatic_signing: false,
path: "MyApp.xcodeproj"
)
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
export_options: {
provisioningProfiles: {
"com.perusahaan.appname" => "match AppStore com.perusahaan.appname"
}
}
)
end
endPenyesuaian Konfigurasi Signing di Xcode (project.pbxproj)
Agar Match bekerja deterministik tanpa intervensi Xcode, ubah pengaturan signing target aplikasi utama:
- Buka
ios/MyApp.xcworkspacedi Xcode. - Pilih target project, buka tab Signing & Capabilities.
- Untuk build configuration Release: Hilangkan centang pada Automatically manage signing.
- Pilih Provisioning Profile: gunakan profile yang di-download oleh Match, dengan format nama:
match AppStore com.perusahaan.appname. - Pilih Signing Certificate:
Apple Distribution. - Untuk konfigurasi Debug, Anda dapat mempertahankan Automatically manage signing untuk kenyamanan simulator, atau mengaturnya ke Manual menggunakan
match Development com.perusahaan.appnamejika perlu running langsung di physical device.
Penting: Menggunakan
update_code_signing_settingsdi Fastfile mengamankan build CI agar tidak tertimpa setting lokal Xcode developer.
Setup Workflow GitHub Actions
Runner GitHub Actions membutuhkan SSH key privat untuk mengakses repository certificates Match, serta passphrase dekripsi.
Simpan variabel berikut di GitHub Secrets:
MATCH_PASSWORD: Kata sandi enkripsi repository sertifikat.MATCH_GIT_PRIVATE_KEY: SSH Private Key yang memiliki akses read ke repository certificates.APP_STORE_CONNECT_KEY_ID: Key ID dari Apple Developer Console.APP_STORE_CONNECT_ISSUER_ID: Issuer ID (UUID).APP_STORE_CONNECT_API_KEY_CONTENT: Konten file.p8dalam format Base64.
Snippet pipeline .github/workflows/ios-build.yml:
name: iOS Release Build
on:
push:
branches: [ main ]
jobs:
build:
runs-on: macos-14
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup SSH Key for Fastlane Match
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.MATCH_GIT_PRIVATE_KEY }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- name: Install JS Dependencies
run: yarn install --frozen-lockfile
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
working-directory: ios
- name: Install CocoaPods
run: |
cd ios
bundle exec pod install
- name: Run Fastlane Release Build
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
run: |
cd ios
bundle exec fastlane build_releaseTroubleshooting Masalah Umum
1. Error: "User interaction is not allowed"
Penyebab: macOS Keychain terkunci atau sistem meminta konfirmasi dialog GUI untuk mengakses private key. Runner CI tidak memiliki interface visual.
Solusi: Pastikan action setup_ci dijalankan di awal lane Fastlane. Action ini membuat keychain sementara dengan password acak, membukanya (unlock), dan menonaktifkan timeout penguncian keychain selama proses build berlangsung.
2. Sertifikat Revoked atau Profil Tidak Valid
Jika sertifikat distribution tidak sengaja ter-revoke melalui web Apple Developer:
- Hapus profil dan sertifikat lama yang rusak menggunakan Fastlane nuke:
bundle exec fastlane match nuke appstore - Generate sertifikat dan profil baru yang fresh:
bundle exec fastlane match appstore - Commit perubahan yang dihasilkan Match ke repo privat sertifikat Anda. CI/CD akan otomatis membaca sertifikat baru pada eksekusi berikutnya tanpa perlu setup manual di Xcode.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!