Amazon Web Services 한국 블로그

AWS CodeBuild, macOS용 Fastlane에 대한 지원 추가

macOS 환경용 AWS CodeBuild에서 Fastlane을 사용할 수 있다는 소식을 발표하게 되어 기쁩니다. AWS CodeBuild는 소스 코드를 컴파일하고, 테스트를 수행하고, 바로 배포 가능한 소프트웨어 패키지를 생성하는 완전관리형의 연속적 통합 서비스입니다.

Fastlane은 모바일 애플리케이션 개발의 다양한 측면을 자동화하도록 설계된 오픈 소스 도구 모음입니다. 모바일 애플리케이션 개발 작업 시 코드 서명, 스크린샷 생성, 베타 배포, App Store 제출과 같은 작업을 관리할 수 있는 중앙 집중식 도구 세트를 제공합니다. 널리 사용되는 지속적 통합 및 지속적 배포(CI/CD) 플랫폼과 통합되며 iOS 및 Android 개발 워크플로를 모두 지원합니다. Fastlane은 상당한 자동화 기능을 제공하지만 설정 및 유지 관리 중에 문제가 발생할 수도 있습니다. Fastlane을 구성하는 것은 복잡할 수 있으며, 특히 Ruby의 구문 및 패키지 관리 시스템에 익숙하지 않은 팀에게는 더욱 그렇습니다. 모바일 플랫폼 또는 서드 파티 서비스에 대한 업데이트로 인해 기존 워크플로를 조정해야 할 수 있으므로 지속적인 작업을 통해 Fastlane과 해당 종속성을 최신 상태로 유지해야 합니다.

2024년 8월에 macOS용 CodeBuild를 출시했을 때, 저희는 빌드 환경에 Fastlane을 설치하고 유지 관리하는 것이 당면 과제 중 하나라는 것을 알고 있었습니다. 사용자 지정 빌드 환경에서 Fastlane을 수동으로 설치할 수도 있었지만 AWS를 사용하면 차별화되지 않는 힘든 작업을 인프라에서 제거하여 비즈니스에 중요한 측면에 더 많은 시간을 할애할 수 있습니다. 오늘부터는 Fastlane이 기본적으로 설치되며 buildspec.yaml 파일에서 익숙한 fastlane build 명령을 사용할 수 있습니다.

Fastlane과 코드 서명
App Store에 애플리케이션을 배포하려면 Apple Developer 포털에서 생성되는 프라이빗 키로 바이너리에 서명해야 합니다. 이 프라이빗 키는 키를 검증하는 인증서와 함께 빌드 프로세스 중에 액세스할 수 있어야 합니다. 개발 팀의 경우 개발 프라이빗 키(선택한 테스트 디바이스에 배포할 때 사용됨)를 팀 구성원 간에 공유해야 하기 때문에 이것이 문제가 될 수 있습니다. 또한 App Store에 바이너리를 업로드하기 전 서명 프로세스 중에 배포 프라이빗 키(App Store에 게시할 때 사용됨)를 사용할 수 있어야 합니다.

Fastlane은 개발자의 개발 및 배포 키와 인증서 관리를 지원한다는 점에서 다재다능한 빌드 시스템입니다. fastlane match를 사용하여 개발 팀에서 서명 자료를 공유하고 개별 개발자의 컴퓨터와 CI 환경에서 안전하고 쉽게 액세스할 수 있도록 할 수 있습니다. match를 사용하면 프라이빗 키, 인증서, 모바일 프로비저닝 프로필을 안전한 공유 스토리지에 저장할 수 있습니다. 개발자용 랩톱이든 클라우드의 서버 머신이든 관계없이 로컬 빌드 환경을 공유 스토리지와 동기화된 상태로 유지할 수 있습니다. 구축 시에는 앱 서명에 필요한 인증서가 안전하게 다운로드되고 codesign 유틸리티에서 인증서를 선택할 수 있도록 빌드 머신이 구성됩니다.

match를 사용하면 GitHub, GitLab, Google Cloud Storage, Azure DevOps, Amazon Simple Storage Service(Amazon S3)를 통해 서명 보안 암호를 공유할 수 있습니다.

