From cc0b4dc1d78343eec2bc83b0c673b6547aebb7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Sun, 30 Aug 2026 13:50:59 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20WIF=20=EC=A0=84=ED=99=98=20=ED=9B=84=20?= =?UTF-8?q?=EB=82=A8=EC=9D=80=20JSON=20=EB=A1=A4=EB=B0=B1=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=EB=A5=BC=20=EA=B1=B7=EB=8A=94=EB=8B=A4=20(#850)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-08-30 릴리스 머지(`1d0d1757`)로 release-distribution 이 WIF 경로를 처음 타고 성공했다(run 33292931533). `Verify WIF configuration is set` → `Authenticate to Google Cloud with WIF` → `Upload the attested APK to Firebase App Distribution` 이 모두 통과해, **장기 JSON 키 없이 승인된 main 배포 1회 성공**이라는 #850 의 마지막 실검증 조건이 충족됐다. 전환 성공 전까지 일부러 살려 두었던 롤백 경로를 이제 걷는다. `setup-release-config` 의 `firebase-service-account-json` 은 이미 **넘기는 워크플로가 0곳**인 죽은 입력이었다. - 입력 정의, 검증·구성 스텝의 env 매핑 2곳, secret 이름 case 분기 - ADC credential 파일 생성 분기(`$RUNNER_TEMP/firebase-service-account.json`) - `firebase-credentials-path` output — 소비하는 워크플로도 0곳이었다 정책 테스트 `firebase-wif-canary-policy.test.mjs` 의 「no workflow carries a long-lived service account JSON」 이 지금까지 워크플로 두 개만 봤다. **워크플로가 안 넘겨도 composite action 이 입력을 들고 있으면 경로는 살아 있는 것**이므로 action.yml 도 검사 대상에 넣었다. 역검증 — 입력을 되살리면 그 테스트가 실패한다(6건 중 1건 fail). `FIREBASE_SERVICE_ACCOUNT_JSON` environment secret 삭제는 코드가 아니라 저장소 설정이라 이 커밋에 담기지 않는다. 머지 후 별도로 처리하고 #850 을 닫는다. --- .../actions/setup-release-config/action.yml | 19 ------------------- .../firebase-wif-canary-policy.test.mjs | 9 +++++++++ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/actions/setup-release-config/action.yml b/.github/actions/setup-release-config/action.yml index ac6bd42c93..a80d545cbb 100644 --- a/.github/actions/setup-release-config/action.yml +++ b/.github/actions/setup-release-config/action.yml @@ -37,10 +37,6 @@ inputs: description: RELEASE_KEY_PASSWORD required: false default: "" - firebase-service-account-json: - description: FIREBASE_SERVICE_ACCOUNT_JSON — 주어지면 ADC credential 파일을 만든다 - required: false - default: "" compose-local-properties: description: "true 면 app/build.gradle.kts 의 signingConfigs.release 가 읽는 local.properties 를 만든다" required: false @@ -50,9 +46,6 @@ outputs: keystore-path: description: decode 된 release keystore 경로 (만들지 않았으면 빈 값) value: ${{ steps.materialise.outputs.keystore-path }} - firebase-credentials-path: - description: Firebase service account credential 경로 (만들지 않았으면 빈 값) - value: ${{ steps.materialise.outputs.firebase-credentials-path }} runs: using: composite @@ -70,7 +63,6 @@ runs: RELEASE_STORE_PASSWORD: ${{ inputs.release-store-password }} RELEASE_KEY_ALIAS: ${{ inputs.release-key-alias }} RELEASE_KEY_PASSWORD: ${{ inputs.release-key-password }} - FIREBASE_SERVICE_ACCOUNT_JSON: ${{ inputs.firebase-service-account-json }} run: | set -euo pipefail @@ -90,7 +82,6 @@ runs: RELEASE_STORE_PASSWORD) value="$RELEASE_STORE_PASSWORD" ;; RELEASE_KEY_ALIAS) value="$RELEASE_KEY_ALIAS" ;; RELEASE_KEY_PASSWORD) value="$RELEASE_KEY_PASSWORD" ;; - FIREBASE_SERVICE_ACCOUNT_JSON) value="$FIREBASE_SERVICE_ACCOUNT_JSON" ;; *) echo "::error::이 action 이 모르는 secret 이름: $name" exit 1 @@ -119,13 +110,11 @@ runs: RELEASE_STORE_PASSWORD: ${{ inputs.release-store-password }} RELEASE_KEY_ALIAS: ${{ inputs.release-key-alias }} RELEASE_KEY_PASSWORD: ${{ inputs.release-key-password }} - FIREBASE_SERVICE_ACCOUNT_JSON: ${{ inputs.firebase-service-account-json }} COMPOSE_LOCAL_PROPERTIES: ${{ inputs.compose-local-properties }} run: | set -euo pipefail keystore_path="" - firebase_credentials_path="" # google-services plugin 이 app/google-services.json 을 요구한다. 파일은 .gitignore 라 # CI 환경엔 없으므로 base64 secret 에서 복원한다. @@ -138,13 +127,6 @@ runs: printf '%s' "$RELEASE_STORE_FILE_B64" | base64 -d > "$keystore_path" fi - # Firebase App Distribution Gradle plugin 은 GOOGLE_APPLICATION_CREDENTIALS 환경변수로 - # service account JSON 경로를 인식한다. 경로만 output 으로 넘기고 export 는 호출자가 한다. - if [ -n "$FIREBASE_SERVICE_ACCOUNT_JSON" ]; then - firebase_credentials_path="$RUNNER_TEMP/firebase-service-account.json" - printf '%s' "$FIREBASE_SERVICE_ACCOUNT_JSON" > "$firebase_credentials_path" - fi - if [ "$COMPOSE_LOCAL_PROPERTIES" = "true" ]; then { echo "KAKAO_NATIVE_APP_KEY=$KAKAO_NATIVE_APP_KEY" @@ -158,5 +140,4 @@ runs: { echo "keystore-path=$keystore_path" - echo "firebase-credentials-path=$firebase_credentials_path" } >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/firebase-wif-canary-policy.test.mjs b/.github/scripts/firebase-wif-canary-policy.test.mjs index 8a430cc93f..ebbdb1793e 100644 --- a/.github/scripts/firebase-wif-canary-policy.test.mjs +++ b/.github/scripts/firebase-wif-canary-policy.test.mjs @@ -10,6 +10,10 @@ const productionWorkflow = await readFile( new URL("../workflows/release-distribution.yml", import.meta.url), "utf8", ); +const releaseConfigAction = await readFile( + new URL("../actions/setup-release-config/action.yml", import.meta.url), + "utf8", +); test("no workflow carries a long-lived service account JSON", () => { // canary 가 성공한 뒤 프로덕션도 WIF 로 전환했다 (#850). 이제 파이프라인 어디에도 장기 키가 @@ -17,6 +21,11 @@ test("no workflow carries a long-lived service account JSON", () => { // 되돌리는 것이 «의도된 롤백» 의 표시다. assert.doesNotMatch(canaryWorkflow, /FIREBASE_SERVICE_ACCOUNT_JSON/); assert.doesNotMatch(productionWorkflow, /FIREBASE_SERVICE_ACCOUNT_JSON/); + // 워크플로가 안 넘겨도 composite action 이 입력을 들고 있으면 경로가 살아 있는 것이다. + // 2026-08-30 실배포 성공 후 그 롤백 경로까지 걷었다 — 입력·env·ADC 파일 생성·output 전부. + assert.doesNotMatch(releaseConfigAction, /FIREBASE_SERVICE_ACCOUNT_JSON/); + assert.doesNotMatch(releaseConfigAction, /firebase-service-account-json/); + assert.doesNotMatch(releaseConfigAction, /firebase-credentials-path/); // 자격 파일 경로를 워크플로가 손으로 지정하면 auth 액션이 export 한 단기 credential 대신 // 낡은 경로를 물 수 있다. 경로는 액션이 정하게 둔다. assert.doesNotMatch(productionWorkflow, /GOOGLE_APPLICATION_CREDENTIALS:/);