Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions docs/release/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ PR 유형과 파일 단위 이력은 사용자 흐름의 의미를 완전히 표

기존 `release-distribution.yml`의 `develop` 수동 배포(`workflow_dispatch`)는 #1029에서 제거했다. 도착지와 산출물 버전이 main 경로와 같아 실익이 PR 생성 한 단계뿐이었던 반면, release keystore와 service account를 임의 ref에 노출하는 표면이었다.

예외적으로 [`firebase-wif-canary.yml`](../../.github/workflows/firebase-wif-canary.yml)은 `develop` 또는 `main`에서 보호 Environment 승인과 명시적 확인을 받은 뒤 실제 APK를 올리는 **수동 인증 호환성 검증 경로**다. 일반 릴리스나 임의 브랜치 배포 수단으로 사용하지 않으며, production JSON 자격을 WIF로 교체하기 전 판정 절차는 [WIF canary runbook](firebase-wif-canary.md)을 따른다.
예외적으로 [`firebase-wif-canary.yml`](../../.github/workflows/firebase-wif-canary.yml)은 `develop` 또는 `main`에서 보호 Environment 승인과 명시적 확인을 받은 뒤 실제 APK를 올리는 **수동 인증 호환성 검증 경로**다. 일반 릴리스나 임의 브랜치 배포 수단으로 사용하지 않는다. production `release-distribution.yml`도 #1463부터 같은 WIF 경로로 인증하고, 장기 JSON 키 경로는 #1566에서 걷었다. canary 실행 조건은 [WIF canary runbook](firebase-wif-canary.md)을 따른다.

`develop` → `main` 릴리스 PR 본문에 다음 섹션을 채운다.