이미 이 중 하나를 사용하고 있고 프로젝트를 CodeBuild로 마이그레이션하고 있다면 작업이 더 간단해집니다. CodeBuild 빌드 환경에서 공유 스토리지에 액세스할 수 있는지 확인하기만 하면 됩니다(데모의 3단계 참조).

작동 방식 살펴보기
Fastlane 또는 CodeBuild가 처음인 사용자를 위해 그 작동 방식을 살펴보도록 하겠습니다.

이 데모에서는 기존 iOS 프로젝트에서 시작하겠습니다. 이 프로젝트는 이미 CodeBuild를 기반으로 구축되도록 구성되어 있습니다. 자세한 내용은 이전 블로그 게시물인 AWS CodeBuild를 사용하여 연속적 통합 파이프라인에 macOS 추가를 참조하세요.

다음 세 단계로 시작하는 방법을 보여 드리겠습니다.

  • 기존 서명 자료를 공유 프라이빗 GitHub 리포지토리로 가져오기
  • 프로젝트를 구축하고 서명하도록 fastlane 구성
  • CodeBuild에서 fastlane 사용

1단계: 서명 자료 가져오기

필자가 읽은 대부분의 fastlane 문서에는 새 키 페어와 새 인증서를 생성하여 시작하는 방법이 설명되어 있습니다. 새 프로젝트의 경우 이 방법이 확실히 맞지만 실제로는 이미 프로젝트와 서명 키가 있을 것입니다. 따라서 첫 번째 단계는 이러한 기존 서명 자료를 가져오는 것이 됩니다.

Apple App Store에서는 개발과 배포에 서로 다른 키와 인증서가 사용됩니다(임시 인증서와 엔터프라이즈 인증서도 있지만 이 글에서는 다루지 않음). 각 용도에 대해 파일 3개(총 6개 파일)가 있어야 합니다.

  • Apple 개발자 콘솔에서 생성하고 다운로드할 수 있는 .mobileprovision 파일. 프로비저닝 프로필은 사용자의 ID, 앱 ID, 앱에 있을 수 있는 권한을 연결합니다.
  • Apple이 프라이빗 키를 검증하기 위해 내보내는 인증서인 .cer 파일. Apple Developer 포털에서 다운로드할 수 있습니다. 인증서를 선택한 다음 Download(다운로드)를 선택합니다.
  • 프라이빗 키가 들어있는 .p12 파일. Apple Developer 포털에서 키를 생성할 때 키를 다운로드할 수 있습니다. 다운로드하지 않았지만 머신에 있는 경우 Apple Keychain 앱에서 내보낼 수 있습니다. 참고로 macOS 15.x에서는 KeyChain.app이 숨겨져 있습니다. open /System/Library/CoreServices/Applications/Keychain\ Access.app을 사용하여 열 수 있습니다. 내보내려는 키를 선택하고 마우스 오른쪽 버튼을 클릭하여 내보내기를 선택합니다.
Keychain에서 p12 파일 내보내기

이러한 파일이 있으면 다음 내용이 포함된 fastlane/Matchfile 파일을 생성하세요.

git_url("https://github.com/sebsto/secret.git")
storage_mode("git")
type("development")
# or use appstore to use the distribution signing key and certificate
# type("appstore")
Ruby

GitHub 리포지토리의 URL을 바꾸고 이 리포지토리가 비공개인지 확인하세요. 이 리포지토리는 서명 키 및 인증서의 스토리지 역할을 합니다.

그런 다음 fastlane match import --type appstore 명령을 사용하여 기존 파일을 가져옵니다. appstoredevelopment라는 각 환경에 대해 명령을 반복합니다.

처음 Fastlane을 사용할 때는 Apple ID 사용자 이름과 암호를 입력하라는 메시지가 표시됩니다. 입력하면 App Store Connect에 연결되어 인증서를 검증하거나 필요한 경우 새 인증서가 생성됩니다. 세션 쿠키는 ~/.fastlane/spaceship/<your apple user id>/cookie에 저장됩니다.

fastlane match를 사용할 때도 암호를 입력해야 합니다. 이 암호를 사용하여 저장소의 서명 자료를 암호화하는 키를 생성합니다. 이 암호는 구축 시 빌드 머신의 서명 자료를 가져오는 데 사용되므로 잊지 마세요.

다음은 명령과 그 출력 전체입니다.

 fastlane match import --type appstore

[] 🚀
[16:43:54]: Successfully loaded '~/amplify-ios-getting-started/code/fastlane/Matchfile' 📄

+-----------------------------------------------------+
| Detected Values from './fastlane/Matchfile'         |
+--------------+--------------------------------------+
| git_url.     | https://github.com/sebsto/secret.git |
| storage_mode | git                                  |
| type         | development                          |
+--------------+--------------------------------------+

[16:43:54]: Certificate (.cer) path:
./secrets/sebsto-apple-dist.cer
[16:44:07]: Private key (.p12) path:
./secrets/sebsto-apple-dist.p12
[16:44:12]: Provisioning profile (.mobileprovision or .provisionprofile) path or leave empty to skip
this file:
./secrets/amplifyiosgettingstarteddist.mobileprovision
[16:44:25]: Cloning remote git repo...
[16:44:25]: If cloning the repo takes too long, you can use the `clone_branch_directly` option in match.
[16:44:27]: Checking out branch master...
[16:44:27]: Enter the passphrase that should be used to encrypt/decrypt your certificates
[16:44:27]: This passphrase is specific per repository and will be stored in your local keychain
[16:44:27]: Make sure to remember the password, as you'll need it when you run match on a different machine
[16:44:27]: Passphrase for Match storage: ********
[16:44:30]: Type passphrase again: ********
security: SecKeychainAddInternetPassword <NULL>: The specified item already exists in the keychain.
[16:44:31]: 🔓 Successfully decrypted certificates repo
[16:44:31]: Repo is at: '/var/folders/14/nwpsn4b504gfp02_mrbyd2jr0000gr/T/d20250131-41830-z7b4ic'
[16:44:31]: Login to App Store Connect (sebsto@mac.com)
[16:44:33]: Enter the passphrase that should be used to encrypt/decrypt your certificates
[16:44:33]: This passphrase is specific per repository and will be stored in your local keychain
[16:44:33]: Make sure to remember the password, as you'll need it when you run match on a different machine
[16:44:33]: Passphrase for Match storage: ********
[16:44:37]: Type passphrase again: ********
security: SecKeychainAddInternetPassword <NULL>: The specified item already exists in the keychain.
[16:44:39]: 🔒 Successfully encrypted certificates repo
[16:44:39]: Pushing changes to remote git repo...
[16:44:40]: Finished uploading files to Git Repo [https://github.com/sebsto/secret.git]
Bash

Fastlane이 내 서명 자료를 Git 리포지토리로 가져왔음을 확인합니다.

Fastlane match - 가져오기 후 github 리포지토리

다음 구축 중에 이러한 서명 자료를 사용하도록 로컬 컴퓨터를 구성할 수도 있습니다.

» fastlane match appstore 

[✔] 🚀 
[17:39:08]: Successfully loaded '~/amplify-ios-getting-started/code/fastlane/Matchfile' 📄

+-----------------------------------------------------+
|   Detected Values from './fastlane/Matchfile'       |
+--------------+--------------------------------------+
| git_url      | https://github.com/sebsto/secret.git |
| storage_mode | git                                  |
| type         | development                          |
+--------------+--------------------------------------+


+-------------------------------------------------------------------------------------------+
|                                 Summary for match 2.226.0                                 |
+----------------------------------------+--------------------------------------------------+
| type                                   | appstore                                         |
| readonly                               | false                                            |
| generate_apple_certs                   | true                                             |
| skip_provisioning_profiles             | false                                            |
| app_identifier                         | ["com.amazonaws.amplify.mobile.getting-started"] |
| username                               | xxxx@xxxxxxxxx                                   |
| team_id                                | XXXXXXXXXX                                       |
| storage_mode                           | git                                              |
| git_url                                | https://github.com/sebsto/secret.git             |
| git_branch                             | master                                           |
| shallow_clone                          | false                                            |
| clone_branch_directly                  | false                                            |
| skip_google_cloud_account_confirmation | false                                            |
| s3_skip_encryption                     | false                                            |
| gitlab_host                            | https://gitlab.com                               |
| keychain_name                          | login.keychain                                   |
| force                                  | false                                            |
| force_for_new_devices                  | false                                            |
| include_mac_in_profiles                | false                                            |
| include_all_certificates               | false                                            |
| force_for_new_certificates             | false                                            |
| skip_confirmation                      | false                                            |
| safe_remove_certs                      | false                                            |
| skip_docs                              | false                                            |
| platform                               | ios                                              |
| derive_catalyst_app_identifier         | false                                            |
| fail_on_name_taken                     | false                                            |
| skip_certificate_matching              | false                                            |
| skip_set_partition_list                | false                                            |
| force_legacy_encryption                | false                                            |
| verbose                                | false                                            |
+----------------------------------------+--------------------------------------------------+

[17:39:08]: Cloning remote git repo...
[17:39:08]: If cloning the repo takes too long, you can use the `clone_branch_directly` option in match.
[17:39:10]: Checking out branch master...
[17:39:10]: Enter the passphrase that should be used to encrypt/decrypt your certificates
[17:39:10]: This passphrase is specific per repository and will be stored in your local keychain
[17:39:10]: Make sure to remember the password, as you'll need it when you run match on a different machine
[17:39:10]: Passphrase for Match storage: ********
[17:39:13]: Type passphrase again: ********
security: SecKeychainAddInternetPassword <NULL>: The specified item already exists in the keychain.
[17:39:15]: 🔓  Successfully decrypted certificates repo
[17:39:15]: Verifying that the certificate and profile are still valid on the Dev Portal...
[17:39:17]: Installing certificate...

+-------------------------------------------------------------------------+
|                          Installed Certificate                          |
+-------------------+-----------------------------------------------------+
| User ID           | XXXXXXXXXX                                          |
| Common Name       | Apple Distribution: Sebastien Stormacq (XXXXXXXXXX) |
| Organisation Unit | XXXXXXXXXX                                          |
| Organisation      | Sebastien Stormacq                                  |
| Country           | US                                                  |
| Start Datetime    | 2024-10-29 09:55:43 UTC                             |
| End Datetime      | 2025-10-29 09:55:42 UTC                             |
+-------------------+-----------------------------------------------------+

[17:39:18]: Installing provisioning profile...

+-------------------------------------------------------------------------------------------------------------------+
|                                          Installed Provisioning Profile                                           |
+---------------------+----------------------------------------------+----------------------------------------------+
| Parameter           | Environment Variable                         | Value                                        |
+---------------------+----------------------------------------------+----------------------------------------------+
| App Identifier      |                                              | com.amazonaws.amplify.mobile.getting-starte  |
|                     |                                              | d                                            |
| Type                |                                              | appstore                                     |
| Platform            |                                              | ios                                          |
| Profile UUID        | sigh_com.amazonaws.amplify.mobile.getting-s  | 4e497882-d80f-4684-945a-8bfec1b310b9         |
|                     | tarted_appstore                              |                                              |
| Profile Name        | sigh_com.amazonaws.amplify.mobile.getting-s  | amplify-ios-getting-started-dist             |
|                     | tarted_appstore_profile-name                 |                                              |
| Profile Path        | sigh_com.amazonaws.amplify.mobile.getting-s  | /Users/stormacq/Library/MobileDevice/Provis  |
|                     | tarted_appstore_profile-path                 | ioning                                       |
|                     |                                              | Profiles/4e497882-d80f-4684-945a-8bfec1b310  |
|                     |                                              | b9.mobileprovision                           |
| Development Team ID | sigh_com.amazonaws.amplify.mobile.getting-s  | XXXXXXXXXX                                   |
|                     | tarted_appstore_team-id                      |                                              |
| Certificate Name    | sigh_com.amazonaws.amplify.mobile.getting-s  | Apple Distribution: Sebastien Stormacq       |
|                     | tarted_appstore_certificate-name             | (XXXXXXXXXX)                                 |
+---------------------+----------------------------------------------+----------------------------------------------+

[17:39:18]: All required keys, certificates and provisioning profiles are installed 🙌
Plain text

2단계: 프로젝트에 서명하도록 Fastlane 구성

fastlane/Fastfile에 Fastlane 빌드 구성 파일을 생성합니다(fastlane init 명령을 사용하여 시작할 수 있음).

default_platform(:ios)

platform :ios do
  before_all do
    setup_ci
  end

  desc "Build and Sign the binary"
  lane :build do
    match(type: "appstore", readonly: true)
    gym(
      scheme: "getting started",
      export_method: "app-store"
    )
  end
end
Ruby

setup_ci 작업이 Fastfilebefore_all 섹션에 추가되었는지 확인합니다. 그렇게 해야 match 작업이 올바르게 작동합니다. 이 작업을 수행하면 올바른 권한이 있는 임시 Fastlane 키체인이 생성됩니다. 이 단계를 수행하지 않으면 빌드가 실패하거나 결과가 일치하지 않을 수 있습니다.

그리고 fastlane build 명령으로 로컬 빌드를 테스트합니다. 키와 인증서를 가져올 때 사용한 암호를 입력한 다음 프로젝트가 구축되고 서명되도록 합니다. 모든 것이 올바르게 구성되면 비슷한 출력이 생성됩니다.

...
[17:58:33]: Successfully exported and compressed dSYM file
[17:58:33]: Successfully exported and signed the ipa file:
[17:58:33]: ~/amplify-ios-getting-started/code/getting started.ipa

+---------------------------------------+
|           fastlane summary            |
+------+------------------+-------------+
| Step | Action           | Time (in s) |
+------+------------------+-------------+
| 1    | default_platform | 0           |
| 2    | setup_ci         | 0           |
| 3    | match            | 36          |
| 4    | gym              | 151         |
+------+------------------+-------------+

[17:58:33]: fastlane.tools finished successfully 🎉
Plain text

3단계: Fastlane을 사용하도록 CodeBuild 구성

다음으로 CodeBuild에서 프로젝트를 생성합니다. 이 작업에 도움이 되는 단계별 가이드를 여기서 다루지는 않겠습니다. 이전 게시물이나 CodeBuild 문서를 참조하시면 됩니다.

Fastlane에 특정한 구성은 하나뿐입니다. 서명 자료에 액세스하려면 Fastlane에서 환경 변수로 전달할 세 가지 비밀 값에 액세스할 수 있어야 합니다.

  • MATCH_PASSWORD, 서명 자료를 가져올 때 입력한 암호. Fastlane은 이 암호를 사용하여 GitHub 리포지토리의 암호화된 파일을 해독합니다.
  • FASTLANE_SESSION, ~/.fastlane/spaceship/<your apple user id>/cookie에 있는 Apple ID 세션 쿠키의 값. 세션 유효 기간은 몇 시간에서 며칠입니다. 세션이 만료되면 랩톱에서 fastlane spaceauth 명령으로 재인증하고 FASTLANE_SESSION 값을 쿠키의 새 값으로 업데이트합니다.
  • MATCH_GIT_BASIC_AUTHORIZATION, GitHub 사용자 이름의 기본 64 인코딩이며 콜론과 비공개 GitHub 리포지토리에 액세스할 수 있는 개인 인증 토큰(PAT)이 뒤따릅니다. PAT는 GitHub 콘솔의 프로필 > 설정 > 개발자 설정 > 개인 액세스 토큰에서 생성할 수 있습니다. 이 명령을 사용하여 환경 변수 echo -n my_git_username:my_git_pat | base64의 값을 생성합니다.

참고로 이 세 가지 값 각각에 대해 AWS Secrets Manager에 있는 암호의 Amazon 리소스 이름(ARN) 또는 일반 텍스트 값을 입력할 수 있습니다. 보안에 민감한 값을 저장하려면 Secrets Manager를 사용하는 것이 좋습니다.

저는 보안에 민감하므로 다음 명령을 사용하여 Secrets Manager에 세 가지 보안 암호를 저장합니다.

aws --region $REGION secretsmanager create-secret --name /CodeBuild/MATCH_PASSWORD --secret-string MySuperSecretPassword
aws --region $REGION secretsmanager create-secret --name /CodeBuild/FASTLANE_SESSION --secret-string $(cat ~/.fastlane/spaceship/my_appleid_username/cookie)
aws --region $REGION secretsmanager create-secret --name /CodeBuild/MATCH_GIT_BASIC_AUTHORIZATION --secret-string $(echo -n my_git_username:my_git_pat | base64)

빌드 프로젝트에서 Secrets Manager에 저장된 보안 암호를 참조하는 경우, 빌드 프로젝트의 서비스 역할은 secretsmanager:GetSecretValue작업을 허용해야 합니다. 프로젝트를 만들 때 새 서비스 역할을 선택한 경우 CodeBuild에 의해 빌드 프로젝트의 기본 서비스 역할에 이 작업이 포함됩니다. 하지만 기존 서비스 역할을 선택한 경우 이 작업을 서비스 역할에 별도로 포함해야 합니다.

이 데모에서는 다음과 같은 AWS Identity and Access Management(IAM) 정책을 사용합니다.

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"secretsmanager:GetSecretValue"
			],
			"Resource": [
				"arn:aws:secretsmanager:us-east-2:012345678912:secret:/CodeBuild/*"
			]
		}
	]
}
JSON

AWS Management Console의 CodeBuild 섹션에서 프로젝트를 생성한 후 세 가지 환경 변수를 입력합니다. 값은 Secrets Manager에 있는 보안 암호의 이름이라는 것을 알 수 있습니다.

Codebuild - Fastlane match를 위한 환경 변수

buildpsec.yaml 파일에서 환경 변수와 해당 Secrets Manager 보안 암호 이름을 정의할 수도 있습니다.

다음으로 프로젝트 루트에 있는 buildspec.yaml 파일을 수정하여 바이너리 구축 및 서명에 fastlane을 사용하도록 합니다. 제 buildspec.yaml 파일은 이제 다음과 같이 표시됩니다.

# buildspec.yml
version: 0.2
phases:
  install:
    commands:
      - code/ci_actions/00_install_rosetta.sh
  pre_build:
    commands:
      - code/ci_actions/02_amplify.sh
  build:
    commands:
      - (cd code && fastlane build)
artifacts:
  name: getting-started-$(date +%Y-%m-%d).ipa
  files:
    - 'getting started.ipa'
  base-directory: 'code'
YAML

백엔드에 대한 Amplify 구성을 수신하려면 Rosetta 및 Amplify 스크립트가 필요합니다. 프로젝트에서 AWS Amplify를 사용하지 않는 경우에는 필요하지 않습니다.

