Skip to content

Firebase App Distribution 인증을 Workload Identity Federation으로 전환 #850

Description

@OnlyReadOneBook

현황

release-distribution은 승인 environment를 사용하지만 Firebase upload 인증은 장기 보관된 FIREBASE_SERVICE_ACCOUNT_JSON secret에 의존한다. 유출·회전 부담을 줄이기 위해 GitHub OIDC와 Google Cloud Workload Identity Federation의 단기 credential로 전환한다.

범위

  • 별도 canary workflow_dispatch에서 Firebase App Distribution Gradle plugin 또는 CLI의 ADC/WIF 호환성을 먼저 실측한다.
  • Google Cloud에 GitHub OIDC용 workload identity pool/provider와 최소 권한 service account를 구성한다.
  • repository, branch, workflow/environment claim을 attribute condition으로 제한한다.
  • full SHA로 고정한 google-github-actions/auth에서 id-token: write로 short-lived credential file을 발급한다.
  • build job은 credential 없이 수행하고, environment 승인 뒤 upload job에서만 token을 발급하도록 분리한다.
  • 성공 canary와 rollback 절차를 확인한 뒤 FIREBASE_SERVICE_ACCOUNT_JSON secret을 제거한다.
  • token·credential path·service account detail을 log/artifact에 남기지 않는다.

완료 조건

  • 장기 JSON key 없이 승인된 develop/main 배포가 Firebase App Distribution에 1회 성공한다.
  • 허용되지 않은 branch·fork·workflow에서는 Google credential 발급이 거부된다.
  • service account 권한은 대상 Firebase App Distribution 업로드에 필요한 최소 범위다.
  • token 만료 시간을 넘기는 build 때문에 upload가 실패하지 않도록 인증 시점을 upload 직전으로 둔다.
  • canary 실패 시 기존 JSON 방식으로 되돌릴 수 있고, 성공 전에는 기존 secret을 삭제하지 않는다.
  • 전환 후 repository/environment secret 목록에 service account JSON이 없다.

근거