Expand All @@ -106,15 +106,14 @@ CI가 사용하는 GitHub Secrets (Settings → Secrets and variables → Action
| `RELEASE_STORE_PASSWORD` | keystore 비밀번호 |
| `RELEASE_KEY_ALIAS` | key alias (`afternote-release`) |
| `RELEASE_KEY_PASSWORD` | key 비밀번호 |
| `FIREBASE_SERVICE_ACCOUNT_JSON` | App Distribution Admin 권한 부여된 service account JSON 원문 |
| `KAKAO_NATIVE_APP_KEY` · `GOOGLE_WEB_CLIENT_ID` · `GOOGLE_SERVICES_JSON_B64` | release distribution·WIF canary·dependency audit가 쓰는 실서비스 앱 설정 |
| `GCP_WORKLOAD_IDENTITY_PROVIDER` · `GCP_FIREBASE_SERVICE_ACCOUNT` | 수동 WIF canary 전용 OIDC 설정 ([runbook](firebase-wif-canary.md)) |
| `GCP_WORKLOAD_IDENTITY_PROVIDER` · `GCP_FIREBASE_SERVICE_ACCOUNT` | release distribution과 WIF canary가 Firebase 업로드 직전에 쓰는 WIF 설정. 서비스 계정 `afternote-ci` 는 이 두 워크플로에만 묶여 있다([Play release runbook](../play-release.md) 사전 준비 3번) |

> base64 인코딩: `base64 -i ~/afternote-release.jks | pbcopy` (macOS)

PR 검증용 lint·unit-test·screenshot은 repository secret 대신
`.github/actions/setup-ci-config`가 만드는 결정적 CI 전용 placeholder를 사용한다. 이 fixture는
배포에 사용할 수 없으며, production `release-distribution.yml`은 계속 승인된 환경의 JSON 자격을 사용한다. WIF canary 성공만으로 이를 제거하지 않는다.
배포에 사용할 수 없으며, production `release-distribution.yml`은 승인된 환경에서 WIF로 받은 단기 자격으로만 업로드한다.

### 배포 provenance: 이 APK 가 어느 commit·run 에서 나왔는지 (#851)

Expand Down
35 changes: 21 additions & 14 deletions docs/release/firebase-wif-canary.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
# Firebase App Distribution WIF canary

이 문서는 기존 `FIREBASE_SERVICE_ACCOUNT_JSON` 인증을 즉시 교체하지 않고, 별도 수동
canary에서 GitHub OIDC와 Google Cloud Workload Identity Federation 호환성을 먼저 확인하는
절차다. 저장소 workflow만 준비하며 Google Cloud·GitHub Settings 변경과 실제 업로드 실행은
별도 승인 범위다.
이 문서는 수동 canary에서 GitHub OIDC와 Google Cloud Workload Identity Federation으로
Firebase App Distribution 업로드를 확인하는 절차다. 처음에는 장기 JSON 키를 WIF로 바꾸기 전의
호환성 검증으로 만들었고, 전환은 끝났다. production `release-distribution.yml`은 #1463부터
WIF로 인증하고, `FIREBASE_SERVICE_ACCOUNT_JSON` 롤백 경로는 #1566에서 걷었다.

## 현재 안전 경계

- production `release-distribution.yml`은 기존 JSON 인증을 그대로 사용한다.
- production `release-distribution.yml`도 같은 WIF 설정으로 인증한다. JSON 키 경로는 없다.
- canary는 `develop` 또는 `main`의 수동 실행과 `release-distribution` Environment 승인을 모두
요구한다.
- signed APK는 인증 전에 빌드하고 같은 runner에서 바로 업로드한다. public Actions artifact로
게시하지 않는다.
- WIF 인증은 업로드 직전에만 수행한다. provider·service account 값, token, 생성된 ADC 상세는
workflow summary나 별도 artifact에 기록하지 않는다.
- canary 실패 시 production workflow나 기존 JSON secret을 변경·삭제하지 않는다.
- canary 실패만으로 production workflow의 인증 설정을 바꾸지 않는다.

## 별도 승인이 필요한 외부 준비
## 외부 준비

다음 항목은 이 PR에서 만들거나 수정하지 않는다.
전환 때 갖춘 Google Cloud 쪽 준비다. provider와 서비스 계정 바인딩의 현재 값은
[Play release runbook](../play-release.md) 사전 준비 3번에 있다.

1. GitHub OIDC용 workload identity pool/provider와 Firebase App Distribution 전용 최소 권한
service account를 준비한다.
2. provider attribute mapping에 `repository`, `repository_owner`, `ref`, `workflow_ref`,
`environment` claim을 포함한다.
3. attribute condition은 최소한 다음을 동시에 제한한다.
3. attribute condition은 처음에 다음을 동시에 제한하도록 설계했다. 지금 condition은 `workflow_ref`
허용 목록에 `release-distribution.yml`과 `release-play-internal.yml`도 들어 있다. 현재 값의 정본은
Play release runbook 사전 준비 3번이다.
- repository: `Afternote/Afternote-FE`
- owner: `Afternote`
- ref: `refs/heads/develop` 또는 `refs/heads/main`
- workflow: `.github/workflows/firebase-wif-canary.yml`
- environment: `release-distribution`
4. 보호된 Environment에 `GCP_WORKLOAD_IDENTITY_PROVIDER`와
`GCP_FIREBASE_SERVICE_ACCOUNT`를 masked secret으로 등록한다.
5. 허용 branch canary 성공뿐 아니라 다른 branch·fork·workflow의 credential 발급 거부도
5. Firebase 서비스 계정 `afternote-ci`의 `roles/iam.workloadIdentityUser`는 저장소 전체가 아니라
`release-distribution.yml@refs/heads/main`, `firebase-wif-canary.yml@refs/heads/main`,
`firebase-wif-canary.yml@refs/heads/develop` 셋의 `attribute.workflow_ref`에만 준다. 같은 provider를
Play 배포 워크플로도 통과하므로, 저장소 단위로 묶으면 그 워크플로가 Firebase 계정을 빌릴 수 있다.
6. 허용 branch canary 성공뿐 아니라 다른 branch·fork·workflow의 credential 발급 거부도
Google Cloud audit evidence로 확인한다.

공식 근거:
Expand All @@ -42,14 +49,14 @@ canary에서 GitHub OIDC와 Google Cloud Workload Identity Federation 호환성
- [Firebase App Distribution CI/CD와 WIF credential configuration](https://firebase.google.com/docs/app-distribution/best-practices-distributing-android-apps-to-qa-testers-with-ci-cd)
- [google-github-actions/auth](https://github.com/google-github-actions/auth)

## 전환 판정
## 전환 판정 (완료)

JSON 경로 제거는 아래가 모두 PASS인 별도 후속 변경에서만 한다.
#1463·#1566 전환 때 쓴 기준이다. 인증 구성을 다시 바꿀 때도 같은 기준으로 확인한다.

- 승인된 `develop` 또는 `main` canary APK가 WIF ADC로 1회 업로드된다.
- Firebase release의 source SHA와 실행 URL이 해당 canary run과 일치한다.
- 허용하지 않은 branch·fork·workflow의 credential 발급이 거부된다.
- 대상 service account 권한이 Firebase App Distribution 업로드 최소 범위로 검토된다.
- 실패 canary 뒤 기존 production JSON 경로가 정상 동작한다.
- 실패 canary 뒤 production 배포 경로가 정상 동작한다.

하나라도 확인되지 않으면 상태는 `UNVERIFIED`이며 기존 JSON secret을 유지한다.
하나라도 확인되지 않으면 상태는 `UNVERIFIED`이며 production 인증 구성을 바꾸지 않는다.
Loading