빌드 파일에는 빌드 환경에서 서명 키를 다운로드하거나 키체인을 준비하는 기능이 없음을 알 수 있습니다. fastlane match가 대신 처리하기 때문입니다.

buildspec.yaml 파일과 ./fastlane 디렉터리를 Git에 추가합니다. 이 파일들을 커밋하고 푸시합니다. git commit -m "add fastlane support" && git push

모든 것이 순조롭게 진행되면 CodeBuild에서 빌드가 실행되고 Succeeded 메시지가 표시됩니다.

Codebuild - 성공 메시지

요금 및 가용성
이제 Fastlane은 macOS용 CodeBuild가 제공되는 모든 리전에서 CodeBuild에 사용되는 모든 macOS 이미지에 추가 비용 없이 사전 설치됩니다. 이 글을 쓰는 시점에는 미국 동부(오하이오, 버지니아 북부), 미국 서부(오리건), 아시아 태평양(시드니), 유럽(프랑크푸르트) 리전이 여기에 해당합니다.

제 경험상 fastlane match를 올바르게 구성하는 데 약간의 시간이 걸립니다. 구성이 완료된 후 CodeBuild에서 구성을 작동하도록 하는 것은 매우 간단합니다. CodeBuild에서 이 작업을 시도하기 전에 로컬 머신에서 제대로 작동하는지 확인하세요. CodeBuild에서 문제가 발생하면 환경 변수의 값을 세 번 확인하고 CodeBuild가 AWS Secrets Manager의 보안 암호에 액세스할 수 있는지 확인하세요.

이제 (macOS에서) 직접 구축해보세요!