Activity

  1. added a commit that references this issue on Aug 23, 2026
  2. changed the title [-]release(cd): Firebase App Distribution 인증을 Workload Identity Federation으로 전환[/-] [+]Firebase App Distribution 인증을 Workload Identity Federation으로 전환[/+] on Aug 28, 2026
  3. added
    release배포·릴리스 파이프라인 및 산출물 작업
    area:platformCI·빌드·릴리스·저장소 운영 영역
    and removed
    enhancementNew feature or request
    on Aug 28, 2026
  4. OnlyReadOneBook commented on Aug 29, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    착수 시도 — FE 코드로 진행할 수 있는 구간이 남아 있지 않다 (2026-08-29 실측)

    동시 착수 후보로 잡고 열어 봤으나, 선행 구성이 하나도 안 돼 있어 코드 쪽에서 더 밀 수 없다. 실측 결과를 남긴다.

    이미 되어 있는 것 — 워크플로는 준비 완료다

    firebase-wif-canary.yml 이 이 이슈 범위의 「별도 canary workflow_dispatch」를 이미 구현하고 있다.

    • id-token: write 로 short-lived credential 발급
    • build job 은 자격 없이 수행하고 upload job 에서만 토큰을 받도록 분리 (완료 조건의 「인증 시점을 upload 직전으로」)
    • deployable ref 제한 + 명시적 업로드 확인 게이트 (Restrict canary to deployable branches)
    • Verify WIF configuration is set 스텝이 GCP_WORKLOAD_IDENTITY_PROVIDER·GCP_FIREBASE_SERVICE_ACCOUNT 존재를 먼저 확인
    • 정책 테스트 firebase-wif-canary-policy.test.mjs 도 함께 있다

    막고 있는 것 — GCP 구성과 secret 등록

    확인 항목 실측
    GCP_WORKLOAD_IDENTITY_PROVIDER secret 없음 (repository secrets · release-distribution environment 둘 다)
    GCP_FIREBASE_SERVICE_ACCOUNT secret 없음 (같음)
    firebase-wif-canary 실행 이력 0건 — 한 번도 돈 적이 없다
    release-distribution environment 의 현행 secret FIREBASE_SERVICE_ACCOUNT_JSON 외 4건(서명 자격)
    release-distribution.yml 의 인증 여전히 secrets.FIREBASE_SERVICE_ACCOUNT_JSON (L99)

    즉 남은 작업은 전부 저장소 밖이다 — Google Cloud 에 workload identity pool/provider 와 최소 권한 service account 를 만들고, attribute condition 으로 repository·branch·workflow claim 을 제한하고, 그 값을 GitHub secret 으로 등록하는 일. FE 코드 변경으로 대체할 수 없고, GCP 콘솔 권한과 repository secret 쓰기 권한이 필요하다.

    release-distribution.yml 전환은 그 다음이다 — 이 이슈의 완료 조건이 「canary 실패 시 기존 JSON 방식으로 되돌릴 수 있고, 성공 전에는 기존 secret 을 삭제하지 않는다」이므로, canary 가 한 번 성공하기 전에 프로덕션 배포 경로를 건드리는 것은 순서를 어기는 것이다.

    다음 한 걸음

    권한 보유자가 GCP pool/provider·service account 를 만들고 두 secret 을 등록하면, 그 즉시 firebase-wif-canary 를 workflow_dispatch 로 한 번 돌려 성공/rollback 을 확인하고 release-distribution.yml 전환으로 넘어갈 수 있다. 워크플로 쪽은 그 시점에 추가로 만들 것이 없다.

  5. OnlyReadOneBook commented on Aug 29, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    ✅ canary 성공 — 장기 JSON 키 없이 업로드됐다 (2026-08-29)

    앞선 코멘트에서 「막고 있는 것」으로 지목한 GCP 구성과 secret 등록을 마치고 canary 를 돌렸다. run 33227932789 — success.

    구성한 것

    항목 값
    Workload Identity Pool github-actions (project afternote-android / 918752702102)
    OIDC Provider afternote-fe — issuer https://token.actions.githubusercontent.com
    attribute mapping google.subject·attribute.repository·attribute.repository_owner·attribute.ref·attribute.workflow_ref
    attribute condition repository == Afternote/Afternote-FE && ref ∈ {refs/heads/develop, refs/heads/main} && workflow_ref ∈ {firebase-wif-canary.yml@*, release-distribution.yml@*}
    Service Account 기존 afternote-ci@afternote-android.iam.gserviceaccount.com 재사용
    SA 바인딩 roles/iam.workloadIdentityUser ← principalSet://…/attribute.repository/Afternote/Afternote-FE
    GitHub secret GCP_WORKLOAD_IDENTITY_PROVIDER·GCP_FIREBASE_SERVICE_ACCOUNT → release-distribution environment

    service account 를 새로 만들지 않았다. 기존 afternote-ci@ 의 역할이 roles/firebaseappdistro.admin 하나뿐이라 「최소 권한」 조건을 이미 만족한다. 새 SA 를 만들면 권한만 같고 관리 지점만 늘어난다.

    Provider 의 attribute condition 에 release-distribution.yml 을 미리 허용해 뒀다 — 프로덕션 전환 때 GCP 를 다시 건드릴 필요가 없다.

    실측 결과

    10. Verify provenance and attestation subject digest: success
    11. Authenticate to Google Cloud with WIF: success
    12. Upload the already-built APK to Firebase App Distribution: success
    

    결정적 증거 — 같은 job 로그의 환경변수 덤프에서 FIREBASE_SERVICE_ACCOUNT_JSON: 이 빈 값이다. canary 는 장기 키를 아예 전달받지 않았고, google-github-actions/auth 가 발급한 단기 credential file 을 GOOGLE_APPLICATION_CREDENTIALS 로 물려 Gradle 플러그인이 그걸로 업로드했다. ADC/WIF 호환성 실측(범위 첫 항목)이 이것으로 끝났다.

    완료 조건 대조

    조건 상태
    장기 JSON key 없이 승인된 develop/main 배포가 App Distribution 에 1회 성공 ✅ develop canary
    service account 권한이 최소 범위 ✅ firebaseappdistro.admin 단일
    인증 시점이 upload 직전 ✅ build → attestation → auth → upload 순서 (정책 테스트가 강제)
    canary 실패 시 롤백 가능, 성공 전 기존 secret 미삭제 ✅ FIREBASE_SERVICE_ACCOUNT_JSON 그대로 유지
    허용되지 않은 branch·fork·workflow 에서 credential 발급 거부 ⚠️ attribute condition 으로 구성은 완료, 거부되는지 실측은 안 함
    전환 후 secret 목록에 service account JSON 이 없다 ❌ 남음

    남은 것

    1. release-distribution.yml 을 WIF 로 전환. 이때 firebase-wif-canary-policy.test.mjs 의 WIF canary retains the production JSON rollback path(프로덕션에 FIREBASE_SERVICE_ACCOUNT_JSON 이 있어야 한다고 단언)가 함께 갱신돼야 한다 — 롤백 경로의 정의가 바뀌는 시점이므로 정책 갱신이 정상 신호다.
    2. 전환 성공 확인 후 FIREBASE_SERVICE_ACCOUNT_JSON secret 제거.
    3. afternote-ci@ 의 USER_MANAGED 키 2개(76cfe5e5… 2026-07-28, f868d9bc… 2026-08-04) 삭제. 이게 이 이슈가 없애려던 장기 자격의 실체다.

    곁가지

    canary 가 40분 넘게 안 돈 이유는 WIF 문제가 아니라 8/27 부터 승인 대기로 멈춰 있던 main 배포(99f0cea9a, PR #1273 머지분)가 firebase-app-distribution-upload concurrency 를 점유하고 있었기 때문이다. 그 배포를 승인해 먼저 내보냈고(성공), 그제야 canary 가 진입했다. 8/24 이후 main 최신이 미배포 상태로 방치돼 있었다는 뜻이라 함께 적어 둔다.

  6. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    현황 — 코드 전환은 끝났고, 남은 것은 실배포 1회다 (2026-08-30 실측)

    완료된 것

    완료 조건 상태 근거
    canary 로 ADC/WIF 호환성 실측 ✅ 0829 run 33227932789 success
    WIF pool/provider·최소 권한 SA 구성 ✅ 0829 코멘트
    google-github-actions/auth full SHA 고정 + id-token: write ✅ release-distribution.yml:170 — @7c6bc770… (v3), id-token: write 는 39행
    build job 은 credential 없이, upload job 에서만 발급 ✅ 「토큰 수명이 짧으므로 빌드·attestation 이 끝난 업로드 직전에 발급한다」 주석과 함께 upload 직전 배치
    토큰·credential path 를 로그에 남기지 않음 ✅ export_environment_variables 로 액션이 주입, 워크플로가 경로를 손으로 쓰지 않는다
    정책 고정 ✅ firebase-wif-canary-policy.test.mjs 가 canary·production 양쪽에 FIREBASE_SERVICE_ACCOUNT_JSON 이 없음을 단언

    전환 커밋은 b8e015c21 (「ci: 릴리스 배포 인증을 장기 JSON 키에서 WIF 로 전환」, 2026-08-29 12:18) 이다.

    🔴 남은 것 — 코드가 아니라 운영이다

    완료 조건 상태 막는 것
    장기 JSON key 없이 승인된 develop/main 배포가 1회 성공 ❌ WIF 전환 후 release-distribution 실행이 0회다
    전환 후 secret 목록에 service account JSON 이 없다 ❌ 위가 선행 — 본문 방침 「성공 전에는 기존 secret 을 삭제하지 않는다」
    • release-distribution 최근 성공은 2026-08-27 이고, 그 시점 워크플로는 아직 JSON 방식이었다(해당 run 의 headSha 로 워크플로 원문을 꺼내 확인).
    • 전환 커밋 b8e015c21 은 develop 에만 있고 origin/main 에는 없다.
    • release-distribution environment secret 에 FIREBASE_SERVICE_ACCOUNT_JSON 이 그대로 있다 — 롤백 경로를 살려 두는 것이 본문 방침이라 지금 지우면 안 된다.

    .github/actions/setup-release-config/action.yml 의 firebase-service-account-json 입력도 같은 이유로 남긴다. 현재 이 입력을 넘기는 워크플로는 0곳이고(canary 는 「의도적으로 넘기지 않는다」 고 주석), JSON 방식으로 되돌릴 수 있는 유일한 경로다.

    그래서 다음 행동

    1. develop 에서 release-distribution 을 1회 실행해 WIF 로 업로드 성공을 확인한다 — environment required reviewer 승인이 필요한 운영 작업이라 코드 변경으로 대신할 수 없다.
    2. 성공하면 같은 PR 에서 ① FIREBASE_SERVICE_ACCOUNT_JSON environment secret 삭제 ② setup-release-config 의 죽은 firebase-service-account-json 입력·분기 제거 를 함께 처리하고 이 이슈를 닫는다.
    3. 실패하면 그 자리에서 JSON 방식으로 롤백하고 원인을 이 이슈에 기록한다.
  7. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    정정 — develop 수동 배포 경로는 존재하지 않는다 (2026-08-30)

    바로 앞 코멘트에서 「develop 에서 release-distribution 을 1회 실행한다」 고 적었는데 그 경로가 없다. 워크플로 트리거를 실측하지 않고 쓴 것이다.

    # 배포 경로는 main 릴리스 머지 하나뿐이다. develop 수동 배포(workflow_dispatch)가 있었지만 실행 …
    on:
      push:
        branches: [main]

    workflow_dispatch 는 없다. 유일한 배포 트리거는 main 브랜치 push 이고, 그것은 곧 develop → main 릴리스 머지다.

    그래서 이 이슈를 닫으려면

    단계 주체
    1. develop → main 릴리스 머지 (WIF 커밋 b8e015c21 이 main 에 들어간다) 릴리스 결정 — 일혁
    2. 그 push 가 release-distribution 을 발동, release-distribution environment 승인 required reviewer 가 1hyok 단독
    3. WIF 로 업로드 성공 확인 —
    4. FIREBASE_SERVICE_ACCOUNT_JSON environment secret 삭제 + setup-release-config 의 죽은 firebase-service-account-json 입력·분기 제거 후속 PR

    b8e015c21 은 develop 에만 있고 origin/main 에는 없으므로, 다음 릴리스 머지가 곧 이 이슈의 실검증이 된다. 별도로 배포를 한 번 더 돌릴 필요가 없다는 뜻이기도 하다.

    실패 시 롤백 경로(setup-release-config 의 JSON 입력 + environment secret)는 그대로 살아 있다 — 본문 방침 「성공 전에는 기존 secret 을 삭제하지 않는다」 대로다.

  8. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    ✅ WIF 경로 실배포 성공 — 완료 조건 1번 충족 (2026-08-30)

    릴리스 PR #1558 이 머지(1d0d1757)되면서 release-distribution 이 WIF 경로로 처음 실행됐고 성공했다. run 33292931533

    스텝 전량 success — 인증 경로 확인

    스텝 결과
    Verify WIF configuration is set ✅
    Authenticate to Google Cloud with WIF ✅
    Upload the attested APK to Firebase App Distribution ✅
    Attest the exact signed APK ✅ (Rekor transparency log 업로드)
    Verify provenance and attestation subject digest ✅

    장기 JSON 키를 쓰지 않고 google-github-actions/auth 가 발급한 단기 credential 로 업로드가 끝났다. release-distribution environment 승인은 일혁이 04:38 에 처리했다.

    완료 조건 대조

    조건 상태
    장기 JSON key 없이 승인된 develop/main 배포가 1회 성공 ✅ 이번 run
    허용되지 않은 branch·fork·workflow 에서 credential 발급 거부 ✅ attribute condition 구성(0829)
    service account 권한이 최소 범위 ✅ 0829 구성
    인증 시점을 upload 직전으로 ✅ 빌드·attestation 뒤에 배치
    canary 실패 시 롤백 가능 ✅ 롤백 경로 보존 중
    전환 후 secret 목록에 service account JSON 이 없다 ❌ 남은 하나

    남은 것 — 롤백 경로 정리

    성공했으므로 이제 지운다.

    1. FIREBASE_SERVICE_ACCOUNT_JSON environment secret 삭제 (release-distribution 환경)
    2. .github/actions/setup-release-config/action.yml 의 firebase-service-account-json 입력과 ADC 파일 생성 분기 제거 — 현재 이 입력을 넘기는 워크플로가 0곳인 죽은 경로다

    둘을 함께 처리하는 PR 로 이 이슈를 닫는다.

  9. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  10. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    ✅ 마지막 완료 조건 충족 — secret 삭제 (2026-08-30)

    release-distribution environment 에서 FIREBASE_SERVICE_ACCOUNT_JSON 을 삭제했다.

    삭제 전:  FIREBASE_SERVICE_ACCOUNT_JSON (2026-08-04)
              GCP_FIREBASE_SERVICE_ACCOUNT · GCP_WORKLOAD_IDENTITY_PROVIDER (2026-08-29)
              RELEASE_KEY_ALIAS · RELEASE_KEY_PASSWORD · RELEASE_STORE_FILE_B64 · RELEASE_STORE_PASSWORD
    
    삭제 후:  GCP_FIREBASE_SERVICE_ACCOUNT · GCP_WORKLOAD_IDENTITY_PROVIDER
              RELEASE_KEY_ALIAS · RELEASE_KEY_PASSWORD · RELEASE_STORE_FILE_B64 · RELEASE_STORE_PASSWORD
    

    레포 secret 목록에도 service account JSON 은 0건이다.

    되돌릴 수 있음을 먼저 확인했다

    GitHub secret 은 값을 읽을 수 없어 삭제가 비가역이다. 그래서 지우기 전에 1Password 백업을 실측했다.

    항목 형태
    Afternote Firebase 서비스 계정 (App Distribution) (0803 생성) 첨부 afternote-android-firebase-adminsdk-fbsvc-93a9fefa24.json
    [로컬 비밀 이관 2026-08-26] afternote-android-firebase-adminsdk-fbsvc-93a9fefa24.json 같은 파일 (이중 백업)

    내용 검증 — type: service_account · project_id: afternote-android · client_email: firebase-adminsdk-fbsvc@afternote-android.iam.gserviceaccount.com · private_key 존재. 필요하면 이 파일로 secret 을 재등록해 JSON 방식으로 되돌릴 수 있다.

    완료 조건 전량

    조건 상태
    canary 로 ADC/WIF 호환성 실측 ✅ 0829
    WIF pool/provider·최소 권한 SA 구성 ✅ 0829
    repository·branch·workflow claim 제한 ✅ attribute condition
    full SHA 고정 google-github-actions/auth + id-token: write ✅
    build 는 credential 없이, upload job 에서만 발급 ✅ upload 직전 배치
    장기 JSON key 없이 승인된 배포 1회 성공 ✅ run 33292931533 (0830)
    canary 실패 시 롤백 가능·성공 전 secret 유지 ✅ 성공까지 보존 후 삭제
    token·credential path 를 로그에 남기지 않음 ✅
    전환 후 secret 목록에 JSON 없음 ✅ 이번 삭제

    남은 코드 정리

    setup-release-config 의 죽은 firebase-service-account-json 입력·ADC 분기·output 제거는 PR #1566 이 처리한다. 그 PR 이 머지되면 이 이슈를 닫는다.

    참고 — GCP 쪽 SA 키(93a9fefa…) 자체는 폐기하지 않았다. 1Password 백업으로 롤백 경로를 남겨 두는 편이 아직 낫다고 판단했다. WIF 가 몇 회 더 안정적으로 돌면 그때 GCP 에서 키를 폐기하는 것이 마지막 정리다.

  11. added a commit that references this issue on Aug 30, 2026
  12. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  13. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    ✅ 완료 — 닫는다 (2026-08-30)

    코드 정리 PR #1566 이 머지(9d8ed59c)되어 마지막 항목까지 끝났다.

    완료 조건 근거
    canary 로 ADC/WIF 호환성 실측 0829 run 33227932789
    WIF pool/provider·최소 권한 SA 구성 0829
    repository·branch·workflow claim 제한 attribute condition
    full SHA 고정 google-github-actions/auth + id-token: write release-distribution.yml:170
    build 는 credential 없이, upload job 에서만 발급 빌드·attestation 뒤 배치
    장기 JSON key 없이 승인된 배포 1회 성공 0830 run 33292931533
    canary 실패 시 롤백 가능·성공 전 secret 유지 성공 확인 후 삭제
    token·credential path 를 로그에 남기지 않음 auth 액션이 주입
    전환 후 secret 목록에 JSON 없음 FIREBASE_SERVICE_ACCOUNT_JSON 삭제 (레포 전체 0건)
    죽은 롤백 경로 제거 PR #1566 — 입력·env 2곳·case 분기·ADC 파일 생성·output

    재유입 가드

    firebase-wif-canary-policy.test.mjs 가 지금까지 워크플로 두 개만 봤는데, composite action 도 검사 대상에 넣었다. 워크플로가 안 넘겨도 action 이 입력을 들고 있으면 경로는 살아 있기 때문이다.

    assert.doesNotMatch(releaseConfigAction, /FIREBASE_SERVICE_ACCOUNT_JSON/);
    assert.doesNotMatch(releaseConfigAction, /firebase-service-account-json/);
    assert.doesNotMatch(releaseConfigAction, /firebase-credentials-path/);

    역검증으로 입력을 되살리면 이 테스트가 실패하는 것을 확인했다.

    남겨 둔 것 하나 — GCP SA 키

    GCP 쪽 service account 키(93a9fefa…)는 폐기하지 않았다. 1Password 백업(Afternote Firebase 서비스 계정 (App Distribution) 첨부 + [로컬 비밀 이관 2026-08-26] 항목, 이중)으로 JSON 방식 롤백 경로를 아직 남겨 두는 편이 낫다고 판단했다.

    WIF 로 몇 회 더 배포가 안정적으로 돌면 그때 GCP 에서 키를 폐기하는 것이 마지막 정리다. 지금 이 이슈를 열어 두고 기다릴 성격은 아니라 닫는다 — 필요해지면 별건으로 연다.

  14. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    and removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:platformCI·빌드·릴리스·저장소 운영 영역release배포·릴리스 파이프라인 및 산출물 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions